{"version":3,"sources":["../src/index.ts","../src/keys.ts","../src/prover.ts","../src/registry.ts","../src/storage.ts","../src/types.ts","../src/encryption.ts","../src/witness.ts","../src/discovery.ts","../src/client.ts"],"sourcesContent":["// Public API surface of the Private Pool V2 SDK.\n//\n// Most files have unique top-level exports and are re-exported via\n// `export *`. A handful of names collide between modules — those are\n// re-exported explicitly here, with a documented canonical choice.\n//\n// For granular access (and smaller bundles), import from subpaths:\n//   import { ... } from '@relai-fi/private-pool-sdk/client'\n//   import { ... } from '@relai-fi/private-pool-sdk/keys'\n//   ...\n\n// ── Modules with unique exports ───────────────────────────────────────────\n\nexport * from './keys';\nexport * from './prover';\nexport * from './registry';\nexport * from './storage';\n\n// ── Modules with name collisions — selective re-exports ──────────────────\n\n// `types.ts` declares `interface DecryptedNote / OwnedNote / EncryptedNoteBlob`,\n// while `encryption.ts` and `discovery.ts` declare `type` aliases of the\n// same names with the actual runtime shape. The `type` aliases are the\n// canonical definitions; the interfaces in `types.ts` are kept for\n// backwards reference only and not re-exported here.\nexport {\n  POOL_DEPTH,\n  ASP_DEPTH,\n  USDC_DECIMALS,\n  type PaymentAddress,\n  type PaymentAddressString,\n  type MerklePath,\n  type DepositWitness,\n  type WithdrawWitness,\n  type JoinSplitWitness,\n  type DepositPublicSignals,\n  type WithdrawPublicSignals,\n  type JoinSplitPublicSignals,\n  type Groth16Proof,\n  type DepositTx,\n  type WithdrawTx,\n  type JoinSplitTx,\n  type PoolConfig,\n} from './types';\n\n// `encryption.ts` is the canonical home of the note-format types.\nexport * from './encryption';\n\n// `witness.ts` exports `deriveNullifier(args: {...})` — the comprehensive,\n// witness-shape variant. `discovery.ts` exports a smaller\n// `deriveNullifier(commitmentField, nullifierSkField)` overload — that one\n// is available via the `/discovery` subpath. The barrel exports the\n// witness version as canonical.\nexport {\n  fieldToBytes,\n  buildDepositWitness,\n  buildWithdrawWitness,\n  buildJoinSplitWitness,\n  recomputeMerkleRoot,\n  enrichOwnedNote,\n  deriveCommitment,\n  deriveNullifier,\n  merkleParent,\n  type DepositInputs,\n  type WithdrawInputs,\n  type JoinSplitInputs,\n} from './witness';\n\n// `discovery.ts` — re-export the high-level discovery API, but skip\n// `deriveNullifier` (already exported from witness.ts above) and the\n// `fieldToBytes32BE / poseidon1` re-exports (canonical home is keys.ts).\nexport {\n  fetchCommitmentLeaves,\n  fetchEncryptedNoteBlobs,\n  fetchNullifierStatus,\n  fetchAllNullifiers,\n  trialDecryptBlobs,\n  discoverOwnedNotes,\n  attachSpentStatus,\n  attachSpentStatusSequential,\n  totalUnspentBalance,\n  balanceBreakdown,\n  type OwnedNote,\n} from './discovery';\n\n// `client.ts` declares its own `fieldToBytes32BE` returning Buffer (used\n// internally for PDA derivation). The canonical Uint8Array version lives\n// in keys.ts and is exported via `export * from './keys'` above; the\n// Buffer version is intentionally not re-exported.\nexport {\n  loadConfig,\n  prepareDepositTransaction,\n  prepareWithdrawTransaction,\n  prepareWithdrawRelayPayload,\n  submitWithdrawViaRelay,\n  prepareJoinSplitTransaction,\n  prepareJoinSplitRelayPayload,\n  submitJoinSplitViaRelay,\n  relaySponsoredTransaction,\n  relayJoinSplitBlob,\n  type PrivatePoolV2Config,\n  type PrepareDepositArgs,\n  type PreparedDepositTx,\n  type PrepareWithdrawArgs,\n  type PreparedWithdrawTx,\n  type PreparedWithdrawRelay,\n  type WithdrawRelayPayload,\n  type PrepareJoinSplitArgs,\n  type PreparedJoinSplitTx,\n  type PreparedJoinSplitRelay,\n  type PrepareJoinSplitRelayArgs,\n  type JoinSplitRelayPayload,\n  type JoinSplitNoteRecipient,\n} from './client';\n","// Private Pool V2 — wallet-derived key tree (ADR-002).\n//\n//   root_sk      = blake2b256(walletSig)[:32]\n//   spending_sk  = blake2b256(root_sk || ASCII(\"relai-pool-v2/spend\"))\n//   viewing_sk   = blake2b256(root_sk || ASCII(\"relai-pool-v2/view\"))\n//   nullifier_sk = blake2b256(root_sk || ASCII(\"relai-pool-v2/null\"))\n//\n//   spending_pk  = Poseidon1(spending_sk_field) mod p   // field element used in commitments\n//   viewing_pk   = X25519.public_from(viewing_sk)        // 32-byte X25519 pubkey\n//\n//   PaymentAddress = base58( spending_pk_bytes_BE || viewing_pk_bytes )\n//\n// `*_sk` is the raw 32-byte blake2b output. `*_sk_field` is the same\n// bytes reduced mod the BN254 scalar field modulus, then re-emitted as\n// a bigint suitable for Poseidon / circuit witness inputs. The two\n// representations are kept side by side because:\n//   - the encryption layer (ADR-003) wants raw bytes for X25519\n//   - the circuit witness (Faza 1) wants field elements\n\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { x25519 } from '@noble/curves/ed25519';\nimport { buildPoseidon } from 'circomlibjs';\nimport bs58 from 'bs58';\n\nconst FIELD_MODULUS =\n  21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nconst TEXT_ENCODER = new TextEncoder();\nconst LABELS = {\n  spending: TEXT_ENCODER.encode('relai-pool-v2/spend'),\n  viewing: TEXT_ENCODER.encode('relai-pool-v2/view'),\n  nullifier: TEXT_ENCODER.encode('relai-pool-v2/null'),\n} as const;\n\nconst PAYMENT_ADDRESS_BYTES = 64; // spending_pk(32) + viewing_pk(32)\n\n// ── Hash helpers ─────────────────────────────────────────────────────────\n\nfunction concatBytes(...parts: Uint8Array[]): Uint8Array {\n  const total = parts.reduce((sum, p) => sum + p.length, 0);\n  const out = new Uint8Array(total);\n  let off = 0;\n  for (const p of parts) {\n    out.set(p, off);\n    off += p.length;\n  }\n  return out;\n}\n\nfunction ensureBytes(label: string, bytes: Uint8Array, expected: number): void {\n  if (bytes.length !== expected) {\n    throw new Error(`${label}: expected ${expected} bytes, got ${bytes.length}`);\n  }\n}\n\n/// 32-byte BE → bigint (reduced mod BN254 scalar field).\nexport function bytesToFieldBE(bytes: Uint8Array): bigint {\n  if (bytes.length === 0) return 0n;\n  let v = 0n;\n  for (const b of bytes) v = (v << 8n) + BigInt(b);\n  return ((v % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n/// bigint → 32-byte BE buffer. Throws if the bigint exceeds 256 bits\n/// (shouldn't happen for field elements, but worth being explicit).\nexport function fieldToBytes32BE(value: bigint): Uint8Array {\n  if (value < 0n) {\n    value = ((value % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n  }\n  const hex = value.toString(16).padStart(64, '0');\n  if (hex.length > 64) {\n    throw new Error(`fieldToBytes32BE: value exceeds 32 bytes (${hex.length / 2})`);\n  }\n  return Uint8Array.from(Buffer.from(hex, 'hex'));\n}\n\n// ── Poseidon singleton ───────────────────────────────────────────────────\n\nlet poseidonInstance: ReturnType<typeof buildPoseidon> extends Promise<infer T> ? T | null : never = null;\nasync function getPoseidon() {\n  if (!poseidonInstance) {\n    poseidonInstance = await buildPoseidon();\n  }\n  return poseidonInstance!;\n}\n\n/// Poseidon-Bn254X5 of one field input → bigint mod p.\nexport async function poseidon1(input: bigint): Promise<bigint> {\n  // circomlibjs has no published .d.ts so the `buildPoseidon` return is\n  // typed as `unknown` upstream. The runtime shape is well-known and\n  // stable; cast to `any` for the call sites.\n  const p = (await getPoseidon()) as any;\n  const h = p([input]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// ── Sub-key derivation ───────────────────────────────────────────────────\n\n/// `root_sk = blake2b256(walletSig)[:32]`. The wallet signature is\n/// produced off-screen by signing the canonical challenge message\n/// (`PRIVATE_POOL_V2_KEY_CHALLENGE`) — the caller is expected to handle\n/// the wallet popup itself.\nexport function deriveRootSk(walletSignature: Uint8Array): Uint8Array {\n  if (!walletSignature || walletSignature.length === 0) {\n    throw new Error('deriveRootSk: walletSignature must be non-empty');\n  }\n  return blake2b(walletSignature, { dkLen: 32 });\n}\n\n/// `<sub_sk> = blake2b256(root_sk || label)`.\nexport function deriveSubkey(rootSk: Uint8Array, label: Uint8Array): Uint8Array {\n  ensureBytes('rootSk', rootSk, 32);\n  return blake2b(concatBytes(rootSk, label), { dkLen: 32 });\n}\n\nexport type PrivatePoolKeyMaterial = {\n  /// Raw 32-byte blake2b sub-keys.\n  rootSk: Uint8Array;\n  spendingSk: Uint8Array;\n  viewingSk: Uint8Array;\n  nullifierSk: Uint8Array;\n  /// Field-element views of the spending/nullifier secrets — what the\n  /// circuit witness consumes. spending_sk_field / nullifier_sk_field\n  /// are `bytesToFieldBE(spendingSk)` etc.\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  /// Public counterparts.\n  spendingPkField: bigint;        // Poseidon1(spendingSkField)\n  viewingPk: Uint8Array;          // X25519.public(viewingSk)\n  /// Encoded payment address, base58( spending_pk_bytes || viewing_pk_bytes ).\n  paymentAddress: string;\n};\n\n/// One-shot key derivation. Returns the full sub-key tree + payment\n/// address. Idempotent; safe to re-run on every page load.\nexport async function derivePrivatePoolKeys(\n  walletSignature: Uint8Array,\n): Promise<PrivatePoolKeyMaterial> {\n  const rootSk = deriveRootSk(walletSignature);\n  const spendingSk = deriveSubkey(rootSk, LABELS.spending);\n  const viewingSk = deriveSubkey(rootSk, LABELS.viewing);\n  const nullifierSk = deriveSubkey(rootSk, LABELS.nullifier);\n\n  const spendingSkField = bytesToFieldBE(spendingSk);\n  const nullifierSkField = bytesToFieldBE(nullifierSk);\n  const spendingPkField = await poseidon1(spendingSkField);\n  const viewingPk = x25519.getPublicKey(viewingSk);\n\n  const paymentAddress = encodePaymentAddress(spendingPkField, viewingPk);\n\n  return {\n    rootSk,\n    spendingSk,\n    viewingSk,\n    nullifierSk,\n    spendingSkField,\n    nullifierSkField,\n    spendingPkField,\n    viewingPk,\n    paymentAddress,\n  };\n}\n\n// ── F2: code-derived key material (private payment codes) ──────────────\n//\n// A one-time `code_seed` (32 bytes) deterministically generates the\n// same key tree as a wallet signature would. The seed plays exactly\n// the role of `walletSignature` in `derivePrivatePoolKeys` — same\n// blake2b labels, same Poseidon derivation, same X25519 viewing key.\n//\n// Why we reuse the existing tree shape:\n//   - The credit-pool leaf binds `owner_pk = spending_pk_field`.\n//     Whatever generates `spending_sk_field` and lets us compute its\n//     Poseidon-1 image works as a \"recipient identity.\" The wallet\n//     case derives via signature; the bearer-code case derives via\n//     seed. Same circuit, same prover, same on-chain verifier.\n//   - The encrypted note blob is keyed by `viewing_pk` (X25519). The\n//     redeemer needs to derive the matching `viewing_sk` from the\n//     same seed at redeem time.\n//\n// Why this is safe:\n//   - The seed is generated client-side and never leaves the issuer's\n//     device (except via the URL/QR they hand to the redeemer).\n//   - Anyone with the seed can re-derive all keys and cash out the\n//     parked leaf. That's exactly the bearer-token semantics we want.\n//   - The seed is large enough (32 bytes = 256 bits) that brute-force\n//     guessing the seed of a specific leaf is not feasible.\n//\n// Important difference from `derivePrivatePoolKeys`:\n//   - No `paymentAddress` is computed — the seed itself encodes both\n//     halves of the addressing, and we don't want to leak a payment\n//     address that ties the code to a long-lived identity.\n\n/// Two seed formats, picked by the issuer per code:\n///\n///   • `long` (default; 32B / 256-bit)\n///     - Encoded as base58 in URL fragment (~44 chars).\n///     - Astronomically brute-force resistant. Industry-standard for\n///       \"fire and forget\" bearer tokens.\n///     - Hard to dictate by voice/SMS — must be shared as URL/QR.\n///\n///   • `short` (16-char Wi-Fi-style base32, 80-bit)\n///     - Encoded as `XXXX-XXXX-XXXX-XXXX` for easy reading.\n///     - ~80 bits of entropy. At 10⁹ hashes/s a single specific code\n///       takes ≈ 38,000 years to brute-force; the on-chain leaf set\n///       gates each guess by Poseidon-derived owner_pk lookup, so\n///       practical attack cost is much higher. Comfortable for typical\n///       consumer amounts; we'd recommend `long` for institutional /\n///       very high value.\n///     - Can be dictated, written on paper, typed into a separate\n///       device — same UX feel as a Wi-Fi password.\n///\n/// Both share the SAME key-derivation algorithm — we just hand\n/// `deriveRootSk` a different number of entropy bytes. The on-chain\n/// proof is identical for both formats; format choice is purely an\n/// issuer-side UX dial.\nconst LONG_SEED_BYTES = 32;\nconst SHORT_SEED_BYTES = 10; // 10 × 8 = 80 bits, encodes as 16 base32 chars\n\nexport type CodeSeedFormat = 'short' | 'long';\n\nexport type CodeKeyMaterial = {\n  /// Raw seed; the only thing the issuer must guard + later share.\n  /// 10 bytes for `short`, 32 bytes for `long`. The on-chain derivation\n  /// works identically either way (blake2b is variable-input).\n  codeSeed: Uint8Array;\n  /// Which format the seed was generated as. Lets callers re-encode\n  /// the same seed back to the right surface (base32 vs base58)\n  /// without recomputing entropy.\n  format: CodeSeedFormat;\n  /// Key tree, identical shape to wallet-derived material.\n  rootSk: Uint8Array;\n  spendingSk: Uint8Array;\n  viewingSk: Uint8Array;\n  nullifierSk: Uint8Array;\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  spendingPkField: bigint;\n  viewingPk: Uint8Array;\n};\n\n/// Generate a fresh code seed using a CSPRNG. The issuer holds this\n/// locally (in IndexedDB if they want cancel capability) and shares\n/// it out-of-band — usually as part of a URL fragment so it never\n/// touches the server.\n///\n/// `format` defaults to `'long'` for safety. Callers wanting the\n/// dictatable Wi-Fi-style format pass `'short'` explicitly.\nexport function generateCodeSeed(format: CodeSeedFormat = 'long'): Uint8Array {\n  const bytes = format === 'short' ? SHORT_SEED_BYTES : LONG_SEED_BYTES;\n  const out = new Uint8Array(bytes);\n  // `crypto` is the same global the rest of the SDK uses for\n  // blinding randomness; available in both browser and Node ≥ 19.\n  crypto.getRandomValues(out);\n  return out;\n}\n\n/// Detect the format of an existing seed by its length. Useful when a\n/// caller has the bytes but not the format flag (e.g. after re-deriving\n/// from a decoded code).\nexport function detectCodeSeedFormat(codeSeed: Uint8Array): CodeSeedFormat {\n  if (codeSeed.length === SHORT_SEED_BYTES) return 'short';\n  if (codeSeed.length === LONG_SEED_BYTES) return 'long';\n  throw new Error(\n    `detectCodeSeedFormat: unsupported seed length ${codeSeed.length} (expected ${SHORT_SEED_BYTES} or ${LONG_SEED_BYTES})`,\n  );\n}\n\n/// Deterministic key derivation from a code seed. Same algorithm as\n/// the wallet-derived path; the only difference is the entropy source\n/// (seed vs wallet signature). Idempotent — calling it twice with the\n/// same seed yields exactly the same key material.\n///\n/// Accepts both short (10-byte) and long (32-byte) seeds; the\n/// `blake2b(seed)` rooting step doesn't care about input length, and\n/// downstream derivation is identical.\nexport async function deriveCodeKeys(\n  codeSeed: Uint8Array,\n): Promise<CodeKeyMaterial> {\n  if (!codeSeed) {\n    throw new Error('deriveCodeKeys: codeSeed required');\n  }\n  if (codeSeed.length !== SHORT_SEED_BYTES && codeSeed.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `deriveCodeKeys: codeSeed must be ${SHORT_SEED_BYTES} or ${LONG_SEED_BYTES} bytes (got ${codeSeed.length})`,\n    );\n  }\n  const format: CodeSeedFormat = codeSeed.length === SHORT_SEED_BYTES ? 'short' : 'long';\n  const rootSk = deriveRootSk(codeSeed);\n  const spendingSk = deriveSubkey(rootSk, LABELS.spending);\n  const viewingSk = deriveSubkey(rootSk, LABELS.viewing);\n  const nullifierSk = deriveSubkey(rootSk, LABELS.nullifier);\n\n  const spendingSkField = bytesToFieldBE(spendingSk);\n  const nullifierSkField = bytesToFieldBE(nullifierSk);\n  const spendingPkField = await poseidon1(spendingSkField);\n  const viewingPk = x25519.getPublicKey(viewingSk);\n\n  return {\n    codeSeed,\n    format,\n    rootSk,\n    spendingSk,\n    viewingSk,\n    nullifierSk,\n    spendingSkField,\n    nullifierSkField,\n    spendingPkField,\n    viewingPk,\n  };\n}\n\n// ── F2: code-seed codec for URLs (Bech32-style) ────────────────────────\n//\n// We encode the 32B seed as base58 — same alphabet the SDK already uses\n// for the payment address, no new dependencies, ~44 ASCII chars. The\n// short fixed length lets us validate decoded seeds with a single\n// length check. A Bech32 checksum is overkill at this size (a typo\n// would already fail the field-element derivation at park-redeem time,\n// just less helpfully) — kept simple to reduce dependency surface.\n\n/// Encode a 32B \"long\" seed for URL / QR transport. Result is base58\n/// of the raw seed bytes — no version byte, no checksum. The caller\n/// is expected to put this in the URL fragment (`#seed=…`) so it never\n/// hits server logs.\nexport function encodeCodeSeed(seed: Uint8Array): string {\n  if (!seed || seed.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `encodeCodeSeed: seed must be exactly ${LONG_SEED_BYTES} bytes (got ${seed?.length ?? 0})`,\n    );\n  }\n  const encoder = (bs58 as { encode?: (b: Uint8Array) => string }).encode\n    ?? (bs58 as { default?: { encode: (b: Uint8Array) => string } }).default?.encode;\n  if (!encoder) {\n    throw new Error('encodeCodeSeed: bs58 encoder missing — incompatible bs58 version');\n  }\n  return encoder(seed);\n}\n\n/// Inverse of `encodeCodeSeed`. Throws on malformed input (wrong\n/// length, non-base58 chars). The caller may want to wrap this in a\n/// try/catch to surface a user-friendly \"invalid code\" error.\nexport function decodeCodeSeed(encoded: string): Uint8Array {\n  if (typeof encoded !== 'string' || encoded.length === 0) {\n    throw new Error('decodeCodeSeed: input must be a non-empty string');\n  }\n  const decoder = (bs58 as { decode?: (s: string) => Uint8Array }).decode\n    ?? (bs58 as { default?: { decode: (s: string) => Uint8Array } }).default?.decode;\n  if (!decoder) {\n    throw new Error('decodeCodeSeed: bs58 decoder missing — incompatible bs58 version');\n  }\n  const out = decoder(encoded);\n  if (out.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `decodeCodeSeed: decoded ${out.length} bytes, expected ${LONG_SEED_BYTES}`,\n    );\n  }\n  return out;\n}\n\n// ── Short-code (Wi-Fi-style base32) codec ──────────────────────────────\n//\n// 10 bytes → 16 base32 characters → rendered as `XXXX-XXXX-XXXX-XXXX`.\n// RFC 4648 base32 alphabet (`A-Z2-7`) is deliberately confusion-free —\n// no 0/O, 1/I/l ambiguity. Issuer can dictate the code over the phone\n// without losing 25 minutes to \"is that an O or zero\" rounds. Dashes\n// are decorative; the decoder strips them.\n//\n// Why base32 over base58 here:\n//   • 5 bits per character → fixed 16-char output for 80 bits, with\n//     no awkward \"≈ 14 chars\" rounding.\n//   • Voice-friendly alphabet.\n//   • Dashes optional in the input — we tolerate either form.\n\nconst BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n\nfunction encodeBase32(bytes: Uint8Array): string {\n  let bits = 0;\n  let value = 0;\n  let out = '';\n  for (const b of bytes) {\n    value = (value << 8) | b;\n    bits += 8;\n    while (bits >= 5) {\n      out += BASE32_ALPHABET[(value >>> (bits - 5)) & 0x1f];\n      bits -= 5;\n    }\n  }\n  if (bits > 0) {\n    out += BASE32_ALPHABET[(value << (5 - bits)) & 0x1f];\n  }\n  return out;\n}\n\nfunction decodeBase32(s: string): Uint8Array {\n  // Normalise: uppercase, drop dashes/whitespace, validate alphabet.\n  const cleaned = s.toUpperCase().replace(/[\\s-]/g, '');\n  if (cleaned.length === 0) throw new Error('decodeBase32: empty input');\n  for (const ch of cleaned) {\n    if (BASE32_ALPHABET.indexOf(ch) === -1) {\n      throw new Error(`decodeBase32: illegal character '${ch}' (allowed: A-Z, 2-7)`);\n    }\n  }\n  let bits = 0;\n  let value = 0;\n  const out: number[] = [];\n  for (const ch of cleaned) {\n    value = (value << 5) | BASE32_ALPHABET.indexOf(ch);\n    bits += 5;\n    if (bits >= 8) {\n      out.push((value >>> (bits - 8)) & 0xff);\n      bits -= 8;\n    }\n  }\n  return Uint8Array.from(out);\n}\n\n/// Format a raw 10-byte seed into the user-facing\n/// `XXXX-XXXX-XXXX-XXXX` representation. Round-trip with\n/// `decodeShortCode` is exact.\nexport function encodeShortCode(seed: Uint8Array): string {\n  if (!seed || seed.length !== SHORT_SEED_BYTES) {\n    throw new Error(\n      `encodeShortCode: seed must be exactly ${SHORT_SEED_BYTES} bytes (got ${seed?.length ?? 0})`,\n    );\n  }\n  const flat = encodeBase32(seed); // 16 chars\n  // Insert a hyphen every 4 chars for readability. `XXXX-XXXX-XXXX-XXXX`.\n  return flat.match(/.{1,4}/g)!.join('-');\n}\n\n/// Inverse of `encodeShortCode`. Accepts the canonical\n/// `XXXX-XXXX-XXXX-XXXX` form, plain `XXXXXXXXXXXXXXXX`, lower-case,\n/// or any combination with extra whitespace — anything a user might\n/// dictate or paste with mild messiness. Throws on illegal characters\n/// or wrong length.\nexport function decodeShortCode(encoded: string): Uint8Array {\n  if (typeof encoded !== 'string') {\n    throw new Error('decodeShortCode: input must be a string');\n  }\n  const out = decodeBase32(encoded);\n  if (out.length !== SHORT_SEED_BYTES) {\n    throw new Error(\n      `decodeShortCode: decoded ${out.length} bytes, expected ${SHORT_SEED_BYTES} (16 base32 chars)`,\n    );\n  }\n  return out;\n}\n\n/// Auto-detect the format of an encoded code string and return the raw\n/// seed bytes. Convenience for redeem flows that accept either format\n/// from a single text input.\n///\n/// Detection rules:\n///   • Strip whitespace + dashes. If the result is exactly 16 chars in\n///     the base32 alphabet → short code, decoded as such.\n///   • Otherwise try base58 → 32-byte long seed.\n///   • Anything else → throw with a hint.\nexport function decodeCodeAuto(encoded: string): {\n  seed: Uint8Array;\n  format: CodeSeedFormat;\n} {\n  if (typeof encoded !== 'string' || encoded.trim().length === 0) {\n    throw new Error('decodeCodeAuto: empty input');\n  }\n  const cleaned = encoded.trim();\n  const base32Cleaned = cleaned.toUpperCase().replace(/[\\s-]/g, '');\n  // Short code: exactly 16 base32 chars.\n  if (\n    base32Cleaned.length === 16\n    && [...base32Cleaned].every((c) => BASE32_ALPHABET.indexOf(c) !== -1)\n  ) {\n    return { seed: decodeShortCode(cleaned), format: 'short' };\n  }\n  // Otherwise assume base58 long. The base58 decoder will throw if\n  // the alphabet doesn't match.\n  try {\n    return { seed: decodeCodeSeed(cleaned), format: 'long' };\n  } catch (err) {\n    throw new Error(\n      `decodeCodeAuto: not a valid short (16 char) or long (~44 char) code: ${err instanceof Error ? err.message : String(err)}`,\n    );\n  }\n}\n\n// ── Payment address codec ────────────────────────────────────────────────\n\n/// `base58(spending_pk_bytes_BE || viewing_pk_bytes)` — the human-shareable\n/// \"send notes to me\" handle. 64 bytes encoded → ~88 base58 chars.\nexport function encodePaymentAddress(spendingPkField: bigint, viewingPk: Uint8Array): string {\n  ensureBytes('viewingPk', viewingPk, 32);\n  const spendingPkBytes = fieldToBytes32BE(spendingPkField);\n  const concat = concatBytes(spendingPkBytes, viewingPk);\n  // bs58@4: bs58.encode(buffer)\n  // bs58@5+: bs58.default.encode(buffer)\n  const encoder = (bs58 as { encode?: (b: Uint8Array) => string }).encode\n    ?? (bs58 as { default?: { encode: (b: Uint8Array) => string } }).default?.encode;\n  if (!encoder) throw new Error('encodePaymentAddress: bs58.encode not found');\n  return encoder(concat);\n}\n\nexport function parsePaymentAddress(address: string): {\n  spendingPkField: bigint;\n  viewingPk: Uint8Array;\n} {\n  const decoder = (bs58 as { decode?: (s: string) => Uint8Array }).decode\n    ?? (bs58 as { default?: { decode: (s: string) => Uint8Array } }).default?.decode;\n  if (!decoder) throw new Error('parsePaymentAddress: bs58.decode not found');\n  const decoded = decoder(address);\n  if (decoded.length !== PAYMENT_ADDRESS_BYTES) {\n    throw new Error(\n      `parsePaymentAddress: decoded length ${decoded.length} != ${PAYMENT_ADDRESS_BYTES}`,\n    );\n  }\n  return {\n    spendingPkField: bytesToFieldBE(decoded.slice(0, 32)),\n    viewingPk: decoded.slice(32, 64),\n  };\n}\n\n// ── Wallet challenge ─────────────────────────────────────────────────────\n\n/// Canonical challenge message the wallet signs to derive the root_sk.\n/// Versioned + namespaced so a sig made for V2 can never be reused for\n/// any other RelAI feature (or any other app's \"sign this\" flow).\nexport const PRIVATE_POOL_V2_KEY_CHALLENGE =\n  'RelAI Private Pool V2 — derive private balance keys.\\n\\nThis signature unlocks your hidden-amount balance under the /app/private surface. It does NOT authorize any spend on its own.';\n\n/// Build the wallet challenge bytes for `signMessage` (UTF-8 of the\n/// human-readable challenge above; matches what the on-screen prompt\n/// will display).\nexport function buildKeyChallengeMessage(): Uint8Array {\n  return TEXT_ENCODER.encode(PRIVATE_POOL_V2_KEY_CHALLENGE);\n}\n","// Private Pool V2 — browser-side Groth16 prover wrapper.\n//\n// One callable per circuit. Each callable:\n//   1. Lazy-loads snarkjs (esm side-import; ~700 KB once cached)\n//   2. Calls `groth16.fullProve(witness, wasmUrl, zkeyUrl)`\n//   3. Packs the proof into the 256-byte layout the on-chain verifier\n//      expects (matches V4 `encodeGroth16Proof` rationale; see\n//      `shielded-browser-prover.ts` for the EVM-style G2 swap details)\n//   4. Returns `{ proofBytes, publicSignals, proveMs }`\n//\n// The wrapper is intentionally NOT a Web Worker (yet). snarkjs runs the\n// WASM witness generator on the main thread; for joinsplit (~33k constraints)\n// fullProve takes ~1s on an M1, ~3-5s on a 5-yr-old laptop. UI freeze is\n// noticeable but tolerable as a v1 — Faza 6 promotes this to a worker\n// once we have UX progress callbacks.\n//\n// Wasm/zkey URLs default to the staged artifacts under `/zk/private-pool-v2/`\n// (the `zk:build-private-pool-v2` script copies `Deposit.wasm`,\n// `deposit_final.zkey`, etc. into `frontend/public/zk/...`).\n\nimport type {\n  DepositWitness,\n  WithdrawWitness,\n  JoinSplitWitness,\n  CashoutProofWitness,\n} from './types';\n\ntype SnarkJsModule = typeof import('snarkjs');\ntype Groth16Proof = {\n  pi_a: [string, string, string];\n  pi_b: [[string, string], [string, string], [string, string]];\n  pi_c: [string, string, string];\n  protocol?: string;\n  curve?: string;\n};\n\nconst DEFAULT_ARTIFACT_BASE = '/zk/private-pool-v2';\n\nconst ARTIFACTS = {\n  deposit: {\n    wasm: 'deposit.wasm',\n    zkey: 'deposit.zkey',\n  },\n  withdraw: {\n    wasm: 'withdraw.wasm',\n    zkey: 'withdraw.zkey',\n  },\n  joinsplit: {\n    wasm: 'joinsplit2x2.wasm',\n    zkey: 'joinsplit2x2.zkey',\n  },\n  /// ADR-012 V3 — cashout from credit pool tree. Smaller circuit\n  /// (~7k constraints, depth-24 tree, no ASP layer) so this prover\n  /// runs noticeably faster than Withdraw on mid-range hardware.\n  cashoutproof: {\n    wasm: 'cashoutproof.wasm',\n    zkey: 'cashoutproof.zkey',\n  },\n} as const;\n\nexport type CircuitName = keyof typeof ARTIFACTS;\n\n// ── snarkjs loader ───────────────────────────────────────────────────────\n\nlet snarkjsModulePromise: Promise<SnarkJsModule> | null = null;\nasync function loadSnarkJsModule(): Promise<SnarkJsModule> {\n  if (!snarkjsModulePromise) {\n    // Dynamic import keeps the ~700 KB snarkjs bundle out of the\n    // initial JS payload — only loaded when the user actually starts\n    // a deposit / withdraw / joinsplit flow.\n    snarkjsModulePromise = import('snarkjs') as Promise<SnarkJsModule>;\n  }\n  return snarkjsModulePromise;\n}\n\n// ── Proof byte packing ──────────────────────────────────────────────────\n//\n// `groth16.exportSolidityCallData` produces the EVM-style ordering with\n// G2 Fp2 swap. Solana's alt_bn128_pairing syscall uses the same\n// convention, so we forward the swapped output as-is. (Verified end-to-end\n// by V4 ASP integration AND by the V2 E2E scripts in\n// `contracts/scripts/private-pool-v2/`.)\n\nfunction fieldToBytes32BE(value: bigint): Uint8Array {\n  const hex = value.toString(16).padStart(64, '0');\n  return Uint8Array.from(Buffer.from(hex, 'hex'));\n}\n\nasync function packProofForSolana(\n  snarkjs: SnarkJsModule,\n  proof: Groth16Proof,\n  publicSignals: string[],\n): Promise<Uint8Array> {\n  if (typeof snarkjs.groth16.exportSolidityCallData !== 'function') {\n    throw new Error('snarkjs.groth16.exportSolidityCallData unavailable');\n  }\n  const rawCalldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals);\n  const parsed = JSON.parse(`[${rawCalldata}]`) as [\n    [string, string],\n    [[string, string], [string, string]],\n    [string, string],\n    string[],\n  ];\n  const [a, b, c] = parsed;\n  const out = new Uint8Array(256);\n  out.set(fieldToBytes32BE(BigInt(a[0])), 0);\n  out.set(fieldToBytes32BE(BigInt(a[1])), 32);\n  out.set(fieldToBytes32BE(BigInt(b[0][0])), 64);\n  out.set(fieldToBytes32BE(BigInt(b[0][1])), 96);\n  out.set(fieldToBytes32BE(BigInt(b[1][0])), 128);\n  out.set(fieldToBytes32BE(BigInt(b[1][1])), 160);\n  out.set(fieldToBytes32BE(BigInt(c[0])), 192);\n  out.set(fieldToBytes32BE(BigInt(c[1])), 224);\n  return out;\n}\n\n// ── Generic prove ────────────────────────────────────────────────────────\n\nexport type ProveResult = {\n  proofBytes: Uint8Array; // 256-byte Solana layout\n  publicSignals: string[]; // decimal field-element strings, in circuit order\n  proveMs: number;\n};\n\nexport type ProveOptions = {\n  /// Override the default `/zk/private-pool-v2/` artifact base. Useful\n  /// for hosting artifacts on a CDN or for swapping in dev artifacts.\n  artifactBaseUrl?: string;\n};\n\nasync function proveCircuit(\n  circuit: CircuitName,\n  witness: Record<string, unknown>,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  const base = (options.artifactBaseUrl ?? DEFAULT_ARTIFACT_BASE).replace(/\\/+$/, '');\n  const wasm = `${base}/${ARTIFACTS[circuit].wasm}`;\n  const zkey = `${base}/${ARTIFACTS[circuit].zkey}`;\n  const snarkjs = await loadSnarkJsModule();\n  const t0 = (typeof performance !== 'undefined' ? performance : Date).now();\n  const { proof, publicSignals } = (await snarkjs.groth16.fullProve(witness, wasm, zkey)) as {\n    proof: Groth16Proof;\n    publicSignals: string[];\n  };\n  const proofBytes = await packProofForSolana(snarkjs, proof, publicSignals);\n  const proveMs = ((typeof performance !== 'undefined' ? performance : Date).now()) - t0;\n  return { proofBytes, publicSignals, proveMs };\n}\n\n// ── Per-circuit thin wrappers (typed witnesses) ──────────────────────────\n\nexport async function proveDeposit(\n  witness: DepositWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('deposit', witness as unknown as Record<string, unknown>, options);\n}\n\nexport async function proveWithdraw(\n  witness: WithdrawWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('withdraw', witness as unknown as Record<string, unknown>, options);\n}\n\nexport async function proveJoinSplit(\n  witness: JoinSplitWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('joinsplit', witness as unknown as Record<string, unknown>, options);\n}\n\n/// ADR-012 V3 — cashout from credit pool tree.\nexport async function proveCashoutProof(\n  witness: CashoutProofWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('cashoutproof', witness as unknown as Record<string, unknown>, options);\n}\n\n/// Untyped escape hatch — useful for benchmarks or for circuits not yet\n/// type-modelled in `private-pool-types.ts`.\nexport async function proveAny(\n  circuit: CircuitName,\n  witness: Record<string, unknown>,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit(circuit, witness, options);\n}\n\n/// Hex preview helper for the proof bytes — handy for logging without\n/// dumping all 256 bytes.\nexport function proofPreview(bytes: Uint8Array): string {\n  if (bytes.length === 0) return '0x';\n  const head = Buffer.from(bytes.slice(0, 8)).toString('hex');\n  const tail = Buffer.from(bytes.slice(-8)).toString('hex');\n  return `0x${head}…${tail} (${bytes.length} bytes)`;\n}\n","// Private Pool V2 — public key registry client (privacy-wallet pattern).\n//\n// Maps `wallet_pubkey → (viewing_pk, spending_pk_field, payment_address)`\n// so a sender can do a private transfer using the recipient's REGULAR\n// Solana wallet pubkey instead of asking them for a long payment address.\n//\n// Registry stores only PUBLIC key material — no privacy leak. Per-payment\n// unlinkability stays intact via the ADR-008 stealth address mechanism.\n//\n// Usage:\n//   - On `/app/private` after `derivePrivatePoolKeys`, call\n//     `registerInRegistry({ ..., signature })` once. Idempotent — subsequent\n//     calls just refresh the `registered_at` timestamp.\n//   - In send flows (`/app/private` Send, `/app/send` instant), call\n//     `lookupInRegistry({ network, walletPubkey })` to resolve a wallet\n//     pubkey into the keys needed for encryption.\n//\n// Both functions throw on network errors; lookup returns `null` on 404\n// (= \"this wallet hasn't registered yet\").\n\nimport type { PrivatePoolKeyMaterial } from './keys'\n\nexport type RegistryEntry = {\n  network: string\n  wallet_pubkey: string\n  viewing_pk_hex: string\n  spending_pk_field: string\n  payment_address: string\n  registered_at: string\n}\n\nfunction buildUrl(apiBaseUrl: string, path: string): string {\n  return new URL(path, apiBaseUrl.endsWith('/') ? apiBaseUrl : apiBaseUrl + '/').toString()\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n  let hex = ''\n  for (const b of bytes) hex += b.toString(16).padStart(2, '0')\n  return hex\n}\n\n/// Build the time-bounded UPDATE challenge. MUST match\n/// `server/src/routes/private-pool-v2.js` PATH B (audit C-5).\n/// Used when re-registering an existing wallet with NEW keys —\n/// without this, a leaked PATH-A signature could forever overwrite\n/// the victim's registry entry and redirect future payments to the\n/// attacker.\nexport function buildRegistryUpdateChallenge(params: {\n  walletPubkey: string\n  paymentAddress: string\n  timestampMs: number\n}): Uint8Array {\n  const message =\n    'RelAI Private Pool V2 — registry UPDATE\\n\\n'\n    + `wallet=${params.walletPubkey}\\n`\n    + `payment_address=${params.paymentAddress}\\n`\n    + `timestamp=${params.timestampMs}\\n`\n  return new TextEncoder().encode(message)\n}\n\n/// POST registry entry — must be called by the wallet owner with their\n/// signature over the canonical key challenge (the same `signature` that\n/// `derivePrivatePoolKeys` was given). Backend Ed25519-verifies against\n/// `walletPubkey`, so no impersonation is possible.\n///\n/// `signature` is the raw `Uint8Array` returned by `wallet.signMessage()`\n/// at onboarding time. Caller is responsible for passing it in.\n///\n/// For RE-registration with NEW keys (audit C-5), the backend requires\n/// a fresh time-bounded UPDATE signature. Pass `updateSignMessage` as\n/// well; the SDK will probe the existing entry and prompt the wallet\n/// for the second sig only when needed (so first-time onboarding stays\n/// at one popup). When `updateSignMessage` is omitted, the SDK falls\n/// back to PATH A and the backend will reject any update-with-different-keys\n/// request with `update_timestamp_invalid` — the caller has to retry\n/// with `updateSignMessage` provided.\nexport async function registerInRegistry(args: {\n  apiBaseUrl: string\n  network: string\n  walletPubkey: string\n  keys: PrivatePoolKeyMaterial\n  signature: Uint8Array\n  /// Optional. When provided, used to sign the UPDATE challenge IF the\n  /// existing registry entry has a different `payment_address` than\n  /// the one being submitted. Skipped on first-time registration.\n  updateSignMessage?: (message: Uint8Array) => Promise<Uint8Array>\n}): Promise<RegistryEntry> {\n  const url = buildUrl(args.apiBaseUrl, 'api/private-pool/v2/registry')\n\n  // Pre-flight: is there an existing entry, and does its payment_address\n  // differ from what we're about to submit? If yes → UPDATE path.\n  let needsUpdateSig = false\n  if (args.updateSignMessage) {\n    try {\n      const existing = await lookupInRegistry({\n        apiBaseUrl: args.apiBaseUrl,\n        network: args.network,\n        walletPubkey: args.walletPubkey,\n      })\n      if (existing && existing.payment_address !== args.keys.paymentAddress) {\n        needsUpdateSig = true\n      }\n    } catch {\n      // Lookup failure → assume first-time registration (matches\n      // backend's PATH A which is more permissive).\n    }\n  }\n\n  let signatureHex: string\n  let updateTimestamp: number | undefined\n  if (needsUpdateSig && args.updateSignMessage) {\n    updateTimestamp = Date.now()\n    const challenge = buildRegistryUpdateChallenge({\n      walletPubkey: args.walletPubkey,\n      paymentAddress: args.keys.paymentAddress,\n      timestampMs: updateTimestamp,\n    })\n    const sig = await args.updateSignMessage(challenge)\n    if (!sig || sig.length !== 64) {\n      throw new Error(`update signature: expected 64 bytes, got ${sig?.length ?? 0}`)\n    }\n    signatureHex = bytesToHex(sig)\n  } else {\n    signatureHex = bytesToHex(args.signature)\n  }\n\n  const r = await fetch(url, {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify({\n      network: args.network,\n      wallet_pubkey: args.walletPubkey,\n      viewing_pk_hex: bytesToHex(args.keys.viewingPk),\n      spending_pk_field: args.keys.spendingPkField.toString(10),\n      payment_address: args.keys.paymentAddress,\n      signature_hex: signatureHex,\n      ...(updateTimestamp !== undefined ? { update_timestamp: updateTimestamp } : {}),\n    }),\n  })\n  if (!r.ok) {\n    const err = await r.text()\n    throw new Error(`registry POST failed: ${r.status} ${err}`)\n  }\n  const j = (await r.json()) as { ok: boolean; entry: RegistryEntry }\n  return j.entry\n}\n\n/// GET registry entry by wallet pubkey. Returns `null` when the wallet\n/// hasn't registered yet (HTTP 404), throws on other network errors.\nexport async function lookupInRegistry(args: {\n  apiBaseUrl: string\n  network: string\n  walletPubkey: string\n}): Promise<RegistryEntry | null> {\n  const url = new URL(\n    `api/private-pool/v2/registry/${encodeURIComponent(args.walletPubkey)}`,\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const r = await fetch(url.toString())\n  if (r.status === 404) return null\n  if (!r.ok) {\n    const err = await r.text()\n    throw new Error(`registry GET failed: ${r.status} ${err}`)\n  }\n  return (await r.json()) as RegistryEntry\n}\n\n/// Helper: convert a registry entry into the shape that\n/// `prepareJoinSplitTransaction` / `prepareDepositTransaction` expect for\n/// `recipient: { spendingPkField, viewingPk }`. Saves the caller a hex\n/// decode + bigint parse round-trip per send.\nexport function registryEntryToRecipient(entry: RegistryEntry): {\n  spendingPkField: bigint\n  viewingPk: Uint8Array\n} {\n  const cleanHex = entry.viewing_pk_hex.startsWith('0x')\n    ? entry.viewing_pk_hex.slice(2)\n    : entry.viewing_pk_hex\n  const viewingPk = new Uint8Array(cleanHex.length / 2)\n  for (let i = 0; i < viewingPk.length; i++) {\n    viewingPk[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16)\n  }\n  return {\n    spendingPkField: BigInt(entry.spending_pk_field),\n    viewingPk,\n  }\n}\n","// Private Pool V2 — local key cache (IndexedDB).\n//\n// Caches `PrivatePoolKeyMaterial` so the user doesn't have to sign the\n// challenge message every time they reload `/app/private`. Two modes:\n//\n//   1. PLAINTEXT (default — opt-out via setupPin)\n//      The cache holds the key material as plain JSON in IndexedDB.\n//      Anyone with read access to the user's browser disk can recover\n//      all V2 keys (spending_sk, viewing_sk, nullifier_sk). The\n//      Settings UI labels this honestly: \"this device-only cache, click\n//      Reset cached keys before leaving a shared computer.\"\n//\n//   2. PIN-ENCRYPTED (opt-in via setupPin)\n//      The cache is AES-GCM encrypted with a key derived via PBKDF2\n//      (200k iterations) from a user-supplied PIN. Each session prompts\n//      for PIN once; the derived key sits in module-level memory until\n//      either (a) the configurable idle timeout elapses, (b) lockNow()\n//      is called, (c) the tab is closed/refreshed.\n//\n// Threat model coverage:\n//   - PLAINTEXT mode: protects against NOTHING beyond browser-storage\n//     isolation (per-origin sandbox). Honest about it.\n//   - PIN-ENCRYPTED mode: protects against stolen-laptop / compromised\n//     device, AS LONG AS the PIN has enough entropy and the idle\n//     timeout is short. PBKDF2 200k iters means a brute-force attempt\n//     on a stolen laptop costs ~50ms per guess (modern CPU) — a 6-digit\n//     numeric PIN falls in ~14 hours, an 8-char alphanumeric PIN takes\n//     ~25 years. We recommend ≥8 chars in the UI.\n//\n// Per ADR-006 amendment (\"silent re-derivation when cache missing\")\n// the wallet signature is always the recovery path: forgot PIN →\n// `clearCachedKeys` → next visit re-derives from a fresh wallet sig.\n// Cache loss is annoying, never funds-loss.\n\nimport { chacha20poly1305 } from '@noble/ciphers/chacha';\nimport { blake2b } from '@noble/hashes/blake2b';\nimport type { PrivatePoolKeyMaterial } from './keys';\n\nconst DB_NAME = 'relai-private-pool-v2'\n// v1 → v2: ACK_STORE for backup-acknowledgement gate.\n// v2 → v3: cache entries gain a `mode` discriminator (plaintext / pin-encrypted).\n//          Existing v1/v2 plaintext entries auto-upgrade on next read by\n//          re-saving as `{ mode: 'plaintext', ... }`.\nconst DB_VERSION = 2\nconst STORE = 'keys'\nconst ACK_STORE = 'backup-ack'\n\nconst CACHE_VERSION = 2  // bumped to 2 with mode-aware entries\nconst PBKDF2_ITERATIONS = 200_000  // ~50ms per guess on modern CPU\nconst PBKDF2_HASH = 'SHA-256'\nconst AES_KEY_BITS = 256\nconst AES_NAME = 'AES-GCM'\nconst SALT_LEN = 16\nconst IV_LEN = 12\n\n// localStorage keys for non-secret settings (lock interval, last-mode hint).\nconst LS_LOCK_INTERVAL_PREFIX = 'relai-pp-v2:lock-interval:'\n\nconst TEXT_ENCODER = new TextEncoder()\nconst TEXT_DECODER = new TextDecoder()\n\n// ── IndexedDB plumbing ──────────────────────────────────────────────────\n\nfunction isAvailable(): boolean {\n  return typeof globalThis !== 'undefined' && typeof globalThis.indexedDB !== 'undefined'\n}\n\nfunction openDb(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    if (!isAvailable()) return reject(new Error('IndexedDB not available'))\n    const req = indexedDB.open(DB_NAME, DB_VERSION)\n    req.onupgradeneeded = () => {\n      const db = req.result\n      // Both stores created on every upgrade — `createObjectStore` is\n      // idempotent in practice via the `contains` guard, and tagging\n      // both here means a fresh-install user gets v2 schema on first\n      // open without needing the lazy-upgrade dance below.\n      if (!db.objectStoreNames.contains(STORE)) {\n        db.createObjectStore(STORE)\n      }\n      if (!db.objectStoreNames.contains(ACK_STORE)) {\n        db.createObjectStore(ACK_STORE)\n      }\n    }\n    req.onsuccess = () => resolve(req.result)\n    req.onerror = () => reject(req.error || new Error('IndexedDB open failed'))\n  })\n}\n\nasync function withStore<T>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => Promise<T> | T): Promise<T> {\n  const db = await openDb()\n  return new Promise<T>((resolve, reject) => {\n    const tx = db.transaction(STORE, mode)\n    const store = tx.objectStore(STORE)\n    Promise.resolve(fn(store)).then((v) => {\n      tx.oncomplete = () => resolve(v)\n      tx.onerror = () => reject(tx.error || new Error('IndexedDB tx error'))\n    }).catch((err) => {\n      try { tx.abort() } catch {}\n      reject(err)\n    })\n  })\n}\n\nfunction idbGet<T>(store: IDBObjectStore, key: IDBValidKey): Promise<T | undefined> {\n  return new Promise((resolve, reject) => {\n    const r = store.get(key)\n    r.onsuccess = () => resolve(r.result as T | undefined)\n    r.onerror = () => reject(r.error)\n  })\n}\n\nfunction idbPut(store: IDBObjectStore, key: IDBValidKey, value: unknown): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const r = store.put(value, key)\n    r.onsuccess = () => resolve()\n    r.onerror = () => reject(r.error)\n  })\n}\n\nfunction idbDelete(store: IDBObjectStore, key: IDBValidKey): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const r = store.delete(key)\n    r.onsuccess = () => resolve()\n    r.onerror = () => reject(r.error)\n  })\n}\n\n// ── Encryption (deterministic from wallet pubkey) ───────────────────────\n\nfunction deriveCacheKey(walletPubkey: string): Uint8Array {\n  // 32-byte key, blake2b-derived from the wallet pubkey + cache version\n  // tag. NOT a security boundary — see threat model in module doc.\n  return blake2b(TEXT_ENCODER.encode(`relai-pool-v2-cache:v${CACHE_VERSION}:${walletPubkey}`), { dkLen: 32 })\n}\n\nfunction deriveCacheNonce(walletPubkey: string): Uint8Array {\n  // 12-byte deterministic nonce (acceptable: same key never used twice\n  // for different plaintexts because each upsert overwrites the\n  // ciphertext at rest, so \"nonce reuse with same plaintext\" never\n  // happens at observable boundary).\n  return blake2b(TEXT_ENCODER.encode(`relai-pool-v2-cache-nonce:v${CACHE_VERSION}:${walletPubkey}`), { dkLen: 12 })\n}\n\n// ── Serialisation ───────────────────────────────────────────────────────\n\nfunction bytesToHex(bytes: Uint8Array): string {\n  return Buffer.from(bytes).toString('hex')\n}\nfunction hexToBytes(hex: string): Uint8Array {\n  return Uint8Array.from(Buffer.from(hex, 'hex'))\n}\n\ntype SerializedKeyMaterial = {\n  v: number\n  rootSk: string\n  spendingSk: string\n  viewingSk: string\n  nullifierSk: string\n  spendingSkField: string\n  nullifierSkField: string\n  spendingPkField: string\n  viewingPk: string\n  paymentAddress: string\n}\n\nfunction serialize(km: PrivatePoolKeyMaterial): SerializedKeyMaterial {\n  return {\n    v: CACHE_VERSION,\n    rootSk: bytesToHex(km.rootSk),\n    spendingSk: bytesToHex(km.spendingSk),\n    viewingSk: bytesToHex(km.viewingSk),\n    nullifierSk: bytesToHex(km.nullifierSk),\n    spendingSkField: km.spendingSkField.toString(10),\n    nullifierSkField: km.nullifierSkField.toString(10),\n    spendingPkField: km.spendingPkField.toString(10),\n    viewingPk: bytesToHex(km.viewingPk),\n    paymentAddress: km.paymentAddress,\n  }\n}\n\nfunction deserialize(s: SerializedKeyMaterial): PrivatePoolKeyMaterial | null {\n  if (!s || s.v !== CACHE_VERSION) return null\n  try {\n    return {\n      rootSk: hexToBytes(s.rootSk),\n      spendingSk: hexToBytes(s.spendingSk),\n      viewingSk: hexToBytes(s.viewingSk),\n      nullifierSk: hexToBytes(s.nullifierSk),\n      spendingSkField: BigInt(s.spendingSkField),\n      nullifierSkField: BigInt(s.nullifierSkField),\n      spendingPkField: BigInt(s.spendingPkField),\n      viewingPk: hexToBytes(s.viewingPk),\n      paymentAddress: s.paymentAddress,\n    }\n  } catch {\n    return null\n  }\n}\n\n// ── Mode-aware entry shape ──────────────────────────────────────────────\n\ntype PlaintextEntry = {\n  mode: 'plaintext'\n  v: number\n  data: SerializedKeyMaterial\n  stored_at: number\n}\n\ntype PinEncryptedEntry = {\n  mode: 'pin-encrypted'\n  v: number\n  ciphertext_b64: string\n  salt_b64: string\n  iv_b64: string\n  iter: number\n  stored_at: number\n}\n\n// Backward-compat: v1 entries from the previous \"deterministic-key\n// chacha20poly1305\" scheme get treated as legacy and decrypted via\n// the same path on first load, then re-saved as plaintext mode.\ntype LegacyV1Entry = { v: 1; ciphertext: string; stored_at: number }\n\ntype CacheEntry = PlaintextEntry | PinEncryptedEntry | LegacyV1Entry\n\n// ── In-memory unlock state (PIN mode only) ──────────────────────────────\n//\n// When a PIN-protected cache is unlocked, the derived AES key sits in\n// module-level memory until either:\n//   - lockNow() is called explicitly\n//   - the configurable idle interval elapses (each operation refreshes it)\n//   - the tab is closed / page refreshes (memory wiped naturally)\n// Refresh = re-prompt PIN.\n\nlet unlockedKey: CryptoKey | null = null\nlet unlockedForWallet: string | null = null\nlet lastActivityAt = 0\n\n/// Lock interval is per-wallet, persisted in localStorage. Defaults to\n/// 8h (one working day) — matches what most users will actively work\n/// on V2 in a single session. Configurable via `setLockInterval`.\n///\n/// Allowed presets (ms): 15min / 1h / 8h / 24h / 0 (never).\n/// Use `LOCK_INTERVAL_PRESETS` for the runtime list.\nexport type LockIntervalMs = number\n\nexport const LOCK_INTERVAL_PRESETS = [\n  15 * 60 * 1000,        // 15 min\n  60 * 60 * 1000,        // 1h\n  8 * 60 * 60 * 1000,    // 8h (default)\n  24 * 60 * 60 * 1000,   // 24h\n  0,                      // never\n] as const\nconst DEFAULT_LOCK_INTERVAL_MS = 8 * 60 * 60 * 1000\n\nexport function getLockInterval(walletPubkey: string): number {\n  if (typeof localStorage === 'undefined') return DEFAULT_LOCK_INTERVAL_MS\n  const v = localStorage.getItem(LS_LOCK_INTERVAL_PREFIX + walletPubkey)\n  if (!v) return DEFAULT_LOCK_INTERVAL_MS\n  const n = Number(v)\n  return Number.isFinite(n) && LOCK_INTERVAL_PRESETS.includes(n) ? n : DEFAULT_LOCK_INTERVAL_MS\n}\n\nexport function setLockInterval(walletPubkey: string, intervalMs: number): void {\n  if (typeof localStorage === 'undefined') return\n  if (!LOCK_INTERVAL_PRESETS.includes(intervalMs)) {\n    throw new Error(`setLockInterval: ${intervalMs} is not one of the allowed presets (15m, 1h, 8h, 24h, never=0)`)\n  }\n  localStorage.setItem(LS_LOCK_INTERVAL_PREFIX + walletPubkey, String(intervalMs))\n}\n\n/// Manually wipe the in-memory unlock key. Next access prompts PIN.\nexport function lockNow(): void {\n  unlockedKey = null\n  unlockedForWallet = null\n  lastActivityAt = 0\n}\n\n/// Returns true when the in-memory unlock key is fresh enough to\n/// satisfy the configured idle window. `false` means PIN is needed.\nfunction isUnlockedFor(walletPubkey: string): boolean {\n  if (!unlockedKey || unlockedForWallet !== walletPubkey) return false\n  const interval = getLockInterval(walletPubkey)\n  if (interval === 0) {\n    // \"Never\" — only manual lockNow / page refresh wipes.\n    return true\n  }\n  if (Date.now() - lastActivityAt > interval) {\n    lockNow()\n    return false\n  }\n  return true\n}\n\nfunction bumpActivity(): void {\n  lastActivityAt = Date.now()\n}\n\n// ── Web Crypto helpers (Subtle API) ─────────────────────────────────────\n\nfunction subtle(): SubtleCrypto {\n  if (typeof globalThis === 'undefined' || !globalThis.crypto?.subtle) {\n    throw new Error('Web Crypto SubtleCrypto not available — PIN protection requires a modern browser')\n  }\n  return globalThis.crypto.subtle\n}\n\nfunction randomBytes(len: number): Uint8Array {\n  const out = new Uint8Array(len)\n  globalThis.crypto.getRandomValues(out)\n  return out\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n  if (typeof Buffer !== 'undefined') return Buffer.from(bytes).toString('base64')\n  let s = ''\n  for (const b of bytes) s += String.fromCharCode(b)\n  return btoa(s)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n  if (typeof Buffer !== 'undefined') return Uint8Array.from(Buffer.from(b64, 'base64'))\n  const s = atob(b64)\n  const out = new Uint8Array(s.length)\n  for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i)\n  return out\n}\n\nasync function deriveAesKeyFromPin(\n  pin: string,\n  salt: Uint8Array,\n  iterations: number,\n): Promise<CryptoKey> {\n  const pinBytes = TEXT_ENCODER.encode(pin)\n  const baseKey = await subtle().importKey(\n    'raw',\n    pinBytes,\n    { name: 'PBKDF2' },\n    false,\n    ['deriveKey'],\n  )\n  return subtle().deriveKey(\n    {\n      name: 'PBKDF2',\n      salt,\n      iterations,\n      hash: PBKDF2_HASH,\n    },\n    baseKey,\n    { name: AES_NAME, length: AES_KEY_BITS },\n    false,  // not extractable\n    ['encrypt', 'decrypt'],\n  )\n}\n\nasync function aesEncrypt(key: CryptoKey, iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> {\n  const ct = await subtle().encrypt({ name: AES_NAME, iv }, key, plaintext)\n  return new Uint8Array(ct)\n}\n\nasync function aesDecrypt(key: CryptoKey, iv: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array> {\n  const pt = await subtle().decrypt({ name: AES_NAME, iv }, key, ciphertext)\n  return new Uint8Array(pt)\n}\n\n// ── Public surface ──────────────────────────────────────────────────────\n\n/// Save the derived key tree to local cache. Mode is determined by\n/// what's already stored:\n///   - PIN-protected entry exists AND in-memory key is unlocked → re-encrypt\n///   - PIN-protected entry exists BUT locked → THROW (caller must unlock first)\n///   - plaintext entry OR no entry → write plaintext\n///\n/// Plaintext is the default for new caches. Users opt in to PIN\n/// protection via `setupPin()`.\nexport async function saveCachedKeys(walletPubkey: string, keys: PrivatePoolKeyMaterial): Promise<void> {\n  if (!isAvailable()) return\n  try {\n    const existing = await readRawEntry(walletPubkey)\n    if (existing && 'mode' in existing && existing.mode === 'pin-encrypted') {\n      if (!isUnlockedFor(walletPubkey)) {\n        throw new Error('saveCachedKeys: cache is PIN-protected and locked — call unlockWithPin first')\n      }\n      const salt = base64ToBytes(existing.salt_b64)\n      const iv = randomBytes(IV_LEN)\n      const plaintext = TEXT_ENCODER.encode(JSON.stringify(serialize(keys)))\n      const ciphertext = await aesEncrypt(unlockedKey!, iv, plaintext)\n      bumpActivity()\n      await writeEntry(walletPubkey, {\n        mode: 'pin-encrypted',\n        v: CACHE_VERSION,\n        ciphertext_b64: bytesToBase64(ciphertext),\n        salt_b64: bytesToBase64(salt),\n        iv_b64: bytesToBase64(iv),\n        iter: PBKDF2_ITERATIONS,\n        stored_at: Date.now(),\n      })\n      return\n    }\n    // Plaintext path — default for new caches and for entries that\n    // were never PIN-protected.\n    await writeEntry(walletPubkey, {\n      mode: 'plaintext',\n      v: CACHE_VERSION,\n      data: serialize(keys),\n      stored_at: Date.now(),\n    })\n  } catch (err) {\n    // Cache failure must NEVER block deposit/spend (ADR-006). Log and\n    // move on; caller falls back to re-derivation from wallet sig.\n    // eslint-disable-next-line no-console\n    console.warn('[private-pool-v2 cache] saveCachedKeys failed:', err)\n  }\n}\n\n/// Try to load cached keys.\n///\n/// Return semantics:\n///   - `null` if NO cache entry, OR cache is PIN-protected and locked\n///     (caller checks `isPinProtected` to distinguish), OR decryption\n///     fails (caller should re-derive from wallet sig).\n///   - `PrivatePoolKeyMaterial` if plaintext OR PIN-unlocked.\n///\n/// To avoid awkward null-checks in callers, see `loadCachedKeysWithStatus`\n/// which returns a richer enum.\nexport async function loadCachedKeys(walletPubkey: string): Promise<PrivatePoolKeyMaterial | null> {\n  const status = await loadCachedKeysWithStatus(walletPubkey)\n  return status.kind === 'ok' ? status.keys : null\n}\n\n/// Rich-status variant — UI uses this to decide whether to show a\n/// PIN prompt, an empty-cache \"sign to derive\" CTA, or just render\n/// the balance.\nexport type LoadCacheStatus =\n  | { kind: 'ok'; keys: PrivatePoolKeyMaterial }\n  | { kind: 'no-cache' }\n  | { kind: 'pin-required' }\n  | { kind: 'corrupted' }\n\nexport async function loadCachedKeysWithStatus(walletPubkey: string): Promise<LoadCacheStatus> {\n  if (!isAvailable()) return { kind: 'no-cache' }\n  try {\n    const entry = await readRawEntry(walletPubkey)\n    if (!entry) return { kind: 'no-cache' }\n\n    // Legacy v1 (pre-mode-aware, deterministic-key chacha20poly1305).\n    // Decrypt with the old scheme, then re-save as plaintext so future\n    // loads take the fast path.\n    if (!('mode' in entry) && entry.v === 1) {\n      try {\n        const key = deriveCacheKey(walletPubkey)\n        const nonce = deriveCacheNonce(walletPubkey)\n        const cipher = chacha20poly1305(key, nonce)\n        const plaintext = cipher.decrypt(hexToBytes(entry.ciphertext))\n        const parsed = JSON.parse(TEXT_DECODER.decode(plaintext)) as SerializedKeyMaterial\n        const keys = deserialize(parsed)\n        if (!keys) return { kind: 'corrupted' }\n        // Lazy upgrade to plaintext mode (honest about it).\n        await writeEntry(walletPubkey, {\n          mode: 'plaintext',\n          v: CACHE_VERSION,\n          data: parsed,\n          stored_at: Date.now(),\n        })\n        return { kind: 'ok', keys }\n      } catch {\n        return { kind: 'corrupted' }\n      }\n    }\n\n    if ('mode' in entry && entry.mode === 'plaintext') {\n      const keys = deserialize(entry.data)\n      return keys ? { kind: 'ok', keys } : { kind: 'corrupted' }\n    }\n\n    if ('mode' in entry && entry.mode === 'pin-encrypted') {\n      if (!isUnlockedFor(walletPubkey)) {\n        return { kind: 'pin-required' }\n      }\n      try {\n        const iv = base64ToBytes(entry.iv_b64)\n        const ciphertext = base64ToBytes(entry.ciphertext_b64)\n        const plaintext = await aesDecrypt(unlockedKey!, iv, ciphertext)\n        const parsed = JSON.parse(TEXT_DECODER.decode(plaintext)) as SerializedKeyMaterial\n        const keys = deserialize(parsed)\n        bumpActivity()\n        return keys ? { kind: 'ok', keys } : { kind: 'corrupted' }\n      } catch {\n        // Decryption failure with a present key usually means corruption.\n        // If the key is wrong (wrong PIN), unlockWithPin would have failed\n        // already — but be defensive here.\n        return { kind: 'corrupted' }\n      }\n    }\n\n    return { kind: 'corrupted' }\n  } catch (err) {\n    console.warn('[private-pool-v2 cache] loadCachedKeysWithStatus failed:', err)\n    return { kind: 'corrupted' }\n  }\n}\n\n/// True if the cache for this wallet is PIN-protected (regardless of\n/// current unlock state). UI uses this to render the right CTA.\nexport async function isPinProtected(walletPubkey: string): Promise<boolean> {\n  if (!isAvailable()) return false\n  try {\n    const entry = await readRawEntry(walletPubkey)\n    return !!(entry && 'mode' in entry && entry.mode === 'pin-encrypted')\n  } catch {\n    return false\n  }\n}\n\n/// True if the in-memory unlock state is fresh (PIN was entered\n/// recently and the idle timer hasn't expired).\nexport function isUnlocked(walletPubkey: string): boolean {\n  return isUnlockedFor(walletPubkey)\n}\n\n/// Convert an existing plaintext cache (or freshly-derived in-memory\n/// keys) into a PIN-protected cache. After success, subsequent loads\n/// require `unlockWithPin` and the in-memory key is set so the current\n/// session can keep using `saveCachedKeys` without re-prompting.\n///\n/// Throws if:\n///   - No cache exists yet (caller must save plaintext first via the\n///     normal flow, then call setupPin)\n///   - PIN protection is already enabled (use `changePin` instead)\n///   - PIN length < 4 chars\nexport async function setupPin(args: {\n  walletPubkey: string\n  pin: string\n  intervalMs?: number\n}): Promise<void> {\n  const { walletPubkey, pin } = args\n  if (!isAvailable()) throw new Error('IndexedDB not available')\n  if (!pin || pin.length < 4) {\n    throw new Error('PIN must be at least 4 characters')\n  }\n  const existing = await readRawEntry(walletPubkey)\n  if (!existing) throw new Error('setupPin: no cache to protect (sign in first)')\n  if ('mode' in existing && existing.mode === 'pin-encrypted') {\n    throw new Error('setupPin: PIN already enabled — use changePin instead')\n  }\n  // Pull plaintext keys out of whatever the current entry is.\n  let keys: PrivatePoolKeyMaterial | null = null\n  if (!('mode' in existing) && existing.v === 1) {\n    // Legacy entry — decrypt via the old deterministic scheme.\n    const key = deriveCacheKey(walletPubkey)\n    const nonce = deriveCacheNonce(walletPubkey)\n    const cipher = chacha20poly1305(key, nonce)\n    const plaintext = cipher.decrypt(hexToBytes(existing.ciphertext))\n    const parsed = JSON.parse(TEXT_DECODER.decode(plaintext)) as SerializedKeyMaterial\n    keys = deserialize(parsed)\n  } else if ('mode' in existing && existing.mode === 'plaintext') {\n    keys = deserialize(existing.data)\n  }\n  if (!keys) throw new Error('setupPin: failed to read existing cache')\n\n  const salt = randomBytes(SALT_LEN)\n  const iv = randomBytes(IV_LEN)\n  const aesKey = await deriveAesKeyFromPin(pin, salt, PBKDF2_ITERATIONS)\n  const plaintext = TEXT_ENCODER.encode(JSON.stringify(serialize(keys)))\n  const ciphertext = await aesEncrypt(aesKey, iv, plaintext)\n  await writeEntry(walletPubkey, {\n    mode: 'pin-encrypted',\n    v: CACHE_VERSION,\n    ciphertext_b64: bytesToBase64(ciphertext),\n    salt_b64: bytesToBase64(salt),\n    iv_b64: bytesToBase64(iv),\n    iter: PBKDF2_ITERATIONS,\n    stored_at: Date.now(),\n  })\n  // Persist the chosen interval (or default) and put the unlock state\n  // in memory so the user doesn't have to re-enter PIN immediately.\n  if (typeof args.intervalMs === 'number') {\n    setLockInterval(walletPubkey, args.intervalMs)\n  }\n  unlockedKey = aesKey\n  unlockedForWallet = walletPubkey\n  bumpActivity()\n}\n\n/// Verify PIN and put the derived AES key in memory for the configured\n/// idle window. Returns true on success, false on wrong PIN. Never\n/// throws on auth failure; callers branch on the boolean.\n///\n/// Implementation: derive the AES key from PIN+salt+iter, attempt to\n/// decrypt the entry. Decryption success = correct PIN.\nexport async function unlockWithPin(args: {\n  walletPubkey: string\n  pin: string\n}): Promise<boolean> {\n  const { walletPubkey, pin } = args\n  if (!isAvailable()) return false\n  const entry = await readRawEntry(walletPubkey)\n  if (!entry || !('mode' in entry) || entry.mode !== 'pin-encrypted') return false\n  try {\n    const salt = base64ToBytes(entry.salt_b64)\n    const iv = base64ToBytes(entry.iv_b64)\n    const ciphertext = base64ToBytes(entry.ciphertext_b64)\n    const aesKey = await deriveAesKeyFromPin(pin, salt, entry.iter || PBKDF2_ITERATIONS)\n    // Trial decrypt — if PIN is wrong, AES-GCM throws on tag mismatch.\n    await aesDecrypt(aesKey, iv, ciphertext)\n    unlockedKey = aesKey\n    unlockedForWallet = walletPubkey\n    bumpActivity()\n    return true\n  } catch {\n    return false\n  }\n}\n\n/// Remove PIN protection — decrypts the cache back to plaintext mode.\n/// Requires the current PIN to authorise. Idempotent on plaintext caches.\nexport async function removePin(args: {\n  walletPubkey: string\n  pin: string\n}): Promise<void> {\n  const ok = await unlockWithPin(args)\n  if (!ok) throw new Error('removePin: PIN incorrect')\n  // After unlock succeeded, decrypt and re-save as plaintext.\n  const status = await loadCachedKeysWithStatus(args.walletPubkey)\n  if (status.kind !== 'ok') throw new Error('removePin: failed to decrypt cache after unlock')\n  await writeEntry(args.walletPubkey, {\n    mode: 'plaintext',\n    v: CACHE_VERSION,\n    data: serialize(status.keys),\n    stored_at: Date.now(),\n  })\n  lockNow()\n}\n\n/// Change PIN — validates old PIN, then re-encrypts with new PIN.\n/// New idle interval is OPTIONAL; if omitted the existing interval is preserved.\nexport async function changePin(args: {\n  walletPubkey: string\n  oldPin: string\n  newPin: string\n  intervalMs?: number\n}): Promise<void> {\n  if (!args.newPin || args.newPin.length < 4) {\n    throw new Error('PIN must be at least 4 characters')\n  }\n  // Verify old PIN by unlocking + reading the keys.\n  const ok = await unlockWithPin({ walletPubkey: args.walletPubkey, pin: args.oldPin })\n  if (!ok) throw new Error('changePin: old PIN incorrect')\n  const status = await loadCachedKeysWithStatus(args.walletPubkey)\n  if (status.kind !== 'ok') throw new Error('changePin: failed to decrypt cache')\n  // Wipe old in-memory state, then re-setup with the new PIN.\n  lockNow()\n  const salt = randomBytes(SALT_LEN)\n  const iv = randomBytes(IV_LEN)\n  const aesKey = await deriveAesKeyFromPin(args.newPin, salt, PBKDF2_ITERATIONS)\n  const plaintext = TEXT_ENCODER.encode(JSON.stringify(serialize(status.keys)))\n  const ciphertext = await aesEncrypt(aesKey, iv, plaintext)\n  await writeEntry(args.walletPubkey, {\n    mode: 'pin-encrypted',\n    v: CACHE_VERSION,\n    ciphertext_b64: bytesToBase64(ciphertext),\n    salt_b64: bytesToBase64(salt),\n    iv_b64: bytesToBase64(iv),\n    iter: PBKDF2_ITERATIONS,\n    stored_at: Date.now(),\n  })\n  if (typeof args.intervalMs === 'number') {\n    setLockInterval(args.walletPubkey, args.intervalMs)\n  }\n  unlockedKey = aesKey\n  unlockedForWallet = args.walletPubkey\n  bumpActivity()\n}\n\n// ── Internal IndexedDB read/write wrappers (mode-aware) ────────────────\n\nasync function readRawEntry(walletPubkey: string): Promise<CacheEntry | null> {\n  const raw = await withStore('readonly', (store) => idbGet<CacheEntry>(store, walletPubkey))\n  return raw ?? null\n}\n\nasync function writeEntry(walletPubkey: string, entry: PlaintextEntry | PinEncryptedEntry): Promise<void> {\n  await withStore('readwrite', (store) => idbPut(store, walletPubkey, entry))\n}\n\n// ── Backup / restore (privacy-wallet pattern) ─────────────────────────────────────\n//\n// Exports the full key tree as a portable JSON file. User keeps it\n// safely (cloud drive, USB, encrypted vault — their choice). Restoring\n// on a fresh device (or after IndexedDB wipe) bypasses the wallet\n// signature challenge — keys flow directly into the in-memory state +\n// IndexedDB cache.\n//\n// Format is plaintext JSON (no extra password) — same as other privacy wallets. The user\n// is responsible for protecting the file. We include a clear warning in\n// the UI before download. Adding password-based encryption is an obvious\n// future enhancement (Faza 6 backlog) — out of scope for the first ship.\n\nconst BACKUP_FORMAT_NAME = 'relai-private-pool-v2-backup'\nconst BACKUP_FORMAT_VERSION = 1\n\nexport type BackupFile = {\n  format: typeof BACKUP_FORMAT_NAME\n  version: number\n  walletPubkey: string\n  paymentAddress: string\n  createdAt: string\n  /// Plaintext serialization of `PrivatePoolKeyMaterial`. Whoever holds\n  /// this file can spend the user's private balance — treat like a seed.\n  keys: SerializedKeyMaterial\n  /// Optional metadata to make UX clearer when restoring (e.g. \"this\n  /// backup is from May 2026, you have 5 notes worth ~3 USDC\").\n  meta?: {\n    note?: string\n  }\n}\n\n/// Build a portable backup object for the given wallet + keys. Returns\n/// the JSON string ready for download. Caller wraps in a Blob and\n/// triggers the file save via DOM (no IO from this module).\nexport function buildBackupFileJson(args: {\n  walletPubkey: string\n  keys: PrivatePoolKeyMaterial\n  note?: string\n}): string {\n  const file: BackupFile = {\n    format: BACKUP_FORMAT_NAME,\n    version: BACKUP_FORMAT_VERSION,\n    walletPubkey: args.walletPubkey,\n    paymentAddress: args.keys.paymentAddress,\n    createdAt: new Date().toISOString(),\n    keys: serialize(args.keys),\n    meta: args.note ? { note: args.note } : undefined,\n  }\n  return JSON.stringify(file, null, 2)\n}\n\n/// Parse a backup JSON file back into `PrivatePoolKeyMaterial`. Returns\n/// `{ ok: false, error }` on malformed input, `{ ok: true, keys, walletPubkey }`\n/// otherwise. Caller validates `walletPubkey` against the connected\n/// wallet (mismatch = warn or block — depends on UX choice).\nexport function parseBackupFileJson(raw: string): {\n  ok: true\n  keys: PrivatePoolKeyMaterial\n  walletPubkey: string\n  paymentAddress: string\n  createdAt: string\n} | { ok: false; error: string } {\n  let parsed: unknown\n  try {\n    parsed = JSON.parse(raw)\n  } catch (e: unknown) {\n    return { ok: false, error: `not a valid JSON file: ${(e as Error)?.message ?? String(e)}` }\n  }\n  if (typeof parsed !== 'object' || parsed === null) {\n    return { ok: false, error: 'backup file must be a JSON object' }\n  }\n  const file = parsed as Partial<BackupFile>\n  if (file.format !== BACKUP_FORMAT_NAME) {\n    return { ok: false, error: `unrecognised format: \"${file.format}\" (expected \"${BACKUP_FORMAT_NAME}\")` }\n  }\n  if (typeof file.version !== 'number' || file.version > BACKUP_FORMAT_VERSION) {\n    return { ok: false, error: `unsupported backup version ${file.version} (max ${BACKUP_FORMAT_VERSION})` }\n  }\n  if (!file.keys || typeof file.keys !== 'object') {\n    return { ok: false, error: 'backup file is missing the keys block' }\n  }\n  if (!file.walletPubkey || typeof file.walletPubkey !== 'string') {\n    return { ok: false, error: 'backup file is missing walletPubkey' }\n  }\n  const km = deserialize(file.keys as SerializedKeyMaterial)\n  if (!km) {\n    return { ok: false, error: 'backup keys block failed deserialisation (corrupt or wrong version)' }\n  }\n  return {\n    ok: true,\n    keys: km,\n    walletPubkey: file.walletPubkey,\n    paymentAddress: file.paymentAddress ?? km.paymentAddress,\n    createdAt: file.createdAt ?? '',\n  }\n}\n\n// ── Backup-acknowledgement gate (per-wallet flag in IndexedDB) ──────────\n//\n// \"Has this user explicitly acknowledged the backup risk?\" — flag stored\n// in the same IndexedDB so the gate doesn't reappear after a refresh.\n// Cleared by `clearCachedKeys` (Reset cached keys) so the gate re-shows\n// after a deliberate wipe.\n\nasync function withAckStore<T>(mode: IDBTransactionMode, fn: (store: IDBObjectStore) => Promise<T> | T): Promise<T> {\n  // ACK_STORE is created in `openDb`'s onupgradeneeded at DB v2, so by\n  // the time we get here the store always exists. Same shape as\n  // `withStore` for the keys store — symmetric.\n  const db = await openDb()\n  return new Promise<T>((resolve, reject) => {\n    const tx = db.transaction(ACK_STORE, mode)\n    const store = tx.objectStore(ACK_STORE)\n    Promise.resolve(fn(store)).then((v) => {\n      tx.oncomplete = () => resolve(v)\n      tx.onerror = () => reject(tx.error || new Error('IDB tx error'))\n    }).catch((err) => { try { tx.abort() } catch {}; reject(err) })\n  })\n}\n\n/// Persist that the user has acknowledged the backup risk for this\n/// wallet. Returns `true` on success, `false` on storage failure (in\n/// which case we just don't gate — better UX than blocking on a\n/// flaky IndexedDB).\nexport async function setBackupAcknowledged(walletPubkey: string): Promise<boolean> {\n  if (!isAvailable()) return false\n  try {\n    await withAckStore('readwrite', (store) => idbPut(store, walletPubkey, {\n      acknowledged_at: new Date().toISOString(),\n    }))\n    return true\n  } catch (err) {\n    // eslint-disable-next-line no-console\n    console.warn('[private-pool-v2 cache] setBackupAcknowledged failed:', err)\n    return false\n  }\n}\n\n/// Has this wallet acknowledged the backup risk in a previous session?\n/// Default: `false`. Returns `false` on storage failure too — that\n/// means the gate shows again, not that we silently let users through.\nexport async function hasBackupAcknowledged(walletPubkey: string): Promise<boolean> {\n  if (!isAvailable()) return false\n  try {\n    const entry = await withAckStore('readonly', (store) =>\n      idbGet<{ acknowledged_at: string }>(store, walletPubkey),\n    )\n    return !!entry?.acknowledged_at\n  } catch {\n    return false\n  }\n}\n\n/// Wipe the cached keys for a given wallet (invoked from a \"Reset keys\"\n/// button in Settings, or after a security incident, or \"forgot PIN\"\n/// recovery flow). The wallet signature can always be re-derive the\n/// keys; the cache is just a UX nicety.\n///\n/// Also clears: in-memory unlock state, lock-interval preference,\n/// backup acknowledgement (so user re-confirms the backup gate after\n/// Reset, never silently-skipping).\nexport async function clearCachedKeys(walletPubkey: string): Promise<void> {\n  if (!isAvailable()) return\n  try {\n    await withStore('readwrite', (store) => idbDelete(store, walletPubkey))\n    await withAckStore('readwrite', (store) => idbDelete(store, walletPubkey))\n    // Wipe the in-memory unlock state too — if the user clicks Reset\n    // while a PIN-protected cache was unlocked, the next access should\n    // see \"no cache\" not \"unlocked-key-without-cache\".\n    if (unlockedForWallet === walletPubkey) lockNow()\n    // Lock-interval preference is per-wallet; reset to default by\n    // removing the localStorage key.\n    if (typeof localStorage !== 'undefined') {\n      localStorage.removeItem(LS_LOCK_INTERVAL_PREFIX + walletPubkey)\n    }\n  } catch (err) {\n    // eslint-disable-next-line no-console\n    console.warn('[private-pool-v2 cache] clearCachedKeys failed:', err)\n  }\n}\n","// Private Pool V2 — concrete TypeScript types.\n//\n// Single source of truth for shapes flowing between:\n//   - Browser key derivation (lib/private-pool-keys.ts)\n//   - Note encryption (lib/private-pool-notes.ts)\n//   - Witness builder (lib/private-pool-witness.ts)\n//   - Browser prover (lib/private-pool-prover.ts)\n//   - On-chain submission (api/private-pool/v2/* routes)\n//   - Node-side circuit smoke tests (contracts/scripts/private-pool-v2/)\n//\n// Reference: docs/PRIVATE_POOL_V2_DESIGN.md §17 (Appendix), ADR-001\n// (commitment), ADR-002 (key derivation), ADR-003 (encryption).\n\n// ─────────────────────────────────────────────────────────────────────\n// Constants — kept in code (not env) because changing them changes\n// the circuit + on-chain program; not a runtime tunable.\n// ─────────────────────────────────────────────────────────────────────\n\n/** Pool merkle tree depth — 4B notes capacity. */\nexport const POOL_DEPTH = 32\n\n/** ASP merkle tree depth — same as pool for symmetric witness costs. */\nexport const ASP_DEPTH = 32\n\n/** USDC base unit (6 decimals → micro-USDC). */\nexport const USDC_DECIMALS = 6\n\n// ─────────────────────────────────────────────────────────────────────\n// Payment address — share-friendly identifier the recipient hands to\n// senders. Encodes both spending and viewing public keys (ADR-002).\n// ─────────────────────────────────────────────────────────────────────\nexport interface PaymentAddress {\n  /** 32-byte spending public key (Poseidon1(spending_sk)) — used in commitments. */\n  spendingPk: Uint8Array\n  /** 32-byte X25519 viewing public key — used to encrypt notes for the recipient. */\n  viewingPk: Uint8Array\n}\n\n/** Base58Check encoding of `spendingPk || viewingPk`. ~88-char string. */\nexport type PaymentAddressString = string\n\n// ─────────────────────────────────────────────────────────────────────\n// Note material — the contents of a single private balance entry.\n//\n// `commitment` is computed from (value, ownerPk, blinding, memo) per\n// ADR-001. `spent` and `leafIndex` are off-chain tracking fields the\n// browser maintains; on-chain data only includes the commitment.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface DecryptedNote {\n  /** Micro-USDC value stored in the note. */\n  value: bigint\n  /** 32-byte recipient pubkey (== owner spending_pk). */\n  ownerPk: Uint8Array\n  /** 32-byte random blinding factor. */\n  blinding: Uint8Array\n  /** Optional 32-byte memo, or null if no memo. */\n  memo: Uint8Array | null\n}\n\nexport interface OwnedNote extends DecryptedNote {\n  /** Position in the pool's commitment tree, set when the note was added. */\n  leafIndex: number\n  /** Slot at which the note was added on chain. */\n  receivedAtSlot: number\n  /** True iff we've observed a tx that spent this note's nullifier. */\n  spent: boolean\n  /** Cached commitment (= Poseidon4 of the note fields). */\n  commitment: Uint8Array\n  /** Cached nullifier (= Poseidon2(commitment, nullifier_sk)). */\n  nullifier: Uint8Array\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Encrypted note blob — the on-chain (or indexer-served) wire format.\n// Per ADR-003: ChaCha20-Poly1305 + X25519 ephemeral.\n// ─────────────────────────────────────────────────────────────────────\nexport interface EncryptedNoteBlob {\n  /** 32-byte commitment of the note this blob describes. */\n  commitment: Uint8Array\n  /** 32-byte X25519 ephemeral public key the sender used. */\n  ephemeralPk: Uint8Array\n  /** ChaCha20-Poly1305 ciphertext + 16-byte tag (96 bytes for memo-less, 128 with memo). */\n  ciphertext: Uint8Array\n  /** Slot at which the blob was added on chain. */\n  slot: number\n  /** Position in the commitment tree (matches OwnedNote.leafIndex). */\n  leafIndex: number\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Merkle inclusion witness — pool or ASP path proving membership.\n// ─────────────────────────────────────────────────────────────────────\nexport interface MerklePath {\n  /** 32-byte hash siblings, leaf-to-root order (length == depth). */\n  pathElements: Uint8Array[]\n  /** Bit per level: 0 = leaf is left child, 1 = leaf is right child (length == depth). */\n  pathIndices: number[]\n  /** 32-byte root that this path verifies under. */\n  root: Uint8Array\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Circuit witnesses — what we feed to snarkjs.fullProve().\n//\n// Field elements are passed as decimal strings (snarkjs's preferred\n// shape). Arrays match circuit signal array sizes (32-deep paths).\n// ─────────────────────────────────────────────────────────────────────\n\n/** Witness for `Deposit.circom`. */\nexport interface DepositWitness {\n  // private\n  ownerPk: string\n  blinding: string\n  memo: string\n\n  // public\n  publicValue: string\n  depositorAddrHash: string\n  fee: string\n}\n\n/** Witness for `Withdraw.circom`. */\nexport interface WithdrawWitness {\n  // private\n  value: string\n  ownerPk: string\n  blinding: string\n  memo: string\n  spendingSk: string\n  nullifierSk: string\n\n  poolPathElements: string[]   // length POOL_DEPTH\n  poolPathIndices: number[]    // length POOL_DEPTH\n  aspPathElements: string[]    // length ASP_DEPTH\n  aspPathIndices: number[]     // length ASP_DEPTH\n\n  // public\n  publicValue: string\n  publicAddress: string\n  fee: string\n}\n\n/** Witness for `CashoutProof.circom` (ADR-012 V3). */\nexport interface CashoutProofWitness {\n  // private\n  value: string\n  ownerPk: string\n  blinding: string\n  memo: string\n  spendingSk: string\n  nullifierSk: string\n\n  // Credit-pool merkle inclusion (depth 24).\n  creditPoolPathElements: string[]\n  creditPoolPathIndices: number[]\n\n  // public\n  publicValue: string\n  publicAddress: string\n  fee: string\n}\n\n/** Witness for `JoinSplit2x2.circom`. */\nexport interface JoinSplitWitness {\n  // ── input note 1 ──\n  inputValue1: string\n  inputOwnerPk1: string\n  inputBlinding1: string\n  inputMemo1: string\n  inputPoolPath1: string[]\n  inputPoolIndices1: number[]\n  inputAspPath1: string[]\n  inputAspIndices1: number[]\n\n  // ── input note 2 ──\n  inputValue2: string\n  inputOwnerPk2: string\n  inputBlinding2: string\n  inputMemo2: string\n  inputPoolPath2: string[]\n  inputPoolIndices2: number[]\n  inputAspPath2: string[]\n  inputAspIndices2: number[]\n\n  // ── spending authority ──\n  spendingSk: string\n  nullifierSk: string\n\n  // ── output note 1 ──\n  outputValue1: string\n  outputOwnerPk1: string\n  outputBlinding1: string\n  outputMemo1: string\n\n  // ── output note 2 ──\n  outputValue2: string\n  outputOwnerPk2: string\n  outputBlinding2: string\n  outputMemo2: string\n\n  // ── public inputs ──\n  publicValueIn: string\n  publicValueOut: string\n  publicAddress: string\n  fee: string\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Public signals — what the verifier consumes after fullProve().\n//\n// Order matches circom's \"outputs in source order, then public inputs\n// alphabetical\" — see circuit headers for canonical layout. The\n// on-chain program reads these in the same order.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface DepositPublicSignals {\n  /** outputCommitment */\n  outputCommitment: string\n  /** depositorAddrHash */\n  depositorAddrHash: string\n  /** fee */\n  fee: string\n  /** publicValue */\n  publicValue: string\n}\n\nexport interface WithdrawPublicSignals {\n  /** nullifier */\n  nullifier: string\n  /** root */\n  root: string\n  /** aspRoot */\n  aspRoot: string\n  /** fee */\n  fee: string\n  /** publicAddress */\n  publicAddress: string\n  /** publicValue */\n  publicValue: string\n}\n\nexport interface JoinSplitPublicSignals {\n  /** inputNullifier1 */\n  inputNullifier1: string\n  /** inputNullifier2 */\n  inputNullifier2: string\n  /** outputCommitment1 */\n  outputCommitment1: string\n  /** outputCommitment2 */\n  outputCommitment2: string\n  /** root */\n  root: string\n  /** aspRoot */\n  aspRoot: string\n  /** fee */\n  fee: string\n  /** publicAddress */\n  publicAddress: string\n  /** publicValueIn */\n  publicValueIn: string\n  /** publicValueOut */\n  publicValueOut: string\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Proof + tx envelopes — what the client sends to the server route.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface Groth16Proof {\n  pi_a: string[]\n  pi_b: string[][]\n  pi_c: string[]\n  protocol: 'groth16'\n  curve: 'bn128'\n}\n\nexport interface DepositTx {\n  proof: Groth16Proof\n  publicSignals: DepositPublicSignals\n  encryptedNote: EncryptedNoteBlob\n}\n\nexport interface WithdrawTx {\n  proof: Groth16Proof\n  publicSignals: WithdrawPublicSignals\n}\n\nexport interface JoinSplitTx {\n  proof: Groth16Proof\n  publicSignals: JoinSplitPublicSignals\n  /** Length 2; one blob per output commitment. */\n  encryptedNotes: [EncryptedNoteBlob, EncryptedNoteBlob]\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Pool config — fetched at /api/private-pool/v2/config.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface PoolConfig {\n  /** Solana program ID hosting the pool (or EVM contract address). */\n  programId: string\n  /** USDC mint / token contract for this pool. */\n  usdcMint: string\n  /** Relayer pubkey (sponsored fee payer). */\n  relayerAddress: string\n  /** Network identifier — `solana-devnet`, `solana`, etc. */\n  network: string\n  /** Current pool merkle root (hex). */\n  currentRoot: string\n  /** Most recent ASP root (hex). */\n  currentAspRoot: string\n  /** Snapshot age — seconds since the ASP root was published. */\n  aspAgeSeconds: number\n  /** 32-byte commitment of `DUMMY_A` — used to pad single-input JoinSplits. */\n  dummyACommitment: string\n  /** 32-byte commitment of `DUMMY_B` — used in 0-input edge cases. */\n  dummyBCommitment: string\n}\n","// Private Pool V2 — encrypted note format (ADR-003).\n//\n//   ChaCha20-Poly1305 AEAD\n//   X25519 ephemeral key agreement → HKDF-SHA256(shared, salt, info=commitment)\n//   nonce = blake2b256(eph_pk || commitment)[:12]\n//   AAD   = commitment (32 bytes)\n//\n// Plaintext layout (memo-less, 80 bytes):\n//   0..8    value     (u64 LE, micro-USDC)\n//   8..40   blinding  (32 bytes BE)\n//   40..72  owner_pk  (32 bytes BE)\n//   72      memo flag (0x00)\n//   73..80  zero pad\n//\n// Plaintext layout (with memo, 112 bytes): same as above but\n//   72      memo flag (0x01)\n//   80..112 memo (32 bytes)\n//\n// On-chain `EncryptedNoteBlobAccount`:\n//   - ephemeral_pk: 32 bytes (separate field)\n//   - ciphertext:   96 or 128 bytes (Vec<u8>) — plaintext + 16-byte Poly1305 tag\n//\n// Conformance vectors: see `frontend/src/__tests__/private-pool-encryption.test.ts`.\n\nimport { chacha20poly1305 } from '@noble/ciphers/chacha';\nimport { x25519 } from '@noble/curves/ed25519';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { sha256 } from '@noble/hashes/sha2';\n\nconst HKDF_SALT = new TextEncoder().encode('RelAI PrivatePool v2 note key');\nconst NONCE_LEN = 12;\nconst TAG_LEN = 16;\n\n// Plaintext layouts (legacy ADR-003 only — stealth was withdrawn,\n// see ADR-008 postmortem):\n//   80B  no memo:   value(8) + blinding(32) + owner_pk(32) + flag=0(1) + 7B pad\n//   112B with memo: above + 32B memo at offset 80, flag=1\nconst PLAINTEXT_NO_MEMO = 80;\nconst PLAINTEXT_WITH_MEMO = 112;\nconst CIPHERTEXT_NO_MEMO = PLAINTEXT_NO_MEMO + TAG_LEN; // 96\nconst CIPHERTEXT_WITH_MEMO = PLAINTEXT_WITH_MEMO + TAG_LEN; // 128\n\nconst EPHEMERAL_PK_LEN = 32;\nconst COMMITMENT_LEN = 32;\n\nexport const PRIVATE_POOL_NOTE_BYTES = {\n  PLAINTEXT_NO_MEMO,\n  PLAINTEXT_WITH_MEMO,\n  CIPHERTEXT_NO_MEMO,\n  CIPHERTEXT_WITH_MEMO,\n  EPHEMERAL_PK_LEN,\n  COMMITMENT_LEN,\n  NONCE_LEN,\n  TAG_LEN,\n} as const;\n\nexport type NoteLayout = 'legacy';\n\nexport type DecryptedNote = {\n  value: bigint;\n  blinding: Uint8Array; // 32 bytes BE\n  ownerPk: Uint8Array; // 32 bytes BE — recipient's master spending_pk\n  memo: Uint8Array | null; // 32 bytes when present, null otherwise\n  /// Always `null` in V1 (stealth withdrawn — see ADR-008 postmortem).\n  /// Field kept on the type so callers don't have to branch on a\n  /// missing property; the encryption layer enforces null.\n  stealthNonce: null;\n  /// Always `'legacy'` in V1.\n  layout: NoteLayout;\n};\n\n// ── Plaintext (de)serialisation ──────────────────────────────────────────\n\nfunction ensureLen(label: string, bytes: Uint8Array, expected: number): void {\n  if (bytes.length !== expected) {\n    throw new Error(`${label}: expected ${expected} bytes, got ${bytes.length}`);\n  }\n}\n\n/// Pack a `DecryptedNote` to its on-chain plaintext bytes.\n///\n/// Layout selection (auto):\n///   - `stealthNonce: null, memo: null` → 80B legacy (ADR-003 v1)\n///   - `memo: null` → 80B (no memo)\n///   - `memo: Uint8Array` → 112B (with memo)\nexport function serializeNotePlaintext(note: DecryptedNote): Uint8Array {\n  ensureLen('blinding', note.blinding, 32);\n  ensureLen('ownerPk', note.ownerPk, 32);\n  if (note.memo) ensureLen('memo', note.memo, 32);\n\n  const isWithMemo = !!note.memo;\n  const len = isWithMemo ? PLAINTEXT_WITH_MEMO : PLAINTEXT_NO_MEMO;\n  const out = new Uint8Array(len);\n\n  const view = new DataView(out.buffer, out.byteOffset, 8);\n  view.setBigUint64(0, note.value, true);\n  out.set(note.blinding, 8);\n  out.set(note.ownerPk, 40);\n  out[72] = isWithMemo ? 0x01 : 0x00;\n  if (isWithMemo) out.set(note.memo!, 80);\n  return out;\n}\n\n/// Inverse of `serializeNotePlaintext`. Throws on malformed inputs.\nexport function parseNotePlaintext(plaintext: Uint8Array): DecryptedNote {\n  const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);\n  const flag = plaintext.length >= 73 ? plaintext[72] : 0xff;\n\n  if (plaintext.length === PLAINTEXT_NO_MEMO && flag === 0x00) {\n    return {\n      value: view.getBigUint64(0, true),\n      blinding: plaintext.slice(8, 40),\n      ownerPk: plaintext.slice(40, 72),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n  }\n  if (plaintext.length === PLAINTEXT_WITH_MEMO && flag === 0x01) {\n    return {\n      value: view.getBigUint64(0, true),\n      blinding: plaintext.slice(8, 40),\n      ownerPk: plaintext.slice(40, 72),\n      memo: plaintext.slice(80, 112),\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n  }\n  throw new Error(\n    `note plaintext: unrecognized layout (length ${plaintext.length}, memo flag ${flag.toString(16)})`,\n  );\n}\n\n// ── Key agreement ────────────────────────────────────────────────────────\n\n/// HKDF-SHA256 with ADR-003 salt + commitment-as-info → 32-byte key.\nfunction deriveSymmetricKey(sharedSecret: Uint8Array, commitment: Uint8Array): Uint8Array {\n  return hkdf(sha256, sharedSecret, HKDF_SALT, commitment, 32);\n}\n\n/// `nonce = blake2b256(eph_pk || commitment)[:12]`.\nfunction deriveNonce(ephemeralPk: Uint8Array, commitment: Uint8Array): Uint8Array {\n  ensureLen('ephemeralPk', ephemeralPk, EPHEMERAL_PK_LEN);\n  ensureLen('commitment', commitment, COMMITMENT_LEN);\n  const concat = new Uint8Array(ephemeralPk.length + commitment.length);\n  concat.set(ephemeralPk, 0);\n  concat.set(commitment, ephemeralPk.length);\n  // dkLen 32 default; we slice the first 12 bytes for the AEAD nonce.\n  return blake2b(concat, { dkLen: 32 }).slice(0, NONCE_LEN);\n}\n\n// ── Encrypt / decrypt ────────────────────────────────────────────────────\n\nexport type EncryptedNoteBlob = {\n  /// X25519 ephemeral pubkey published with the blob. Pure scalar mult,\n  /// reveals nothing about the recipient.\n  ephemeralPk: Uint8Array; // 32 bytes\n  /// Plaintext + Poly1305 tag (96 or 128 bytes total).\n  ciphertext: Uint8Array;\n  /// Mirrors the on-chain commitment for AAD binding.\n  commitment: Uint8Array; // 32 bytes\n};\n\nexport type EncryptArgs = {\n  /// Plaintext note to encrypt — recipient's ownerPk + (optional)\n  /// `stealthNonce` are committed to inside.\n  note: DecryptedNote;\n  /// Recipient's X25519 viewing-key pubkey (per ADR-002).\n  recipientViewingPk: Uint8Array; // 32 bytes\n  /// Note commitment (Poseidon4 of value/owner_pk/blinding/memo). Used as\n  /// AEAD AAD + HKDF info — binds the ciphertext to a single commitment.\n  commitment: Uint8Array; // 32 bytes\n  /// Optional ephemeral keypair override (for deterministic test vectors).\n  /// Production code generates fresh randomness per note.\n  ephemeralKeypairOverride?: { sk: Uint8Array; pk: Uint8Array };\n};\n\n/// Encrypt a note for a recipient. Returns the publishable blob fields.\n///\n/// Domain-separation guarantees:\n///   - HKDF info=commitment makes the AEAD key unique per note (a leak\n///     of one key doesn't cascade)\n///   - AAD=commitment makes the ciphertext non-portable (can't be moved\n///     to a different commitment without breaking decryption)\n///   - Deterministic nonce (eph_pk + commitment) avoids the catastrophic\n///     \"weak browser entropy\" path; uniqueness is by construction since\n///     ephemeral_pk is fresh per call.\nexport function encryptNote(args: EncryptArgs): EncryptedNoteBlob {\n  ensureLen('recipientViewingPk', args.recipientViewingPk, 32);\n  ensureLen('commitment', args.commitment, COMMITMENT_LEN);\n\n  let ephSk: Uint8Array;\n  let ephPk: Uint8Array;\n  if (args.ephemeralKeypairOverride) {\n    ephSk = args.ephemeralKeypairOverride.sk;\n    ephPk = args.ephemeralKeypairOverride.pk;\n    ensureLen('ephemeralKeypairOverride.sk', ephSk, 32);\n    ensureLen('ephemeralKeypairOverride.pk', ephPk, 32);\n  } else {\n    ephSk = x25519.utils.randomPrivateKey();\n    ephPk = x25519.getPublicKey(ephSk);\n  }\n\n  const sharedSecret = x25519.getSharedSecret(ephSk, args.recipientViewingPk);\n  const key = deriveSymmetricKey(sharedSecret, args.commitment);\n  const nonce = deriveNonce(ephPk, args.commitment);\n\n  const plaintext = serializeNotePlaintext(args.note);\n  const cipher = chacha20poly1305(key, nonce, args.commitment);\n  const ciphertext = cipher.encrypt(plaintext);\n\n  if (\n    ciphertext.length !== CIPHERTEXT_NO_MEMO\n    && ciphertext.length !== CIPHERTEXT_WITH_MEMO\n  ) {\n    throw new Error(`encryptNote: unexpected ciphertext length ${ciphertext.length}`);\n  }\n  return {\n    ephemeralPk: ephPk,\n    ciphertext,\n    commitment: args.commitment,\n  };\n}\n\n/// Try to decrypt a blob with the given viewing secret. Returns the note\n/// when the auth tag verifies, or `null` when it doesn't (i.e. \"this\n/// blob isn't mine\"). Never throws on tag-mismatch — that's the\n/// expected hot path during note discovery.\nexport function tryDecryptNote(args: {\n  blob: EncryptedNoteBlob;\n  viewingSk: Uint8Array; // 32 bytes\n}): DecryptedNote | null {\n  ensureLen('viewingSk', args.viewingSk, 32);\n  ensureLen('blob.ephemeralPk', args.blob.ephemeralPk, 32);\n  ensureLen('blob.commitment', args.blob.commitment, COMMITMENT_LEN);\n  const len = args.blob.ciphertext.length;\n  if (len !== CIPHERTEXT_NO_MEMO && len !== CIPHERTEXT_WITH_MEMO) {\n    return null;\n  }\n\n  const sharedSecret = x25519.getSharedSecret(args.viewingSk, args.blob.ephemeralPk);\n  const key = deriveSymmetricKey(sharedSecret, args.blob.commitment);\n  const nonce = deriveNonce(args.blob.ephemeralPk, args.blob.commitment);\n  const cipher = chacha20poly1305(key, nonce, args.blob.commitment);\n  let plaintext: Uint8Array;\n  try {\n    plaintext = cipher.decrypt(args.blob.ciphertext);\n  } catch {\n    // Tag verification failed — not our blob, or corrupted.\n    return null;\n  }\n  try {\n    return parseNotePlaintext(plaintext);\n  } catch {\n    // Decrypted but plaintext layout is bad — bail rather than return\n    // garbage. Should never happen on a well-formed blob.\n    return null;\n  }\n}\n\n/// Strict variant: throws on auth-tag mismatch. Use this when the\n/// caller asserts the blob is theirs (e.g. after a successful trial-decrypt\n/// on a different blob, this one MUST match too).\nexport function decryptNote(args: { blob: EncryptedNoteBlob; viewingSk: Uint8Array }): DecryptedNote {\n  const out = tryDecryptNote(args);\n  if (!out) throw new Error('decryptNote: ciphertext failed Poly1305 verification');\n  return out;\n}\n\n// ── X25519 helpers (re-exported so callers don't import @noble directly) ─\n\n/// Generate a fresh X25519 keypair. Used for per-note ephemerals AND\n/// for the user's long-lived viewing key when initialising private balance.\nexport function generateX25519Keypair(): { sk: Uint8Array; pk: Uint8Array } {\n  const sk = x25519.utils.randomPrivateKey();\n  const pk = x25519.getPublicKey(sk);\n  return { sk, pk };\n}\n\n/// Derive an X25519 viewing pubkey from a 32-byte secret. The secret\n/// itself is produced by ADR-002's HKDF-from-wallet-sig path (see\n/// `private-pool-keys.ts`).\nexport function x25519PublicFromSecret(viewingSk: Uint8Array): Uint8Array {\n  ensureLen('viewingSk', viewingSk, 32);\n  return x25519.getPublicKey(viewingSk);\n}\n\n","// Private Pool V2 — circuit witness builders + crypto helpers.\n//\n// Off-circuit mirrors of the in-circuit primitives + assembly logic\n// that converts (user intent, on-chain state, owned notes) into the\n// witness shape that snarkjs.fullProve() expects.\n//\n// Every primitive here MUST produce the same field element as its\n// circom counterpart in `contracts/circuits/private-pool-v2/lib.circom`.\n// Conformance is checked by Node-side smoke tests in\n// `contracts/scripts/private-pool-v2/`.\n//\n// Poseidon implementation: `circomlibjs` (already a dep, used by V4).\n// Async-init via `loadPoseidonBuilder()` — first call awaits the\n// builder, subsequent calls reuse the cached instance.\n//\n// Reference: ADR-001 (commitment), ADR-002 (key derivation), ADR-003\n// (encryption — separate file `private-pool-notes.ts`).\n\nimport {\n  type DepositWitness,\n  type WithdrawWitness,\n  type JoinSplitWitness,\n  type DecryptedNote,\n  type OwnedNote,\n  type MerklePath,\n  type PaymentAddress,\n  POOL_DEPTH,\n  ASP_DEPTH,\n} from './types'\n\n// ─────────────────────────────────────────────────────────────────────\n// Poseidon loader — same pattern as `shielded-private-links.ts`.\n// `circomlibjs.buildPoseidon()` returns a callable that takes an array\n// of field elements (bigint) and returns a Uint8Array which we lift\n// back to bigint via `F.toObject`. We cache the builder promise so\n// every call after the first is sync-ish.\n// ─────────────────────────────────────────────────────────────────────\n\ninterface PoseidonField {\n  toObject: (value: unknown) => bigint | string | number\n}\ninterface PoseidonBuilder {\n  (inputs: bigint[]): unknown\n  F: PoseidonField\n}\n\nlet poseidonBuilderPromise: Promise<PoseidonBuilder> | null = null\n\nasync function loadPoseidonBuilder(): Promise<PoseidonBuilder> {\n  if (!poseidonBuilderPromise) {\n    poseidonBuilderPromise = import('circomlibjs').then(\n      (mod) => (mod as { buildPoseidon: () => Promise<PoseidonBuilder> }).buildPoseidon(),\n    )\n  }\n  return await poseidonBuilderPromise\n}\n\nfunction liftPoseidonResult(builder: PoseidonBuilder, value: unknown): bigint {\n  const lifted = builder.F && typeof builder.F.toObject === 'function'\n    ? builder.F.toObject(value)\n    : value\n  const big = BigInt(String(lifted))\n  return ((big % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n}\n\nasync function poseidonHash(inputs: bigint[]): Promise<bigint> {\n  const builder = await loadPoseidonBuilder()\n  return liftPoseidonResult(builder, builder(inputs))\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Field-element helpers — all circuit signals are BN254 scalar field\n// elements. snarkjs accepts them as decimal strings; the bigint API\n// keeps the rest of our code reasonable.\n// ─────────────────────────────────────────────────────────────────────\n\n/** BN254 scalar field modulus. */\nexport const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n\n\n/** Convert 32 little-endian bytes (or fewer; right-padded) to a field element. */\nexport function bytesToField(bytes: Uint8Array): bigint {\n  if (bytes.length > 32) throw new Error('bytesToField: input > 32 bytes')\n  let n = 0n\n  for (let i = bytes.length - 1; i >= 0; i--) {\n    n = (n << 8n) | BigInt(bytes[i] ?? 0)\n  }\n  return n % FIELD_MODULUS\n}\n\n/** Convert a field element to 32 little-endian bytes. */\nexport function fieldToBytes(n: bigint): Uint8Array {\n  const out = new Uint8Array(32)\n  let x = ((n % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  for (let i = 0; i < 32; i++) {\n    out[i] = Number(x & 0xffn)\n    x >>= 8n\n  }\n  return out\n}\n\n/** Field element → snarkjs witness string (decimal). */\nexport function fieldStr(n: bigint): string {\n  return n.toString(10)\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Commitment & nullifier — off-circuit mirrors of lib.circom.\n// ─────────────────────────────────────────────────────────────────────\n\n/**\n * Commitment per ADR-001:\n *   commitment = Poseidon4(value, owner_pk, blinding, memo_or_zero)\n */\nexport async function deriveCommitment(args: {\n  value: bigint\n  ownerPk: bigint\n  blinding: bigint\n  memo: bigint | null\n}): Promise<bigint> {\n  const memo = args.memo ?? 0n\n  return poseidonHash([args.value, args.ownerPk, args.blinding, memo])\n}\n\n/**\n * Spending pubkey per ADR-002:\n *   spending_pk = Poseidon1(spending_sk)\n */\nexport async function deriveSpendingPk(spendingSk: bigint): Promise<bigint> {\n  return poseidonHash([spendingSk])\n}\n\n/**\n * Nullifier per ADR-002:\n *   nullifier = Poseidon2(commitment, nullifier_sk)\n */\nexport async function deriveNullifier(args: {\n  commitment: bigint\n  nullifierSk: bigint\n}): Promise<bigint> {\n  return poseidonHash([args.commitment, args.nullifierSk])\n}\n\n/**\n * Merkle node hash per lib.circom MerkleRoot template:\n *   parent = Poseidon2(left, right)\n *\n * Used by witness builders that need to verify their own merkle path\n * against a published root before submitting a proof (catches\n * stale-witness bugs early).\n */\nexport async function merkleParent(left: bigint, right: bigint): Promise<bigint> {\n  return poseidonHash([left, right])\n}\n\n/**\n * Recompute a merkle root from a leaf + path. Useful for client-side\n * sanity check (\"does my path actually verify against the published\n * root?\") before burning proof time.\n */\nexport async function recomputeMerkleRoot(args: {\n  leaf: bigint\n  pathElements: bigint[]\n  pathIndices: number[]\n}): Promise<bigint> {\n  if (args.pathElements.length !== args.pathIndices.length) {\n    throw new Error('recomputeMerkleRoot: pathElements / pathIndices length mismatch')\n  }\n  let hash = args.leaf\n  for (let i = 0; i < args.pathElements.length; i++) {\n    const sibling = args.pathElements[i]!\n    const idx = args.pathIndices[i]!\n    if (idx !== 0 && idx !== 1) {\n      throw new Error(`recomputeMerkleRoot: pathIndices[${i}] must be 0 or 1, got ${idx}`)\n    }\n    if (idx === 0) {\n      hash = await merkleParent(hash, sibling)\n    } else {\n      hash = await merkleParent(sibling, hash)\n    }\n  }\n  return hash\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Owned note enrichment — given a DecryptedNote, fill in the cached\n// commitment + nullifier so the rest of the pipeline can reuse them.\n// ─────────────────────────────────────────────────────────────────────\n\nexport async function enrichOwnedNote(args: {\n  note: DecryptedNote\n  leafIndex: number\n  receivedAtSlot: number\n  spent: boolean\n  nullifierSk: bigint\n}): Promise<OwnedNote> {\n  const { note, leafIndex, receivedAtSlot, spent, nullifierSk } = args\n  const commitment = await deriveCommitment({\n    value: note.value,\n    ownerPk: bytesToField(note.ownerPk),\n    blinding: bytesToField(note.blinding),\n    memo: note.memo ? bytesToField(note.memo) : null,\n  })\n  const nullifier = await deriveNullifier({ commitment, nullifierSk })\n  return {\n    ...note,\n    leafIndex,\n    receivedAtSlot,\n    spent,\n    commitment: fieldToBytes(commitment),\n    nullifier: fieldToBytes(nullifier),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Deposit witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface DepositInputs {\n  /** Output note being created. */\n  recipient: PaymentAddress\n  /** Random 32-byte blinding for the new note. */\n  blinding: Uint8Array\n  /** Optional 32-byte memo. */\n  memo: Uint8Array | null\n  /** USDC value entering the pool, in micro-USDC. */\n  publicValue: bigint\n  /** Hash of the SPL source account (binds proof to caller). */\n  depositorAddrHash: bigint\n  /** Relayer fee, micro-USDC. */\n  fee: bigint\n}\n\nexport function buildDepositWitness(inputs: DepositInputs): DepositWitness {\n  // Note: Deposit witness needs no Poseidon hashing — the circuit\n  // computes the commitment from inputs. So this builder stays sync.\n\n  if (inputs.publicValue < 0n) throw new Error('publicValue must be non-negative')\n  if (inputs.fee < 0n) throw new Error('fee must be non-negative')\n  if (inputs.fee > inputs.publicValue) throw new Error('fee exceeds publicValue')\n\n  return {\n    ownerPk: fieldStr(bytesToField(inputs.recipient.spendingPk)),\n    blinding: fieldStr(bytesToField(inputs.blinding)),\n    memo: fieldStr(inputs.memo ? bytesToField(inputs.memo) : 0n),\n    publicValue: fieldStr(inputs.publicValue),\n    depositorAddrHash: fieldStr(inputs.depositorAddrHash),\n    fee: fieldStr(inputs.fee),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Withdraw witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface WithdrawInputs {\n  /** The note being spent (must have spent: false). */\n  note: OwnedNote\n  /** Caller's spending key (to prove authority). */\n  spendingSk: bigint\n  /** Caller's nullifier key (to derive `note.nullifier`). */\n  nullifierSk: bigint\n  /** Pool merkle inclusion proof for `note.commitment`. */\n  poolPath: MerklePath\n  /** ASP merkle inclusion proof for `note.commitment`. */\n  aspPath: MerklePath\n  /** Recipient address hash (binds the proof). */\n  publicAddress: bigint\n  /** USDC paid out to recipient (== note.value − fee). */\n  publicValue: bigint\n  /** Relayer fee, micro-USDC. */\n  fee: bigint\n}\n\nexport async function buildWithdrawWitness(inputs: WithdrawInputs): Promise<WithdrawWitness> {\n  // Sanity checks — fail fast before the prover spends 30s on garbage.\n  if (inputs.note.spent) throw new Error('cannot withdraw an already-spent note')\n  if (inputs.publicValue + inputs.fee !== inputs.note.value) {\n    throw new Error(\n      `balance mismatch: publicValue (${inputs.publicValue}) + fee (${inputs.fee}) ` +\n      `≠ note.value (${inputs.note.value})`,\n    )\n  }\n  if (inputs.poolPath.pathElements.length !== POOL_DEPTH) {\n    throw new Error(`pool path depth ${inputs.poolPath.pathElements.length} ≠ ${POOL_DEPTH}`)\n  }\n  if (inputs.aspPath.pathElements.length !== ASP_DEPTH) {\n    throw new Error(`asp path depth ${inputs.aspPath.pathElements.length} ≠ ${ASP_DEPTH}`)\n  }\n\n  // Verify the witness reconstructs to the published root before\n  // submitting — catches stale paths or bad indexer data early.\n  const leaf = bytesToField(inputs.note.commitment)\n  const recomputedPool = await recomputeMerkleRoot({\n    leaf,\n    pathElements: inputs.poolPath.pathElements.map(bytesToField),\n    pathIndices: inputs.poolPath.pathIndices,\n  })\n  if (recomputedPool !== bytesToField(inputs.poolPath.root)) {\n    throw new Error('pool path does not verify against published root')\n  }\n\n  return {\n    value: fieldStr(inputs.note.value),\n    ownerPk: fieldStr(bytesToField(inputs.note.ownerPk)),\n    blinding: fieldStr(bytesToField(inputs.note.blinding)),\n    memo: fieldStr(inputs.note.memo ? bytesToField(inputs.note.memo) : 0n),\n    spendingSk: fieldStr(inputs.spendingSk),\n    nullifierSk: fieldStr(inputs.nullifierSk),\n    poolPathElements: inputs.poolPath.pathElements.map((b) => fieldStr(bytesToField(b))),\n    poolPathIndices: inputs.poolPath.pathIndices,\n    aspPathElements: inputs.aspPath.pathElements.map((b) => fieldStr(bytesToField(b))),\n    aspPathIndices: inputs.aspPath.pathIndices,\n    publicValue: fieldStr(inputs.publicValue),\n    publicAddress: fieldStr(inputs.publicAddress),\n    fee: fieldStr(inputs.fee),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// JoinSplit2x2 witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface JoinSplitInputs {\n  /** Two input notes being spent. */\n  inputs: [OwnedNote, OwnedNote]\n  /** Merkle paths for each input (pool + ASP). */\n  inputPoolPaths: [MerklePath, MerklePath]\n  inputAspPaths: [MerklePath, MerklePath]\n\n  /** Caller's spending key (must derive to both inputs' ownerPk). */\n  spendingSk: bigint\n  /** Caller's nullifier key. */\n  nullifierSk: bigint\n\n  /** Two output notes being created. */\n  outputs: [DecryptedNote, DecryptedNote]\n\n  /** Public-side flow. Set ONE of in/out non-zero, the other to 0n. */\n  publicValueIn: bigint\n  publicValueOut: bigint\n  /** Counterparty hash if publicValueIn or publicValueOut > 0; else 0n. */\n  publicAddress: bigint\n  /** Relayer fee. */\n  fee: bigint\n}\n\nexport async function buildJoinSplitWitness(inputs: JoinSplitInputs): Promise<JoinSplitWitness> {\n  // Hard assertions — fail fast.\n  if (inputs.inputs[0].spent || inputs.inputs[1].spent) {\n    throw new Error('cannot spend an already-spent note')\n  }\n  if (\n    inputs.inputs[0].commitment.every((b, i) => b === inputs.inputs[1].commitment[i])\n  ) {\n    throw new Error(\n      'both inputs share the same commitment — would produce identical nullifiers; ' +\n      'use distinct dummy notes (DUMMY_A vs DUMMY_B from pool config) for padding',\n    )\n  }\n  if (inputs.publicValueIn > 0n && inputs.publicValueOut > 0n) {\n    throw new Error('only one of publicValueIn / publicValueOut may be non-zero')\n  }\n\n  // Balance equation:\n  //   sum(inputValues) + publicValueIn === sum(outputValues) + publicValueOut + fee\n  const inputSum = inputs.inputs[0].value + inputs.inputs[1].value\n  const outputSum = inputs.outputs[0].value + inputs.outputs[1].value\n  const lhs = inputSum + inputs.publicValueIn\n  const rhs = outputSum + inputs.publicValueOut + inputs.fee\n  if (lhs !== rhs) {\n    throw new Error(\n      `balance mismatch: ${inputSum} + ${inputs.publicValueIn} (in) ≠ ${outputSum} + ` +\n      `${inputs.publicValueOut} (out) + ${inputs.fee} (fee)`,\n    )\n  }\n\n  // Verify both pool paths against same root (the circuit asserts this).\n  const root0 = bytesToField(inputs.inputPoolPaths[0].root)\n  const root1 = bytesToField(inputs.inputPoolPaths[1].root)\n  if (root0 !== root1) {\n    throw new Error('both inputs must verify against the same pool root snapshot')\n  }\n\n  // ASP roots same check.\n  const asp0 = bytesToField(inputs.inputAspPaths[0].root)\n  const asp1 = bytesToField(inputs.inputAspPaths[1].root)\n  if (asp0 !== asp1) {\n    throw new Error('both inputs must verify against the same ASP root snapshot')\n  }\n\n  // Verify each input's witness path against its claimed root —\n  // rejects stale or wrong indexer data before we burn 30-60s of CPU.\n  for (let i = 0; i < 2; i++) {\n    const note = inputs.inputs[i]!\n    const poolPath = inputs.inputPoolPaths[i]!\n    const aspPath = inputs.inputAspPaths[i]!\n    const leaf = bytesToField(note.commitment)\n    const recomputedPool = await recomputeMerkleRoot({\n      leaf,\n      pathElements: poolPath.pathElements.map(bytesToField),\n      pathIndices: poolPath.pathIndices,\n    })\n    if (recomputedPool !== bytesToField(poolPath.root)) {\n      throw new Error(`input ${i} pool path does not verify against root`)\n    }\n    const recomputedAsp = await recomputeMerkleRoot({\n      leaf,\n      pathElements: aspPath.pathElements.map(bytesToField),\n      pathIndices: aspPath.pathIndices,\n    })\n    if (recomputedAsp !== bytesToField(aspPath.root)) {\n      throw new Error(`input ${i} ASP path does not verify against root`)\n    }\n  }\n\n  // Spend authority sanity — derived spending_pk should match BOTH\n  // input notes' ownerPk. Circuit constraint G enforces this; we\n  // catch wallet-mixup bugs before the prover.\n  const spendingPk = await deriveSpendingPk(inputs.spendingSk)\n  for (let i = 0; i < 2; i++) {\n    const note = inputs.inputs[i]!\n    const ownerField = bytesToField(note.ownerPk)\n    if (spendingPk !== ownerField) {\n      throw new Error(\n        `input ${i} ownerPk does not match the provided spending key — ` +\n        `did you switch wallets? expected ${spendingPk}, got ${ownerField}`,\n      )\n    }\n  }\n\n  return {\n    inputValue1:       fieldStr(inputs.inputs[0].value),\n    inputOwnerPk1:     fieldStr(bytesToField(inputs.inputs[0].ownerPk)),\n    inputBlinding1:    fieldStr(bytesToField(inputs.inputs[0].blinding)),\n    inputMemo1:        fieldStr(inputs.inputs[0].memo ? bytesToField(inputs.inputs[0].memo) : 0n),\n    inputPoolPath1:    inputs.inputPoolPaths[0].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputPoolIndices1: inputs.inputPoolPaths[0].pathIndices,\n    inputAspPath1:     inputs.inputAspPaths[0].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputAspIndices1:  inputs.inputAspPaths[0].pathIndices,\n\n    inputValue2:       fieldStr(inputs.inputs[1].value),\n    inputOwnerPk2:     fieldStr(bytesToField(inputs.inputs[1].ownerPk)),\n    inputBlinding2:    fieldStr(bytesToField(inputs.inputs[1].blinding)),\n    inputMemo2:        fieldStr(inputs.inputs[1].memo ? bytesToField(inputs.inputs[1].memo) : 0n),\n    inputPoolPath2:    inputs.inputPoolPaths[1].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputPoolIndices2: inputs.inputPoolPaths[1].pathIndices,\n    inputAspPath2:     inputs.inputAspPaths[1].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputAspIndices2:  inputs.inputAspPaths[1].pathIndices,\n\n    spendingSk:  fieldStr(inputs.spendingSk),\n    nullifierSk: fieldStr(inputs.nullifierSk),\n\n    outputValue1:    fieldStr(inputs.outputs[0].value),\n    outputOwnerPk1:  fieldStr(bytesToField(inputs.outputs[0].ownerPk)),\n    outputBlinding1: fieldStr(bytesToField(inputs.outputs[0].blinding)),\n    outputMemo1:     fieldStr(inputs.outputs[0].memo ? bytesToField(inputs.outputs[0].memo) : 0n),\n\n    outputValue2:    fieldStr(inputs.outputs[1].value),\n    outputOwnerPk2:  fieldStr(bytesToField(inputs.outputs[1].ownerPk)),\n    outputBlinding2: fieldStr(bytesToField(inputs.outputs[1].blinding)),\n    outputMemo2:     fieldStr(inputs.outputs[1].memo ? bytesToField(inputs.outputs[1].memo) : 0n),\n\n    publicValueIn:  fieldStr(inputs.publicValueIn),\n    publicValueOut: fieldStr(inputs.publicValueOut),\n    publicAddress:  fieldStr(inputs.publicAddress),\n    fee:            fieldStr(inputs.fee),\n  }\n}\n","// Private Pool V2 — note discovery + ownership tracking.\n//\n// Recipient flow (\"walk into a coffee shop, open my wallet\"):\n//   1. Pull every encrypted note blob from `/api/private-pool/v2/notes`.\n//   2. Trial-decrypt each one with the user's `viewing_sk`.\n//   3. The blobs that decrypt cleanly are the user's notes — collect them.\n//   4. For each owned note, query `/api/private-pool/v2/nullifier/:n`\n//      to determine spent status.\n//   5. Sum unspent values for the displayed balance.\n//\n// Privacy properties this preserves:\n//   - The server doesn't know which notes are yours (it serves every blob).\n//   - The nullifier check tells the server \"is this nullifier spent?\"\n//     without revealing whose. Each query is unlinkable IF the queries\n//     are spread over time (request batching → linkability).\n//   - We trial-decrypt every blob client-side; the only metadata leak is\n//     network timing on the bulk fetch, which is benign because blobs\n//     are public anyway.\n\nimport { tryDecryptNote, type DecryptedNote, type EncryptedNoteBlob } from './encryption';\nimport { poseidon1, fieldToBytes32BE } from './keys';\nimport { buildPoseidon } from 'circomlibjs';\n\nconst FIELD_MODULUS =\n  21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\n// ── API shapes (mirror server/src/routes/private-pool-v2.js) ────────────\n\nexport type EncryptedNoteApiBlob = {\n  pda: string;\n  commitment_hex: string;\n  ephemeral_pk_hex: string;\n  ciphertext_b64: string;\n  ciphertext_len: number;\n};\n\nexport type CommitmentLeafApiEntry = {\n  pda: string;\n  commitment_hex: string;\n  commitment_decimal: string;\n  leaf_index: number;\n  block_slot: number;\n};\n\nexport type NullifierStatusApi = {\n  network: string;\n  nullifier: string;\n  consumed: boolean;\n  used_at_slot?: number;\n  pda?: string;\n};\n\n// ── Owned-note shape ─────────────────────────────────────────────────────\n\nexport type OwnedNote = DecryptedNote & {\n  /// 32-byte commitment as bytes (BE field element). Mirrors the on-chain\n  /// `commitment` field on `CommitmentLeafAccount`.\n  commitment: Uint8Array;\n  /// `0x…64-char-hex` representation of `commitment`.\n  commitmentHex: string;\n  /// `commitment` as a bigint reduced mod the BN254 scalar field. The\n  /// circuit witness uses this form directly.\n  commitmentField: bigint;\n  /// Position in the merkle tree (or `null` when the indexer hasn't\n  /// caught the leaf record yet — happens for ~1s after a fresh deposit).\n  leafIndex: number | null;\n  /// Block slot the leaf was inserted in (best-effort timestamp).\n  blockSlot: number | null;\n  /// Has the matching nullifier been published on chain?\n  spent: boolean;\n  /// When `spent === true`, the slot the nullifier landed in.\n  spentAtSlot?: number;\n};\n\n// ── HTTP helpers ─────────────────────────────────────────────────────────\n\nfunction buildUrl(apiBaseUrl: string, path: string, query: Record<string, string | number | undefined> = {}): string {\n  const url = new URL(path, apiBaseUrl.endsWith('/') ? apiBaseUrl : apiBaseUrl + '/');\n  for (const [k, v] of Object.entries(query)) {\n    if (v !== undefined) url.searchParams.set(k, String(v));\n  }\n  return url.toString();\n}\n\nasync function getJson<T>(url: string, init?: RequestInit): Promise<T> {\n  const r = await fetch(url, init);\n  if (!r.ok) throw new Error(`GET ${url} → ${r.status} ${await r.text()}`);\n  return (await r.json()) as T;\n}\n\n/// Pull every encrypted blob the indexer has published. Filtering is\n/// done client-side (we trial-decrypt each one with our viewing key).\nexport async function fetchEncryptedNoteBlobs(args: {\n  apiBaseUrl: string;\n  network: string;\n  sinceSlot?: number;\n}): Promise<EncryptedNoteApiBlob[]> {\n  const url = buildUrl(args.apiBaseUrl, 'api/private-pool/v2/notes', {\n    network: args.network,\n    sinceSlot: args.sinceSlot,\n  });\n  const body = await getJson<{ blobs: EncryptedNoteApiBlob[] }>(url);\n  return body.blobs;\n}\n\n/// Pull every commitment-leaf record (used to attach `leafIndex` to\n/// each owned note). Cheap enough to call alongside the blob fetch.\nexport async function fetchCommitmentLeaves(args: {\n  apiBaseUrl: string;\n  network: string;\n  since?: number;\n}): Promise<CommitmentLeafApiEntry[]> {\n  const url = buildUrl(args.apiBaseUrl, 'api/private-pool/v2/leaves', {\n    network: args.network,\n    since: args.since,\n  });\n  const body = await getJson<{ leaves: CommitmentLeafApiEntry[] }>(url);\n  return body.leaves;\n}\n\n/// Per-note nullifier check. The server returns `consumed: true|false`.\n/// Frontend should NOT batch all of these into a single request — see\n/// the privacy notes at the top of this module.\n///\n/// Note for hot-path balance reads: prefer `fetchAllNullifiers` (one\n/// round-trip + local `Set.has()`) over calling this in a Promise.all.\n/// Both reach the same on-chain markers, but the batch endpoint\n/// dedupes the cost across every owned note + every concurrent\n/// surface loading at once.\nexport async function fetchNullifierStatus(args: {\n  apiBaseUrl: string;\n  network: string;\n  nullifier: bigint | Uint8Array | string;\n}): Promise<NullifierStatusApi> {\n  let asString: string;\n  if (typeof args.nullifier === 'bigint') {\n    asString = '0x' + args.nullifier.toString(16).padStart(64, '0');\n  } else if (args.nullifier instanceof Uint8Array) {\n    asString = '0x' + Buffer.from(args.nullifier).toString('hex');\n  } else {\n    asString = String(args.nullifier);\n  }\n  const url = buildUrl(\n    args.apiBaseUrl,\n    `api/private-pool/v2/nullifier/${encodeURIComponent(asString)}`,\n    { network: args.network },\n  );\n  return getJson<NullifierStatusApi>(url);\n}\n\n/// Pull every consumed nullifier hex in one request. Used by\n/// `attachSpentStatus` to do membership checks locally — strictly\n/// cheaper than N parallel hits to `/nullifier/:n` (single RPC call\n/// server-side, single cache slot, no per-PDA round-trip).\n///\n/// Trade-off vs. the per-nullifier variant: privacy-wise, on this\n/// endpoint the server still doesn't learn *which* of these\n/// nullifiers belong to the caller (frontend membership-tests them\n/// privately). It's strictly better than parallel /nullifier/:n\n/// fetches, where the server learned the exact set of nullifiers the\n/// caller wanted to check.\nexport async function fetchAllNullifiers(args: {\n  apiBaseUrl: string;\n  network: string;\n}): Promise<string[]> {\n  const url = buildUrl(args.apiBaseUrl, 'api/private-pool/v2/nullifiers/all', {\n    network: args.network,\n  });\n  const body = await getJson<{ nullifiers: string[] }>(url);\n  return body.nullifiers;\n}\n\n// ── Discovery primitives ─────────────────────────────────────────────────\n\nfunction hexToBytes(hex: string): Uint8Array {\n  const clean = hex.startsWith('0x') ? hex.slice(2) : hex;\n  return Uint8Array.from(Buffer.from(clean, 'hex'));\n}\n\n/// Trial-decrypt a list of encrypted blobs with `viewingSk`. Returns the\n/// blobs the auth tag accepts — i.e. the user's own notes. Pure (no RPC).\nexport function trialDecryptBlobs(args: {\n  blobs: EncryptedNoteApiBlob[];\n  viewingSk: Uint8Array;\n  /// Optional bound — stop after this many decryptions (test/dev hook;\n  /// production callers leave it undefined).\n  limit?: number;\n}): Array<{ blob: EncryptedNoteApiBlob; note: DecryptedNote }> {\n  const out: Array<{ blob: EncryptedNoteApiBlob; note: DecryptedNote }> = [];\n  for (const blob of args.blobs) {\n    const ephPk = hexToBytes(blob.ephemeral_pk_hex);\n    const commitment = hexToBytes(blob.commitment_hex);\n    const ciphertext = Uint8Array.from(Buffer.from(blob.ciphertext_b64, 'base64'));\n    const ownedBlob: EncryptedNoteBlob = { ephemeralPk: ephPk, ciphertext, commitment };\n    const note = tryDecryptNote({ blob: ownedBlob, viewingSk: args.viewingSk });\n    if (note) {\n      out.push({ blob, note });\n      if (args.limit && out.length >= args.limit) break;\n    }\n  }\n  return out;\n}\n\n/// One-shot fetch + trial-decrypt + leaf attachment. Returns owned notes\n/// without spent flags — call `attachSpentStatus` after to check chain.\n///\n/// `apiBaseUrl` should NOT include the trailing `/api/private-pool/v2`\n/// segment — pass the host root (e.g. `https://api.relai.fi`) and this\n/// helper appends the path.\nexport async function discoverOwnedNotes(args: {\n  apiBaseUrl: string;\n  network: string;\n  viewingSk: Uint8Array;\n}): Promise<OwnedNote[]> {\n  const [blobs, leaves] = await Promise.all([\n    fetchEncryptedNoteBlobs(args),\n    fetchCommitmentLeaves(args),\n  ]);\n  const leafByCommitment = new Map<string, CommitmentLeafApiEntry>();\n  for (const l of leaves) leafByCommitment.set(l.commitment_hex.toLowerCase(), l);\n\n  const decrypted = trialDecryptBlobs({ blobs, viewingSk: args.viewingSk });\n  return decrypted.map(({ blob, note }) => {\n    const commitment = hexToBytes(blob.commitment_hex);\n    const leaf = leafByCommitment.get(blob.commitment_hex.toLowerCase()) ?? null;\n    return {\n      ...note,\n      commitment,\n      commitmentHex: blob.commitment_hex,\n      commitmentField: BigInt(leaf?.commitment_decimal ?? '0x' + blob.commitment_hex.slice(2)),\n      leafIndex: leaf?.leaf_index ?? null,\n      blockSlot: leaf?.block_slot ?? null,\n      spent: false,\n    };\n  });\n}\n\n// ── Nullifier derivation + spent status ──────────────────────────────────\n\nlet cachedPoseidon: Awaited<ReturnType<typeof buildPoseidon>> | null = null;\nasync function getPoseidonInstance() {\n  if (!cachedPoseidon) cachedPoseidon = await buildPoseidon();\n  return cachedPoseidon!;\n}\n\n/// `nullifier = Poseidon2(commitment, nullifier_sk)`. Matches the\n/// circuit's `NullifierHash` template. Use this off-circuit when\n/// querying `fetchNullifierStatus` to check whether a note is spent.\nexport async function deriveNullifier(commitmentField: bigint, nullifierSkField: bigint): Promise<bigint> {\n  const p = (await getPoseidonInstance()) as any;\n  const h = p([commitmentField, nullifierSkField]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n/// Decorate `notes` with their `spent` flag by checking each nullifier\n/// against the API.\n///\n/// Two evolutionary steps got us here:\n///   1. Original sequential per-note check — privacy-by-spreading. Was\n///      10-30 s of \"Loading…\" with a few dozen notes.\n///   2. Parallel `Promise.all` of per-note `/nullifier/:n` fetches —\n///      faster, but still N RPC calls server-side (and a 20-note\n///      wallet × every surface reloading at once = browser\n///      connection-limit pile-ups + duplicated `getProgramAccounts`\n///      pressure).\n///\n/// Current shape: ONE request to `/nullifiers/all` returning every\n/// consumed nullifier hex, then we Poseidon-derive each note's\n/// nullifier locally and check membership against a `Set`. N → 1\n/// round-trips; the membership check is O(N) on a Set lookup, dwarfed\n/// by the network call.\n///\n/// Privacy: the server-side batch fetch is content-agnostic (it\n/// hands back the full list regardless of caller), so it doesn't learn\n/// which subset belongs to this user. Strictly better than the parallel\n/// /nullifier/:n flow, where the server saw the exact set the caller\n/// wanted to test.\n///\n/// Loss-of-detail: the per-note `spentAtSlot` field used to be\n/// populated from `/nullifier/:n`. The batch endpoint omits it to keep\n/// the payload tight; if a caller needs `spentAtSlot` for a specific\n/// note (e.g. transaction-history reconciliation), they can call\n/// `fetchNullifierStatus` for that single nullifier on-demand. The hot\n/// balance-fetch path doesn't need it.\n///\n/// If you genuinely need the spread-over-time per-note variant (e.g.\n/// a paranoid privacy-mode toggle), call `attachSpentStatusSequential`\n/// below.\nexport async function attachSpentStatus(args: {\n  apiBaseUrl: string;\n  network: string;\n  notes: OwnedNote[];\n  nullifierSkField: bigint;\n}): Promise<OwnedNote[]> {\n  // Fetch all consumed nullifiers + derive ours in parallel (the\n  // Poseidon hashes are independent of the network round-trip, so\n  // there's no reason to wait for one before the other).\n  const [consumedHex, ownedNullifierHex] = await Promise.all([\n    fetchAllNullifiers({ apiBaseUrl: args.apiBaseUrl, network: args.network }).catch((): string[] => []),\n    Promise.all(\n      args.notes.map(async (n) => {\n        const nullifier = await deriveNullifier(n.commitmentField, args.nullifierSkField);\n        return '0x' + nullifier.toString(16).padStart(64, '0');\n      }),\n    ),\n  ])\n  // Normalise to lowercase so the membership check isn't bitten by\n  // 0xAbCd vs 0xabcd casing drift between client and server.\n  const consumedSet = new Set(consumedHex.map((h) => h.toLowerCase()))\n  return args.notes.map((note, i) => ({\n    ...note,\n    spent: consumedSet.has(ownedNullifierHex[i].toLowerCase()),\n  }))\n}\n\n/// Sequential variant — kept for callers who want the original\n/// privacy-conscious behaviour (one nullifier check per note, spread\n/// over time). Slow with N > 10 notes; do not use on UI hot paths.\nexport async function attachSpentStatusSequential(args: {\n  apiBaseUrl: string;\n  network: string;\n  notes: OwnedNote[];\n  nullifierSkField: bigint;\n}): Promise<OwnedNote[]> {\n  const out: OwnedNote[] = [];\n  for (const note of args.notes) {\n    const nullifier = await deriveNullifier(note.commitmentField, args.nullifierSkField);\n    try {\n      const status = await fetchNullifierStatus({\n        apiBaseUrl: args.apiBaseUrl,\n        network: args.network,\n        nullifier,\n      });\n      out.push({ ...note, spent: status.consumed, spentAtSlot: status.used_at_slot });\n    } catch {\n      out.push({ ...note });\n    }\n  }\n  return out;\n}\n\n/// Sum `value` across every unspent note. Returns micro-USDC bigint.\n/// Caller divides by 10^6 for human display.\nexport function totalUnspentBalance(notes: OwnedNote[]): bigint {\n  return notes.reduce((sum, n) => (n.spent ? sum : sum + n.value), 0n);\n}\n\n/// Same shape as `totalUnspentBalance` but breaks down per memo-tag\n/// (handy for receipts / reconciliation).\nexport function balanceBreakdown(notes: OwnedNote[]): {\n  totalMicroUsdc: bigint;\n  unspentCount: number;\n  spentCount: number;\n} {\n  let totalMicroUsdc = 0n;\n  let unspentCount = 0;\n  let spentCount = 0;\n  for (const n of notes) {\n    if (n.spent) spentCount++;\n    else {\n      totalMicroUsdc += n.value;\n      unspentCount++;\n    }\n  }\n  return { totalMicroUsdc, unspentCount, spentCount };\n}\n\n// re-exports for callers that don't want to pull `private-pool-keys`\n// directly (most discovery callers stay in this module).\nexport { fieldToBytes32BE, poseidon1 } from './keys';\n","// Private Pool V2 — high-level orchestration helper.\n//\n// Glues together the foundational libs into the user-facing flows:\n//   - `prepareDepositTransaction(...)` — build witness, prove, build ix,\n//     return a versioned tx ready for the wallet adapter to sign+send\n//   - `prepareWithdrawTransaction(...)` — same, but consumes a note\n//   - `prepareJoinSplitTransaction(...)` — same, 2 inputs / 2 outputs\n//   - `loadConfig(network)` — fetch + cache pool addresses from\n//     `/api/private-pool/v2/config`\n//\n// The wallet adapter takes care of signing + sending (`wallet.sendTransaction`).\n// Backend orkiestracji przyjdzie w v1.1 (sponsored fee payer); MVP is\n// fully client-driven.\n\nimport {\n  AddressLookupTableProgram,\n  ComputeBudgetProgram,\n  Connection,\n  PublicKey,\n  SystemProgram,\n  TransactionInstruction,\n  TransactionMessage,\n  VersionedTransaction,\n} from '@solana/web3.js';\n\nimport { deriveCommitment, deriveNullifier as deriveNullifierAsync, merkleParent } from './witness';\nimport { proveDeposit, proveWithdraw, proveJoinSplit, proveCashoutProof } from './prover';\nimport { parsePaymentAddress, deriveCodeKeys, type CodeKeyMaterial } from './keys';\n// ADR-008 (stealth addresses) was withdrawn 2026-05-08 — see the ADR\n// markdown postmortem. All notes are now legacy ADR-003 (master_pk\n// owner_pk). Future stealth needs a curve-based scheme + circuit\n// upgrade.\nimport {\n  fieldToBytes32BE as keysFieldToBytes32BE,\n  type PrivatePoolKeyMaterial,\n} from './keys';\nimport {\n  encryptNote,\n  tryDecryptNote,\n  type DecryptedNote,\n  PRIVATE_POOL_NOTE_BYTES,\n} from './encryption';\nimport type { OwnedNote } from './discovery';\n\n// ── Config fetcher ───────────────────────────────────────────────────────\n\nexport type PrivatePoolV2Config = {\n  network: string;\n  cluster: 'devnet' | 'mainnet';\n  programIds: {\n    pool: string;\n    depositVerifier: string;\n    withdrawVerifier: string;\n    joinsplitVerifier: string;\n  };\n  poolPda: string;\n  vaultAta: string;\n  usdcMint: string;\n  aspAuthority: string;\n  tokenProgramId: string;\n  associatedTokenProgramId: string;\n  /// Backend's sponsored-fee-payer pubkey (`null` when the backend\n  /// doesn't have a keypair configured — fall back to user-pays-fees).\n  /// When set, frontend builds v0 messages with `payerKey: feePayer`\n  /// so the wallet popup never asks the user for SOL.\n  feePayer: string | null;\n  constants: {\n    poolDepth: number;\n    aspDepth: number;\n    rootHistorySize: number;\n    aspRootHistorySize: number;\n    encryptedNoteBytes: number;\n    encryptedNoteWithMemoBytes: number;\n    usdcDecimals: number;\n  };\n};\n\nconst configCache = new Map<string, PrivatePoolV2Config>();\n\nexport async function loadConfig(args: {\n  apiBaseUrl: string;\n  network: string;\n}): Promise<PrivatePoolV2Config> {\n  const cacheKey = `${args.apiBaseUrl}|${args.network}`;\n  const cached = configCache.get(cacheKey);\n  if (cached) return cached;\n  const url = new URL('api/private-pool/v2/config', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString());\n  if (!r.ok) throw new Error(`loadConfig: ${r.status} ${await r.text()}`);\n  const cfg = (await r.json()) as PrivatePoolV2Config;\n  configCache.set(cacheKey, cfg);\n  return cfg;\n}\n\n// ── PDA helpers (mirror server `pdas.js` shape so the client can derive\n//    the same addresses without round-tripping the backend) ─────────────\n\nexport function fieldToBytes32BE(value: bigint): Buffer {\n  return Buffer.from(keysFieldToBytes32BE(value));\n}\n\nfunction deriveLeafPda(programId: PublicKey, poolPda: PublicKey, commitmentBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-leaf'), poolPda.toBuffer(), commitmentBytes],\n    programId,\n  );\n}\nfunction deriveBlobPda(programId: PublicKey, poolPda: PublicKey, commitmentBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-enc'), poolPda.toBuffer(), commitmentBytes],\n    programId,\n  );\n}\nfunction deriveNullifierPda(programId: PublicKey, poolPda: PublicKey, nullifierBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-nullifier'), poolPda.toBuffer(), nullifierBytes],\n    programId,\n  );\n}\n\nfunction deriveAta(mint: PublicKey, owner: PublicKey, tokenProgram: PublicKey, atokenProgram: PublicKey): PublicKey {\n  return PublicKey.findProgramAddressSync(\n    [owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],\n    atokenProgram,\n  )[0];\n}\n\n// ── Anchor + borsh primitives (frontend mirror) ──────────────────────────\n\nasync function anchorDiscriminator(name: string): Promise<Buffer> {\n  // Use Web Crypto SHA-256 — same output as Node `crypto.createHash`.\n  const enc = new TextEncoder().encode(`global:${name}`);\n  const digest = await crypto.subtle.digest('SHA-256', enc);\n  return Buffer.from(new Uint8Array(digest).slice(0, 8));\n}\n\nfunction writeU64LE(value: bigint | number): Buffer {\n  // The browser polyfill for `Buffer` (`buffer` npm package) doesn't\n  // always implement BigInt-flavoured methods like writeBigUInt64LE\n  // — older versions ship without them. Drop down to DataView, which\n  // is part of the ES standard and supports `setBigUint64` everywhere\n  // we run (Node + every modern browser).\n  const buf = Buffer.alloc(8);\n  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n  view.setBigUint64(0, BigInt(value), true /* littleEndian */);\n  return buf;\n}\n\nfunction writeAnchorVecU8(bytes: Uint8Array): Buffer {\n  const out = Buffer.alloc(4 + bytes.length);\n  out.writeUInt32LE(bytes.length, 0);\n  Buffer.from(bytes).copy(out, 4);\n  return out;\n}\n\n// ── poseidon_addr_hash mirror — match the on-chain handler ──────────────\n\nimport { buildPoseidon } from 'circomlibjs';\n\nconst FIELD_MODULUS =\n  21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nlet cachedPoseidon: any = null;\nasync function getPoseidon(): Promise<any> {\n  if (!cachedPoseidon) cachedPoseidon = await buildPoseidon();\n  return cachedPoseidon;\n}\n\n/// Match the Rust `poseidon_addr_hash`: reduce the SPL pubkey\n/// (interpreted as a 256-bit BE big-int) modulo the BN254 scalar\n/// field, then Poseidon-1.\n///\n/// Both sides do PROPER `bytes mod p` reduction (audit H-3). The\n/// previous `& 0x1f` mask threw away 3 bits of pre-image entropy;\n/// this version preserves full ~254-bit field uniformity.\n///\n/// On-chain side: `reduce_into_bn254_fr` in\n/// `programs/solana-private-pool-v2/src/lib.rs` — hand-rolled\n/// 8-bound subtraction loop against a hard-coded BN254 modulus\n/// constant (avoids pulling `ark_bn254` for one mod op). Both\n/// implementations are covered by 6 unit tests including a Python\n/// cross-reference for the worst-case input.\nasync function poseidonAddrHash(pubkey: PublicKey): Promise<bigint> {\n  const bytes = Buffer.from(pubkey.toBytes());\n  let acc = 0n;\n  for (const b of bytes) acc = (acc << 8n) + BigInt(b);\n  acc = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n  const p = await getPoseidon();\n  const h = p([acc]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// ── Deposit flow ─────────────────────────────────────────────────────────\n\nexport type PrepareDepositArgs = {\n  cfg: PrivatePoolV2Config;\n  /// Wallet pubkey of the depositor (user signing the tx). The proof\n  /// binds `depositor_addr_hash` to this exact pubkey; the on-chain\n  /// handler re-hashes the signer at runtime and rejects on mismatch.\n  depositor: PublicKey;\n  /// Recipient identity. For self-deposit (typical onboarding) pass the\n  /// caller's own derived `spendingPkField`. For depositing to someone\n  /// else, parse their payment address and use that `spendingPkField`.\n  recipientSpendingPkField: bigint;\n  /// 32-byte X25519 viewing pubkey of the recipient. Used to encrypt the\n  /// note for them; the on-chain blob lets them trial-decrypt later.\n  recipientViewingPk: Uint8Array;\n  /// micro-USDC amount entering the pool (full, fee will be subtracted\n  /// inside the circuit to compute the note's value).\n  publicValue: bigint;\n  /// Relayer fee in micro-USDC. Most MVP flows set this to 0.\n  fee: bigint;\n  /// Optional 32-byte memo. `null` for memo-less.\n  memo: Uint8Array | null;\n  /// Leave on-chain ciphertext or send `null` to skip blob storage and\n  /// rely on off-chain delivery (e.g. self-deposit where the depositor\n  /// already has the plaintext locally).\n  storeBlobOnChain?: boolean;\n  // (ADR-008 `useStealthAddress` flag removed — see ADR markdown\n  // postmortem. All notes are now master_pk legacy.)\n};\n\nexport type PreparedDepositTx = {\n  /// Versioned tx ready for `wallet.sendTransaction(tx, conn)`.\n  transaction: VersionedTransaction;\n  /// Computed commitment (hex + field) — useful for showing the user a\n  /// \"this is your note ID\" preview before signing.\n  commitmentHex: string;\n  commitmentField: bigint;\n  /// PDA addresses the handler is going to allocate. Frontend can poll\n  /// these post-confirmation to detect when the indexer has indexed the\n  /// fresh leaf.\n  leafPda: PublicKey;\n  blobPda: PublicKey;\n  /// Local copies of the note material — caller stashes these in\n  /// IndexedDB / localStorage so the recipient can recover the note\n  /// from local state even if the on-chain blob is skipped.\n  ownedNoteSecret: {\n    blinding: Uint8Array;\n    ownerPk: Uint8Array;\n    memo: Uint8Array | null;\n    valueMicroUsdc: bigint;\n  };\n  /// Time spent on Groth16 prove (for UX progress feedback).\n  proveMs: number;\n};\n\n/// Build a deposit tx end-to-end. Caller's responsibility to sign+send\n/// via the wallet adapter.\nexport async function prepareDepositTransaction(\n  args: PrepareDepositArgs,\n  options: { connection: Connection; recentBlockhash?: string } = {} as any,\n): Promise<PreparedDepositTx> {\n  const conn = options.connection;\n  if (!conn) throw new Error('prepareDepositTransaction: connection required');\n\n  // 1. Witness — note value = publicValue - fee.\n  const noteValue = args.publicValue - args.fee;\n  if (noteValue <= 0n) throw new Error('prepareDepositTransaction: noteValue must be positive');\n\n  // Fresh per-note blinding. Real wallets pull from a CSPRNG.\n  const blinding = crypto.getRandomValues(new Uint8Array(32));\n  // Reduce blinding to a field element (just clear top 2 bits).\n  blinding[0] = blinding[0] & 0x3f;\n  const blindingField = bytesToField(blinding);\n\n  // Legacy ADR-003 only: owner_pk = recipient's master spending_pk.\n  const ownerPkField = args.recipientSpendingPkField;\n\n  const memoField = args.memo ? bytesToField(args.memo) : 0n;\n  const commitmentField = await deriveCommitment({\n    value: noteValue,\n    ownerPk: ownerPkField,\n    blinding: blindingField,\n    memo: memoField,\n  });\n\n  const depositorAddrHashField = await poseidonAddrHash(args.depositor);\n\n  // Witness shape mirrors `Deposit.circom` signal declaration order.\n  const witness = {\n    ownerPk: ownerPkField.toString(10),\n    blinding: blindingField.toString(10),\n    memo: memoField.toString(10),\n    publicValue: args.publicValue.toString(10),\n    depositorAddrHash: depositorAddrHashField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').DepositWitness;\n\n  // 2. Prove.\n  const { proofBytes, proveMs } = await proveDeposit(witness);\n\n  // 3. Optional: encrypt the note for the recipient.\n  const commitmentBytes = fieldToBytes32BE(commitmentField);\n  let encryptedNote = new Uint8Array(0);\n  let ephemeralPk = new Uint8Array(32);\n  if (args.storeBlobOnChain !== false) {\n    // ownerPk inside the plaintext mirrors the on-chain commitment's\n    // stealth pubkey when stealth is on (so the recipient's\n    // self-recovery sanity check matches), or the master pubkey for\n    // legacy notes. Either way, recipient does the same Poseidon4\n    // commitment recompute and verifies match.\n    const note: DecryptedNote = {\n      value: noteValue,\n      blinding,\n      ownerPk: fieldToBytes32BE(ownerPkField),\n      memo: args.memo,\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n    const blob = encryptNote({\n      note,\n      recipientViewingPk: args.recipientViewingPk,\n      commitment: commitmentBytes,\n    });\n    encryptedNote = blob.ciphertext;\n    ephemeralPk = blob.ephemeralPk;\n  }\n\n  // 4. Build deposit_note ix.\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const depositorAta = deriveAta(usdcMint, args.depositor, tokenProgram, atokenProgram);\n\n  const [leafPda] = deriveLeafPda(programId, poolPda, commitmentBytes);\n  const [blobPda] = deriveBlobPda(programId, poolPda, commitmentBytes);\n\n  const disc = await anchorDiscriminator('deposit_note');\n  const ixData = Buffer.concat([\n    disc,\n    commitmentBytes,\n    fieldToBytes32BE(depositorAddrHashField),\n    writeU64LE(args.publicValue),\n    writeU64LE(args.fee),\n    writeAnchorVecU8(encryptedNote),\n    Buffer.from(ephemeralPk),\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const verifierProgram = new PublicKey(args.cfg.programIds.depositVerifier);\n\n  const depositIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: args.depositor, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: leafPda, isSigner: false, isWritable: true },\n      { pubkey: blobPda, isSigner: false, isWritable: true },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      { pubkey: depositorAta, isSigner: false, isWritable: true },\n      { pubkey: verifierProgram, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  // Pre-flight: vault ATA + depositor ATA must exist. Add `CreateIdempotent`\n  // ixs for any missing ones — cheaper than a full pre-tx round-trip.\n  const preIxs: TransactionInstruction[] = [];\n  const vaultExists = !!(await conn.getAccountInfo(vaultAta));\n  const depositorAtaExists = !!(await conn.getAccountInfo(depositorAta));\n  if (!vaultExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(args.depositor, vaultAta, poolPda, usdcMint, tokenProgram, atokenProgram));\n  }\n  if (!depositorAtaExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(args.depositor, depositorAta, args.depositor, usdcMint, tokenProgram, atokenProgram));\n  }\n\n  // Compute budget — deposit verifier is cheap (~50k CU), but stay safe.\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });\n\n  // Recent blockhash.\n  const blockhash = options.recentBlockhash\n    ?? (await conn.getLatestBlockhash('confirmed')).blockhash;\n\n  // Sponsored fee payer when backend exposed `feePayer`; otherwise user\n  // pays SOL fees themselves. The wallet popup wording differs noticeably\n  // — sponsored case shows ~0 SOL deduction, fallback shows ~5k lamports.\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : args.depositor;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, ...preIxs, depositIx],\n  }).compileToV0Message();\n  const tx = new VersionedTransaction(message);\n\n  return {\n    transaction: tx,\n    commitmentHex: '0x' + commitmentBytes.toString('hex'),\n    commitmentField,\n    leafPda,\n    blobPda,\n    ownedNoteSecret: {\n      blinding,\n      ownerPk: fieldToBytes32BE(args.recipientSpendingPkField),\n      memo: args.memo,\n      valueMicroUsdc: noteValue,\n    },\n    proveMs,\n  };\n}\n\n// ── shared helpers ───────────────────────────────────────────────────────\n\nfunction bytesToField(bytes: Uint8Array): bigint {\n  if (bytes.length === 0) return 0n;\n  let v = 0n;\n  for (const b of bytes) v = (v << 8n) + BigInt(b);\n  return ((v % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\nfunction buildCreateIdempotentAtaIx(\n  payer: PublicKey,\n  ata: PublicKey,\n  owner: PublicKey,\n  mint: PublicKey,\n  tokenProgram: PublicKey,\n  atokenProgram: PublicKey,\n): TransactionInstruction {\n  return new TransactionInstruction({\n    programId: atokenProgram,\n    keys: [\n      { pubkey: payer, isSigner: true, isWritable: true },\n      { pubkey: ata, isSigner: false, isWritable: true },\n      { pubkey: owner, isSigner: false, isWritable: false },\n      { pubkey: mint, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n    ],\n    data: Buffer.from([1]), // CreateIdempotent discriminator\n  });\n}\n\n// ── Merkle reconstruction (frontend mirror) ─────────────────────────────\n\n/// Build the path for `leafIndex` in a depth-`depth` sparse tree whose\n/// occupied leaves are `leaves` (insertion order).\nasync function buildMerkleTreeFromLeaves(\n  leaves: bigint[],\n  leafIndex: number,\n  depth: number,\n): Promise<{ pathElements: bigint[]; pathIndices: number[]; root: bigint }> {\n  if (leafIndex < 0 || leafIndex >= leaves.length) {\n    throw new Error(`merkle path: leafIndex ${leafIndex} out of range [0, ${leaves.length})`);\n  }\n  const zeroHashes: bigint[] = [0n];\n  for (let i = 0; i < depth; i++) {\n    zeroHashes.push(await merkleParent(zeroHashes[i], zeroHashes[i]));\n  }\n  const pathElements: bigint[] = [];\n  const pathIndices: number[] = [];\n  let layer = [...leaves];\n  let cur = leafIndex;\n  for (let level = 0; level < depth; level++) {\n    const isRight = cur % 2;\n    const sibling =\n      isRight === 0\n        ? layer[cur + 1] !== undefined\n          ? layer[cur + 1]\n          : zeroHashes[level]\n        : layer[cur - 1] !== undefined\n        ? layer[cur - 1]\n        : zeroHashes[level];\n    pathElements.push(sibling);\n    pathIndices.push(isRight);\n    const next: bigint[] = [];\n    for (let i = 0; i < layer.length; i += 2) {\n      const l = layer[i];\n      const r = i + 1 < layer.length ? layer[i + 1] : zeroHashes[level];\n      next.push(await merkleParent(l, r));\n    }\n    layer = next;\n    cur = Math.floor(cur / 2);\n  }\n  // Compute root.\n  let rootLayer = [...leaves];\n  for (let level = 0; level < depth; level++) {\n    const next: bigint[] = [];\n    for (let i = 0; i < rootLayer.length; i += 2) {\n      const l = rootLayer[i];\n      const r = i + 1 < rootLayer.length ? rootLayer[i + 1] : zeroHashes[level];\n      next.push(await merkleParent(l, r));\n    }\n    if (rootLayer.length === 0) {\n      rootLayer = [zeroHashes[depth]];\n      break;\n    }\n    rootLayer = next.length > 0 ? next : [zeroHashes[level + 1]];\n  }\n  return { pathElements, pathIndices, root: rootLayer[0] };\n}\n\n// ── Withdraw flow ────────────────────────────────────────────────────────\n\nexport type PrepareWithdrawArgs = {\n  cfg: PrivatePoolV2Config;\n  /// User's master spending_sk (the one derived from the wallet sig).\n  /// For ADR-008 stealth notes the circuit needs the per-note\n  /// `noteStealthSpendingSkField` instead — pass that via the field\n  /// below; this `spendingSkField` is the fallback used for legacy\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  /// The note to consume (one OwnedNote-shaped value; we accept just the\n  /// fields the witness needs so callers can pass a slim subset).\n  note: {\n    value: bigint;\n    blinding: bigint;\n    memo: bigint;\n    commitmentField: bigint;\n    leafIndex: number;\n  };\n  /// All commitments currently in the pool, in insertion order. Caller\n  /// fetches via `GET /api/private-pool/v2/leaves`.\n  allLeaves: bigint[];\n  /// Pool root the proof will be built against. Must match what the\n  /// reconstructed merkle root yields for `allLeaves`. Caller pulls this\n  /// from `pool.latest_root` (or any of the last 64 root_history entries).\n  poolRoot: bigint;\n  /// Active ASP root that contains `note.commitmentField`. Caller is\n  /// responsible for ensuring the ASP authority has published this root.\n  aspRoot: bigint;\n  /// ASP merkle path for the note's commitment (also caller's responsibility).\n  aspPath: { pathElements: bigint[]; pathIndices: number[] };\n  /// Recipient SPL pubkey — Poseidon-1 hashed for the proof.\n  recipient: PublicKey;\n  /// micro-USDC paid out to recipient (full note value − fee).\n  publicValue: bigint;\n  fee: bigint;\n};\n\nexport type PreparedWithdrawTx = {\n  transaction: VersionedTransaction;\n  nullifierField: bigint;\n  nullifierPda: PublicKey;\n  proveMs: number;\n};\n\nexport async function prepareWithdrawTransaction(\n  args: PrepareWithdrawArgs,\n  options: { connection: Connection; payer: PublicKey; recentBlockhash?: string },\n): Promise<PreparedWithdrawTx> {\n  // 1. Reconstruct the pool merkle path for the note's leaf.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  );\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareWithdrawTransaction: reconstructed pool root ${poolPath.root.toString(16)} does not match supplied ${args.poolRoot.toString(16)} — likely stale leaves array`,\n    );\n  }\n\n  // 2. Derive the public address binding (Poseidon-1 of recipient pubkey).\n  const publicAddressField = await poseidonAddrHash(args.recipient);\n\n  // Legacy ADR-003: owner_pk = poseidon1(master_spending_sk). The\n  // commitment on chain was minted with this exact owner_pk, so the\n  // circuit's `poseidon1(spendingSk) == ownerPk` check tautologically\n  // holds when both sides come from the same master sk.\n  const p = await getPoseidon();\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n\n  // Witness shape — mirrors `Withdraw.circom` signal order. Same rationale\n  // as deposit: we build the field-string map directly, bypassing the\n  // builder that expects Uint8Array shapes.\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness;\n\n  // 4. Prove + pack.\n  const { proofBytes, proveMs } = await proveWithdraw(witness);\n\n  // 5. Compute the nullifier (matches what the circuit emits).\n  const nullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  });\n  const nullifierBytes = fieldToBytes32BE(nullifierField);\n\n  // 6. Build the ix.\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const recipientAta = deriveAta(usdcMint, args.recipient, tokenProgram, atokenProgram);\n  const verifier = new PublicKey(args.cfg.programIds.withdrawVerifier);\n\n  const [nullifierPda] = deriveNullifierPda(programId, poolPda, nullifierBytes);\n\n  const disc = await anchorDiscriminator('withdraw');\n  const ixData = Buffer.concat([\n    disc,\n    nullifierBytes,\n    fieldToBytes32BE(args.poolRoot),\n    fieldToBytes32BE(args.aspRoot),\n    fieldToBytes32BE(publicAddressField),\n    writeU64LE(args.publicValue),\n    writeU64LE(args.fee),\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const withdrawIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: options.payer, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: nullifierPda, isSigner: false, isWritable: true },\n      { pubkey: args.recipient, isSigner: false, isWritable: false },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      { pubkey: recipientAta, isSigner: false, isWritable: true },\n      { pubkey: verifier, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  // Pre-flight: recipient ATA may not exist yet on a fresh wallet.\n  const preIxs: TransactionInstruction[] = [];\n  const recipientAtaExists = !!(await options.connection.getAccountInfo(recipientAta));\n  if (!recipientAtaExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(options.payer, recipientAta, args.recipient, usdcMint, tokenProgram, atokenProgram));\n  }\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });\n\n  const blockhash = options.recentBlockhash\n    ?? (await options.connection.getLatestBlockhash('confirmed')).blockhash;\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : options.payer;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, ...preIxs, withdrawIx],\n  }).compileToV0Message();\n  const tx = new VersionedTransaction(message);\n  return { transaction: tx, nullifierField, nullifierPda, proveMs };\n}\n\n// ── Relayer-signed withdraw (ADR-010) ────────────────────────────────────\n//\n// Companion to `prepareWithdrawTransaction`: instead of returning a\n// VersionedTransaction the caller has to sign + broadcast (which puts\n// the user's wallet pubkey into the on-chain `signers` array),\n// `prepareWithdrawRelayPayload` returns a typed bundle the SDK ships\n// to `POST /api/private-pool/v2/relay/withdraw`. The backend wraps it\n// in a tx with `caller = relayer_keypair` and broadcasts.\n//\n// Net effect on chain: the user's wallet never appears as a signer of\n// the withdraw tx. The proof is the only authorisation primitive\n// (recipient is bound via `public_address = Poseidon(recipient)`;\n// amount + fee + roots are public inputs the verifier cross-checks).\n// See ADR-010 §Threat model for what the relayer can / cannot do.\n\nexport type WithdrawRelayPayload = {\n  network: string\n  nullifier_hex: string\n  root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  recipient: string\n  recipient_ata: string\n  proof_b64: string\n}\n\nexport type PreparedWithdrawRelay = {\n  payload: WithdrawRelayPayload\n  nullifierField: bigint\n  proveMs: number\n}\n\nexport async function prepareWithdrawRelayPayload(\n  args: PrepareWithdrawArgs & { network: string },\n): Promise<PreparedWithdrawRelay> {\n  // The proof + public-input derivation is identical to\n  // `prepareWithdrawTransaction`; only the output shape differs.\n  // We inline the steps rather than refactoring the existing function\n  // to share a helper because every step inside that function is\n  // tightly couple to the on-chain ix layout (witness shape, account\n  // ordering) — duplicating a few lines keeps each path independently\n  // auditable.\n\n  // 1. Pool merkle path.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareWithdrawRelayPayload: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — likely stale leaves array`,\n    )\n  }\n\n  // 2. Recipient binding.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. Witness (same shape as the tx flow; see `prepareWithdrawTransaction`).\n  // CRITICAL: this must match `Withdraw.circom` signal inputs EXACTLY.\n  // The circuit derives poolRoot/aspRoot internally from the path elements\n  // and verifies leafIndex via `poolPathIndices` — adding those as separate\n  // signals fails witness gen with \"Signal X not found\" under strict snarkjs.\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n\n  const { proofBytes, proveMs } = await proveWithdraw(witness)\n\n  // 4. Nullifier (matches the circuit's emission).\n  const nullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n  const nullifierBytes = fieldToBytes32BE(nullifierField)\n\n  // 5. Pre-derive the recipient ATA so the server doesn't re-do the\n  //    derivation work + we can cross-check the proof's binding before\n  //    burning relayer SOL.\n  const usdcMint = new PublicKey(args.cfg.usdcMint)\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId)\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId)\n  const recipientAta = deriveAta(usdcMint, args.recipient, tokenProgram, atokenProgram)\n\n  // 6. Build the JSON payload. All bigint values serialised as\n  //    decimal strings — the server parses them with `BigInt(…)`,\n  //    bypassing JS Number precision loss above 2^53.\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n\n  const payload: WithdrawRelayPayload = {\n    network: args.network,\n    nullifier_hex: toHex32(nullifierField),\n    root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, nullifierField, proveMs }\n}\n\n/**\n * Submit a prepared relay payload. Returns the on-chain tx signature\n * + an explorer URL when confirmation succeeds.\n *\n * Network-error handling: if the POST itself fails (server down,\n * timeout, etc.), the error is rethrown for the caller to retry —\n * the on-chain state is unchanged because no tx was built. If the\n * POST returns a structured error (e.g. `already_consumed`,\n * `fee_below_min`), it's surfaced as a typed exception with the\n * server's error code on `cause.error`.\n */\nexport async function submitWithdrawViaRelay(args: {\n  apiBaseUrl: string\n  payload: WithdrawRelayPayload\n}): Promise<{ signature: string; confirmed: boolean; explorerUrl: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/withdraw',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  const body = (await res.json().catch(() => ({}))) as Record<string, unknown>\n  if (!res.ok) {\n    const err = new Error(\n      `relay/withdraw rejected (HTTP ${res.status}): ${body.error ?? 'unknown'} — ${body.detail ?? ''}`,\n    )\n    ;(err as Error & { cause?: unknown }).cause = body\n    throw err\n  }\n  return {\n    signature: String(body.signature ?? ''),\n    confirmed: Boolean(body.confirmed),\n    explorerUrl: String(body.explorerUrl ?? ''),\n  }\n}\n\n// ── JoinSplit flow ───────────────────────────────────────────────────────\n//\n// Solana's 1232-byte tx limit forces a couple of compromises here:\n//   - Use a v0 tx with an Address Lookup Table (caller manages the LUT,\n//     normally created once per pool and reused).\n//   - Encrypted-note blobs go off-chain (per ADR-004 amendment); caller\n//     POSTs them to `/api/private-pool/v2/notes/relay-blob` AFTER the tx\n//     confirms.\n//\n// Caller is responsible for:\n//   - publishing an ASP root containing both input commitments (separate\n//     `add_asp_root` tx, gated by ASP authority signer)\n//   - allocating + extending the LUT and waiting for it to land\n//   - encrypting the recipient + change ciphertexts and uploading them\n//     to the relay (we return the cipher material so the caller can do\n//     this in parallel with confirmation polling)\n\nexport type JoinSplitNoteRecipient = {\n  /// Recipient's public spending key field (Poseidon1 of their spending_sk).\n  spendingPkField: bigint;\n  /// Recipient's X25519 viewing pubkey (32 bytes) — used for blob encryption.\n  viewingPk: Uint8Array;\n};\n\nexport type PrepareJoinSplitArgs = {\n  cfg: PrivatePoolV2Config;\n  /// Sender's keys (must be the owner of both input notes).\n  senderSpendingSkField: bigint;\n  senderSpendingPkField: bigint; // Poseidon1(senderSpendingSkField) — mirror for owner_pk equality\n  senderNullifierSkField: bigint;\n  senderViewingPk: Uint8Array; // for self-encrypting the change note\n  /// Two input notes the sender owns + their merkle paths in the pool tree.\n  /// All notes are legacy ADR-003 (master_pk owner_pk) in V1.\n  inputNotes: [\n    { value: bigint; blinding: bigint; memo: bigint; commitmentField: bigint; leafIndex: number },\n    { value: bigint; blinding: bigint; memo: bigint; commitmentField: bigint; leafIndex: number },\n  ];\n  allLeaves: bigint[];\n  poolRoot: bigint;\n  aspRoot: bigint;\n  aspPaths: [\n    { pathElements: bigint[]; pathIndices: number[] },\n    { pathElements: bigint[]; pathIndices: number[] },\n  ];\n  /// Recipient identity. Output 1 goes here; output 2 is sender's change.\n  recipient: JoinSplitNoteRecipient;\n  /// Amount transferred to recipient (micro-USDC). Must satisfy\n  /// `recipientAmount + changeAmount + fee == in1.value + in2.value`.\n  recipientAmount: bigint;\n  fee: bigint;\n  /// Optional 32-byte memo attached to the recipient's output note —\n  /// the recipient sees it after decrypting their blob. Use cases:\n  /// invoice IDs (binds the JoinSplit to a specific invoice), payment\n  /// reason codes, free-text labels (UTF-8 truncated). Pass `null` for\n  /// memo-less notes (default).\n  recipientMemo?: Uint8Array | null;\n  /// Lookup table to fold constant accounts into. Caller created earlier.\n  lookupTable: import('@solana/web3.js').AddressLookupTableAccount;\n};\n\nexport type PreparedJoinSplitTx = {\n  transaction: VersionedTransaction;\n  nullifiers: [bigint, bigint];\n  outputCommitments: [bigint, bigint];\n  /// Materials the caller MUST relay to the backend after confirmation,\n  /// otherwise the recipient never sees the new note.\n  blobs: [\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n  ];\n  proveMs: number;\n};\n\nexport async function prepareJoinSplitTransaction(\n  args: PrepareJoinSplitArgs,\n  options: { connection: Connection; payer: PublicKey; recentBlockhash?: string },\n): Promise<PreparedJoinSplitTx> {\n  // 1. Balance check before anything expensive.\n  const totalIn = args.inputNotes[0].value + args.inputNotes[1].value;\n  const changeAmount = totalIn - args.recipientAmount - args.fee;\n  if (changeAmount < 0n) {\n    throw new Error(\n      `prepareJoinSplitTransaction: insufficient input value (have ${totalIn}, need ${args.recipientAmount + args.fee})`,\n    );\n  }\n\n  // All notes are legacy ADR-003 in V1 (stealth removed — see ADR-008\n  // postmortem). Both inputs spend under the master spending_sk, both\n  // outputs land under the recipient's / sender's master spending_pk.\n  const inputSpendingSkField = args.senderSpendingSkField;\n  const inputOwnerPkField = args.senderSpendingPkField;\n\n  // 2. Reconstruct merkle paths for both inputs.\n  const path1 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[0].leafIndex, args.cfg.constants.poolDepth);\n  const path2 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[1].leafIndex, args.cfg.constants.poolDepth);\n  if (path1.root !== args.poolRoot || path2.root !== args.poolRoot) {\n    throw new Error('prepareJoinSplitTransaction: pool root mismatch on reconstruction');\n  }\n\n  // 3. Generate fresh blindings for the two output notes.\n  const out1Blinding = randomFieldBytes();\n  const out2Blinding = randomFieldBytes();\n  const out1BlindingField = bytesToField(out1Blinding);\n  const out2BlindingField = bytesToField(out2Blinding);\n\n  // Recipient memo (optional 32-byte tag). The change note has no memo.\n  const recipientMemo = args.recipientMemo ?? null;\n  if (recipientMemo && recipientMemo.length !== 32) {\n    throw new Error(`prepareJoinSplitTransaction: recipientMemo must be 32 bytes, got ${recipientMemo.length}`);\n  }\n  const recipientMemoField = recipientMemo ? bytesToField(recipientMemo) : 0n;\n\n  // Legacy ADR-003 outputs — recipient + change notes carry the master\n  // `spending_pk` of their respective owners. Per-recipient\n  // unlinkability is weaker (two transfers to the same pubkey share\n  // the same `owner_pk`) but amounts + sender stay hidden via the\n  // anonymity set — V1 trade-off until a curve-based stealth scheme\n  // ships (see ADR-008 postmortem).\n  const recipientOwnerPk = args.recipient.spendingPkField;\n  const changeOwnerPk = args.senderSpendingPkField;\n\n  // Witness shape mirrors `JoinSplit2x2.circom` signal declaration order.\n  // Built directly from bigint inputs (cheaper than going through the\n  // Uint8Array-shaped builder API).\n  //\n  // `inputOwnerPkN` and `spendingSk` are the stealth-aware values\n  // (= master pair when both inputs are non-stealth, = stealth pair\n  // when both inputs share the same stealth nonce). Outputs always use\n  // the per-payment stealth `owner_pk` — the recipient/sender unwrap\n  // their nonce from the blob during discovery.\n  const witness = {\n    inputValue1: args.inputNotes[0].value.toString(10),\n    inputOwnerPk1: inputOwnerPkField.toString(10),\n    inputBlinding1: args.inputNotes[0].blinding.toString(10),\n    inputMemo1: args.inputNotes[0].memo.toString(10),\n    inputPoolPath1: path1.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices1: path1.pathIndices,\n    inputAspPath1: args.aspPaths[0].pathElements.map((e) => e.toString(10)),\n    inputAspIndices1: args.aspPaths[0].pathIndices,\n\n    inputValue2: args.inputNotes[1].value.toString(10),\n    inputOwnerPk2: inputOwnerPkField.toString(10),\n    inputBlinding2: args.inputNotes[1].blinding.toString(10),\n    inputMemo2: args.inputNotes[1].memo.toString(10),\n    inputPoolPath2: path2.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices2: path2.pathIndices,\n    inputAspPath2: args.aspPaths[1].pathElements.map((e) => e.toString(10)),\n    inputAspIndices2: args.aspPaths[1].pathIndices,\n\n    spendingSk: inputSpendingSkField.toString(10),\n    nullifierSk: args.senderNullifierSkField.toString(10),\n\n    outputValue1: args.recipientAmount.toString(10),\n    outputOwnerPk1: recipientOwnerPk.toString(10),\n    outputBlinding1: out1BlindingField.toString(10),\n    outputMemo1: recipientMemoField.toString(10),\n\n    outputValue2: changeAmount.toString(10),\n    outputOwnerPk2: changeOwnerPk.toString(10),\n    outputBlinding2: out2BlindingField.toString(10),\n    outputMemo2: '0',\n\n    publicValueIn: '0',\n    publicValueOut: '0',\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').JoinSplitWitness;\n\n  // 5. Prove.\n  const { proofBytes, proveMs } = await proveJoinSplit(witness);\n\n  // 6. Compute output commitments + nullifiers (witness builder didn't\n  //    surface them as bigints, so re-derive from the public signals — or\n  //    cleaner: recompute via Poseidon4).\n  //\n  //    Output commitments use the stealth `owner_pk` so they match the\n  //    on-chain leaf the verifier emits from the public-input `outputOwnerPkN`.\n  const out1Commitment = await poseidonHashFour(\n    args.recipientAmount,\n    recipientOwnerPk,\n    out1BlindingField,\n    recipientMemoField,\n  );\n  const out2Commitment = await poseidonHashFour(\n    changeAmount,\n    changeOwnerPk,\n    out2BlindingField,\n    0n,\n  );\n  const n1 = await deriveNullifierAsync({\n    commitment: args.inputNotes[0].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  });\n  const n2 = await deriveNullifierAsync({\n    commitment: args.inputNotes[1].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  });\n\n  // 7. Encrypt blobs (recipient note + sender's change note).\n  //\n  //    Plaintext carries:\n  //      - `ownerPk`: the stealth `owner_pk` so the recipient's\n  //        Poseidon4 commitment recompute (during decrypt sanity-check)\n  //        matches the on-chain leaf\n  //      - `stealthNonce`: lets the recipient derive\n  //        `stealth_spending_sk = poseidon2(master_sk, nonce)`\n  //        so they can spend later\n  //      - `layout: 'stealth'`: triggers the 112B/144B serializer path\n  const oc1Bytes = fieldToBytes32BE(out1Commitment);\n  const oc2Bytes = fieldToBytes32BE(out2Commitment);\n  const recipientBlob = encryptNote({\n    note: {\n      value: args.recipientAmount,\n      blinding: out1Blinding,\n      ownerPk: fieldToBytes32BE(recipientOwnerPk),\n      memo: recipientMemo,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.recipient.viewingPk,\n    commitment: oc1Bytes,\n  });\n  const changeBlob = encryptNote({\n    note: {\n      value: changeAmount,\n      blinding: out2Blinding,\n      ownerPk: fieldToBytes32BE(changeOwnerPk),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.senderViewingPk,\n    commitment: oc2Bytes,\n  });\n\n  // 8. Build the ix. Empty `encrypted_note` per output (ADR-004 amendment:\n  //    JoinSplit blobs go off-chain via the relay).\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const verifier = new PublicKey(args.cfg.programIds.joinsplitVerifier);\n\n  const n1Bytes = fieldToBytes32BE(n1);\n  const n2Bytes = fieldToBytes32BE(n2);\n  const [nMarker1] = deriveNullifierPda(programId, poolPda, n1Bytes);\n  const [nMarker2] = deriveNullifierPda(programId, poolPda, n2Bytes);\n  const [leaf1] = deriveLeafPda(programId, poolPda, oc1Bytes);\n  const [leaf2] = deriveLeafPda(programId, poolPda, oc2Bytes);\n  const [blob1] = deriveBlobPda(programId, poolPda, oc1Bytes);\n  const [blob2] = deriveBlobPda(programId, poolPda, oc2Bytes);\n\n  const disc = await anchorDiscriminator('joinsplit');\n  const ixData = Buffer.concat([\n    disc,\n    n1Bytes, n2Bytes,\n    oc1Bytes, oc2Bytes,\n    fieldToBytes32BE(args.poolRoot),\n    fieldToBytes32BE(args.aspRoot),\n    fieldToBytes32BE(0n), // public_address (pure internal)\n    writeU64LE(0n),  // public_value_in\n    writeU64LE(0n),  // public_value_out\n    writeU64LE(args.fee),\n    writeAnchorVecU8(new Uint8Array(0)), // encrypted_note_1 — empty (off-chain)\n    writeAnchorVecU8(new Uint8Array(0)), // encrypted_note_2 — empty (off-chain)\n    Buffer.alloc(32),                    // ephemeral_pk_1 — zero (unused)\n    Buffer.alloc(32),                    // ephemeral_pk_2 — zero (unused)\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const joinsplitIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: options.payer, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: nMarker1, isSigner: false, isWritable: true },\n      { pubkey: nMarker2, isSigner: false, isWritable: true },\n      { pubkey: leaf1, isSigner: false, isWritable: true },\n      { pubkey: leaf2, isSigner: false, isWritable: true },\n      { pubkey: blob1, isSigner: false, isWritable: true },\n      { pubkey: blob2, isSigner: false, isWritable: true },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      // pure internal: pass payer as spl_counterparty + payer ATA\n      { pubkey: options.payer, isSigner: false, isWritable: false },\n      { pubkey: deriveAta(usdcMint, options.payer, tokenProgram, atokenProgram), isSigner: false, isWritable: true },\n      { pubkey: verifier, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 800_000 });\n  const blockhash = options.recentBlockhash\n    ?? (await options.connection.getLatestBlockhash('confirmed')).blockhash;\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : options.payer;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, joinsplitIx],\n  }).compileToV0Message([args.lookupTable]);\n  const tx = new VersionedTransaction(message);\n\n  return {\n    transaction: tx,\n    nullifiers: [n1, n2],\n    outputCommitments: [out1Commitment, out2Commitment],\n    blobs: [\n      {\n        commitmentHex: '0x' + Buffer.from(oc1Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(recipientBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(recipientBlob.ciphertext).toString('base64'),\n        ciphertextLen: recipientBlob.ciphertext.length,\n      },\n      {\n        commitmentHex: '0x' + Buffer.from(oc2Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(changeBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(changeBlob.ciphertext).toString('base64'),\n        ciphertextLen: changeBlob.ciphertext.length,\n      },\n    ],\n    proveMs,\n  };\n}\n\n// ── Relayer-signed joinsplit (ADR-010) ───────────────────────────────────\n//\n// Sibling of `prepareWithdrawRelayPayload`. Same trade-offs and same\n// \"caller is unbound\" trick the Anchor program already supports. Top-up\n// (`public_value_in > 0`) is rejected server-side because the SPL\n// transfer in that branch requires the counterparty's signing\n// authority; this function still accepts pure-internal and mid-flow\n// withdraw shapes.\n\nexport type JoinSplitRelayPayload = {\n  network: string\n  root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  input_nullifiers_hex: [string, string]\n  output_commitments_hex: [string, string]\n  ephemeral_pks_b64: [string, string]\n  encrypted_notes_b64: [string, string]\n  spl_counterparty: string\n  spl_counterparty_ata: string\n  public_value_in: string\n  public_value_out: string\n  fee: string\n  proof_b64: string\n}\n\nexport type PreparedJoinSplitRelay = {\n  payload: JoinSplitRelayPayload\n  nullifiers: [bigint, bigint]\n  outputCommitments: [bigint, bigint]\n  /// Same shape as `PreparedJoinSplitTx.blobs` — the SDK still POSTs\n  /// these to /notes/relay-blob AFTER the relay tx confirms, so the\n  /// recipient can trial-decrypt. The relay tx ITSELF carries empty\n  /// `encrypted_note_*` Vec<u8> (ADR-004 amendment).\n  blobs: [\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n  ]\n  proveMs: number\n}\n\n/// Args for the relay flow. Same as `PrepareJoinSplitArgs` minus the\n/// `lookupTable` (the server builds a legacy Transaction without LUT\n/// since with only one signer the joinsplit ix fits comfortably under\n/// the 1232-byte tx size limit).\nexport type PrepareJoinSplitRelayArgs = Omit<PrepareJoinSplitArgs, 'lookupTable'> & {\n  network: string\n  /// Sender's wallet pubkey — used to derive the depositor's own ATA\n  /// which the on-chain ix struct requires as `spl_counterparty` even\n  /// for pure-internal flows (handler short-circuits the SPL movement\n  /// when both public values are zero).\n  payer: PublicKey\n}\n\nexport async function prepareJoinSplitRelayPayload(\n  args: PrepareJoinSplitRelayArgs,\n): Promise<PreparedJoinSplitRelay> {\n  // Balance + path reconstruction, mirroring `prepareJoinSplitTransaction`\n  // (no shared helper for the same reason as withdraw — keep each\n  // path independently auditable).\n  const totalIn = args.inputNotes[0].value + args.inputNotes[1].value\n  const changeAmount = totalIn - args.recipientAmount - args.fee\n  if (changeAmount < 0n) {\n    throw new Error(\n      `prepareJoinSplitRelayPayload: insufficient input value (have ${totalIn}, need ${args.recipientAmount + args.fee})`,\n    )\n  }\n\n  const path1 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[0].leafIndex, args.cfg.constants.poolDepth)\n  const path2 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[1].leafIndex, args.cfg.constants.poolDepth)\n  if (path1.root !== args.poolRoot || path2.root !== args.poolRoot) {\n    throw new Error('prepareJoinSplitRelayPayload: pool root mismatch on reconstruction')\n  }\n\n  // Fresh blindings for the two outputs.\n  const out1Blinding = randomFieldBytes()\n  const out2Blinding = randomFieldBytes()\n  const out1BlindingField = bytesToField(out1Blinding)\n  const out2BlindingField = bytesToField(out2Blinding)\n\n  // Recipient memo (optional 32-byte tag); change note has no memo.\n  const recipientMemo = args.recipientMemo ?? null\n  if (recipientMemo && recipientMemo.length !== 32) {\n    throw new Error(`prepareJoinSplitRelayPayload: recipientMemo must be 32 bytes, got ${recipientMemo.length}`)\n  }\n  const recipientMemoField = recipientMemo ? bytesToField(recipientMemo) : 0n\n\n  // Owner_pk per output. Same legacy ADR-003 rule as the tx flow.\n  const recipientOwnerPk = args.recipient.spendingPkField\n  const changeOwnerPk = args.senderSpendingPkField\n\n  // Witness — identical shape to the tx path; the proof bytes carry\n  // the same public-input commitments.\n  const witness = {\n    inputValue1: args.inputNotes[0].value.toString(10),\n    inputOwnerPk1: args.senderSpendingPkField.toString(10),\n    inputBlinding1: args.inputNotes[0].blinding.toString(10),\n    inputMemo1: args.inputNotes[0].memo.toString(10),\n    inputPoolPath1: path1.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices1: path1.pathIndices,\n    inputAspPath1: args.aspPaths[0].pathElements.map((e) => e.toString(10)),\n    inputAspIndices1: args.aspPaths[0].pathIndices,\n\n    inputValue2: args.inputNotes[1].value.toString(10),\n    inputOwnerPk2: args.senderSpendingPkField.toString(10),\n    inputBlinding2: args.inputNotes[1].blinding.toString(10),\n    inputMemo2: args.inputNotes[1].memo.toString(10),\n    inputPoolPath2: path2.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices2: path2.pathIndices,\n    inputAspPath2: args.aspPaths[1].pathElements.map((e) => e.toString(10)),\n    inputAspIndices2: args.aspPaths[1].pathIndices,\n\n    spendingSk: args.senderSpendingSkField.toString(10),\n    nullifierSk: args.senderNullifierSkField.toString(10),\n\n    outputValue1: args.recipientAmount.toString(10),\n    outputOwnerPk1: recipientOwnerPk.toString(10),\n    outputBlinding1: out1BlindingField.toString(10),\n    outputMemo1: recipientMemoField.toString(10),\n\n    outputValue2: changeAmount.toString(10),\n    outputOwnerPk2: changeOwnerPk.toString(10),\n    outputBlinding2: out2BlindingField.toString(10),\n    outputMemo2: '0',\n\n    publicValueIn: '0',\n    publicValueOut: '0',\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').JoinSplitWitness\n\n  const { proofBytes, proveMs } = await proveJoinSplit(witness)\n\n  // Output commitments + nullifiers (derived post-prove so we don't\n  // need a separate witness pass).\n  const out1Commitment = await poseidonHashFour(args.recipientAmount, recipientOwnerPk, out1BlindingField, recipientMemoField)\n  const out2Commitment = await poseidonHashFour(changeAmount, changeOwnerPk, out2BlindingField, 0n)\n  const n1 = await deriveNullifierAsync({\n    commitment: args.inputNotes[0].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  })\n  const n2 = await deriveNullifierAsync({\n    commitment: args.inputNotes[1].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  })\n\n  // Encrypt both blobs (recipient + change). Plaintext same shape as\n  // the tx path so the recipient's decrypt code is identical.\n  const oc1Bytes = fieldToBytes32BE(out1Commitment)\n  const oc2Bytes = fieldToBytes32BE(out2Commitment)\n  const recipientBlob = encryptNote({\n    note: {\n      value: args.recipientAmount,\n      blinding: out1Blinding,\n      ownerPk: fieldToBytes32BE(recipientOwnerPk),\n      memo: recipientMemo,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.recipient.viewingPk,\n    commitment: oc1Bytes,\n  })\n  const changeBlob = encryptNote({\n    note: {\n      value: changeAmount,\n      blinding: out2Blinding,\n      ownerPk: fieldToBytes32BE(changeOwnerPk),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.senderViewingPk,\n    commitment: oc2Bytes,\n  })\n\n  // Pure-internal sub-case: pass payer as spl_counterparty + their\n  // ATA. The handler short-circuits SPL movement when both public\n  // values are zero, so these accounts are unused on chain but the\n  // ix struct requires them.\n  const usdcMint = new PublicKey(args.cfg.usdcMint)\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId)\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId)\n  const payerAta = deriveAta(usdcMint, args.payer, tokenProgram, atokenProgram)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n\n  const payload: JoinSplitRelayPayload = {\n    network: args.network,\n    root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(0n),\n    input_nullifiers_hex: [toHex32(n1), toHex32(n2)],\n    output_commitments_hex: [toHex32(out1Commitment), toHex32(out2Commitment)],\n    // The on-chain ix struct includes ephemeral_pk_* even though the\n    // Anchor handler ignores them when encrypted_note_* is empty.\n    // We pass zeros (matches `prepareJoinSplitTransaction`).\n    ephemeral_pks_b64: [\n      Buffer.from(new Uint8Array(32)).toString('base64'),\n      Buffer.from(new Uint8Array(32)).toString('base64'),\n    ],\n    encrypted_notes_b64: ['', ''], // off-chain via /notes/relay-blob\n    spl_counterparty: args.payer.toBase58(),\n    spl_counterparty_ata: payerAta.toBase58(),\n    public_value_in: '0',\n    public_value_out: '0',\n    fee: args.fee.toString(10),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return {\n    payload,\n    nullifiers: [n1, n2],\n    outputCommitments: [out1Commitment, out2Commitment],\n    blobs: [\n      {\n        commitmentHex: '0x' + Buffer.from(oc1Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(recipientBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(recipientBlob.ciphertext).toString('base64'),\n        ciphertextLen: recipientBlob.ciphertext.length,\n      },\n      {\n        commitmentHex: '0x' + Buffer.from(oc2Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(changeBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(changeBlob.ciphertext).toString('base64'),\n        ciphertextLen: changeBlob.ciphertext.length,\n      },\n    ],\n    proveMs,\n  }\n}\n\n/**\n * Submit a prepared joinsplit relay payload. Same shape as\n * `submitWithdrawViaRelay`. Returns the on-chain tx signature on\n * success; throws with `cause.error` on structured server errors\n * (e.g. `not_relayable`, `already_consumed`).\n */\nexport async function submitJoinSplitViaRelay(args: {\n  apiBaseUrl: string\n  payload: JoinSplitRelayPayload\n}): Promise<{ signature: string; confirmed: boolean; explorerUrl: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/joinsplit',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  const body = (await res.json().catch(() => ({}))) as Record<string, unknown>\n  if (!res.ok) {\n    const err = new Error(\n      `relay/joinsplit rejected (HTTP ${res.status}): ${body.error ?? 'unknown'} — ${body.detail ?? ''}`,\n    )\n    ;(err as Error & { cause?: unknown }).cause = body\n    throw err\n  }\n  return {\n    signature: String(body.signature ?? ''),\n    confirmed: Boolean(body.confirmed),\n    explorerUrl: String(body.explorerUrl ?? ''),\n  }\n}\n\n// ── Internal helpers ────────────────────────────────────────────────────\n\nfunction randomFieldBytes(): Uint8Array {\n  const out = crypto.getRandomValues(new Uint8Array(32));\n  out[0] = out[0] & 0x3f; // keep within BN254 field\n  return out;\n}\n\nasync function poseidonHashFour(a: bigint, b: bigint, c: bigint, d: bigint): Promise<bigint> {\n  const p = await getPoseidon();\n  const h = p([a, b, c, d]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// Re-export the parser so callers don't need to pull `private-pool-keys`\n// directly when they're only translating a payment-address string.\nexport { parsePaymentAddress };\n\n/// Submit a user-signed (sponsored-fee-payer) tx to the backend for\n/// fee-payer signing + RPC relay. Returns the cluster signature once\n/// the tx confirms (or unconfirmed signature with a `confirmed: false`\n/// flag if the cluster lagged on confirmation).\n///\n/// `signedTx` is the result of `wallet.signTransaction(prepared.transaction)` —\n/// user provides their own signature, backend adds fee-payer sig + sends.\nexport async function relaySponsoredTransaction(args: {\n  apiBaseUrl: string;\n  network: string;\n  signedTx: VersionedTransaction;\n}): Promise<{ signature: string; confirmed: boolean }> {\n  const url = new URL('api/private-pool/v2/relay-tx', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify({\n      tx_b64: Buffer.from(args.signedTx.serialize()).toString('base64'),\n    }),\n  });\n  if (!r.ok) {\n    const err = await r.text();\n    throw new Error(`relay-tx failed: ${r.status} ${err}`);\n  }\n  const j = await r.json() as { signature: string; confirmed?: boolean };\n  return { signature: j.signature, confirmed: j.confirmed !== false };\n}\n\n// ── ADR-012 V3 — unlinkable cashout via credit pool tree ────────────────\n//\n// V3 differs from ADR-011 v2 in shape:\n//   - Park outputs a fresh commitment to the credit pool merkle tree\n//     (NOT a per-recipient PDA). The on-chain claim_credit_v3 tx has\n//     NO recipient account in its account list — recipient is bound\n//     only through the publicAddress hash + the ownerPk inside the\n//     encrypted commitment.\n//   - Cashout is a ZK proof of membership in the credit pool tree\n//     against a fresh credit_nullifier (Poseidon2(commitment,\n//     nullifier_sk), like main-pool spends). Recipient pubkey appears\n//     only as `recipient_ata` owner check at cashout time, decoupled\n//     from any specific Park.\n//   - Encrypted blob is keyed by credit_commitment (not by PDA),\n//     mirroring main-pool deposit blobs. Discovery is trial-decrypt\n//     against the recipient's viewing_sk, just like Send notes.\n\nexport type ClaimCreditV3RelayPayload = {\n  network: string\n  // ── Source-side (Withdraw proof on the pool note being spent) ────\n  source_nullifier_hex: string\n  source_root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  source_proof_b64: string\n  // ── Credit-pool side (new commitment + encrypted blob) ───────────\n  credit_commitment_hex: string\n  /// Base64 of the encrypted blob — server stores it on chain at the\n  /// credit-pool blob PDA `[b\"ppv2-enc\", pool, credit_commitment]`.\n  encrypted_note_b64: string\n}\n\nexport type PreparedClaimCreditV3Relay = {\n  payload: ClaimCreditV3RelayPayload\n  /// Source note's nullifier (for indexer correlation + dedup).\n  sourceNullifierField: bigint\n  /// Credit-pool commitment (for the recipient's cashout flow).\n  creditCommitmentField: bigint\n  /// Plaintext credit-pool note details (the recipient gets these\n  /// by decrypting `encrypted_note_b64` with their viewing_sk — we\n  /// expose them here for testing + side-channel persistence).\n  creditNote: {\n    value: bigint\n    blinding: Uint8Array\n    ownerPk: Uint8Array\n  }\n  proveMs: number\n}\n\nexport async function prepareClaimCreditV3RelayPayload(args: {\n  cfg: PrivatePoolV2Config\n  /// Source pool note being spent.\n  note: {\n    value: bigint\n    blinding: bigint\n    memo: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Recipient's spending_pk field (becomes ownerPk in the new credit\n  /// commitment). From payment_address or self-derivation.\n  recipientSpendingPkField: bigint\n  /// Recipient's viewing_pk (32B) for encrypting the note.\n  recipientViewingPk: Uint8Array\n  /// Recipient's wallet pubkey — binds the source proof's publicAddress.\n  /// Same wallet must show up at cashout time to satisfy ATA owner check.\n  recipient: PublicKey\n  allLeaves: bigint[]\n  poolRoot: bigint\n  aspRoot: bigint\n  aspPath: { pathElements: bigint[]; pathIndices: number[] }\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedClaimCreditV3Relay> {\n  // 1. Build source merkle path (same as v2 claim_credit).\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareClaimCreditV3RelayPayload: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — stale leaves`,\n    )\n  }\n\n  // 2. Recipient binding for source proof's publicAddress.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. Source-side Withdraw proof witness (identical shape to atomic\n  // withdraw + v2 claim_credit — no extra fields).\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const sourceWitness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n  const { proofBytes: sourceProofBytes, proveMs } = await proveWithdraw(sourceWitness)\n\n  // 4. Source-side nullifier — same Poseidon2(commitment, nullifierSk).\n  const sourceNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  // 5. Build the FRESH credit-pool note.\n  //   value = recipient amount (after relayer fee carved off)\n  //   ownerPk = recipient's spending_pk_field — recipient proves\n  //             knowledge at cashout via SpendingPkFromSk(spendingSk)\n  //   blinding = fresh random, modular-reduced into BN254 field\n  //   memo = 0 (V3 doesn't use the memo slot)\n  const creditValue = args.publicValue - args.fee\n  if (creditValue <= 0n) {\n    throw new Error('prepareClaimCreditV3RelayPayload: creditValue <= 0 (publicValue must exceed fee)')\n  }\n  const randomFieldBytes32 = (): Uint8Array => {\n    const raw = crypto.getRandomValues(new Uint8Array(32))\n    let acc = 0n\n    for (const b of raw) acc = (acc << 8n) + BigInt(b)\n    const reduced = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    return new Uint8Array(fieldToBytes32BE(reduced))\n  }\n  const blindingBytes = randomFieldBytes32()\n  let blindingField = 0n\n  for (const b of blindingBytes) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const recipientOwnerPkBytes = fieldToBytes32BE(args.recipientSpendingPkField)\n\n  const creditCommitmentField = await deriveCommitment({\n    value: creditValue,\n    ownerPk: args.recipientSpendingPkField,\n    blinding: blindingField,\n    memo: 0n,\n  })\n  const commitmentBytes = fieldToBytes32BE(creditCommitmentField)\n\n  // 6. Encrypt the fresh credit-pool note for the recipient.\n  // Layout matches the main-pool legacy ADR-003 path: 96B ciphertext\n  // (no memo, no stealth). On-chain handler accepts this via the\n  // existing is_valid_ciphertext_len check.\n  const noteForEnc: DecryptedNote = {\n    value: creditValue,\n    blinding: blindingBytes,\n    ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    memo: null,\n    stealthNonce: null,\n    layout: 'legacy',\n  }\n  const encryptedBlob = encryptNote({\n    note: noteForEnc,\n    recipientViewingPk: args.recipientViewingPk,\n    commitment: new Uint8Array(commitmentBytes),\n  })\n  // Server expects `ephemeral_pk || ciphertext` packed contiguously\n  // for the on-chain `encrypted_note` arg (matches main-pool blob layout).\n  const encryptedNoteFull = new Uint8Array(32 + encryptedBlob.ciphertext.length)\n  encryptedNoteFull.set(encryptedBlob.ephemeralPk, 0)\n  encryptedNoteFull.set(encryptedBlob.ciphertext, 32)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: ClaimCreditV3RelayPayload = {\n    network: args.network,\n    source_nullifier_hex: toHex32(sourceNullifierField),\n    source_root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    source_proof_b64: Buffer.from(sourceProofBytes).toString('base64'),\n    credit_commitment_hex: toHex32(creditCommitmentField),\n    encrypted_note_b64: Buffer.from(encryptedNoteFull).toString('base64'),\n  }\n\n  return {\n    payload,\n    sourceNullifierField,\n    creditCommitmentField,\n    creditNote: {\n      value: creditValue,\n      blinding: blindingBytes,\n      ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    },\n    proveMs,\n  }\n}\n\nexport async function submitClaimCreditV3ViaRelay(args: {\n  apiBaseUrl: string\n  payload: ClaimCreditV3RelayPayload\n}): Promise<{ signature: string; credit_commitment_hex: string; credit_root_hex: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/claim-credit-v3',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.payload.network)\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  if (!res.ok) {\n    const body = await res.json().catch(() => ({}))\n    throw new Error(\n      `relay/claim-credit-v3 rejected (HTTP ${res.status}): ${(body as any).error ?? 'unknown'} — ${(body as any).detail ?? ''}`,\n    )\n  }\n  return res.json() as Promise<{ signature: string; credit_commitment_hex: string; credit_root_hex: string }>\n}\n\nexport type CashOutV3RelayPayload = {\n  network: string\n  credit_nullifier_hex: string\n  credit_pool_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  recipient: string\n  recipient_ata: string\n  proof_b64: string\n}\n\nexport type PreparedCashOutV3Relay = {\n  payload: CashOutV3RelayPayload\n  creditNullifierField: bigint\n  proveMs: number\n}\n\nexport async function prepareCashOutV3RelayPayload(args: {\n  cfg: PrivatePoolV2Config\n  /// Credit-pool note being cashed out — recipient decrypted it from\n  /// the encrypted blob on the credit-pool tree leaf.\n  note: {\n    value: bigint\n    blinding: bigint\n    ownerPkField: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Recipient's keys (proves knowledge of spendingSk that matches\n  /// note.ownerPk, and derives the credit-pool nullifier).\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Recipient SPL accounts.\n  recipient: PublicKey\n  recipientAta: PublicKey\n  /// Snapshot of all credit-pool leaves for merkle-path reconstruction.\n  /// Server's `/credit-pool/leaves` endpoint returns this.\n  allCreditLeaves: bigint[]\n  creditPoolRoot: bigint\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedCashOutV3Relay> {\n  // 1. Build credit-pool merkle path.\n  const creditPath = await buildMerkleTreeFromLeaves(\n    args.allCreditLeaves,\n    args.note.leafIndex,\n    24, // CREDIT_POOL_TREE_DEPTH from ADR-012\n  )\n  if (creditPath.root !== args.creditPoolRoot) {\n    throw new Error(\n      `prepareCashOutV3RelayPayload: reconstructed credit root ${creditPath.root.toString(16)} != supplied ${args.creditPoolRoot.toString(16)} — stale credit leaves`,\n    )\n  }\n\n  // 2. Recipient binding.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. CashoutProof witness — mirrors Withdraw witness shape minus ASP.\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: args.note.ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: '0',\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    creditPoolPathElements: creditPath.pathElements.map((e) => e.toString(10)),\n    creditPoolPathIndices: creditPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').CashoutProofWitness\n  const { proofBytes, proveMs } = await proveCashoutProof(witness)\n\n  // 4. Credit-pool nullifier.\n  const creditNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: CashOutV3RelayPayload = {\n    network: args.network,\n    credit_nullifier_hex: toHex32(creditNullifierField),\n    credit_pool_root_hex: toHex32(args.creditPoolRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: args.recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, creditNullifierField, proveMs }\n}\n\n// ── F2 — Bearer-mode park + cashout (private payment codes) ────────────\n//\n// Bearer mode parks a credit-pool leaf without binding it to a specific\n// recipient wallet. The leaf's `owner_pk` is derived from a one-time\n// `code_seed`; whoever ends up with the seed can derive the matching\n// `spending_sk_field` and cash out to any wallet they choose. The\n// `public_address` field in the proof witness is set to 0, signalling\n// the on-chain handler to skip the recipient-binding check (per the\n// ADR-012 addendum + Anchor change deployed alongside this code).\n//\n// Why this is safe: the leaf's commitment still binds value + owner_pk\n// + blinding. The proof still verifies the prover knew the\n// spending_sk that hashes to owner_pk. The credit_nullifier still\n// prevents double-spend. The only thing different from a normal park\n// is that any wallet — not a specific bound one — can be the\n// `recipient` on the cashout side.\n//\n// This is the primitive behind F2 (private payment codes) and F3\n// (public tip jars) in CREDIT_POOL_FEATURE_ROADMAP.md.\n\n/// Park a pool note into a code-bound credit-pool leaf. Returns the\n/// prepared relay payload PLUS the `codeKeys` (so the caller can\n/// encode `codeKeys.codeSeed` into a redemption URL or QR).\n///\n/// The caller should NOT show `codeKeys.codeSeed` anywhere except\n/// directly to the issuer — once the seed is shared, anyone with it\n/// can cash out the leaf. Treat it like the seed phrase of a hot\n/// wallet: high blast radius, single-purpose, ephemeral.\nexport async function prepareClaimCreditV3RelayPayloadAsBearer(args: {\n  cfg: PrivatePoolV2Config\n  /// Source pool note being spent by the issuer.\n  note: {\n    value: bigint\n    blinding: bigint\n    memo: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Issuer's keys — proves ownership of the source note.\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Either pass an externally-generated `codeKeys` (when the caller\n  /// wants to control the seed lifecycle / persist it for cancel) OR\n  /// pass `codeSeed` and let us derive. One must be set.\n  codeKeys?: CodeKeyMaterial\n  codeSeed?: Uint8Array\n  allLeaves: bigint[]\n  poolRoot: bigint\n  aspRoot: bigint\n  aspPath: { pathElements: bigint[]; pathIndices: number[] }\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedClaimCreditV3Relay & { codeKeys: CodeKeyMaterial }> {\n  // 0. Resolve code keys — caller-supplied wins; fall back to deriving\n  // from a passed-in seed. Throws if neither is set.\n  let codeKeys: CodeKeyMaterial\n  if (args.codeKeys) {\n    codeKeys = args.codeKeys\n  } else if (args.codeSeed) {\n    codeKeys = await deriveCodeKeys(args.codeSeed)\n  } else {\n    throw new Error(\n      'prepareClaimCreditV3RelayPayloadAsBearer: must pass codeKeys OR codeSeed',\n    )\n  }\n\n  // 1. Build source merkle path — identical to the non-bearer path.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareClaimCreditV3RelayPayloadAsBearer: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — stale leaves`,\n    )\n  }\n\n  // 2. Source-side Withdraw proof witness. Crucially, `publicAddress`\n  // is set to 0 instead of `poseidon_addr_hash(recipient)` — that's\n  // what flags this leaf as bearer-mode for the on-chain cashout.\n  const sourceWitness = {\n    value: args.note.value.toString(10),\n    ownerPk: ((await getPoseidon())\n      ? '0' // placeholder; reassigned below\n      : '0'),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n  // Recompute ownerPk properly (the placeholder above keeps TS happy\n  // during literal init; the real value is the issuer's spendingPk).\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  ;(sourceWitness as unknown as { ownerPk: string }).ownerPk = ownerPkField.toString(10)\n  const { proofBytes: sourceProofBytes, proveMs } = await proveWithdraw(sourceWitness)\n\n  // 3. Source-side nullifier.\n  const sourceNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  // 4. Build the fresh credit-pool leaf — same as cross-wallet park,\n  // except the recipient's `spending_pk_field` is the CODE-derived\n  // public, not a wallet-derived one. Whoever has `codeSeed` can later\n  // derive the matching `spending_sk_field` to satisfy ownership.\n  const creditValue = args.publicValue - args.fee\n  if (creditValue <= 0n) {\n    throw new Error('prepareClaimCreditV3RelayPayloadAsBearer: creditValue <= 0 (publicValue must exceed fee)')\n  }\n  const randomFieldBytes32 = (): Uint8Array => {\n    const raw = crypto.getRandomValues(new Uint8Array(32))\n    let acc = 0n\n    for (const b of raw) acc = (acc << 8n) + BigInt(b)\n    const reduced = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    return new Uint8Array(fieldToBytes32BE(reduced))\n  }\n  const blindingBytes = randomFieldBytes32()\n  let blindingField = 0n\n  for (const b of blindingBytes) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const recipientOwnerPkBytes = fieldToBytes32BE(codeKeys.spendingPkField)\n\n  const creditCommitmentField = await deriveCommitment({\n    value: creditValue,\n    ownerPk: codeKeys.spendingPkField,\n    blinding: blindingField,\n    memo: 0n,\n  })\n  const commitmentBytes = fieldToBytes32BE(creditCommitmentField)\n\n  // 5. Encrypt the credit-pool note for the code's viewing key. The\n  // redeemer derives the same viewing_sk from the seed and decrypts.\n  const noteForEnc: DecryptedNote = {\n    value: creditValue,\n    blinding: blindingBytes,\n    ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    memo: null,\n    stealthNonce: null,\n    layout: 'legacy',\n  }\n  const encryptedBlob = encryptNote({\n    note: noteForEnc,\n    recipientViewingPk: codeKeys.viewingPk,\n    commitment: new Uint8Array(commitmentBytes),\n  })\n  const encryptedNoteFull = new Uint8Array(32 + encryptedBlob.ciphertext.length)\n  encryptedNoteFull.set(encryptedBlob.ephemeralPk, 0)\n  encryptedNoteFull.set(encryptedBlob.ciphertext, 32)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: ClaimCreditV3RelayPayload = {\n    network: args.network,\n    source_nullifier_hex: toHex32(sourceNullifierField),\n    source_root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    // Bearer mode signal — on-chain cash_out_v3 skips the recipient\n    // binding check when this is all-zero (see Anchor addendum).\n    public_address_hex: toHex32(0n),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    source_proof_b64: Buffer.from(sourceProofBytes).toString('base64'),\n    credit_commitment_hex: toHex32(creditCommitmentField),\n    encrypted_note_b64: Buffer.from(encryptedNoteFull).toString('base64'),\n  }\n\n  return {\n    payload,\n    sourceNullifierField,\n    creditCommitmentField,\n    creditNote: {\n      value: creditValue,\n      blinding: blindingBytes,\n      ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    },\n    proveMs,\n    codeKeys,\n  }\n}\n\n/// Cash out a code-bound credit-pool leaf to an arbitrary wallet.\n/// `args.note` must come from `findCreditPoolLeafByCommitment` (or\n/// equivalent — the caller has to know value/blinding/ownerPkField\n/// of the leaf, typically obtained by decrypting the on-chain blob\n/// with `codeKeys.viewingSk`).\n///\n/// The proof uses `publicAddress = 0`, matching how the leaf was\n/// parked. On-chain cash_out_v3 sees the zero binding and skips the\n/// `Poseidon(recipient) == publicAddress` check, allowing any wallet\n/// to be the destination.\nexport async function prepareCashOutV3RelayPayloadAsBearer(args: {\n  cfg: PrivatePoolV2Config\n  /// Credit-pool leaf being cashed out. Decrypted client-side from\n  /// the on-chain blob using the seed-derived viewing_sk.\n  note: {\n    value: bigint\n    blinding: bigint\n    ownerPkField: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Code keys — derive these once from the seed and reuse for\n  /// discovery + cashout. We need `spendingSkField` (for the proof)\n  /// and `nullifierSkField` (for the credit_nullifier).\n  codeKeys: CodeKeyMaterial\n  /// Destination wallet — can be ANY wallet, not bound to anything\n  /// committed at park time. SPL transfer lands at this wallet's ATA.\n  recipient: PublicKey\n  recipientAta: PublicKey\n  /// Snapshot of all credit-pool leaves for the merkle-path proof.\n  allCreditLeaves: bigint[]\n  creditPoolRoot: bigint\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedCashOutV3Relay> {\n  // 1. Reconstruct the credit-pool merkle path.\n  const creditPath = await buildMerkleTreeFromLeaves(\n    args.allCreditLeaves,\n    args.note.leafIndex,\n    24, // CREDIT_POOL_TREE_DEPTH from ADR-012\n  )\n  if (creditPath.root !== args.creditPoolRoot) {\n    throw new Error(\n      `prepareCashOutV3RelayPayloadAsBearer: reconstructed credit root ${creditPath.root.toString(16)} != supplied ${args.creditPoolRoot.toString(16)} — stale credit leaves`,\n    )\n  }\n\n  // 2. Cashout proof witness — same shape as the non-bearer path,\n  // EXCEPT publicAddress = 0 (mirrors the bearer-mode park witness).\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: args.note.ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: '0',\n    spendingSk: args.codeKeys.spendingSkField.toString(10),\n    nullifierSk: args.codeKeys.nullifierSkField.toString(10),\n    creditPoolPathElements: creditPath.pathElements.map((e) => e.toString(10)),\n    creditPoolPathIndices: creditPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').CashoutProofWitness\n  const { proofBytes, proveMs } = await proveCashoutProof(witness)\n\n  // 3. Credit-pool nullifier (Poseidon2(commitment, nullifier_sk),\n  // where nullifier_sk is the CODE's, not a wallet's).\n  const creditNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.codeKeys.nullifierSkField,\n  })\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: CashOutV3RelayPayload = {\n    network: args.network,\n    credit_nullifier_hex: toHex32(creditNullifierField),\n    credit_pool_root_hex: toHex32(args.creditPoolRoot),\n    // Bearer mode — zero public address. On-chain handler skips\n    // the recipient binding check; `args.recipient` is whatever\n    // wallet the redeemer wants paid out to.\n    public_address_hex: toHex32(0n),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: args.recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, creditNullifierField, proveMs }\n}\n\n/// Find a single credit-pool leaf by its commitment hex. Used at\n/// redeem time: the issuer's URL carries `commitment=…` alongside\n/// `seed=…`, and we look the leaf up directly rather than trial-\n/// decrypting every leaf in the tree.\n///\n/// Returns the leaf's plaintext fields (value, blinding, ownerPk,\n/// leafIndex) after decrypting the on-chain blob with the code-\n/// derived viewing_sk. Returns null if the leaf doesn't exist or\n/// the blob can't be decrypted with the supplied viewing key (= the\n/// seed doesn't actually correspond to this leaf).\nexport async function fetchCreditPoolLeafForBearerRedeem(args: {\n  apiBaseUrl: string\n  network: string\n  commitmentHex: string\n  codeKeys: CodeKeyMaterial\n}): Promise<{\n  value: bigint\n  blinding: bigint\n  ownerPkField: bigint\n  commitmentField: bigint\n  leafIndex: number\n  alreadySpent: boolean\n} | null> {\n  const target = args.commitmentHex.toLowerCase().startsWith('0x')\n    ? args.commitmentHex.toLowerCase()\n    : '0x' + args.commitmentHex.toLowerCase()\n\n  // 1. Pull the credit-pool leaves snapshot + check spent state for\n  // each candidate nullifier we can derive from this seed.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const r = await fetch(url.toString(), { cache: 'no-store' })\n  if (!r.ok) return null\n  const body = (await r.json()) as {\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      // The /credit-pool/leaves response joins blob fields inline\n      // (not nested under a sub-object). See\n      // server/src/routes/private-pool-v2.js:3084-3100.\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n      ciphertext_len: number | null\n    }>\n  }\n  const match = body.leaves.find(\n    (l) => l.commitment_hex.toLowerCase() === target,\n  )\n  if (!match) return null\n  if (!match.ephemeral_pk_hex || !match.ciphertext_b64) return null\n\n  // 2. Decrypt the blob with the code's viewing_sk.\n  const ephPk = Uint8Array.from(Buffer.from(match.ephemeral_pk_hex.replace(/^0x/, ''), 'hex'))\n  let ciphertext = Uint8Array.from(Buffer.from(match.ciphertext_b64, 'base64'))\n  // V3 blobs may be stored as `ephPk || ciphertext` (128B / 160B); strip\n  // the prefix if it matches the blob's ephemeral pk.\n  if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n    let matchesPrefix = true\n    for (let i = 0; i < 32; i++) {\n      if (ciphertext[i] !== ephPk[i]) { matchesPrefix = false; break }\n    }\n    if (matchesPrefix) ciphertext = ciphertext.slice(32)\n  }\n  const decrypted = tryDecryptNote({\n    blob: {\n      ephemeralPk: ephPk,\n      ciphertext,\n      commitment: Uint8Array.from(Buffer.from(target.replace(/^0x/, ''), 'hex')),\n    },\n    viewingSk: args.codeKeys.viewingSk,\n  })\n  if (!decrypted) return null\n\n  // 3. Convert blinding bytes → bigint field. Same reduction the\n  // park flow used when generating the blinding.\n  let blindingField = 0n\n  for (const b of decrypted.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  let ownerPkField = 0n\n  for (const b of decrypted.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n  ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n\n  // 4. Spent-state check. Compute the credit_nullifier using the\n  // code's nullifier_sk and ask the indexer if it's already used.\n  const commitmentField = BigInt(target)\n  const credNull = await deriveNullifierAsync({\n    commitment: commitmentField,\n    nullifierSk: args.codeKeys.nullifierSkField,\n  })\n  let alreadySpent = false\n  try {\n    const nullUrl = new URL(\n      'api/private-pool/v2/nullifiers/all',\n      args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n    )\n    nullUrl.searchParams.set('network', args.network)\n    const nr = await fetch(nullUrl.toString(), { cache: 'no-store' })\n    if (nr.ok) {\n      const nb = (await nr.json()) as { nullifiers?: string[] }\n      const credNullHex = '0x' + credNull.toString(16).padStart(64, '0')\n      alreadySpent = (nb.nullifiers ?? []).some(\n        (n) => n.toLowerCase() === credNullHex.toLowerCase(),\n      )\n    }\n  } catch {\n    // Best-effort. If we can't reach the indexer, leave alreadySpent\n    // = false; the on-chain cash_out_v3 will fail loudly if it's\n    // actually already spent (duplicate nullifier account).\n  }\n\n  return {\n    value: decrypted.value,\n    blinding: blindingField,\n    ownerPkField,\n    commitmentField,\n    leafIndex: match.leaf_index,\n    alreadySpent,\n  }\n}\n\n/// Code-only discovery: given just the code-derived keys (no\n/// commitment hint), scan every leaf in the credit-pool tree and\n/// trial-decrypt its blob with `codeKeys.viewingSk`. The first leaf\n/// that decrypts successfully — and whose `owner_pk` matches our\n/// derived `spendingPkField` — is \"ours\" by construction (the leaf's\n/// commitment binds to `Poseidon4(value, owner_pk, blinding, memo)`,\n/// and we put exactly this `owner_pk` in at park time).\n///\n/// Used by the redeem page when the URL fragment carries only `code=`\n/// without `commitment=`, or when the user pastes a raw code into the\n/// text-input form. Same end result as `fetchCreditPoolLeafForBearerRedeem`,\n/// just slower on big trees (scan cost = N × ChaCha20-Poly1305 attempt,\n/// ≈ 1–2 ms per leaf in practice).\n///\n/// Returns `null` when no leaf decrypts — meaning either the code\n/// doesn't belong to this network's credit pool, or the park hasn't\n/// confirmed yet.\nexport async function discoverCreditPoolLeafByCodeKeys(args: {\n  apiBaseUrl: string\n  network: string\n  codeKeys: CodeKeyMaterial\n}): Promise<{\n  value: bigint\n  blinding: bigint\n  ownerPkField: bigint\n  commitmentField: bigint\n  commitmentHex: string\n  leafIndex: number\n  alreadySpent: boolean\n} | null> {\n  // 1. Pull the credit-pool leaves + blobs in one round-trip.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const r = await fetch(url.toString(), { cache: 'no-store' })\n  if (!r.ok) return null\n  const body = (await r.json()) as {\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n      ciphertext_len: number | null\n    }>\n  }\n\n  // 2. Pre-compute the spent-nullifier set so we can flag a redeemed\n  // leaf in a single response, without a per-row spent check after\n  // we find the match.\n  let spentSet: Set<string> = new Set()\n  try {\n    const nullUrl = new URL(\n      'api/private-pool/v2/nullifiers/all',\n      args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n    )\n    nullUrl.searchParams.set('network', args.network)\n    const nr = await fetch(nullUrl.toString(), { cache: 'no-store' })\n    if (nr.ok) {\n      const nj = (await nr.json()) as { nullifiers?: string[] }\n      spentSet = new Set((nj.nullifiers ?? []).map((n) => n.toLowerCase()))\n    }\n  } catch {\n    // Best-effort: if we can't fetch nullifiers, the cashout tx\n    // itself will fail loudly on duplicate-nullifier; the UI just\n    // won't show the \"already spent\" hint upfront.\n  }\n\n  // 3. Walk every leaf. Trial-decrypt is cheap; we can afford the\n  // full scan. First hit wins — by construction a code can only\n  // belong to one leaf (the issuer's claim_credit_v3 created\n  // exactly one).\n  for (const leaf of body.leaves) {\n    if (!leaf.ephemeral_pk_hex || !leaf.ciphertext_b64) continue\n\n    const ephPk = Uint8Array.from(\n      Buffer.from(leaf.ephemeral_pk_hex.replace(/^0x/, ''), 'hex'),\n    )\n    let ciphertext = Uint8Array.from(Buffer.from(leaf.ciphertext_b64, 'base64'))\n    // V3 blobs may be stored as `ephPk || ciphertext` (128B / 160B);\n    // strip the prefix if it matches the blob's ephemeral pk. Same\n    // normalisation `fetchCreditPoolLeafForBearerRedeem` applies.\n    if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n      let matchesPrefix = true\n      for (let i = 0; i < 32; i++) {\n        if (ciphertext[i] !== ephPk[i]) { matchesPrefix = false; break }\n      }\n      if (matchesPrefix) ciphertext = ciphertext.slice(32)\n    }\n\n    const target = leaf.commitment_hex.toLowerCase().startsWith('0x')\n      ? leaf.commitment_hex.toLowerCase()\n      : '0x' + leaf.commitment_hex.toLowerCase()\n\n    const decrypted = tryDecryptNote({\n      blob: {\n        ephemeralPk: ephPk,\n        ciphertext,\n        commitment: Uint8Array.from(Buffer.from(target.replace(/^0x/, ''), 'hex')),\n      },\n      viewingSk: args.codeKeys.viewingSk,\n    })\n    if (!decrypted) continue\n\n    // Decrypted — but a SUCCESSFUL trial-decrypt only means the blob\n    // was encrypted with our viewing key. We still need to check\n    // owner_pk binds to our spending_pk_field, otherwise the code\n    // belongs to a different recipient who happens to share viewing\n    // keys (impossible in practice — both derive deterministically\n    // from the same seed — but worth asserting for safety).\n    let ownerPkField = 0n\n    for (const b of decrypted.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n    ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (ownerPkField !== args.codeKeys.spendingPkField) continue\n\n    // Convert blinding bytes → field. Same reduction the park flow\n    // used when generating the blinding.\n    let blindingField = 0n\n    for (const b of decrypted.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n    blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n\n    const commitmentField = BigInt(target)\n\n    // Spent-check via the pre-fetched nullifier set.\n    const credNull = await deriveNullifierAsync({\n      commitment: commitmentField,\n      nullifierSk: args.codeKeys.nullifierSkField,\n    })\n    const credNullHex = '0x' + credNull.toString(16).padStart(64, '0')\n    const alreadySpent = spentSet.has(credNullHex.toLowerCase())\n\n    return {\n      value: decrypted.value,\n      blinding: blindingField,\n      ownerPkField,\n      commitmentField,\n      commitmentHex: target,\n      leafIndex: leaf.leaf_index,\n      alreadySpent,\n    }\n  }\n  return null\n}\n\nexport async function submitCashOutV3ViaRelay(args: {\n  apiBaseUrl: string\n  payload: CashOutV3RelayPayload\n}): Promise<{ signature: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/cash-out-v3',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.payload.network)\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  if (!res.ok) {\n    const body = await res.json().catch(() => ({}))\n    throw new Error(\n      `relay/cash-out-v3 rejected (HTTP ${res.status}): ${(body as any).error ?? 'unknown'} — ${(body as any).detail ?? ''}`,\n    )\n  }\n  return res.json() as Promise<{ signature: string }>\n}\n\n/// Fetch the current credit-pool merkle leaves for path reconstruction.\n/// Returns leaves indexed in insertion order (leaf_index = array index).\nexport async function fetchCreditPoolLeaves(args: {\n  apiBaseUrl: string\n  network: string\n}): Promise<{ commitmentField: bigint; leafIndex: number }[]> {\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) {\n    throw new Error(`fetchCreditPoolLeaves failed: HTTP ${res.status}`)\n  }\n  const body = (await res.json()) as {\n    leaves: { commitment_hex: string; leaf_index: number }[]\n  }\n  return body.leaves.map((l) => ({\n    commitmentField: BigInt(l.commitment_hex),\n    leafIndex: l.leaf_index,\n  }))\n}\n\n/// Fetch the latest credit-pool merkle root (caller uses it as the\n/// `creditPoolRoot` witness input). Server returns the on-chain\n/// `latest_root` from the CreditPoolStateAccount.\nexport async function fetchCreditPoolLatestRoot(args: {\n  apiBaseUrl: string\n  network: string\n}): Promise<bigint> {\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/root',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) {\n    throw new Error(`fetchCreditPoolLatestRoot failed: HTTP ${res.status}`)\n  }\n  const body = (await res.json()) as { root_hex: string }\n  return BigInt(body.root_hex)\n}\n\nexport type V3CashableCreditPoolNote = {\n  /// recipient-amount (post-fee) stored in the note\n  value: bigint\n  blindingField: bigint\n  /// owner_pk field — recipient's spending_pk_field, embedded in commitment\n  ownerPkField: bigint\n  commitmentField: bigint\n  commitmentHex: string\n  leafIndex: number\n  claimedAtSlot: number\n  /// Poseidon2(commitment, nullifier_sk) — for spent-set membership check\n  nullifierField: bigint\n  spent: boolean\n}\n\n/// Discover credit-pool notes this device can cash out. Pulls\n/// `/credit-pool/leaves` (which now joins encrypted blobs server-side),\n/// trial-decrypts each blob with `viewingSk`, then filters to entries\n/// where the decrypted `ownerPk` matches the caller's `spendingPkField`\n/// — that's how we tell \"this note is mine\" without on-chain marker.\n///\n/// Spent check uses `/nullifiers/all` (same shared spent-set as main\n/// pool — every credit_credit_v3 / cash_out_v3 nullifier is in there).\nexport async function discoverCashableCreditPoolNotesV3(args: {\n  apiBaseUrl: string\n  network: string\n  viewingSk: Uint8Array\n  /// Caller's spending_pk_field (Poseidon-1 of spending_sk).\n  spendingPkField: bigint\n  nullifierSkField: bigint\n}): Promise<V3CashableCreditPoolNote[]> {\n  // 1. Pull leaves + inline blobs in one round trip.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) throw new Error(`discoverCashableCreditPoolNotesV3: HTTP ${res.status}`)\n  const body = (await res.json()) as {\n    warming_up: boolean\n    initialized: boolean\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      claimed_at_slot: number\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n    }>\n  }\n  if (body.warming_up || !body.initialized) return []\n\n  // 2. Trial-decrypt each blob; filter to those that decode AND whose\n  // ownerPk matches our spendingPkField.\n  const candidates: V3CashableCreditPoolNote[] = []\n  for (const leaf of body.leaves) {\n    if (!leaf.ephemeral_pk_hex || !leaf.ciphertext_b64) {\n      console.warn('[discoverV3] leaf skipped: no blob', { leaf_index: leaf.leaf_index, commit: leaf.commitment_hex })\n      continue\n    }\n    const ephHex = leaf.ephemeral_pk_hex.startsWith('0x') ? leaf.ephemeral_pk_hex.slice(2) : leaf.ephemeral_pk_hex\n    const commitHex = leaf.commitment_hex.startsWith('0x') ? leaf.commitment_hex.slice(2) : leaf.commitment_hex\n    const ephPk = Uint8Array.from(Buffer.from(ephHex, 'hex'))\n    const commitment = Uint8Array.from(Buffer.from(commitHex, 'hex'))\n    let ciphertext = Uint8Array.from(Buffer.from(leaf.ciphertext_b64, 'base64'))\n    // V3 quirk: claim_credit_v3 handler stores `encrypted_note` (the full\n    // ephPk || ciphertext package, 128B) into the blob's `ciphertext`\n    // field — unlike deposit_note which gets ephPk as a separate\n    // instruction arg and stores only the pure ciphertext (96B).\n    // The frontend tryDecryptNote expects pure ciphertext, so we\n    // strip the 32-byte ephPk prefix here when present.\n    // Detection: ciphertext_len in (CIPHERTEXT_NO_MEMO + 32,\n    // CIPHERTEXT_WITH_MEMO + 32) and the first 32 bytes match the\n    // separately-stored ephPk field.\n    if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n      const prefix = ciphertext.slice(0, 32)\n      let matches = true\n      for (let i = 0; i < 32; i++) {\n        if (prefix[i] !== ephPk[i]) { matches = false; break }\n      }\n      if (matches) {\n        ciphertext = ciphertext.slice(32)\n      }\n    }\n    const note = tryDecryptNote({\n      blob: { ephemeralPk: ephPk, commitment, ciphertext },\n      viewingSk: args.viewingSk,\n    })\n    if (!note) {\n      console.warn('[discoverV3] tryDecryptNote returned null', {\n        leaf_index: leaf.leaf_index,\n        commit: leaf.commitment_hex.slice(0, 18),\n        ciphertext_len: ciphertext.length,\n      })\n      continue\n    }\n    // Filter: note.ownerPk must match caller's spending_pk. We compare\n    // the bigint forms (decoded from BE bytes) because the field\n    // element is what the circuit + commitment recompute use.\n    let ownerPkField = 0n\n    for (const b of note.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n    ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (ownerPkField !== args.spendingPkField) {\n      console.warn('[discoverV3] ownerPk mismatch', {\n        leaf_index: leaf.leaf_index,\n        note_ownerPk: '0x' + ownerPkField.toString(16),\n        caller_spendingPk: '0x' + args.spendingPkField.toString(16),\n      })\n      continue\n    }\n    // blinding field\n    let blindingField = 0n\n    for (const b of note.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n    blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    // Commitment field — recompute from plaintext to make sure the\n    // on-chain leaf actually binds the decrypted (value, ownerPk,\n    // blinding, 0) tuple. Saves us from acting on tampered storage.\n    const recomputedCommitmentField = await deriveCommitment({\n      value: note.value,\n      ownerPk: ownerPkField,\n      blinding: blindingField,\n      memo: 0n,\n    })\n    let commitmentFieldFromHex = 0n\n    for (const b of commitment) commitmentFieldFromHex = (commitmentFieldFromHex << 8n) + BigInt(b)\n    commitmentFieldFromHex = ((commitmentFieldFromHex % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (recomputedCommitmentField !== commitmentFieldFromHex) {\n      console.warn('[discoverV3] commitment recompute mismatch', {\n        leaf_index: leaf.leaf_index,\n        recomputed: '0x' + recomputedCommitmentField.toString(16),\n        on_chain: '0x' + commitmentFieldFromHex.toString(16),\n        note_value: note.value.toString(),\n        memo_in_note: note.memo ? `bytes[${note.memo.length}]` : 'null',\n      })\n      continue\n    }\n    // Nullifier — Poseidon2(commitment, nullifier_sk).\n    const nullifierField = await deriveNullifierAsync({\n      commitment: recomputedCommitmentField,\n      nullifierSk: args.nullifierSkField,\n    })\n    candidates.push({\n      value: note.value,\n      blindingField,\n      ownerPkField,\n      commitmentField: recomputedCommitmentField,\n      commitmentHex: '0x' + commitHex,\n      leafIndex: leaf.leaf_index,\n      claimedAtSlot: leaf.claimed_at_slot,\n      nullifierField,\n      spent: false,\n    })\n  }\n\n  // 3. Mark spent based on the shared spent-set.\n  if (candidates.length === 0) return []\n  try {\n    const consumed = await (async () => {\n      const u = new URL(\n        'api/private-pool/v2/nullifiers/all',\n        args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n      )\n      u.searchParams.set('network', args.network)\n      const r = await fetch(u.toString())\n      if (!r.ok) return new Set<string>()\n      const b = (await r.json()) as { nullifiers: string[] }\n      return new Set(b.nullifiers.map((n) => n.toLowerCase()))\n    })()\n    for (const c of candidates) {\n      const nHex = '0x' + c.nullifierField.toString(16).padStart(64, '0')\n      if (consumed.has(nHex.toLowerCase())) c.spent = true\n    }\n  } catch {\n    // Spent-check best-effort. Leave spent=false on failure; cashout\n    // will hit on-chain `already_consumed` if the user tries one.\n  }\n  return candidates\n}\n\n/// POST a JoinSplit blob to the off-chain relay. Caller invokes this AFTER\n/// the on-chain tx confirms (otherwise the relay points at a commitment\n/// that doesn't exist yet).\nexport async function relayJoinSplitBlob(args: {\n  apiBaseUrl: string;\n  network: string;\n  blob: PreparedJoinSplitTx['blobs'][number];\n  sourceTx?: string;\n  senderPubkey?: string;\n}): Promise<void> {\n  const url = new URL('api/private-pool/v2/notes/relay-blob', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify({\n      commitment_hex: args.blob.commitmentHex,\n      ephemeral_pk_hex: args.blob.ephemeralPkHex,\n      ciphertext_b64: args.blob.ciphertextB64,\n      source_tx: args.sourceTx ?? null,\n      sender_pubkey: args.senderPubkey ?? null,\n    }),\n  });\n  if (!r.ok) throw new Error(`relayJoinSplitBlob: ${r.status} ${await r.text()}`);\n}\n"],"mappings":"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","names":["bs58","fieldToBytes32BE","import_blake2b","TEXT_ENCODER","bytesToHex","plaintext","import_chacha","import_ed25519","import_blake2b","FIELD_MODULUS","import_circomlibjs","FIELD_MODULUS","buildUrl","hexToBytes","deriveNullifier","import_circomlibjs","fieldToBytes32BE","FIELD_MODULUS","cachedPoseidon","getPoseidon","bytesToField"]}