Lighthouse 01LMPRK
Documentation . the keeper's manual

Read the light.

LMPRK is a Solana ZK light client. It returns proofs you can verify yourself, so a balance, an NFT, or an account is trusted because the math holds -- not because one RPC said so. This manual covers the model, the api.lmprk.fun HTTP surface, the @lmprk/sdk verifier, and the five clients that read the same proof.

Backendpolling…
01 . the model

A sealed letter, not a phone call.

Ask a single RPC node "what is this balance?" and you are trusting that one node to tell the truth. It can lie, lag, or serve you a stale fork, and you have no way to tell. A light client removes that trust: instead of an answer, it asks for a proof-- a sealed letter that anyone can open and check against the chain's own signatures. The keeper turns the lamp; every ship reads its own position.

i
Signature aggregation

Each proof is anchored to a recent slot and the validator that led it. The slot snapshot carries a signer count and a threshold; a proof is only honoured when enough of the validator set has signed the head.

ii
Merkle inclusion

The account's state is hashed into a leaf and folded up a merkle path of sibling hashes to a single state root. Recomputing the root from the leaf + path and matching it to the signed snapshot proves the account was really in that state.

iii
Slot verification

The root is bound to a specific slot and leader before it is signed. A proof says 'this was true at slot N', so a verifier always knows exactly how fresh the light is, down to the slot.

How a proof is read
  1. 1The backend pulls the current slot and its leader, then gathers the account state plus the signatures that touched it.
  2. 2It builds a merkle tree over that witness set and signs the root, slot, and leader together -- producing a compact, packed proof.
  3. 3Your client (browser, SDK, extension, desktop, or CLI) recomputes the root and checks the signature. If anything is off, the light stays dark and no value is shown.

Helius and QuickNode sit behind the backend purely as accelerators on a failover chain -- they are never trusted authorities, and their keys never leave the server. The client only ever sees the proof.

02 . http

API reference.

Base URL https://api.lmprk.fun. All responses are JSON. Proofs are valid for a short window (valid_until in unix seconds) and are cached server-side; pass a fresh request after a proof ages out.

RPC failover chain

The backend keeps an ordered chain of RPC endpoints and walks down it on failure: Helius QuickNode→ public nodes (api.mainnet-beta.solana.com, publicnode, ankr). Premium keys live only in server env (HELIUS_RPC_URL, QUICKNODE_RPC_URL) and are never exposed to the client. Call /rpc-proxy/health to see which tier is currently lit.

GET/health

Service health and which RPC tier is currently answering. Returns ok unless every endpoint on the failover chain is down.

Response
statusstring
"ok" or "degraded"
slotnumber
latest slot seen by the probe
rpcstring
active tier: Helius | QuickNode | public | down
rpc_endpoint_indexnumber
index into the failover chain
rpc_chain_lengthnumber
number of endpoints in the chain
cachestring
cache backend status
uptime_secnumber
seconds since process start
versionstring
service version
Example
curl https://api.lmprk.fun/health
GET/slot/latest

The latest slot with its blockhash and leader. Cached for 5 seconds.

Response
slotnumber
current slot
blockhashstring
recent blockhash (base58)
leaderstring
slot leader pubkey, or 'unknown'
last_valid_block_heightnumber
expiry height for the blockhash
timestampnumber
unix seconds when fetched
cachedboolean
true if served from the 5s cache
Example
curl https://api.lmprk.fun/slot/latest
POST/verify/balance

Generate and locally verify a balance proof for an account at a slot.

Request body (JSON)
addressstring
Solana account address (required)
slotnumber
optional preferred slot; defaults to latest
Response
verifiedboolean
true if the packed proof self-verifies
balance_lamportsnumber
account balance in lamports
slotnumber
slot the proof is bound to
proofstring
base58 packed proof bytes
fullobject
complete proof: root, leader, leader_signatures, aggregator_pubkey, valid_until, generated_at, account_exists
cachedboolean
true if served from the 60s cache
Example
curl -X POST https://api.lmprk.fun/verify/balance \
  -H "Content-Type: application/json" \
  -d '{"address":"So11111111111111111111111111111111111111112"}'
