Service health and which RPC tier is currently answering. Returns ok unless every endpoint on the failover chain is down.
curl https://api.lmprk.fun/health
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.
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.
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.
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.
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.
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.
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.
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.
Service health and which RPC tier is currently answering. Returns ok unless every endpoint on the failover chain is down.
curl https://api.lmprk.fun/health
The latest slot with its blockhash and leader. Cached for 5 seconds.
curl https://api.lmprk.fun/slot/latest
Generate and locally verify a balance proof for an account at a slot.
curl -X POST https://api.lmprk.fun/verify/balance \
-H "Content-Type: application/json" \
-d '{"address":"So11111111111111111111111111111111111111112"}'Prove whether an owner holds a given mint. Works for regular and compressed NFTs via DAS.
curl -X POST https://api.lmprk.fun/verify/nft \
-H "Content-Type: application/json" \
-d '{"owner":"<owner-pubkey>","mint":"<mint-pubkey>"}'Prove the data hash, owner program, and lamports of an arbitrary account.
curl -X POST https://api.lmprk.fun/verify/account \
-H "Content-Type: application/json" \
-d '{"address":"<pubkey>"}'The primary read endpoint: a fresh-or-cached balance proof for an address, returned flat.
curl https://api.lmprk.fun/proof/So11111111111111111111111111111111111111112
Published @lmprk/sdk metadata: version, install command, runtimes.
curl https://api.lmprk.fun/sdk/version
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.
curl -X POST https://api.lmprk.fun/rpc-proxy \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}'Inspect the failover chain: which tier is active and the host of each endpoint (never the key).
curl https://api.lmprk.fun/rpc-proxy/health
@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.
npm install @lmprk/sdkimport {
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.verifyProof(proof: StateProof): VerifyResultFull 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): Uint8ArrayDomain-separated leaf hash (lmprk:leaf:v1) over raw account bytes.
hashNode(left: Uint8Array, right: Uint8Array): Uint8ArrayDomain-separated inner-node hash (lmprk:node:v1) combining two children.
computeRoot(path: MerklePath, leaf: Uint8Array): Uint8ArrayFold a leaf up its sibling path to the merkle root.
foldStep(acc: Uint8Array, step: MerkleStep): Uint8ArrayApply one merkle step, hashing on the correct side ('left' | 'right').
pathDepth(path: MerklePath): numberNumber of steps in a merkle path.
emptyPath(): MerklePathAn empty path ({ steps: [] }) -- handy for single-leaf trees and tests.
proofByteSize(proof: StateProof): numberComputed serialized size of a proof in bytes.
hexToBytes(hex: string): Uint8ArrayDecode a hex string (with or without 0x) to bytes; throws on odd length.
bytesToHex(bytes: Uint8Array): stringEncode bytes to a lowercase hex string.
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 / Base58String aliases for hex- and base58-encoded values.
PROTOCOL_NAME = "lmprk/v1"Protocol identifier verifyProof requires.
PROTOCOL_VERSION = 1Protocol version verifyProof requires.
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.
Verify a balance in the browser against the live backend -- no install, the fastest way to watch a proof clear.
open https://lmprk.fun/demoBring a StateProof from any transport and verify it locally with verifyProof. The verifier core for every other client.
npm install @lmprk/sdkDrops a small lamp next to balances on the dApps you already use; it pulses only when the proof holds.
load /lmprk-extension.zip via chrome://extensionsA lantern in the menu bar or system tray that polls the same backend on an interval, even when the browser is closed.
install the .dmg (macOS) or .exe (Windows)A binary for CI, cron, and watchdogs. Exits 0 only when the proof holds, so pipelines can gate on the lamp.
npm install -g @lmprk/cli