Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nhestrompia/shielded-x402/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Client SDK provides utilities for managing shielded notes locally, including indexing commitments, tracking notes, and encrypting note data for storage or transmission.

LocalNoteIndexer

LocalNoteIndexer is an in-memory note indexer for tracking shielded deposits and spends.

Constructor

import { LocalNoteIndexer } from '@shielded-x402/client';

const noteStore = new LocalNoteIndexer();

Methods

ingestDeposit

Record a new deposit event and store the associated note.
noteStore.ingestDeposit(
  event: DepositEvent,
  note: ShieldedNote
): void
event
DepositEvent
required
note
ShieldedNote
required
The full note details (amount, rho, pkHash, commitment, leafIndex)
Example
const { note, leafIndex } = await sdk.deposit(100_000_000n, ownerPkHash);

noteStore.ingestDeposit(
  {
    commitment: note.commitment,
    leafIndex,
    amount: note.amount
  },
  note
);

ingestSpend

Record a spend event, adding merchant and change commitments to the tree.
noteStore.ingestSpend(
  event: SpendEvent
): void
event
SpendEvent
required
Example
const prepared = await sdk.prepare402Payment(
  requirement,
  note,
  witness,
  nullifierSecret
);

noteStore.ingestSpend({
  merchantCommitment: prepared.response.merchantCommitment,
  changeCommitment: prepared.response.changeCommitment
});

// Also store the change note for future spending
const changeNote = prepared.changeNote;
noteStore.getNotes().push(changeNote);

getCommitments

Retrieve all commitments in tree order.
const commitments = noteStore.getCommitments(): Hex[]
commitments
Hex[]
Array of commitment hashes in the order they were added to the tree
Example
import { buildWitnessFromCommitments } from '@shielded-x402/client';

const commitments = noteStore.getCommitments();
const witness = buildWitnessFromCommitments(commitments, note.leafIndex);

getNotes

Retrieve all stored notes.
const notes = noteStore.getNotes(): ShieldedNote[]
notes
ShieldedNote[]
Array of all shielded notes stored in the indexer
Example
const notes = noteStore.getNotes();
const totalBalance = notes.reduce((sum, n) => sum + n.amount, 0n);

console.log('Total balance:', totalBalance);
console.log('Note count:', notes.length);

// Find spendable note
const spendableNote = notes.find(n => n.amount >= 50_000_000n);

Note Encryption

The SDK provides utilities for encrypting and decrypting notes for secure storage or transmission.

Symmetric Encryption

encryptNoteSymmetric

Encrypt a note using AES-256-GCM with a symmetric key.
import { encryptNoteSymmetric } from '@shielded-x402/client';

const ciphertext = encryptNoteSymmetric(
  note: ShieldedNote,
  key: Buffer
): Hex
note
ShieldedNote
required
Note to encrypt
key
Buffer
required
32-byte AES key
ciphertext
Hex
Encrypted note (includes IV, auth tag, and ciphertext)
Example
import { randomBytes } from 'node:crypto';

const aesKey = randomBytes(32);
const encrypted = encryptNoteSymmetric(note, aesKey);

// Store encrypted note
await db.storeEncryptedNote(note.commitment, encrypted);

decryptNoteSymmetric

Decrypt a note encrypted with encryptNoteSymmetric.
import { decryptNoteSymmetric } from '@shielded-x402/client';

const note = decryptNoteSymmetric(
  ciphertext: Hex,
  key: Buffer
): ShieldedNote
ciphertext
Hex
required
Encrypted note data
key
Buffer
required
32-byte AES key used for encryption
note
ShieldedNote
Decrypted note
Example
const encrypted = await db.getEncryptedNote(commitment);
const note = decryptNoteSymmetric(encrypted, aesKey);

console.log('Decrypted note:', note.amount);

Public Key Encryption

generateNoteEncryptionKeyPair

Generate an ECDH key pair for note encryption.
import { generateNoteEncryptionKeyPair } from '@shielded-x402/client';

const keyPair = generateNoteEncryptionKeyPair(): NoteEncryptionKeyPair
NoteEncryptionKeyPair
object
Example
const keyPair = generateNoteEncryptionKeyPair();

console.log('Public key:', keyPair.publicKey);
// Store private key securely
await wallet.storeKey('note-encryption-key', keyPair.privateKey);

encryptNoteForPublicKey

Encrypt a note for a recipient’s public key using ECIES.
import { encryptNoteForPublicKey } from '@shielded-x402/client';

const envelope = encryptNoteForPublicKey(
  note: ShieldedNote,
  recipientPublicKey: Hex,
  aad?: Uint8Array
): Hex
note
ShieldedNote
required
Note to encrypt
recipientPublicKey
Hex
required
Recipient’s ECDH public key
aad
Uint8Array
Optional additional authenticated data
envelope
Hex
Encrypted envelope (version + ephemeral pubkey + IV + auth tag + ciphertext)
Example
const merchantPubKey = '0x04...'; // Merchant's public key
const encryptedReceipt = encryptNoteForPublicKey(
  merchantNote,
  merchantPubKey
);

// Send encrypted receipt to merchant
await sendToMerchant(encryptedReceipt);

decryptNoteWithPrivateKey

Decrypt a note encrypted with encryptNoteForPublicKey.
import { decryptNoteWithPrivateKey } from '@shielded-x402/client';