POST/verify/nft

Prove whether an owner holds a given mint. Works for regular and compressed NFTs via DAS.

Request body (JSON)
ownerstring
owner account address (required)
mintstring
NFT mint address (required)
Response
verifiedboolean
true if the packed proof self-verifies
ownedboolean
true if owner currently holds the mint
slotnumber
slot the proof is bound to
proofstring
base58 packed proof bytes
fullobject
complete proof incl. compressed flag and ownership_path
cachedboolean
true if served from the 120s cache
Example
curl -X POST https://api.lmprk.fun/verify/nft \
  -H "Content-Type: application/json" \
  -d '{"owner":"<owner-pubkey>","mint":"<mint-pubkey>"}'
POST/verify/account

Prove the data hash, owner program, and lamports of an arbitrary account.

Request body (JSON)
addressstring
Solana account address (required)
Response
verifiedboolean
true if the packed proof self-verifies
data_hashstring
sha256(account.data) as hex
slotnumber
slot the proof is bound to
proofstring
base58 packed proof bytes
fullobject
complete proof incl. owner_program, lamports, data_len
cachedboolean
true if served from the 60s cache
Example
curl -X POST https://api.lmprk.fun/verify/account \
  -H "Content-Type: application/json" \
  -d '{"address":"<pubkey>"}'
GET/proof/:address

The primary read endpoint: a fresh-or-cached balance proof for an address, returned flat.

Path params
:addressstring
Solana account address in the path
Response
addressstring
the queried address
balance_lamportsnumber
account balance in lamports
account_existsboolean
true if the account is funded or active
proof_bytesstring
base58 packed proof
slot / root / leadervarious
slot, hex root, leader pubkey
leader_signaturesstring[]
aggregated signature(s)
aggregator_pubkeystring
key the verifier checks against
valid_until / generated_atnumber
unix-second proof window
Example
curl https://api.lmprk.fun/proof/So11111111111111111111111111111111111111112
GET/sdk/version

Published @lmprk/sdk metadata: version, install command, runtimes.

Response
name / versionstring
package name and version
installstring
npm install command
minimum_nodestring
minimum Node version
runtimestring[]
supported runtimes: node, deno, bun, browser
npm_url / github_url / docs_urlstring
canonical links
Example
curl https://api.lmprk.fun/sdk/version
POST/rpc-proxy

Transparent Solana JSON-RPC proxy. Forwards a jsonrpc 2.0 body down the failover chain so clients borrow Helius/QuickNode without ever seeing the key. Single or batch (max 20). Rate limit: 1 req/sec/IP. Max body 64 KB.

Request body (JSON)
jsonrpc"2.0"
protocol version (required)
methodstring
any Solana RPC method (required)
paramsunknown
method params (optional)
idstring | number | null
request id
Response
(jsonrpc body)object
upstream RPC result passed through verbatim
x-lmprk-rpc-tierheader
which tier answered
x-lmprk-rpc-endpoint-indexheader
chain index that answered
Example
curl -X POST https://api.lmprk.fun/rpc-proxy \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}'
GET/rpc-proxy/health

Inspect the failover chain: which tier is active and the host of each endpoint (never the key).

Response
status / active_tierstring
chain health and current tier
chain_lengthnumber
endpoints in the chain
chain_tiersarray
{ index, tier, host } per endpoint
slotnumber
latest slot
rate_limit / max_body_bytes / max_batch_sizevarious
proxy limits
Example
curl https://api.lmprk.fun/rpc-proxy/health
03 . typescript

SDK reference.

@lmprk/sdk is a dependency-light TypeScript verifier. It does not fetch anything itself -- you bring a StateProof from any transport and the SDK recomputes the merkle root and checks it against the signed slot snapshot. The same code runs in Node, Deno, Bun, and the browser.

Install
npm install @lmprk/sdk
Verify a proof
import {
  verifyProof,
  computeRoot,
  hashLeaf,
  type StateProof,
  PROTOCOL_NAME,
} from "@lmprk/sdk";

