Skip to content
LogoLogo

Light client

Check whether a block touches your addresses or assets without downloading it, and without trusting the node that serves the data. Background: Light clients & filters.

What the example does

  1. Fetches filter headers (getFilterHeaders, GET /v1/filters/headers) and each block's filter (getBlockFilter, GET /v1/blocks/{id}/filter): the block hash, height and parent hash, the filter, its filter header, the coinbase and the coinbase's merkle branch.
  2. Verifies the filter against the filter-header chain, and the coinbase carrying the "ARKF" commitment against the block's merkle root, so the filter is covered by proof of work. The TypeScript SDK bundles both checks as verifyBlockFilterProof; the building blocks are verifyFilter and verifyMerkleBranch.
  3. Tests the watched address's lock against the filter locally with matchFilter; the node never learns what is watched. Only matching blocks need downloading.
  4. Verifies the latest signed checkpoint against the network's signer group with verifyCheckpoint, so the client knows which blocks are final.
/**
 * Light client: verify a block filter against its header and coinbase commitment, test it
 * for our address, and verify the latest signed checkpoint.
 *
 *   ARREKTA_URL=http://127.0.0.1:28332 npx tsx examples/light-client.ts
 */
import {
  createClient,
  decodeAddress,
  fromHex,
  getBlock,
  getBlockFilter,
  getChain,
  getCheckpoints,
  getFilterHeaders,
  keychainFromSeed,
  matchFilter,
  networkSignerSet,
  verifyBlockFilterProof,
  verifyCheckpoint,
} from 'arrekta-sdk';
 
const URL = process.env.ARREKTA_URL ?? 'http://127.0.0.1:28332';
const SEED = fromHex(process.env.ARREKTA_SEED ?? 'a1'.repeat(32));
 
// 1. Connect and pick the tip.
const client = createClient({ url: URL });
const chain = await getChain(client);
const wallet = keychainFromSeed(SEED, { network: chain.network });
const height = chain.height;
 
// 2. Filter headers: the tip's header must chain from its parent's.
const { headers } = await getFilterHeaders(client, { start: Math.max(height - 1, 0), count: 2 });
const prevHeader = height > 0 ? headers[0]!.filter_header! : '00'.repeat(32);
 
// 3. Fetch the tip's filter and verify it (coinbase commitment, merkle branch, header).
const f = await getBlockFilter(client, { id: height });
const header = await getBlock(client, { id: height, txs: 'none' });
// the proof's merkle root must be the PoW-checked header's
const ok = header.merkle_root === f.merkle_root && verifyBlockFilterProof(f, prevHeader).ok;
console.log(`block ${height}: filter verified = ${ok}`);
 
// 4. Test the filter locally for our address (the node never learns what we watch).
const mine = matchFilter(f.prev_hash, f.filter, [decodeAddress(wallet.address(0), chain.network)]);
console.log(`block ${height} may pay ${wallet.address(0)}: ${mine}`);
 
// 5. Verify the latest signed checkpoint against the network's signer group.
const { latest } = await getCheckpoints(client);
const signers = networkSignerSet(chain.network);
if (latest && signers) {
  console.log(`checkpoint ${latest.height}: final = ${verifyCheckpoint(latest.hex, signers, chain.network).final}`);
} else {
  console.log('no checkpoint yet');
}

Source: sdk/typescript/examples/light-client.ts (updated 2026-11-02) · sdk/python/examples/light_client.py (updated 2026-11-02). Runnable against a regtest node; CI runs every example.

For many blocks, fetch filter headers in batches with getFilterHeaders({ start, count }) and compare them across several nodes before downloading filters.