SDK & Integration
SDK: Merkle tree & proofs
The MerkleTree class, inclusion proofs, the constants that must match the contracts, and formatProof — the one transform that reorders a snarkjs proof into the (a, b, c) shape the Solidity verifier expects.
The SDK's MerkleTree mirrors the on-chain MerkleTreeWithHistory exactly: same depth, same ZERO_VALUE, same Poseidon node ordering. You rebuild it client-side from indexed leaves to generate inclusion proofs.
MerkleTree
import { MerkleTree, LEVELS, ZERO_VALUE } from "@shh/sdk";
const tree = new MerkleTree(LEVELS, leaves); // leaves: bigint[]
const i = tree.indexOf(commitment); // -1 if absent
const root = tree.root();
const path = tree.proof(i); // { pathElements, pathIndices }indexOf(leaf)- Insertion index of a leaf, or
-1. root()- Current Poseidon root (
bigint). proof(index)- A
MerklePath:pathElements[LEVELS]andpathIndicesfor the inclusion proof. zeros/levels- Precomputed empty-subtree values and the configured depth — used to pad dummy inputs.
Formatting proofs for Solidity
snarkjs emits a proof as pi_a, pi_b, pi_c. The Solidity verifier wants (a, b, c) with the b coordinates swapped. formatProof does exactly that:
import { formatProof } from "@shh/sdk";
const sol = formatProof(snarkjsProof);
// sol.a: [bigint, bigint]
// sol.b: [[bigint, bigint], [bigint, bigint]] (G2 coords reordered)
// sol.c: [bigint, bigint]Reconstructing trees from the backend
The wallet backend indexes leaves so clients don't have to scan the chain. Fetch them, build the tree, and you're ready to prove:
const leaves = await fetch("/api/pool/leaves").then((r) => r.json());
const tree = new MerkleTree(LEVELS, leaves.map(BigInt));The endpoints are documented in the Wallet backend API.