const note = decryptNoteWithPrivateKey(
  envelopeCiphertext: Hex,
  recipientPrivateKey: Hex,
  aad?: Uint8Array
): ShieldedNote
envelopeCiphertext
Hex
required
Encrypted envelope from encryptNoteForPublicKey
recipientPrivateKey
Hex
required
Recipient’s ECDH private key
aad
Uint8Array
Additional authenticated data (must match encryption AAD)
note
ShieldedNote
Decrypted note
Example
const merchantPrivateKey = await wallet.getKey('note-encryption-key');
const note = decryptNoteWithPrivateKey(
  encryptedReceipt,
  merchantPrivateKey
);

console.log('Received note:', note.amount);

Serialization

serializeNote

Serialize a note to JSON string.
import { serializeNote } from '@shielded-x402/client';

const json = serializeNote(note: ShieldedNote): string
note
ShieldedNote
required
Note to serialize
json
string
JSON string representation
Example
const json = serializeNote(note);
await fs.writeFile('note.json', json);

deserializeNote

Deserialize a JSON string to a note.
import { deserializeNote } from '@shielded-x402/client';

const note = deserializeNote(value: string): ShieldedNote
value
string
required
JSON string from serializeNote
note
ShieldedNote
Deserialized note
Example
const json = await fs.readFile('note.json', 'utf8');
const note = deserializeNote(json);

console.log('Loaded note:', note.commitment);

Complete Example

import {
  ShieldedClientSDK,
  LocalNoteIndexer,
  buildWitnessFromCommitments,
  encryptNoteSymmetric,
  decryptNoteSymmetric
} from '@shielded-x402/client';
import { randomBytes } from 'node:crypto';

// Initialize SDK and indexer
const sdk = new ShieldedClientSDK({
  endpoint: RELAYER_URL,
  signer: async (message) => account.signMessage({ message })
});

const noteStore = new LocalNoteIndexer();
const nullifierSecrets = new Map<string, Hex>();
const encryptionKey = randomBytes(32);

// Deposit
const { note, leafIndex } = await sdk.deposit(100_000_000n, ownerPkHash);

noteStore.ingestDeposit(
  { commitment: note.commitment, leafIndex, amount: note.amount },
  note
);

const nullifierSecret = '0x...';
nullifierSecrets.set(note.commitment, nullifierSecret);

// Encrypt and store
const encrypted = encryptNoteSymmetric(note, encryptionKey);
await db.store(note.commitment, encrypted);

// Later: spend
const notes = noteStore.getNotes();
const spendableNote = notes.find(n => n.amount >= 40_000_000n);

if (spendableNote) {
  const commitments = noteStore.getCommitments();
  const witness = buildWitnessFromCommitments(commitments, spendableNote.leafIndex);
  const secret = nullifierSecrets.get(spendableNote.commitment);
  
  const prepared = await sdk.prepare402Payment(
    requirement,
    spendableNote,
    witness,
    secret
  );
  
  // Track spend
  noteStore.ingestSpend({
    merchantCommitment: prepared.response.merchantCommitment,
    changeCommitment: prepared.response.changeCommitment
  });
  
  // Store change note
  const changeNote = prepared.changeNote;
  noteStore.getNotes().push(changeNote);
  nullifierSecrets.set(changeNote.commitment, prepared.changeNullifierSecret);
  
  const encryptedChange = encryptNoteSymmetric(changeNote, encryptionKey);
  await db.store(changeNote.commitment, encryptedChange);
}

// Query balance
const allNotes = noteStore.getNotes();
const totalBalance = allNotes.reduce((sum, n) => sum + n.amount, 0n);
console.log('Total balance:', totalBalance);

Best Practices

LocalNoteIndexer stores notes in memory only. Persist to disk for production:
class PersistentNoteIndexer extends LocalNoteIndexer {
  async ingestDeposit(event: DepositEvent, note: ShieldedNote) {
    super.ingestDeposit(event, note);
    await this.saveToDb();
  }
  
  async ingestSpend(event: SpendEvent) {
    super.ingestSpend(event);
    await this.saveToDb();
  }
  
  private async saveToDb() {
    const state = {
      commitments: this.getCommitments(),
      notes: this.getNotes()
    };
    await db.saveNoteState(state);
  }
}
Nullifier secrets must be kept secret. Always encrypt them:
import { scryptSync } from 'node:crypto';

const password = await promptUserPassword();
const key = scryptSync(password, 'salt', 32);

const encryptedSecret = encryptNoteSymmetric(
  { ...note, rho: nullifierSecret },
  key
);
Mark notes as spent to avoid double-spend attempts:
interface TrackedNote extends ShieldedNote {
  spent: boolean;
  spentAt?: Date;
}

const notes: TrackedNote[] = [];

async function spendNote(note: TrackedNote) {
  if (note.spent) {
    throw new Error('Note already spent');
  }
  
  // ... perform spend
  
  note.spent = true;
  note.spentAt = new Date();
}
For O(1) lookups, index notes by commitment:
const notesByCommitment = new Map<string, ShieldedNote>();

noteStore.getNotes().forEach(note => {
  notesByCommitment.set(note.commitment, note);
});

const note = notesByCommitment.get(commitment);

Type Definitions

DepositEvent

interface DepositEvent {
  commitment: Hex;    // Note commitment
  leafIndex: number;  // Position in Merkle tree
  amount: bigint;     // Deposit amount
}

SpendEvent

interface SpendEvent {
  merchantCommitment: Hex;  // Merchant's note commitment
  changeCommitment: Hex;    // Spender's change note commitment
}

NoteState

interface NoteState {
  commitments: Hex[];        // All commitments in tree order
  notes: ShieldedNote[];     // All tracked notes
}

StoredNote

interface StoredNote {
  note: ShieldedNote;    // The note
  ciphertext?: Hex;      // Optional encrypted form
}

See Also

Build docs developers (and LLMs) love