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 ShieldedClientSDK class handles anonymous proof-backed x402 payment payload construction. It manages deposits, spending proofs, and zero-knowledge proof generation for privacy-preserving payments.

Constructor

const sdk = new ShieldedClientSDK(config: ShieldedClientConfig)
config
ShieldedClientConfig
required
Configuration object for the SDK

Methods

deposit

Create a new shielded note and optionally deposit onchain.
await sdk.deposit(
  amount: bigint,
  ownerPkHash: Hex
): Promise<{ note: ShieldedNote; txHash?: Hex; leafIndex: number }>
amount
bigint
required
Amount to deposit (in smallest denomination)
ownerPkHash
Hex
required
Hash of the owner’s public key (BN254 field element)
note
ShieldedNote
The created shielded note
txHash
Hex
Transaction hash if depositFn was provided and executed
leafIndex
number
Index in the Merkle tree

Example

const { note, txHash } = await sdk.deposit(
  100_000_000n, // 100 USDC (6 decimals)
  '0x1234...' // owner pkHash
);

console.log('Note commitment:', note.commitment);
console.log('Leaf index:', note.leafIndex);

buildSpendProof

Build a spend proof bundle without generating the actual zero-knowledge proof (placeholder proof 0x00).
sdk.buildSpendProof(
  params: SpendBuildParams
): SpendProofBundle
params
SpendBuildParams
required
Parameters for building the spend proof
SpendProofBundle
object

buildSpendProofWithProvider

Build a spend proof bundle and generate the actual zero-knowledge proof using the configured proofProvider.
await sdk.buildSpendProofWithProvider(
  params: SpendBuildParams
): Promise<SpendProofBundle>
Parameters and return type are identical to buildSpendProof, but the returned proof field will contain a real zero-knowledge proof instead of 0x00.

Example

const bundle = await sdk.buildSpendProofWithProvider({
  note,
  witness,
  nullifierSecret: '0xabc...',
  merchantPubKey: '0x1234...',
  merchantAddress: '0x5678...',
  amount: 40_000_000n,
  challengeNonce: '0x9999...',
  encryptedReceipt: '0x'
});

console.log('Proof:', bundle.response.proof);
console.log('Change amount:', bundle.changeNote.amount);

prepare402Payment

Prepare a complete 402 payment including proof generation and signature.
await sdk.prepare402Payment(
  requirement: PaymentRequirement,
  note: ShieldedNote,
  witness: MerkleWitness,
  nullifierSecret: Hex,
  baseHeaders?: HeadersInit
): Promise<Prepared402Payment>
requirement
PaymentRequirement
required
Payment requirement from 402 response (parsed from X-Payment-Required header)
note
ShieldedNote
required
Note to spend
witness
MerkleWitness
required
Merkle witness for the note
nullifierSecret
Hex
required
Nullifier secret for the note
baseHeaders
HeadersInit
Optional base headers to include in the payment request
Prepared402Payment
object

Example

const prepared = await sdk.prepare402Payment(
  requirement,
  note,
  witness,
  nullifierSecret,
  { 'User-Agent': 'MyAgent/1.0' }
);

// Use prepared.headers in retry request
const response = await fetch(url, {
  headers: prepared.headers
});

pay402

Sign a payment response and generate the X-Payment-Signature header.
await sdk.pay402(
  paymentResponse: ShieldedPaymentResponse,
  requirement: PaymentRequirement,
  challengeNonce: Hex
): Promise<{
  payload: string;
  signature: string;
  paymentSignatureHeader: string;
}>
paymentResponse
ShieldedPaymentResponse
required
The payment response containing proof and public inputs
requirement
PaymentRequirement
required
Original payment requirement
challengeNonce
Hex
required
Challenge nonce from the requirement
payload
string
JSON-stringified payment response
signature
string
Signature of the payload
paymentSignatureHeader
string
Complete X-Payment-Signature header value

Utility Functions