// A StateProof is fetched however you like -- e.g. from
// the LMPRK service, a file, or a message. The SDK does
// the trusting part: it recomputes the root from the leaf
// and merkle path, then checks it against the signed slot
// snapshot. No network calls happen inside the SDK.
declare const proof: StateProof;

const { valid, reason, computedRoot } = verifyProof(proof);
if (!valid) {
  throw new Error(`lmprk: proof rejected -- ${reason}`);
}
// 'valid' means: protocol + version matched, the snapshot
// had >= threshold signers, and the recomputed root equals
// snapshot.stateRoot. computedRoot is the hex root you re-derived.
Verification
verifyProof(proof: StateProof): VerifyResult

Full check: rejects on protocol/version mismatch, signer count below snapshot.threshold, or a recomputed root that differs from snapshot.stateRoot. Returns { valid, reason?, computedRoot }.

hashLeaf(bytes: Uint8Array): Uint8Array

Domain-separated leaf hash (lmprk:leaf:v1) over raw account bytes.

hashNode(left: Uint8Array, right: Uint8Array): Uint8Array

Domain-separated inner-node hash (lmprk:node:v1) combining two children.

Merkle
computeRoot(path: MerklePath, leaf: Uint8Array): Uint8Array

Fold a leaf up its sibling path to the merkle root.

foldStep(acc: Uint8Array, step: MerkleStep): Uint8Array

Apply one merkle step, hashing on the correct side ('left' | 'right').

pathDepth(path: MerklePath): number

Number of steps in a merkle path.

emptyPath(): MerklePath

An empty path ({ steps: [] }) -- handy for single-leaf trees and tests.

proofByteSize(proof: StateProof): number

Computed serialized size of a proof in bytes.

Encoding
hexToBytes(hex: string): Uint8Array

Decode a hex string (with or without 0x) to bytes; throws on odd length.

bytesToHex(bytes: Uint8Array): string

Encode bytes to a lowercase hex string.

Types
StateProof

{ protocol, version, snapshot: SlotSnapshot, address: Base58, accountData: HexString, path: MerklePath }

SlotSnapshot

{ head: SlotInfo, stateRoot: HexString, validatorSetSize: number, threshold: number }

SlotInfo

{ slot: BN, blockhash: HexString, parentSlot: BN, signerCount: number }

MerklePath / MerkleStep / MerkleSide

{ steps: MerkleStep[] } ; { sibling: HexString, side: MerkleSide } ; 'left' | 'right'.

VerifyResult

{ valid: boolean, reason?: string, computedRoot: string }.

HexString / Base58

String aliases for hex- and base58-encoded values.

Constants
PROTOCOL_NAME = "lmprk/v1"

Protocol identifier verifyProof requires.

PROTOCOL_VERSION = 1

Protocol version verifyProof requires.

04 . carriers

Five clients, one proof.

Every client reads the same proof from the same backend. The surface differs; the light does not. None of them touch a seed phrase or proxy a signature -- they read what is already in front of you and only let it through when the proof clears.

01
Live demo
web . /demo

Verify a balance in the browser against the live backend -- no install, the fastest way to watch a proof clear.

Open demo
open https://lmprk.fun/demo
02
TypeScript SDK
@lmprk/sdk . node / deno / bun / browser

Bring a StateProof from any transport and verify it locally with verifyProof. The verifier core for every other client.

SDK page
npm install @lmprk/sdk
03
Chrome extension
Manifest V3 . browser companion

Drops a small lamp next to balances on the dApps you already use; it pulses only when the proof holds.

Download
load /lmprk-extension.zip via chrome://extensions
04
Desktop companion
macOS . Windows native

A lantern in the menu bar or system tray that polls the same backend on an interval, even when the browser is closed.

Download
install the .dmg (macOS) or .exe (Windows)
05
CLI
@lmprk/cli . scriptable

A binary for CI, cron, and watchdogs. Exits 0 only when the proof holds, so pipelines can gate on the lamp.

Download
npm install -g @lmprk/cli