buildWitnessFromCommitments

Derive a Merkle witness from a list of commitments.
import { buildWitnessFromCommitments } from '@shielded-x402/client';

const witness = buildWitnessFromCommitments(
  commitments: Hex[],
  targetIndex: number
): MerkleWitness
commitments
Hex[]
required
Array of commitment hashes in tree order
targetIndex
number
required
Index of the note to prove (0-based)
MerkleWitness
object

Example

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

Proof Generation

Using a ProofProvider

To generate real zero-knowledge proofs, configure a ProofProvider:
import { createProofProvider } from '@shielded-x402/client';

// Create proof provider (loads bundled circuit)
const proofProvider = await createProofProvider();

const sdk = new ShieldedClientSDK({
  endpoint: RELAYER_URL,
  signer: async (message) => account.signMessage({ message }),
  proofProvider
});

Custom Circuit

Load a custom Noir circuit artifact:
import { createNoirJsProofProviderFromCircuit } from '@shielded-x402/client';
import circuit from './my-circuit.json';

const proofProvider = await createNoirJsProofProviderFromCircuit(circuit, {
  backendProofOptions: { verifierTarget: 'evm' }
});

Low-Level ProofProvider

Implement a custom proof provider:
import { createNoirJsProofProvider } from '@shielded-x402/client';
import { Noir } from '@noir-lang/noir_js';
import { UltraHonkBackend } from '@aztec/bb.js';

const noir = new Noir(circuit);
const backend = new UltraHonkBackend(circuit.bytecode);

const proofProvider = createNoirJsProofProvider({
  noir,
  backend,
  enforcePublicInputsMatch: true
});

Type Definitions

ShieldedNote

interface ShieldedNote {
  amount: bigint;
  rho: Hex;              // Random blinding factor
  pkHash: Hex;           // Owner public key hash
  commitment: Hex;       // Commitment = H(amount, rho, pkHash)
  leafIndex: number;     // Position in Merkle tree
}

MerkleWitness

interface MerkleWitness {
  root: Hex;             // Merkle root
  path: Hex[];           // Sibling hashes
  indexBits: number[];   // Binary path (0/1)
}

ShieldedPaymentResponse

interface ShieldedPaymentResponse {
  proof: Hex;                    // Zero-knowledge proof
  publicInputs: Hex[];           // [nullifier, root, merchantCommitment, changeCommitment, challengeHash, amount]
  nullifier: Hex;                // Note nullifier (prevents double-spend)
  root: Hex;                     // Merkle root
  merchantCommitment: Hex;       // Commitment for merchant note
  changeCommitment: Hex;         // Commitment for change note
  challengeHash: Hex;            // Hash of challenge parameters
  encryptedReceipt: Hex;         // Encrypted receipt data
  txHint: string;                // Transaction hint (e.g., 'leaf:0')
}

Best Practices

Nullifier secrets are critical for spending notes. Store them encrypted alongside note commitments in a secure wallet or database.
const nullifierSecretsByCommitment = new Map<string, Hex>();
nullifierSecretsByCommitment.set(note.commitment, changeNullifierSecret);
After each spend, store the returned change note and its nullifier secret for future transactions.
const prepared = await sdk.prepare402Payment(...);
noteStore.ingestSpend({
  merchantCommitment: prepared.response.merchantCommitment,
  changeCommitment: prepared.response.changeCommitment
});
nullifierSecrets.set(
  prepared.changeNote.commitment,
  prepared.changeNullifierSecret
);
The SDK enforces public input matching by default. Disable only if you trust the proof provider:
const provider = createNoirJsProofProvider({
  noir,
  backend,
  enforcePublicInputsMatch: false // Not recommended
});
All random values (rho, nullifierSecret) are automatically generated within the BN254 field modulus. When providing custom values, ensure they are < 21888242871839275222246405745257275088548364400416034343698204186575808495617n.

See Also

Build docs developers (and LLMs) love