@toon-protocol/sdk 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7LBYFU4L.js → chunk-RAZRWSJF.js} +493 -33
- package/dist/chunk-RAZRWSJF.js.map +1 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/{swap-Oub-0vqU.d.ts → swap-PFQTJZA7.d.ts} +335 -2
- package/dist/swap.d.ts +1 -1
- package/dist/swap.js +21 -1
- package/package.json +1 -1
- package/dist/chunk-7LBYFU4L.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/identity.ts","../src/gift-wrap.ts","../src/stream-receipts.ts","../src/swap-handler.ts","../src/stream-swap.ts"],"sourcesContent":["/**\n * SDK-specific error classes for @toon-protocol/sdk.\n * All errors extend ToonError from @toon-protocol/core for a consistent error hierarchy.\n */\n\nimport { ToonError } from '@toon-protocol/core';\n\n/**\n * Error thrown when identity operations fail.\n * Used for invalid mnemonics, invalid secret keys, and key derivation failures.\n */\nexport class IdentityError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'IDENTITY_ERROR', cause);\n this.name = 'IdentityError';\n }\n}\n\n/**\n * Error thrown when node lifecycle operations fail.\n * Used for start/stop failures, configuration errors, etc.\n */\nexport class NodeError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'NODE_ERROR', cause);\n this.name = 'NodeError';\n }\n}\n\n/**\n * Error thrown when handler dispatch operations fail.\n * Used for handler registration conflicts, missing handlers, and dispatch errors.\n */\nexport class HandlerError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'HANDLER_ERROR', cause);\n this.name = 'HandlerError';\n }\n}\n\n/**\n * Error thrown when Schnorr signature verification fails.\n * Used for invalid signatures, malformed events, and verification pipeline errors.\n */\nexport class VerificationError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'VERIFICATION_ERROR', cause);\n this.name = 'VerificationError';\n }\n}\n\n/**\n * Error thrown when payment validation fails.\n * Used for pricing calculation errors, insufficient payment, and pricing policy violations.\n */\nexport class PricingError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'PRICING_ERROR', cause);\n this.name = 'PricingError';\n }\n}\n\n/**\n * Error thrown when NIP-59 gift wrap or NIP-44 FULFILL encryption operations fail.\n * Used for wrap/unwrap failures, decryption errors, and malformed gift wrap events.\n */\nexport class GiftWrapError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'GIFT_WRAP_ERROR', cause);\n this.name = 'GiftWrapError';\n }\n}\n\n/**\n * Error thrown when swap handler orchestration fails.\n * Used for rate-conversion errors (invalid format, zero, overflow guards),\n * unsupported pair lookups, and issuer-boundary failures that are NOT\n * gift-wrap-specific. Gift-wrap failures continue to surface as `GiftWrapError`.\n */\nexport class SwapHandlerError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'SWAP_HANDLER_ERROR', cause);\n this.name = 'SwapHandlerError';\n }\n}\n\n/**\n * Error thrown by the sender-side `streamSwap()` API (Story 12.5).\n *\n * All failures are categorized by a narrow `code` so callers can branch on\n * cause. `INVALID_*` codes are construction-time validation failures (thrown\n * synchronously before any packet fires). `FULFILL_DECODE_FAILED` surfaces\n * when the Swap returns `accepted: true` but the FULFILL data cannot be\n * decoded — this is a non-fatal per-packet error and is captured in\n * `StreamSwapResult.errors[]`.\n */\nexport class StreamSwapError extends Error {\n readonly code:\n | 'INVALID_AMOUNT'\n | 'INVALID_CHUNKING'\n | 'INVALID_PAIR'\n | 'INVALID_STATE'\n | 'INVALID_CHAIN_RECIPIENT'\n | 'FULFILL_DECODE_FAILED';\n // Not declared on Error in lib.es5; ES2022 adds it, but some tsconfigs\n // still target older libs. Declare explicitly for cross-version safety.\n declare readonly cause?: unknown;\n\n constructor(\n code: StreamSwapError['code'],\n message: string,\n options?: { cause?: unknown }\n ) {\n super(message);\n this.name = 'StreamSwapError';\n this.code = code;\n if (options && 'cause' in options) {\n Object.defineProperty(this, 'cause', {\n value: options.cause,\n enumerable: false,\n writable: true,\n configurable: true,\n });\n }\n }\n}\n\n/**\n * Error thrown by the sender-side `buildSettlementTx()` helper (Story 12.6).\n *\n * Settlement is a post-swap one-shot computation, so this error class is\n * THROWN synchronously (unlike `streamSwap` which routes per-packet failures\n * through `StreamSwapResult`). Callers are expected to wrap the call in\n * `try/catch`.\n *\n * Narrow `code` union lets callers branch on cause — see\n * `_bmad-output/implementation-artifacts/12-6-build-settlement-tx.md` AC-11\n * for the per-code semantics.\n *\n * @since 12.6\n * @stable — Epic 13 Chain Bridge DVM depends on this error shape.\n */\nexport class SettlementTxError extends Error {\n readonly code:\n | 'INVALID_INPUT'\n | 'MISSING_SETTLEMENT_METADATA'\n | 'UNSUPPORTED_CHAIN'\n | 'MISSING_RECIPIENT'\n | 'RECIPIENT_MISMATCH'\n | 'SWAP_SIGNER_MISMATCH'\n | 'DUPLICATE_NONCE'\n | 'NON_MONOTONIC_CUMULATIVE'\n | 'INVALID_SIGNATURE_LENGTH'\n | 'INVALID_SIGNATURE_V'\n | 'ENCODING_FAILED';\n declare readonly cause?: unknown;\n\n constructor(\n code: SettlementTxError['code'],\n message: string,\n options?: { cause?: unknown }\n ) {\n super(message);\n this.name = 'SettlementTxError';\n this.code = code;\n if (options && 'cause' in options) {\n Object.defineProperty(this, 'cause', {\n value: options.cause,\n enumerable: false,\n writable: true,\n configurable: true,\n });\n }\n }\n}\n","/**\n * Unified identity module for @toon-protocol/sdk.\n *\n * Derives both a Nostr pubkey (x-only Schnorr, BIP-340) and an EVM address\n * (Keccak-256) from a single secp256k1 private key, following the NIP-06\n * derivation standard.\n *\n * Both Nostr and EVM use the secp256k1 elliptic curve, so a single 12-word\n * seed phrase can recover the complete identity across both layers.\n */\n\nimport {\n generateMnemonic as _generateMnemonic,\n validateMnemonic,\n mnemonicToSeedSync,\n} from '@scure/bip39';\nimport { wordlist } from '@scure/bip39/wordlists/english.js';\nimport { HDKey } from '@scure/bip32';\nimport { getPublicKey } from 'nostr-tools/pure';\nimport { secp256k1 } from '@noble/curves/secp256k1.js';\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { keccak_256 } from '@noble/hashes/sha3.js';\nimport { hmac } from '@noble/hashes/hmac.js';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport { IdentityError } from './errors.js';\n\n/**\n * Represents a node's complete identity: secret key, Nostr pubkey, and EVM address.\n */\nexport interface NodeIdentity {\n /** The 32-byte secp256k1 secret key. */\n secretKey: Uint8Array;\n /** The x-only Schnorr public key (32 bytes, 64 lowercase hex characters). */\n pubkey: string;\n /** The EIP-55 checksummed EVM address (0x-prefixed, 42 characters). */\n evmAddress: string;\n}\n\n/**\n * Solana Ed25519 identity derived via SLIP-0010 from a BIP-39 mnemonic.\n */\nexport interface SolanaIdentity {\n /** 64-byte Ed25519 keypair (32-byte private key + 32-byte public key). */\n secretKey: Uint8Array;\n /** Base58-encoded Ed25519 public key (Solana address). */\n publicKey: string;\n}\n\n/**\n * Mina Pallas identity derived from a BIP-39 mnemonic via mina-signer.\n */\nexport interface MinaIdentity {\n /** Hex-encoded Pallas private key. */\n privateKey: string;\n /** Base58 Mina public key (B62 prefix). */\n publicKey: string;\n}\n\n/**\n * Full multi-chain identity derived from a single BIP-39 mnemonic.\n * Extends NodeIdentity (Nostr + EVM) with Solana and optionally Mina.\n */\nexport interface ToonIdentity extends NodeIdentity {\n /** Solana Ed25519 identity (always populated from mnemonic derivation). */\n solana: SolanaIdentity;\n /** Mina Pallas identity (undefined when mina-signer is not installed). */\n mina?: MinaIdentity;\n}\n\n/**\n * Options for mnemonic-based key derivation.\n */\nexport interface FromMnemonicOptions {\n /** Key index in the NIP-06 derivation path. Defaults to 0. */\n accountIndex?: number;\n}\n\n/**\n * Generates a valid 12-word BIP-39 mnemonic using 128-bit entropy.\n *\n * @returns A space-separated string of 12 BIP-39 English words.\n */\nexport function generateMnemonic(): string {\n return _generateMnemonic(wordlist, 128);\n}\n\n/**\n * Derives a complete NodeIdentity from a BIP-39 mnemonic phrase.\n *\n * Uses the NIP-06 derivation path: m/44'/1237'/0'/0/{accountIndex}\n *\n * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).\n * @param options - Optional derivation options (accountIndex defaults to 0).\n * @returns The derived NodeIdentity with secretKey, pubkey, and evmAddress.\n * @throws {IdentityError} If the mnemonic is invalid.\n */\nexport function fromMnemonic(\n mnemonic: string,\n options?: FromMnemonicOptions\n): ToonIdentity {\n if (!validateMnemonic(mnemonic, wordlist)) {\n throw new IdentityError(\n `Invalid BIP-39 mnemonic: the provided words do not form a valid mnemonic phrase`\n );\n }\n\n const accountIndex = options?.accountIndex ?? 0;\n\n if (\n !Number.isInteger(accountIndex) ||\n accountIndex < 0 ||\n accountIndex > MAX_BIP32_INDEX\n ) {\n throw new IdentityError(\n `Invalid accountIndex: expected a non-negative integer (0 to ${MAX_BIP32_INDEX}), got ${String(accountIndex)}`\n );\n }\n\n const path = `m/44'/1237'/0'/0/${accountIndex}`;\n\n let seed: Uint8Array | undefined;\n try {\n seed = mnemonicToSeedSync(mnemonic);\n const hdKey = HDKey.fromMasterSeed(seed).derive(path);\n\n if (!hdKey.privateKey) {\n throw new IdentityError(`Failed to derive private key at path ${path}`);\n }\n\n const secretKey = hdKey.privateKey;\n const base = deriveIdentity(secretKey);\n\n // Derive Solana Ed25519 identity from the same seed via SLIP-0010,\n // varying by accountIndex so distinct indices yield distinct Solana keys.\n const solana = deriveSolanaIdentity(seed, accountIndex);\n\n return { ...base, solana };\n } catch (error: unknown) {\n if (error instanceof IdentityError) {\n throw error;\n }\n throw new IdentityError(\n `Key derivation failed at path ${path}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Best-effort zeroing of the intermediate seed to reduce the window\n // during which sensitive material remains in memory. This is not a\n // guarantee (JS has no secure-erase primitive), but it limits exposure.\n if (seed) {\n seed.fill(0);\n }\n }\n}\n\n/**\n * Derives a complete NodeIdentity from an existing 32-byte secret key.\n *\n * @param secretKey - A 32-byte secp256k1 secret key.\n * @returns The derived NodeIdentity with secretKey, pubkey, and evmAddress.\n * @throws {IdentityError} If the secret key is not exactly 32 bytes.\n */\nexport function fromSecretKey(secretKey: Uint8Array): NodeIdentity {\n if (!(secretKey instanceof Uint8Array)) {\n throw new IdentityError(\n `Invalid secret key: expected Uint8Array, got ${secretKey === null ? 'null' : typeof secretKey}`\n );\n }\n\n if (secretKey.length !== 32) {\n throw new IdentityError(\n `Invalid secret key: expected 32 bytes, got ${secretKey.length} bytes`\n );\n }\n\n try {\n return deriveIdentity(secretKey);\n } catch (error: unknown) {\n if (error instanceof IdentityError) {\n throw error;\n }\n throw new IdentityError(\n `Invalid secret key: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Maximum valid BIP-32 non-hardened child index (2^31 - 1).\n * Values at or above 2^31 are reserved for hardened derivation.\n */\nconst MAX_BIP32_INDEX = 0x7fffffff;\n\n/**\n * Shared helper that computes pubkey and evmAddress from a 32-byte secret key.\n * Returns a defensive copy of the secret key to prevent external mutation.\n */\nfunction deriveIdentity(secretKey: Uint8Array): NodeIdentity {\n // Compute x-only Schnorr pubkey (32 bytes hex) via nostr-tools\n const pubkey = getPublicKey(secretKey);\n\n // Compute EVM address from the uncompressed secp256k1 public key\n const evmAddress = computeEvmAddress(secretKey);\n\n // Return a defensive copy of the secret key to prevent external mutation\n // from affecting the identity or vice versa\n return { secretKey: new Uint8Array(secretKey), pubkey, evmAddress };\n}\n\n/**\n * Computes an EIP-55 checksummed EVM address from a secp256k1 private key.\n *\n * Steps:\n * 1. Compute the full uncompressed public key (65 bytes: 0x04 + 64 bytes X,Y)\n * 2. Strip the 0x04 prefix to get 64 bytes\n * 3. Hash with Keccak-256\n * 4. Take the last 20 bytes\n * 5. Format as 0x-prefixed hex with EIP-55 checksum\n */\nfunction computeEvmAddress(secretKey: Uint8Array): string {\n // Get the uncompressed public key (65 bytes: 0x04 prefix + 64 bytes X,Y)\n const uncompressedPubkey = secp256k1.getPublicKey(secretKey, false);\n\n // Strip the 0x04 prefix (first byte), leaving 64 bytes of X,Y coordinates\n const pubkeyWithoutPrefix = uncompressedPubkey.slice(1);\n\n // Hash with Keccak-256\n const hash = keccak_256(pubkeyWithoutPrefix);\n\n // Take last 20 bytes\n const addressBytes = hash.slice(-20);\n const addressHex = bytesToHex(addressBytes);\n\n // Apply EIP-55 mixed-case checksum\n return toChecksumAddress(addressHex);\n}\n\n/**\n * Applies EIP-55 mixed-case checksum encoding to an Ethereum address.\n *\n * EIP-55 rules:\n * - Hash the lowercase hex address (without 0x) with Keccak-256\n * - For each hex character in the address:\n * - If the corresponding nibble in the hash is >= 8, uppercase it\n * - Otherwise, lowercase it\n *\n * @param addressHex - The 40-character lowercase hex address (without 0x prefix).\n * @returns The checksummed address with 0x prefix.\n */\nfunction toChecksumAddress(addressHex: string): string {\n const lower = addressHex.toLowerCase();\n const hash = bytesToHex(keccak_256(new TextEncoder().encode(lower)));\n\n let checksummed = '0x';\n for (let i = 0; i < 40; i++) {\n const char = lower[i];\n const hashChar = hash[i];\n if (char === undefined || hashChar === undefined) {\n throw new IdentityError(\n `Unexpected undefined at index ${i} during checksum computation`\n );\n }\n const hashNibble = parseInt(hashChar, 16);\n checksummed += hashNibble >= 8 ? char.toUpperCase() : char;\n }\n\n return checksummed;\n}\n\n// ---------------------------------------------------------------------------\n// SLIP-0010 Ed25519 HD Key Derivation\n// ---------------------------------------------------------------------------\n\n/**\n * SLIP-0010 master key and child key derivation for Ed25519.\n *\n * Ed25519 SLIP-0010 only supports hardened derivation. The path\n * m/44'/501'/0'/0' is standard for Solana wallets.\n *\n * @param seed - The BIP-39 seed (64 bytes).\n * @param path - Array of hardened indices (must have 0x80000000 bit set).\n * @returns The derived 32-byte Ed25519 private key.\n */\nfunction slip0010Derive(seed: Uint8Array, path: number[]): Uint8Array {\n const encoder = new TextEncoder();\n\n // Master key: HMAC-SHA512(\"ed25519 seed\", seed)\n let I = hmac(sha512, encoder.encode('ed25519 seed'), seed);\n let key = I.slice(0, 32);\n let chainCode = I.slice(32);\n\n // Child derivation (hardened only)\n for (const index of path) {\n const data = new Uint8Array(37);\n data[0] = 0x00;\n data.set(key, 1);\n // Write index as big-endian uint32\n data[33] = (index >>> 24) & 0xff;\n data[34] = (index >>> 16) & 0xff;\n data[35] = (index >>> 8) & 0xff;\n data[36] = index & 0xff;\n\n I = hmac(sha512, chainCode, data);\n key = I.slice(0, 32);\n chainCode = I.slice(32);\n }\n\n return key;\n}\n\n/**\n * SLIP-0010 path for Solana, varying the hardened account component by\n * `accountIndex`: m/44'/501'/{accountIndex}'/0' (all hardened). At\n * `accountIndex` 0 this is the canonical m/44'/501'/0'/0', so existing\n * (index-0) keys are unchanged.\n */\nfunction solanaPath(accountIndex: number): number[] {\n return [\n 0x8000002c, // 44'\n 0x800001f5, // 501'\n (0x80000000 + accountIndex) >>> 0, // {accountIndex}'\n 0x80000000, // 0'\n ];\n}\n\n/**\n * Derives a Solana Ed25519 identity from a BIP-39 seed using SLIP-0010.\n *\n * @param accountIndex - Hardened account index (default 0). Distinct indices\n * yield distinct keypairs from the same seed.\n */\nfunction deriveSolanaIdentity(\n seed: Uint8Array,\n accountIndex = 0\n): SolanaIdentity {\n const privateKey = slip0010Derive(seed, solanaPath(accountIndex));\n const publicKeyBytes = ed25519.getPublicKey(privateKey);\n\n // Solana keypair = 32-byte private key + 32-byte public key = 64 bytes\n const keypair = new Uint8Array(64);\n keypair.set(privateKey, 0);\n keypair.set(publicKeyBytes, 32);\n\n return { secretKey: keypair, publicKey: base58Encode(publicKeyBytes) };\n}\n\n// ---------------------------------------------------------------------------\n// Mina Pallas Key Derivation (optional, requires mina-signer)\n// ---------------------------------------------------------------------------\n\n/**\n * Derives a Mina Pallas identity from a BIP-39 seed.\n *\n * Uses BIP-32 secp256k1 derivation at path m/44'/12586'/{accountIndex}'/0/0 to\n * produce a 32-byte scalar, then converts to Mina key format via mina-signer.\n * At `accountIndex` 0 this is the canonical m/44'/12586'/0'/0/0, so existing\n * (index-0) keys are unchanged.\n *\n * @param seed - The BIP-39 seed (64 bytes).\n * @param accountIndex - Hardened account index (default 0). Distinct indices\n * yield distinct keys from the same seed.\n * @returns The Mina identity, or undefined if mina-signer is not installed.\n */\nasync function deriveMinaIdentity(\n seed: Uint8Array,\n accountIndex = 0\n): Promise<MinaIdentity | undefined> {\n const path = `m/44'/12586'/${accountIndex}'/0/0`;\n const hdKey = HDKey.fromMasterSeed(seed).derive(path);\n\n if (!hdKey.privateKey) {\n throw new IdentityError(\n `Failed to derive Mina private key at path ${path}`\n );\n }\n\n const keyBytes = new Uint8Array(hdKey.privateKey);\n // Clamp the top 2 bits so the big-endian scalar is within the Pallas\n // base-field order. A raw BIP-32 child scalar can exceed it (~75% of 256-bit\n // values do), and mina-signer then rejects it with\n // \"Scalar: inputs larger than … are not allowed\". Matches the client's\n // deriveMinaKey and swap's deriveSwapKeys, so all three produce the SAME Mina\n // key for a given mnemonic + accountIndex.\n keyBytes[0] = (keyBytes[0] ?? 0) & 0x3f;\n const hexKey = bytesToHex(keyBytes);\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const MinaSignerLib: any = await import('mina-signer');\n const Client =\n 'default' in MinaSignerLib ? MinaSignerLib.default : MinaSignerLib;\n const client = new Client({ network: 'mainnet' });\n\n // mina-signer's derivePublicKey expects a Mina base58check (\"EK…\") private\n // key, NOT a raw hex scalar. Convert first — passing hex makes mina-signer\n // throw `fromBase58: invalid character`, which the old broad catch silently\n // swallowed (so Mina derivation always returned undefined). Keep the\n // exposed privateKey as hex for consumer compatibility (e.g. the client's\n // MinaSigner accepts hex and converts internally).\n const minaBase58PrivateKey = hexToMinaBase58PrivateKey(hexKey);\n const publicKey: string = client.derivePublicKey(minaBase58PrivateKey);\n return { privateKey: hexKey, publicKey };\n } catch (err: unknown) {\n // Degrade gracefully ONLY when mina-signer is genuinely absent (optional\n // dependency). Surface any other failure instead of masking it — the broad\n // catch previously hid the hex-vs-base58 key-format bug above.\n const code = (err as { code?: string } | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {\n return undefined;\n }\n throw new IdentityError(\n `Mina identity derivation failed at path ${path}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n err instanceof Error ? err : undefined\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public Multi-Chain Functions\n// ---------------------------------------------------------------------------\n\n/**\n * Derives a full multi-chain ToonIdentity from a BIP-39 mnemonic,\n * including async Mina derivation (requires mina-signer).\n *\n * Chains derived:\n * - Nostr (secp256k1): m/44'/1237'/0'/0/{accountIndex}\n * - EVM (secp256k1): same key as Nostr, Keccak-256 for address\n * - Solana (Ed25519): m/44'/501'/{accountIndex}'/0' (SLIP-0010)\n * - Mina (Pallas): m/44'/12586'/{accountIndex}'/0/0 (optional, requires mina-signer)\n *\n * All four chains vary by `accountIndex`, so distinct indices yield fully\n * distinct multi-chain identities from one seed. Index 0 is unchanged from the\n * historical fixed paths.\n *\n * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).\n * @param options - Optional derivation options (accountIndex defaults to 0).\n * @returns The derived ToonIdentity with all chain identities populated.\n * @throws {IdentityError} If the mnemonic is invalid.\n */\nexport async function fromMnemonicFull(\n mnemonic: string,\n options?: FromMnemonicOptions\n): Promise<ToonIdentity> {\n // Derive Nostr + EVM + Solana synchronously\n const identity = fromMnemonic(mnemonic, options);\n const accountIndex = options?.accountIndex ?? 0;\n\n // Attempt async Mina derivation (varies by accountIndex, like the others)\n let seed: Uint8Array | undefined;\n try {\n seed = mnemonicToSeedSync(mnemonic);\n const mina = await deriveMinaIdentity(seed, accountIndex);\n if (mina) {\n return { ...identity, mina };\n }\n } finally {\n if (seed) {\n seed.fill(0);\n }\n }\n\n return identity;\n}\n\n/**\n * Generates a random Solana Ed25519 keypair (non-deterministic).\n *\n * For deterministic derivation from a mnemonic, use fromMnemonic() instead.\n *\n * @returns A SolanaIdentity with random keypair.\n */\nexport function generateSolanaKeypair(): SolanaIdentity {\n const privateKey = ed25519.utils.randomSecretKey();\n const publicKeyBytes = ed25519.getPublicKey(privateKey);\n\n const keypair = new Uint8Array(64);\n keypair.set(privateKey, 0);\n keypair.set(publicKeyBytes, 32);\n\n return { secretKey: keypair, publicKey: base58Encode(publicKeyBytes) };\n}\n\n// ---------------------------------------------------------------------------\n// Base58 Encoding/Decoding\n// ---------------------------------------------------------------------------\n\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\n/**\n * Encodes a byte array to a Base58 string (Bitcoin/Solana alphabet).\n */\nexport function base58Encode(bytes: Uint8Array): string {\n // Count leading zeros\n let zeros = 0;\n for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;\n\n let value = 0n;\n for (const byte of bytes) {\n value = value * 256n + BigInt(byte);\n }\n\n let result = '';\n while (value > 0n) {\n result = BASE58_ALPHABET[Number(value % 58n)] + result;\n value = value / 58n;\n }\n\n // Add leading '1's for leading zero bytes\n for (let i = 0; i < zeros; i++) {\n result = '1' + result;\n }\n\n return result || '1';\n}\n\n/**\n * Decodes a Base58 string to a byte array (Bitcoin/Solana alphabet).\n */\nexport function base58Decode(str: string): Uint8Array {\n // Count leading '1's (zero bytes)\n let zeros = 0;\n for (let i = 0; i < str.length && str[i] === '1'; i++) zeros++;\n\n let value = 0n;\n for (const ch of str) {\n const idx = BASE58_ALPHABET.indexOf(ch);\n if (idx === -1) throw new IdentityError(`Invalid base58 character: ${ch}`);\n value = value * 58n + BigInt(idx);\n }\n\n // Convert bigint to bytes\n const hex = value === 0n ? '' : value.toString(16);\n const hexPadded = hex.length % 2 ? '0' + hex : hex;\n const rawBytes: number[] = [];\n for (let i = 0; i < hexPadded.length; i += 2) {\n rawBytes.push(parseInt(hexPadded.slice(i, i + 2), 16));\n }\n\n const result = new Uint8Array(zeros + rawBytes.length);\n result.set(rawBytes, zeros);\n return result;\n}\n","/**\n * NIP-59 Gift Wrap Integration for ILP Packets (Story 12.2)\n *\n * Provides privacy-preserving encoding/decoding of swap packets using the\n * NIP-59 three-layer gift wrap construction (rumor -> seal -> gift wrap).\n * Intermediary peers routing ILP packets see only opaque TOON-encoded binary\n * in the data field -- they cannot determine the event kind, sender identity,\n * or swap intent. Only the destination Swap can unwrap and process.\n *\n * Also provides NIP-44 encryption/decryption for FULFILL claim return path\n * (D12-008), using ephemeral keys so intermediaries on the return path see\n * only opaque ciphertext.\n *\n * @module\n */\n\nimport { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport type { UnsignedEvent, NostrEvent } from 'nostr-tools/pure';\nimport { createRumor, createSeal, createWrap } from 'nostr-tools/nip59';\nimport {\n encrypt as nip44Encrypt,\n decrypt as nip44Decrypt,\n getConversationKey,\n} from 'nostr-tools/nip44';\nimport {\n encodeEventToToon,\n decodeEventFromToon,\n} from '@toon-protocol/core/toon';\nimport { buildIlpPrepare } from '@toon-protocol/core';\nimport type { IlpPreparePacket } from '@toon-protocol/core';\n\nimport { GiftWrapError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Type definitions\n// ---------------------------------------------------------------------------\n\n/** Parameters for {@link wrapSwapPacket}. */\nexport interface WrapSwapPacketParams {\n /** Unsigned inner event containing swap metadata. */\n rumor: UnsignedEvent;\n /** Sender's secp256k1 secret key. */\n senderSecretKey: Uint8Array;\n /** Swap's compressed hex pubkey (64 chars). */\n recipientPubkey: string;\n}\n\n/** Result of {@link wrapSwapPacket}. */\nexport interface WrapSwapPacketResult {\n /** Fully-formed kind:1059 gift wrap event signed by a fresh ephemeral key. */\n giftWrap: NostrEvent;\n /** The ephemeral pubkey used for the outer gift wrap layer. */\n ephemeralPubkey: string;\n}\n\n/** Parameters for {@link unwrapSwapPacket}. */\nexport interface UnwrapSwapPacketParams {\n /** A kind:1059 gift wrap event. */\n giftWrap: NostrEvent;\n /** Recipient's (Swap's) secret key. */\n recipientSecretKey: Uint8Array;\n}\n\n/** Result of {@link unwrapSwapPacket}. */\nexport interface UnwrapSwapPacketResult {\n /** The decrypted inner rumor (unsigned). */\n rumor: UnsignedEvent;\n /** The sender's real pubkey (extracted from the seal layer). */\n senderPubkey: string;\n}\n\n/** Parameters for {@link wrapSwapPacketToToon}. */\nexport interface WrapSwapPacketToToonParams {\n /** Unsigned inner event containing swap metadata. */\n rumor: UnsignedEvent;\n /** Sender's secp256k1 secret key. */\n senderSecretKey: Uint8Array;\n /** Swap's compressed hex pubkey (64 chars). */\n recipientPubkey: string;\n /** ILP destination address. */\n destination: string;\n /** Payment amount in ILP units (bigint). */\n amount: bigint;\n /**\n * Per-packet expiry. Propagated onto the produced PREPARE as an ISO 8601\n * `expiresAt` string (rolling-swap R7 leg ordering). When omitted, the\n * transport applies its default (timeout-derived, ~30s).\n */\n expiresAt?: Date;\n}\n\n/** Result of {@link wrapSwapPacketToToon}. */\nexport interface WrapSwapPacketToToonResult {\n /** Ready-to-send ILP PREPARE packet with TOON-encoded gift wrap as data. */\n ilpPrepare: IlpPreparePacket;\n /** The ephemeral pubkey used for the outer gift wrap layer. */\n ephemeralPubkey: string;\n}\n\n/** Parameters for {@link unwrapSwapPacketFromToon}. */\nexport interface UnwrapSwapPacketFromToonParams {\n /** The data field from an incoming ILP PREPARE (TOON-encoded gift wrap). */\n toonData: Uint8Array;\n /** Recipient's (Swap's) secret key. */\n recipientSecretKey: Uint8Array;\n}\n\n/** Parameters for {@link encryptFulfillClaim}. */\nexport interface EncryptFulfillClaimParams {\n /** The signed claim bytes to encrypt. */\n claimData: Uint8Array;\n /** The original sender's pubkey (recovered from unwrap). */\n senderPubkey: string;\n}\n\n/** Result of {@link encryptFulfillClaim}. */\nexport interface EncryptFulfillClaimResult {\n /** NIP-44 encrypted claim bytes. */\n ciphertext: Uint8Array;\n /** The Swap's ephemeral pubkey (included in FULFILL so sender can decrypt). */\n ephemeralPubkey: string;\n}\n\n/** Parameters for {@link decryptFulfillClaim}. */\nexport interface DecryptFulfillClaimParams {\n /** The encrypted claim bytes from the FULFILL data field. */\n ciphertext: Uint8Array;\n /** The Swap's ephemeral pubkey from the FULFILL data field. */\n ephemeralPubkey: string;\n /** The sender's (recipient of FULFILL) secret key. */\n recipientSecretKey: Uint8Array;\n}\n\n// ---------------------------------------------------------------------------\n// Input validation helpers\n// ---------------------------------------------------------------------------\n\n/** Validate a 32-byte secret key. */\nfunction validateSecretKey(key: Uint8Array, paramName: string): void {\n if (!(key instanceof Uint8Array) || key.length !== 32) {\n throw new GiftWrapError(\n `${paramName} must be a 32-byte Uint8Array, got ${key instanceof Uint8Array ? `${key.length} bytes` : typeof key}`\n );\n }\n}\n\n/** Validate a 64-char lowercase hex pubkey. */\nfunction validatePubkey(pubkey: string, paramName: string): void {\n if (typeof pubkey !== 'string' || !/^[0-9a-f]{64}$/.test(pubkey)) {\n throw new GiftWrapError(\n `${paramName} must be a 64-character lowercase hex string`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Core wrap/unwrap functions (AC-1, AC-2)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-59 three-layer gift wrap a swap packet.\n *\n * Constructs a kind:1059 gift wrap event using a fresh ephemeral keypair.\n * The rumor is sealed with the sender's real key, then wrapped with the\n * ephemeral key. Intermediaries see only the ephemeral pubkey.\n *\n * Each invocation generates a fresh ephemeral keypair for forward secrecy\n * and message unlinkability (risk R-006).\n *\n * @throws {GiftWrapError} If the wrap operation fails (invalid keys, crypto failure).\n */\nexport function wrapSwapPacket(\n params: WrapSwapPacketParams\n): WrapSwapPacketResult {\n const { rumor, senderSecretKey, recipientPubkey } = params;\n\n validateSecretKey(senderSecretKey, 'senderSecretKey');\n validatePubkey(recipientPubkey, 'recipientPubkey');\n\n try {\n // Use nostr-tools nip59 building blocks so we can capture the ephemeral pubkey.\n // Step 1: Create rumor (adds id + pubkey derived from senderSecretKey)\n const rumorEvent = createRumor(rumor, senderSecretKey);\n\n // Step 2: Create seal (kind:13, NIP-44 encrypted rumor, randomized timestamp)\n const seal = createSeal(rumorEvent, senderSecretKey, recipientPubkey);\n\n // Step 3: Create gift wrap (kind:1059, fresh ephemeral key, NIP-44 encrypted seal)\n const giftWrap = createWrap(seal, recipientPubkey);\n\n // The ephemeral pubkey is the pubkey on the gift wrap event\n const ephemeralPubkey = giftWrap.pubkey;\n\n return { giftWrap, ephemeralPubkey };\n } catch (error) {\n throw new GiftWrapError(\n `Failed to wrap swap packet: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Unwrap a NIP-59 gift-wrapped swap packet, recovering the inner rumor\n * and the sender's real pubkey.\n *\n * Performs two-layer decryption: gift wrap -> seal -> rumor.\n * The sender pubkey is extracted from the seal's pubkey field.\n *\n * @throws {GiftWrapError} If kind is not 1059, decryption fails, or structure is malformed.\n */\nexport function unwrapSwapPacket(\n params: UnwrapSwapPacketParams\n): UnwrapSwapPacketResult {\n const { giftWrap, recipientSecretKey } = params;\n\n validateSecretKey(recipientSecretKey, 'recipientSecretKey');\n\n // Guard against null/undefined/non-object giftWrap before property access\n if (!giftWrap || typeof giftWrap !== 'object') {\n throw new GiftWrapError('giftWrap must be a non-null object');\n }\n\n // Validate kind before attempting decryption\n if (giftWrap.kind !== 1059) {\n throw new GiftWrapError('Expected kind:1059 gift wrap');\n }\n\n let conversationKey1: Uint8Array | null = null;\n let conversationKey2: Uint8Array | null = null;\n\n try {\n // Layer 1: Decrypt the gift wrap to recover the seal.\n // The gift wrap was encrypted with ECDH(ephemeralPrivkey, recipientPubkey),\n // so we decrypt with ECDH(recipientSecretKey, giftWrap.pubkey).\n conversationKey1 = getConversationKey(recipientSecretKey, giftWrap.pubkey);\n const sealJson = nip44Decrypt(giftWrap.content, conversationKey1);\n const seal = JSON.parse(sealJson) as NostrEvent;\n\n // Validate seal kind (must be kind:13 per NIP-59)\n if (seal.kind !== 13) {\n throw new GiftWrapError(\n `Expected kind:13 seal inside gift wrap, got kind:${seal.kind}`\n );\n }\n\n // Extract and validate the sender's real pubkey from the seal\n const senderPubkey = seal.pubkey;\n validatePubkey(senderPubkey, 'seal.pubkey (sender identity)');\n\n // Layer 2: Decrypt the seal to recover the rumor.\n // The seal was encrypted with ECDH(senderSecretKey, recipientPubkey),\n // so we decrypt with ECDH(recipientSecretKey, senderPubkey).\n conversationKey2 = getConversationKey(recipientSecretKey, senderPubkey);\n const rumorJson = nip44Decrypt(seal.content, conversationKey2);\n const rumor = JSON.parse(rumorJson) as UnsignedEvent & {\n id?: string;\n sig?: string;\n };\n\n // Strip sig if present (rumor should be unsigned)\n delete rumor.sig;\n\n return { rumor, senderPubkey };\n } catch (error) {\n if (error instanceof GiftWrapError) {\n throw error;\n }\n throw new GiftWrapError(\n `Failed to unwrap swap packet: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Zero ECDH-derived conversation keys (defense-in-depth)\n if (conversationKey1) {\n conversationKey1.fill(0);\n conversationKey1 = null;\n }\n if (conversationKey2) {\n conversationKey2.fill(0);\n conversationKey2 = null;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Convenience wrappers for TOON + ILP integration (AC-3, AC-4)\n// ---------------------------------------------------------------------------\n\n/**\n * Convenience: gift wrap a swap packet, encode to TOON binary, and build\n * an ILP PREPARE packet.\n *\n * This is the path that `streamSwap()` (Story 12.5) will use in its\n * per-packet loop.\n */\nexport function wrapSwapPacketToToon(\n params: WrapSwapPacketToToonParams\n): WrapSwapPacketToToonResult {\n const {\n rumor,\n senderSecretKey,\n recipientPubkey,\n destination,\n amount,\n expiresAt,\n } = params;\n\n // Step 1: Gift wrap\n const { giftWrap, ephemeralPubkey } = wrapSwapPacket({\n rumor,\n senderSecretKey,\n recipientPubkey,\n });\n\n // Step 2: Encode to TOON binary\n const toonBinary = encodeEventToToon(giftWrap);\n\n // Step 3: Build ILP PREPARE packet\n const ilpPrepare = buildIlpPrepare({\n destination,\n amount,\n data: toonBinary,\n expiresAt,\n });\n\n return { ilpPrepare, ephemeralPubkey };\n}\n\n/**\n * Convenience: decode TOON binary from an incoming ILP PREPARE data field\n * and unwrap the gift-wrapped swap packet.\n *\n * This is the path that the Swap handler (Story 12.3) will use to process\n * incoming swap packets.\n */\nexport function unwrapSwapPacketFromToon(\n params: UnwrapSwapPacketFromToonParams\n): UnwrapSwapPacketResult {\n const { toonData, recipientSecretKey } = params;\n\n if (!(toonData instanceof Uint8Array) || toonData.length === 0) {\n throw new GiftWrapError('toonData must be a non-empty Uint8Array');\n }\n\n try {\n // Step 1: Decode TOON binary to NostrEvent\n const giftWrap = decodeEventFromToon(toonData);\n\n // Step 2: Unwrap\n return unwrapSwapPacket({ giftWrap, recipientSecretKey });\n } catch (error) {\n if (error instanceof GiftWrapError) {\n throw error;\n }\n throw new GiftWrapError(\n `Failed to unwrap swap packet from TOON: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// FULFILL encryption/decryption (AC-5, AC-6)\n// ---------------------------------------------------------------------------\n\n/**\n * Encrypt a FULFILL claim for the return path (D12-008).\n *\n * Generates a fresh ephemeral keypair and NIP-44 encrypts the claim data.\n * The ephemeral pubkey is included alongside the ciphertext so the sender\n * can decrypt. The ephemeral privkey is discarded after encryption.\n *\n * @throws {GiftWrapError} If claimData is empty or encryption fails.\n */\nexport function encryptFulfillClaim(\n params: EncryptFulfillClaimParams\n): EncryptFulfillClaimResult {\n const { claimData, senderPubkey } = params;\n\n validatePubkey(senderPubkey, 'senderPubkey');\n\n if (!(claimData instanceof Uint8Array)) {\n throw new GiftWrapError(\n `claimData must be a Uint8Array, got ${typeof claimData}`\n );\n }\n\n if (claimData.length === 0) {\n throw new GiftWrapError('claimData must not be empty');\n }\n\n // Generate fresh ephemeral keypair (let-scoped for GC after return)\n let ephemeralSecretKey: Uint8Array | null = generateSecretKey();\n const ephemeralPubkey = getPublicKey(ephemeralSecretKey);\n let conversationKey: Uint8Array | null = null;\n\n try {\n // NIP-44 encrypt: ECDH(ephemeralPrivkey, senderPubkey)\n conversationKey = getConversationKey(ephemeralSecretKey, senderPubkey);\n\n // NIP-44 encrypts strings, so base64-encode the claim bytes\n const claimString = Buffer.from(claimData).toString('base64');\n const ciphertextString = nip44Encrypt(claimString, conversationKey);\n\n // Convert ciphertext string to Uint8Array for transport\n const ciphertext = new TextEncoder().encode(ciphertextString);\n\n return { ciphertext, ephemeralPubkey };\n } catch (error) {\n if (error instanceof GiftWrapError) {\n throw error;\n }\n throw new GiftWrapError(\n `Failed to encrypt FULFILL claim: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Zero sensitive key material before releasing for GC (defense-in-depth)\n if (ephemeralSecretKey) {\n ephemeralSecretKey.fill(0);\n ephemeralSecretKey = null;\n }\n if (conversationKey) {\n conversationKey.fill(0);\n conversationKey = null;\n }\n }\n}\n\n/**\n * Decrypt a FULFILL claim from the return path.\n *\n * Uses the sender's secret key and the Swap's ephemeral pubkey (from the\n * FULFILL data field) to NIP-44 decrypt the claim bytes.\n *\n * @throws {GiftWrapError} If decryption fails (wrong key, malformed ciphertext).\n */\nexport function decryptFulfillClaim(\n params: DecryptFulfillClaimParams\n): Uint8Array {\n const { ciphertext, ephemeralPubkey, recipientSecretKey } = params;\n\n validateSecretKey(recipientSecretKey, 'recipientSecretKey');\n validatePubkey(ephemeralPubkey, 'ephemeralPubkey');\n\n if (!(ciphertext instanceof Uint8Array) || ciphertext.length === 0) {\n throw new GiftWrapError('ciphertext must be a non-empty Uint8Array');\n }\n\n let conversationKey: Uint8Array | null = null;\n\n try {\n // NIP-44 decrypt: ECDH(recipientSecretKey, ephemeralPubkey)\n conversationKey = getConversationKey(recipientSecretKey, ephemeralPubkey);\n\n // Convert ciphertext Uint8Array back to string\n const ciphertextString = new TextDecoder().decode(ciphertext);\n\n // NIP-44 decrypt returns a string (base64-encoded claim bytes)\n const claimString = nip44Decrypt(ciphertextString, conversationKey);\n\n // Decode base64 back to Uint8Array\n return new Uint8Array(Buffer.from(claimString, 'base64'));\n } catch (error) {\n if (error instanceof GiftWrapError) {\n throw error;\n }\n throw new GiftWrapError(\n `Failed to decrypt FULFILL claim: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Zero ECDH-derived conversation key (defense-in-depth)\n if (conversationKey) {\n conversationKey.fill(0);\n conversationKey = null;\n }\n }\n}\n","/**\n * rfc-0039-style stream receipts (issue #84, rolling-swap spec §7.2).\n *\n * Per fulfilled packet the maker attaches a compact SIGNED receipt to the\n * FULFILL accept-metadata: a monotone cumulative proof of asset-B delivered\n * in the session. The receipt chain is the sender's portable audit/dispute\n * artifact today (it makes the maker-debt bound provable to a third party)\n * and the natural input to a future escrow layer. Receipts are\n * session-scoped via `streamNonce`; the latest receipt supersedes all\n * earlier ones, mirroring the highest-nonce rule for claims.\n *\n * ## Deviations from Interledger rfc-0039 (documented per spec §7.2 — the\n * TOON-native equivalent deliberately does not import the STREAM framing):\n *\n * - **Signature, not HMAC.** rfc-0039 authenticates receipts with\n * HMAC-SHA256 over a pre-shared 32-byte Receipt Secret, so only a party\n * holding the secret can verify. TOON receipts are BIP-340 Schnorr\n * signatures by the maker's receipt key (default: the maker's Nostr\n * identity key — the same `swapPubkey` the sender already discovered via\n * kind:10032), making every receipt verifiable by ANY third party with\n * the maker's pubkey. No Receipt Secret exists or is provisioned.\n * - **Nonce provisioning.** rfc-0039's Verifier generates the Receipt Nonce\n * and communicates it out-of-band (SPSP headers). Here the sender (who is\n * its own verifier today) generates a 16-byte `streamNonce` per\n * `streamSwap()` invocation and communicates it in-band as the rumor's\n * `stream-nonce` tag. Legacy makers ignore the unknown tag.\n * - **Encoding.** JSON object inside the accept-metadata dict rather than a\n * CANONICAL-OER STREAM frame. A deterministic length-prefixed binary\n * encoding ({@link encodeReceiptSigningPayload}) is used ONLY as the\n * signing preimage.\n * - **Fields.** `seq` (maker's per-session fulfill counter — enables hole\n * detection, absent from rfc-0039), `rate` + `rateTimestamp` (the quote\n * tape entry, attested under the maker signature) are added; the STREAM\n * `Stream ID` is dropped (one logical stream per session — the\n * `streamNonce` IS the stream identity); `Total Received` is a decimal\n * string (bigint range) rather than UInt64.\n *\n * rfc-0039 semantics preserved: one receipt per fulfilled packet, each\n * carrying a progressively higher cumulative total; latest supersedes;\n * verification = authenticity check + monotonicity against previously seen\n * receipts for the same nonce.\n *\n * @module\n */\n\nimport { schnorr } from '@noble/curves/secp256k1.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Wire version of the TOON stream receipt. */\nexport const STREAM_RECEIPT_VERSION = 1 as const;\n\n/** Domain-separation tag prefixed to the receipt signing payload. */\nexport const STREAM_RECEIPT_SIGNING_TAG = 'toon.stream-receipt.v1';\n\n/** The signed fields of a {@link StreamReceipt} (everything except `sig`). */\nexport interface StreamReceiptFields {\n /** Receipt wire version — always {@link STREAM_RECEIPT_VERSION}. */\n v: typeof STREAM_RECEIPT_VERSION;\n /**\n * Session identifier: 32-char lowercase hex (16 bytes), generated by the\n * sender per stream and echoed by the maker on every receipt.\n */\n streamNonce: string;\n /**\n * Maker's per-session fulfill counter, 1-based, strictly increasing by 1\n * per fulfilled packet. Gaps observed by the sender indicate receipts it\n * never received (hole detection).\n */\n seq: number;\n /**\n * Running total of asset B delivered in this session (target micro-units,\n * decimal string). Monotone non-decreasing in `seq`; the latest receipt\n * supersedes all earlier ones.\n */\n cumulativeDelivered: string;\n /** The maker's quote `R_i` applied to the packet this receipt rode on (decimal string). */\n rate: string;\n /** Unix ms timestamp when the maker's rate source produced {@link rate}. */\n rateTimestamp: number;\n}\n\n/**\n * A signed stream receipt as carried on the FULFILL accept-metadata under\n * the `receipt` key (rolling-swap spec §7.2).\n */\nexport interface StreamReceipt extends StreamReceiptFields {\n /**\n * 128-char lowercase hex BIP-340 Schnorr signature by the maker's receipt\n * key over `sha256(encodeReceiptSigningPayload(fields))`.\n */\n sig: string;\n}\n\n/**\n * Aggregate view of the receipts harvested from one stream — the serialized\n * audit/dispute artifact's in-memory shape. Returned on\n * `StreamSwapResult.receipts` (present on abort too, covering what filled).\n */\nexport interface StreamReceiptChain {\n /** The sender-generated session nonce receipts were verified against. */\n streamNonce: string;\n /** All verified receipts, sorted by ascending `seq`. */\n receipts: StreamReceipt[];\n /** Highest-`seq` receipt — supersedes all others (rfc-0039 latest-wins). */\n latest?: StreamReceipt;\n /**\n * `latest.cumulativeDelivered`, or `'0'` when no receipt was received —\n * the signed total of asset B the maker attests to having delivered.\n */\n totalDelivered: string;\n /**\n * Missing `seq` values in `[1, latest.seq]`: fulfilled packets whose\n * receipts the sender never received (lost FULFILLs, or maker skips).\n */\n holes: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Validation helpers\n// ---------------------------------------------------------------------------\n\nconst STREAM_NONCE_REGEX = /^[0-9a-f]{32}$/;\nconst SIG_REGEX = /^[0-9a-f]{128}$/;\nconst HEX64_REGEX = /^[0-9a-f]{64}$/;\nconst DECIMAL_UINT_REGEX = /^(0|[1-9]\\d*)$/;\nconst RATE_REGEX = /^(0|[1-9]\\d*)(\\.\\d+)?$/;\n\n/** True iff `value` is a well-formed 32-char lowercase hex stream nonce. */\nexport function isValidStreamNonce(value: unknown): value is string {\n return typeof value === 'string' && STREAM_NONCE_REGEX.test(value);\n}\n\n/**\n * Structurally validate an untrusted metadata `receipt` value into a\n * {@link StreamReceipt}. Checks shape ONLY — no signature or monotonicity\n * verification (that is {@link verifyStreamReceipt} / {@link ReceiptChainTracker}).\n *\n * @throws {Error} with a human-readable reason when malformed. Callers\n * (e.g. `decodeFulfillMetadata`) wrap into their own error taxonomy.\n */\nexport function parseStreamReceipt(value: unknown): StreamReceipt {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('receipt must be an object');\n }\n const obj = value as Record<string, unknown>;\n if (obj['v'] !== STREAM_RECEIPT_VERSION) {\n throw new Error(\n `receipt.v must be ${STREAM_RECEIPT_VERSION}, got ${String(obj['v'])}`\n );\n }\n if (!isValidStreamNonce(obj['streamNonce'])) {\n throw new Error('receipt.streamNonce must be 32-char lowercase hex');\n }\n const seq = obj['seq'];\n if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < 1) {\n throw new Error('receipt.seq must be a positive safe integer');\n }\n const cumulativeDelivered = obj['cumulativeDelivered'];\n if (\n typeof cumulativeDelivered !== 'string' ||\n !DECIMAL_UINT_REGEX.test(cumulativeDelivered)\n ) {\n throw new Error(\n 'receipt.cumulativeDelivered must be a non-negative integer decimal string'\n );\n }\n const rate = obj['rate'];\n if (\n typeof rate !== 'string' ||\n !RATE_REGEX.test(rate) ||\n /^0(\\.0+)?$/.test(rate)\n ) {\n throw new Error('receipt.rate must be a positive decimal string');\n }\n const rateTimestamp = obj['rateTimestamp'];\n if (\n typeof rateTimestamp !== 'number' ||\n !Number.isInteger(rateTimestamp) ||\n rateTimestamp <= 0\n ) {\n throw new Error(\n 'receipt.rateTimestamp must be a positive integer (unix ms)'\n );\n }\n const sig = obj['sig'];\n if (typeof sig !== 'string' || !SIG_REGEX.test(sig)) {\n throw new Error('receipt.sig must be 128-char lowercase hex');\n }\n return {\n v: STREAM_RECEIPT_VERSION,\n streamNonce: obj['streamNonce'] as string,\n seq,\n cumulativeDelivered,\n rate,\n rateTimestamp,\n sig,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Canonical signing payload + sign/verify\n// ---------------------------------------------------------------------------\n\n/**\n * Deterministic binary encoding of the signed receipt fields — the signing\n * preimage. Layout (all integers big-endian):\n *\n * ```\n * len(tag):u32 || tag(utf8) || v:u8 || streamNonce(16 raw bytes)\n * || seq:u64 || len:u32 || cumulativeDelivered(utf8)\n * || len:u32 || rate(utf8) || rateTimestamp:u64\n * ```\n *\n * Length prefixes remove concatenation ambiguity between the variable-width\n * decimal strings (same rationale as the swap-handler replay packet-id hash).\n */\nexport function encodeReceiptSigningPayload(\n fields: StreamReceiptFields\n): Uint8Array {\n if (!isValidStreamNonce(fields.streamNonce)) {\n throw new Error('streamNonce must be 32-char lowercase hex');\n }\n const tag = Buffer.from(STREAM_RECEIPT_SIGNING_TAG, 'utf8');\n const cumulative = Buffer.from(fields.cumulativeDelivered, 'utf8');\n const rate = Buffer.from(fields.rate, 'utf8');\n const buf = Buffer.alloc(\n 4 + tag.length + 1 + 16 + 8 + 4 + cumulative.length + 4 + rate.length + 8\n );\n let off = 0;\n buf.writeUInt32BE(tag.length, off);\n off += 4;\n tag.copy(buf, off);\n off += tag.length;\n buf.writeUInt8(fields.v, off);\n off += 1;\n Buffer.from(hexToBytes(fields.streamNonce)).copy(buf, off);\n off += 16;\n buf.writeBigUInt64BE(BigInt(fields.seq), off);\n off += 8;\n buf.writeUInt32BE(cumulative.length, off);\n off += 4;\n cumulative.copy(buf, off);\n off += cumulative.length;\n buf.writeUInt32BE(rate.length, off);\n off += 4;\n rate.copy(buf, off);\n off += rate.length;\n buf.writeBigUInt64BE(BigInt(fields.rateTimestamp), off);\n return new Uint8Array(buf);\n}\n\n/**\n * Sign the receipt fields with the maker's receipt secret key (32-byte\n * secp256k1 scalar — by default the maker's Nostr identity key). Returns\n * the complete {@link StreamReceipt} with the BIP-340 Schnorr `sig` over\n * `sha256(encodeReceiptSigningPayload(fields))`.\n */\nexport function signStreamReceipt(\n fields: StreamReceiptFields,\n secretKey: Uint8Array\n): StreamReceipt {\n if (!(secretKey instanceof Uint8Array) || secretKey.length !== 32) {\n throw new Error('receipt secretKey must be a 32-byte Uint8Array');\n }\n const digest = sha256(encodeReceiptSigningPayload(fields));\n const sig = bytesToHex(schnorr.sign(digest, secretKey));\n return { ...fields, sig };\n}\n\n/**\n * Verify a receipt's Schnorr signature against the maker's receipt pubkey\n * (64-char lowercase hex x-only key — by default the maker's `swapPubkey`).\n * Authenticity only; monotonicity/session checks live in\n * {@link ReceiptChainTracker}.\n */\nexport function verifyStreamReceipt(\n receipt: StreamReceipt,\n makerPubkey: string\n): boolean {\n if (typeof makerPubkey !== 'string' || !HEX64_REGEX.test(makerPubkey)) {\n return false;\n }\n if (typeof receipt.sig !== 'string' || !SIG_REGEX.test(receipt.sig)) {\n return false;\n }\n try {\n const { sig: _sig, ...fields } = receipt;\n const digest = sha256(encodeReceiptSigningPayload(fields));\n return schnorr.verify(\n hexToBytes(receipt.sig),\n digest,\n hexToBytes(makerPubkey)\n );\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Sender-side accumulation — ReceiptChainTracker\n// ---------------------------------------------------------------------------\n\n/** Outcome of {@link ReceiptChainTracker.add}. */\nexport type ReceiptAddResult =\n | { ok: true }\n | {\n ok: false;\n /** Machine-readable rejection reason. */\n code:\n | 'BAD_SIGNATURE'\n | 'WRONG_STREAM_NONCE'\n | 'DUPLICATE_SEQ'\n | 'NON_MONOTONIC';\n message: string;\n };\n\n/**\n * Accumulates and verifies the receipt chain for ONE stream session.\n *\n * Per added receipt it checks, in order:\n * 1. `streamNonce` matches the session (a receipt for another session is\n * worthless as proof for this one),\n * 2. the Schnorr signature verifies against the maker's receipt pubkey,\n * 3. `seq` is not a duplicate,\n * 4. `cumulativeDelivered` is monotone in `seq` against every receipt seen\n * so far: non-decreasing with ascending `seq` (rfc-0039 \"progressively\n * higher amount\"; non-strict so a zero-target packet — possible under\n * `applyRate` truncation — does not read as maker equivocation).\n *\n * Receipts may be added OUT OF ORDER (adaptive W>1 streams complete in\n * arbitrary order); monotonicity is enforced against both the nearest lower\n * and nearest higher `seq` neighbors.\n */\nexport class ReceiptChainTracker {\n readonly #streamNonce: string;\n readonly #makerPubkey: string;\n /** Verified receipts, kept sorted by ascending seq. */\n readonly #receipts: StreamReceipt[] = [];\n readonly #seqs = new Set<number>();\n\n constructor(input: { streamNonce: string; makerPubkey: string }) {\n if (!isValidStreamNonce(input.streamNonce)) {\n throw new Error('streamNonce must be 32-char lowercase hex');\n }\n if (\n typeof input.makerPubkey !== 'string' ||\n !HEX64_REGEX.test(input.makerPubkey)\n ) {\n throw new Error('makerPubkey must be a 64-char lowercase hex string');\n }\n this.#streamNonce = input.streamNonce;\n this.#makerPubkey = input.makerPubkey;\n }\n\n get streamNonce(): string {\n return this.#streamNonce;\n }\n\n /** Number of verified receipts accumulated so far. */\n get size(): number {\n return this.#receipts.length;\n }\n\n /**\n * Verify + accumulate one receipt. On `{ok: false}` the receipt is NOT\n * added and the chain is unchanged.\n */\n add(receipt: StreamReceipt): ReceiptAddResult {\n if (receipt.streamNonce !== this.#streamNonce) {\n return {\n ok: false,\n code: 'WRONG_STREAM_NONCE',\n message: `receipt streamNonce ${receipt.streamNonce} does not match session ${this.#streamNonce}`,\n };\n }\n if (!verifyStreamReceipt(receipt, this.#makerPubkey)) {\n return {\n ok: false,\n code: 'BAD_SIGNATURE',\n message: `receipt seq ${receipt.seq} signature does not verify against maker key ${this.#makerPubkey}`,\n };\n }\n if (this.#seqs.has(receipt.seq)) {\n return {\n ok: false,\n code: 'DUPLICATE_SEQ',\n message: `duplicate receipt seq ${receipt.seq}`,\n };\n }\n // Binary-search the sorted insert position, then check both neighbors.\n let lo = 0;\n let hi = this.#receipts.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n const midReceipt = this.#receipts[mid] as StreamReceipt;\n if (midReceipt.seq < receipt.seq) lo = mid + 1;\n else hi = mid;\n }\n const cumulative = BigInt(receipt.cumulativeDelivered);\n const lower = lo > 0 ? this.#receipts[lo - 1] : undefined;\n const higher = lo < this.#receipts.length ? this.#receipts[lo] : undefined;\n if (lower && BigInt(lower.cumulativeDelivered) > cumulative) {\n return {\n ok: false,\n code: 'NON_MONOTONIC',\n message: `receipt seq ${receipt.seq} cumulativeDelivered ${receipt.cumulativeDelivered} is below seq ${lower.seq}'s ${lower.cumulativeDelivered}`,\n };\n }\n if (higher && BigInt(higher.cumulativeDelivered) < cumulative) {\n return {\n ok: false,\n code: 'NON_MONOTONIC',\n message: `receipt seq ${receipt.seq} cumulativeDelivered ${receipt.cumulativeDelivered} is above seq ${higher.seq}'s ${higher.cumulativeDelivered}`,\n };\n }\n this.#receipts.splice(lo, 0, receipt);\n this.#seqs.add(receipt.seq);\n return { ok: true };\n }\n\n /**\n * Snapshot the accumulated chain: sorted receipts, the superseding latest,\n * the signed total delivered, and any `seq` holes in `[1, latest.seq]`.\n */\n chain(): StreamReceiptChain {\n const receipts = [...this.#receipts];\n const latest = receipts[receipts.length - 1];\n const holes: number[] = [];\n if (latest) {\n for (let seq = 1; seq <= latest.seq; seq++) {\n if (!this.#seqs.has(seq)) holes.push(seq);\n }\n }\n return {\n streamNonce: this.#streamNonce,\n receipts,\n ...(latest !== undefined && { latest }),\n totalDelivered: latest?.cumulativeDelivered ?? '0',\n holes,\n };\n }\n}\n\n/**\n * Serialize a receipt chain into the portable audit/dispute artifact: a\n * stable, versioned JSON document. Any third party holding the maker's\n * receipt pubkey can re-verify every receipt in it offline (the enclosed\n * receipts are self-contained signed statements).\n */\nexport function serializeReceiptChain(chain: StreamReceiptChain): string {\n return JSON.stringify({\n kind: 'toon.stream-receipt-chain',\n v: STREAM_RECEIPT_VERSION,\n streamNonce: chain.streamNonce,\n totalDelivered: chain.totalDelivered,\n holes: chain.holes,\n receipts: chain.receipts.map((r) => ({\n v: r.v,\n streamNonce: r.streamNonce,\n seq: r.seq,\n cumulativeDelivered: r.cumulativeDelivered,\n rate: r.rate,\n rateTimestamp: r.rateTimestamp,\n sig: r.sig,\n })),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Maker-side session state — ReceiptSessionStore\n// ---------------------------------------------------------------------------\n\n/** Per-session receipt issuance state kept by the maker. */\nexport interface ReceiptSessionState {\n /** Last issued seq (0 = none issued yet). */\n seq: number;\n /** Cumulative asset-B delivered in the session so far (target micro-units). */\n cumulativeDelivered: bigint;\n}\n\n/**\n * Minimal synchronous store contract for maker-side receipt session state,\n * keyed by `streamNonce`.\n *\n * SYNCHRONOUS by design: the swap handler performs the read-increment-write\n * inside one microtask so concurrent packets in the same session get\n * distinct, gapless `seq` values and consistent cumulative totals. An\n * operator-supplied store that persists (e.g. alongside the claim/channel\n * store, so receipt state survives restarts like claims do) should\n * write-through asynchronously in the background while serving reads from\n * memory. The default is {@link BoundedReceiptSessions} (in-memory LRU).\n */\nexport interface ReceiptSessionStoreLike {\n get(streamNonce: string): ReceiptSessionState | undefined;\n set(streamNonce: string, state: ReceiptSessionState): unknown;\n delete(streamNonce: string): boolean;\n}\n\n/**\n * Default ceiling for the maker's receipt-session LRU. Same DoS rationale\n * as `DEFAULT_SEEN_PACKET_IDS_CAP`: bounds memory against an attacker\n * minting unlimited distinct stream nonces. An evicted (or maker-restarted)\n * session restarts at seq 1 — the sender's tracker then rejects the\n * duplicate seq loudly rather than accepting a forked chain.\n */\nexport const DEFAULT_RECEIPT_SESSIONS_CAP = 10_000;\n\n/**\n * Bounded in-memory LRU {@link ReceiptSessionStoreLike}. Access-order\n * eviction: active sessions stay pinned while stale ones age out.\n */\nexport class BoundedReceiptSessions implements ReceiptSessionStoreLike {\n readonly #map = new Map<string, ReceiptSessionState>();\n readonly #cap: number;\n\n constructor(cap: number = DEFAULT_RECEIPT_SESSIONS_CAP) {\n if (!Number.isInteger(cap) || cap <= 0) {\n throw new Error(\n `BoundedReceiptSessions cap must be a positive integer, got ${cap}`\n );\n }\n this.#cap = cap;\n }\n\n get size(): number {\n return this.#map.size;\n }\n\n get cap(): number {\n return this.#cap;\n }\n\n get(streamNonce: string): ReceiptSessionState | undefined {\n const state = this.#map.get(streamNonce);\n if (state === undefined) return undefined;\n // Access-order promotion.\n this.#map.delete(streamNonce);\n this.#map.set(streamNonce, state);\n return state;\n }\n\n set(streamNonce: string, state: ReceiptSessionState): this {\n this.#map.delete(streamNonce);\n this.#map.set(streamNonce, state);\n while (this.#map.size > this.#cap) {\n const first = this.#map.keys().next();\n if (first.done) break;\n this.#map.delete(first.value);\n }\n return this;\n }\n\n delete(streamNonce: string): boolean {\n return this.#map.delete(streamNonce);\n }\n}\n\n/**\n * Maker-side: advance the session state for one fulfilled packet and issue\n * the corresponding signed receipt. The read-increment-write against the\n * store happens synchronously (no awaits) so concurrent handler invocations\n * interleave safely.\n *\n * MUST only be called for a packet that is being ACCEPTED — a rejected\n * packet gets no receipt and MUST NOT advance the session.\n */\nexport function issueSessionReceipt(input: {\n sessions: ReceiptSessionStoreLike;\n streamNonce: string;\n /** Asset-B amount delivered by THIS packet (target micro-units). */\n deliveredAmount: bigint;\n /** The quote-tape rate applied to this packet. */\n rate: string;\n /** The quote-tape timestamp for {@link rate}. */\n rateTimestamp: number;\n /** Maker receipt secret key (32-byte secp256k1). */\n secretKey: Uint8Array;\n}): StreamReceipt {\n const { sessions, streamNonce, deliveredAmount, rate, rateTimestamp } = input;\n if (deliveredAmount < 0n) {\n throw new Error(\n `deliveredAmount must be non-negative, got ${deliveredAmount}`\n );\n }\n const prev = sessions.get(streamNonce) ?? {\n seq: 0,\n cumulativeDelivered: 0n,\n };\n const next: ReceiptSessionState = {\n seq: prev.seq + 1,\n cumulativeDelivered: prev.cumulativeDelivered + deliveredAmount,\n };\n const receipt = signStreamReceipt(\n {\n v: STREAM_RECEIPT_VERSION,\n streamNonce,\n seq: next.seq,\n cumulativeDelivered: next.cumulativeDelivered.toString(),\n rate,\n rateTimestamp,\n },\n input.secretKey\n );\n // Commit AFTER signing succeeds: a signing failure leaves the session\n // untouched so the packet can still be accepted receipt-less (or retried)\n // without burning a seq.\n sessions.set(streamNonce, next);\n return receipt;\n}\n","/**\n * Swap Handler (Story 12.3)\n *\n * `createSwapHandler()` factory produces a kind:1059 `Handler` that:\n * 1. Unwraps an incoming NIP-59 gift-wrapped ILP swap packet (via Story 12.2).\n * 2. Identifies the requested `SwapPair` from inner-rumor `swap-from` / `swap-to` tags.\n * 3. Applies a per-packet exchange rate (pair.rate or live rateProvider hook).\n * 4. Delegates signed claim issuance to a pluggable `ClaimIssuer` (Story 12.4).\n * 5. NIP-44 encrypts the claim with an ephemeral key (Story 12.2) for return.\n *\n * The handler is a pure application-layer composition — no connector, routing,\n * or wallet code lives here. See `_bmad-output/epics/epic-12-token-swap-primitive.md`\n * for D12-001/D12-008/D12-009/D12-010 and the scope fence.\n *\n * Transport encoding: the `accept()` metadata emits `claim` as a base64-encoded\n * NIP-44 ciphertext, `ephemeralPubkey` as 64-char lowercase hex, and optional\n * `claimId`. The sender-side `streamSwap()` (Story 12.5) base64-decodes `claim`\n * before calling `decryptFulfillClaim`.\n *\n * @module\n */\n\nimport { createHash } from 'node:crypto';\nimport type { UnsignedEvent } from 'nostr-tools/pure';\nimport type { SwapPair } from '@toon-protocol/core';\n\nimport { GiftWrapError, SwapHandlerError } from './errors.js';\nimport { unwrapSwapPacketFromToon, encryptFulfillClaim } from './gift-wrap.js';\nimport type { Handler } from './handler-registry.js';\nimport { base58Decode } from './identity.js';\nimport {\n BoundedReceiptSessions,\n isValidStreamNonce,\n issueSessionReceipt,\n type ReceiptSessionStoreLike,\n type StreamReceipt,\n} from './stream-receipts.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Parameters passed to a {@link ClaimIssuer.issueClaim} call. */\nexport interface IssueClaimParams {\n /** Source-asset amount received by the Swap (ILP packet amount, source micro-units). */\n sourceAmount: bigint;\n /** Target-asset amount owed to the sender (post-rate-conversion, target micro-units). */\n targetAmount: bigint;\n /** The `SwapPair` this packet is being priced against. */\n pair: SwapPair;\n /**\n * The sender's real Nostr pubkey (extracted from the unwrapped seal).\n *\n * Identity-layer key only: used by the Swap for inventory ledger keying and\n * the sender→channel sticky binding (`channelState.reserve()` /\n * `channelState.release()`). The Swap MUST NOT pass this to chain-layer\n * signers as the balance-proof `recipient` — use {@link chainRecipient}\n * for that (Story 12.9 D12-011).\n */\n senderPubkey: string;\n /**\n * The sender's chain-specific payout address for `pair.to.chain`\n * (Story 12.9 AC-10). Extracted and format-validated from the rumor's\n * `chain-recipient` tag by the swap handler. REQUIRED. This is the\n * address the Swap's `PaymentChannelSigner` MUST use as the balance-proof\n * `recipient` (e.g., 20-byte EVM address, 32-byte Solana Ed25519 pubkey).\n */\n chainRecipient: string;\n /** The inner rumor (for optional Swap-side context; may be ignored by the issuer). */\n rumor: UnsignedEvent;\n}\n\n/**\n * Result returned from {@link ClaimIssuer.issueClaim}.\n *\n * Story 12.6 extension: additive settlement-context fields let the sender\n * reconstruct the balance-proof message hash for signature verification and\n * on-chain settlement (see `buildSettlementTx()`).\n */\nexport interface IssueClaimResult {\n /** Signed claim bytes ready for NIP-44 encryption (chain-specific format). */\n claim: Uint8Array;\n /** Optional Swap-side claim ID for logging/tracing. */\n claimId?: string;\n // --- Story 12.6 settlement-context fields (additive, all optional for\n // one-story-cycle backward compat; the Swap SHOULD emit all of them) ---\n /** Channel identifier on the target chain. */\n channelId?: string;\n /** Balance-proof nonce (monotonically increasing per channel). */\n nonce?: bigint;\n /** Cumulative transferred amount on the channel (target micro-units). */\n cumulativeAmount?: bigint;\n /** Recipient address (the sender's target-asset address). */\n recipient?: string;\n /** Swap's on-chain signer address. */\n swapSignerAddress?: string;\n}\n\n/**\n * Pluggable signed-claim issuer. Story 12.3 defines only the contract — the\n * concrete multi-chain implementation ships in Story 12.4.\n *\n * The issuer owns inventory accounting and signing-key material. The handler\n * relies on `issueClaim()` being atomic with inventory debit: if the call\n * resolves, the target-asset amount MUST be considered committed from the\n * Swap's reserves. If the call throws, no inventory change SHOULD have occurred.\n */\nexport interface ClaimIssuer {\n /**\n * Produce a signed off-chain payment-channel claim in the target asset.\n *\n * @throws Error (or subclass) on insufficient reserves, unsupported pair,\n * or signing failure. Errors with `code === 'INSUFFICIENT_INVENTORY'` or\n * messages matching `/insufficient/i` are surfaced as ILP T04; all other\n * errors as T00.\n */\n issueClaim(params: IssueClaimParams): Promise<IssueClaimResult>;\n}\n\n/** Parameters for {@link applyRate}. */\nexport interface ApplyRateParams {\n /** Source amount in source micro-units. */\n sourceAmount: bigint;\n /** `SwapPair.from.assetScale` (number of decimals on source side). */\n fromScale: number;\n /** `SwapPair.to.assetScale` (number of decimals on target side). */\n toScale: number;\n /** Decimal-string rate (target whole-units per source whole-unit). */\n rate: string;\n}\n\n/**\n * A fresh quote returned by a {@link CreateSwapHandlerConfig.rateProvider}\n * hook (issue #82, rolling-swap quote tape).\n *\n * `rateTimestamp` is the unix-ms time at which the maker's rate SOURCE\n * produced this quote (its feed tick), not the time the handler resolved it.\n * Both fields are echoed verbatim into the FULFILL accept-metadata so the\n * sender can read the quote tape `(R_1, t_1), (R_2, t_2), …` off the fills.\n */\nexport interface RateQuote {\n /** Decimal-string rate matching `SwapPair.rate` format: /^(0|[1-9]\\d*)(\\.\\d+)?$/. */\n rate: string;\n /** Unix ms timestamp when the rate source produced this quote. Positive integer. */\n rateTimestamp: number;\n}\n\n/** Minimal pino-compatible logger interface. */\nexport interface SwapHandlerLogger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\n/**\n * Minimal `Set`-like contract the handler requires from\n * `seenPacketIds`. Both the native `Set<string>` and\n * {@link BoundedSeenPacketIds} satisfy this; operators can swap in a\n * persistent/remote-backed replacement too.\n */\nexport interface SeenPacketIdsLike {\n has(value: string): boolean;\n add(value: string): unknown;\n delete(value: string): boolean;\n}\n\n/** Configuration for {@link createSwapHandler}. */\nexport interface CreateSwapHandlerConfig {\n /** Swap's secp256k1 secret key for unwrapping gift-wrapped packets (32 bytes). */\n recipientSecretKey: Uint8Array;\n /** Swap pairs this Swap currently supports. */\n swapPairs: SwapPair[];\n /** Claim issuer delegate (Story 12.4 plugs in the multi-chain implementation). */\n claimIssuer: ClaimIssuer;\n /**\n * Optional live-rate override hook. When provided, the handler calls this per\n * packet instead of reading `pair.rate`. This is the rolling-swap fresh-quote\n * seam (issue #82): the resolved rate `R_i` and its quote timestamp are\n * emitted on every FULFILL's accept-metadata as the quote tape.\n *\n * MAY return either:\n * - a decimal string matching `SwapPair.rate` format\n * /^(0|[1-9]\\d*)(\\.\\d+)?$/ (legacy shape; the handler stamps\n * `rateTimestamp` with its own resolution time), or\n * - a {@link RateQuote} `{ rate, rateTimestamp }` so the rate source's own\n * tick time travels on the tape.\n */\n rateProvider?: (\n pair: SwapPair\n ) => string | RateQuote | Promise<string | RateQuote>;\n /**\n * Optional replay-protection set. When provided, the handler uses it\n * VERBATIM (operator-owned — bounding/persistence is the operator's\n * responsibility). When OMITTED (Story 12.8 AC-14), the handler\n * defaults to a {@link BoundedSeenPacketIds} with cap\n * {@link DEFAULT_SEEN_PACKET_IDS_CAP}. Any `Set`-shaped object with\n * `has`/`add`/`delete` (and a `size` getter for test introspection)\n * satisfies the contract.\n */\n seenPacketIds?: SeenPacketIdsLike;\n /**\n * Optional dedicated receipt signing key (issue #84, rfc-0039 stream\n * receipts; rolling-swap spec §7.2). 32-byte secp256k1 secret key used to\n * BIP-340-sign the per-fulfill {@link StreamReceipt}. When OMITTED,\n * receipts are signed with `recipientSecretKey` — the maker's Nostr\n * identity key — so senders verify against the `swapPubkey` they already\n * discovered via kind:10032. Provide a separate key when the receipt\n * signer should be provisioned independently of the identity key (e.g.\n * the swap#47 coupled engine binding receipts to the chain-B claim\n * signer); senders then verify via `StreamSwapParams.receiptPubkey`.\n */\n receiptSecretKey?: Uint8Array;\n /**\n * Optional receipt session store (issue #84). Tracks per-`streamNonce`\n * `{seq, cumulativeDelivered}` so receipt totals are monotone within a\n * session. Defaults to a {@link BoundedReceiptSessions} in-memory LRU.\n * Operators that persist claims should back this with the same storage so\n * receipt state survives restarts alongside the claim stream (a lost\n * session restarts at seq 1, which senders reject as a forked chain).\n */\n receiptSessions?: ReceiptSessionStoreLike;\n /** Optional pino-compatible logger. Defaults to a no-op logger. */\n logger?: SwapHandlerLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Story 12.8 AC-14 — bounded seenPacketIds default\n// ---------------------------------------------------------------------------\n\n/**\n * Default ceiling for the `seenPacketIds` replay-protection set when the\n * operator does not supply a custom `Set`. Rationale: 10_000 packet-ids at\n * ~64 bytes each yields a ~640KB memory ceiling — high enough to absorb\n * legitimate bursts, low enough to bound DoS via distinct-id flooding.\n *\n * When the default bounded set is in use, eviction is LAST-ACCESS order\n * (LRU), NOT insertion order: a replay attacker who retries the same\n * packet-id forever would, under insertion-order LRU, have their entry\n * evicted after 10_000 new ids pass through — re-opening the replay\n * window. Access-order keeps frequently-replayed ids pinned, so the\n * window stays closed.\n *\n * Operators may override by supplying a custom `Set<string>` (e.g. a\n * persistent backing store). `createSwapHandler` uses the supplied set\n * verbatim without size-capping.\n */\nexport const DEFAULT_SEEN_PACKET_IDS_CAP = 10_000;\n\n/**\n * ILP REJECT codes emitted by `createSwapHandler()`.\n *\n * Exported as named constants so tests (Story 12.8 AC-3 forbids hardcoded\n * error-code strings) and downstream integrators can assert against the\n * same symbolic source the handler uses. Values follow RFC 27 / ILPv4\n * reject-code conventions (F01 = malformed, F02 = unreachable, F04 =\n * duplicate, F06 = unsupported pair, T00 = transient internal, T04 =\n * insufficient liquidity).\n */\nexport const SWAP_HANDLER_REJECT_CODES = {\n /** Malformed / invalid PREPARE content — gift-wrap shape or amount invalid. */\n INVALID_GIFT_WRAP: 'F01',\n /** No route — the handler did not match a registered destination. */\n UNREACHABLE: 'F02',\n /** Duplicate packet — `seenPacketIds` replay hit. */\n DUPLICATE_PACKET: 'F04',\n /** Requested swap pair is not advertised by this Swap. */\n UNSUPPORTED_PAIR: 'F06',\n /** Transient internal failure — signing, rate provider, or unexpected. */\n INTERNAL: 'T00',\n /** Insufficient Swap inventory for the requested amount. */\n INSUFFICIENT_LIQUIDITY: 'T04',\n} as const;\n\n/**\n * Human-readable reject messages emitted by `createSwapHandler()`.\n *\n * Tests may assert against these verbatim rather than matching regexes\n * (Story 12.8 AC-3 guidance).\n */\nexport const SWAP_HANDLER_REJECT_MESSAGES = {\n INVALID_GIFT_WRAP: 'Invalid gift wrap',\n INVALID_AMOUNT: 'Invalid amount',\n UNREACHABLE: 'Unreachable',\n DUPLICATE_PACKET: 'Duplicate packet',\n UNSUPPORTED_PAIR: 'Unsupported swap pair',\n INTERNAL: 'Internal error',\n INSUFFICIENT_LIQUIDITY: 'Insufficient liquidity',\n RATE_PROVIDER: 'Rate provider error',\n RATE_CONVERSION: 'Rate conversion error',\n} as const;\n\n/**\n * LRU-ish `Set<string>`: re-adding an existing element promotes it to\n * \"most recently accessed\"; `has()` also promotes. Eviction occurs on\n * `add()` when size exceeds the cap — the least-recently-accessed entry\n * (Map insertion-order head) is removed.\n *\n * Exported as `@internal` for Story 12.8 AC-10 test introspection.\n *\n * @internal\n */\nexport class BoundedSeenPacketIds {\n readonly #map = new Map<string, true>();\n readonly #cap: number;\n readonly [Symbol.toStringTag] = 'Set';\n\n constructor(cap: number = DEFAULT_SEEN_PACKET_IDS_CAP) {\n if (!Number.isInteger(cap) || cap <= 0) {\n throw new Error(\n `BoundedSeenPacketIds cap must be a positive integer, got ${cap}`\n );\n }\n this.#cap = cap;\n }\n\n get size(): number {\n return this.#map.size;\n }\n\n /** Exposed for AC-10 test introspection. */\n get cap(): number {\n return this.#cap;\n }\n\n has(value: string): boolean {\n if (!this.#map.has(value)) return false;\n // Access-order promotion: re-insert so iteration order puts this at tail.\n this.#map.delete(value);\n this.#map.set(value, true);\n return true;\n }\n\n add(value: string): this {\n if (this.#map.has(value)) {\n // Promote to most-recently-accessed.\n this.#map.delete(value);\n this.#map.set(value, true);\n return this;\n }\n this.#map.set(value, true);\n while (this.#map.size > this.#cap) {\n // Evict least-recently-accessed (Map iteration is insertion order;\n // first key is the oldest that hasn't been re-added).\n const first = this.#map.keys().next();\n if (first.done) break;\n this.#map.delete(first.value);\n }\n return this;\n }\n\n delete(value: string): boolean {\n return this.#map.delete(value);\n }\n\n clear(): void {\n this.#map.clear();\n }\n\n forEach(\n callback: (value: string, value2: string, set: Set<string>) => void,\n thisArg?: unknown\n ): void {\n for (const k of this.#map.keys()) {\n callback.call(thisArg, k, k, this);\n }\n }\n\n *[Symbol.iterator](): IterableIterator<string> {\n yield* this.#map.keys();\n }\n\n *keys(): IterableIterator<string> {\n yield* this.#map.keys();\n }\n\n *values(): IterableIterator<string> {\n yield* this.#map.keys();\n }\n\n *entries(): IterableIterator<[string, string]> {\n for (const k of this.#map.keys()) yield [k, k];\n }\n}\n\n// ---------------------------------------------------------------------------\n// applyRate helper (AC-8)\n// ---------------------------------------------------------------------------\n\nconst RATE_REGEX = /^(0|[1-9]\\d*)(\\.\\d+)?$/;\n\n/**\n * Apply a decimal-string exchange rate to a source amount across asset scales.\n * Uses BigInt arithmetic throughout — never coerces to `Number` — to preserve\n * 18-decimal EVM precision (Epic 11 retro MAX_SAFE_INTEGER guard).\n *\n * Rounds toward zero (integer division), which economically favors the Swap\n * (standard market-maker convention).\n *\n * @throws {SwapHandlerError} If rate format is invalid, rate is zero, or\n * sourceAmount is not positive.\n */\nexport function applyRate(params: ApplyRateParams): bigint {\n const { sourceAmount, fromScale, toScale, rate } = params;\n\n if (!RATE_REGEX.test(rate)) {\n throw new SwapHandlerError(`Invalid rate format: ${rate}`);\n }\n // Reject any zero-valued rate regardless of decimal presentation.\n // RATE_REGEX matches `'0'`, `'0.0'`, `'0.00'`, etc. — all semantically\n // \"not quoting\". Previous check only caught the bare `'0'` form, letting\n // `'0.0'` slip through and produce a zero-valued targetAmount that the\n // sender's rate-deviation guard could not catch (expectedTargetAmount=0n\n // skips the deviation math). (Story 12.5 code-review pass #3.)\n if (/^0(\\.0+)?$/.test(rate)) {\n throw new SwapHandlerError('Rate is zero (pair not quoting)');\n }\n if (sourceAmount <= 0n) {\n throw new SwapHandlerError(\n `sourceAmount must be positive, got ${sourceAmount}`\n );\n }\n\n const dotIdx = rate.indexOf('.');\n const integerPart = dotIdx === -1 ? rate : rate.slice(0, dotIdx);\n const fractionalPart = dotIdx === -1 ? '' : rate.slice(dotIdx + 1);\n\n const rateNumerator = BigInt(integerPart + fractionalPart);\n const rateDenominator = 10n ** BigInt(fractionalPart.length);\n\n const scaleUp = 10n ** BigInt(toScale);\n const scaleDown = 10n ** BigInt(fromScale);\n\n return (\n (sourceAmount * rateNumerator * scaleUp) / (rateDenominator * scaleDown)\n );\n}\n\n// ---------------------------------------------------------------------------\n// findSwapPair helper (AC-7)\n// ---------------------------------------------------------------------------\n\n/**\n * Find the `SwapPair` identified by the rumor's `swap-from` / `swap-to` tags.\n *\n * Each tag value is parsed as `<assetCode>:<chain>`, split on the FIRST `:`\n * so multi-segment chain IDs like `evm:base:8453` remain intact as the chain\n * portion. Returns `null` for any malformed/missing tag — the handler\n * interprets `null` as \"unsupported pair\" and rejects via ILP F06.\n */\nexport function findSwapPair(\n rumor: UnsignedEvent,\n pairs: SwapPair[]\n): SwapPair | null {\n const fromTag = findTagValue(rumor, 'swap-from');\n const toTag = findTagValue(rumor, 'swap-to');\n\n if (!fromTag || !toTag) return null;\n\n const fromParts = splitAssetChain(fromTag);\n const toParts = splitAssetChain(toTag);\n if (!fromParts || !toParts) return null;\n\n for (const pair of pairs) {\n if (\n pair.from.assetCode === fromParts.assetCode &&\n pair.from.chain === fromParts.chain &&\n pair.to.assetCode === toParts.assetCode &&\n pair.to.chain === toParts.chain\n ) {\n return pair;\n }\n }\n return null;\n}\n\nfunction findTagValue(\n rumor: UnsignedEvent,\n tagName: string\n): string | undefined {\n if (!Array.isArray(rumor.tags)) return undefined;\n for (const t of rumor.tags) {\n if (Array.isArray(t) && t[0] === tagName && typeof t[1] === 'string') {\n return t[1];\n }\n }\n return undefined;\n}\n\nfunction splitAssetChain(\n raw: string\n): { assetCode: string; chain: string } | null {\n const idx = raw.indexOf(':');\n if (idx <= 0 || idx === raw.length - 1) return null;\n const assetCode = raw.slice(0, idx);\n const chain = raw.slice(idx + 1);\n if (!assetCode || !chain) return null;\n return { assetCode, chain };\n}\n\n// ---------------------------------------------------------------------------\n// Story 12.9 AC-2 / AC-8 — chain-recipient format validation\n// ---------------------------------------------------------------------------\n//\n// Duplicated (intentionally small) from `stream-swap.ts` to avoid a circular\n// module cycle (`stream-swap` imports `applyRate` from this file). Guardrail\n// 8.5 sanctions local duplication rather than introducing a shared helper\n// package. Rules MUST match the sender-side validator byte-for-byte.\n\nconst SWAP_HANDLER_EVM_ADDRESS_REGEX = /^0x[0-9a-f]{40}$/;\nconst SWAP_HANDLER_BASE58_REGEX = /^[1-9A-HJ-NP-Za-km-z]+$/;\n\n/**\n * Validate a chain-recipient address against `chain`. MUST remain in sync\n * with `validateChainAddress(value, chain, 'address')` in `stream-swap.ts`.\n */\nexport function validateChainRecipient(value: string, chain: string): boolean {\n if (typeof value !== 'string' || value.length === 0) return false;\n if (chain.startsWith('evm:')) {\n return SWAP_HANDLER_EVM_ADDRESS_REGEX.test(value);\n }\n if (chain.startsWith('solana:')) {\n if (!SWAP_HANDLER_BASE58_REGEX.test(value)) return false;\n if (value.length < 32 || value.length > 44) return false;\n try {\n return base58Decode(value).length === 32;\n } catch {\n return false;\n }\n }\n if (chain.startsWith('mina:')) {\n return SWAP_HANDLER_BASE58_REGEX.test(value) && value.length >= 32;\n }\n return value.length > 0;\n}\n\n/**\n * Extract the sender-supplied chain-recipient address from the rumor's\n * `chain-recipient` tag (Story 12.9 AC-8). Returns `null` if the tag is\n * missing or malformed for `chain`.\n */\nexport function findChainRecipient(\n rumor: UnsignedEvent,\n chain: string\n): string | null {\n const raw = findTagValue(rumor, 'chain-recipient');\n if (!raw) return null;\n if (!validateChainRecipient(raw, chain)) return null;\n return raw;\n}\n\n// ---------------------------------------------------------------------------\n// No-op logger\n// ---------------------------------------------------------------------------\n\nconst noop = (): void => undefined;\nconst NOOP_LOGGER: SwapHandlerLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n};\n\n// ---------------------------------------------------------------------------\n// createSwapHandler factory (AC-3..AC-12)\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a kind:1059 Swap inbound-swap handler.\n *\n * The returned `Handler` is a pure closure over `config`; two calls with the\n * same config yield two independent-but-equivalent handlers. Register via\n * `node.handlers.on(1059, handler)` (Story 12.7).\n *\n * @throws {SwapHandlerError} At construction time if config is malformed.\n */\nexport function createSwapHandler(config: CreateSwapHandlerConfig): Handler {\n // Construction-time validation (pre-empt Story 12.2 retro finding #1).\n if (\n !(config.recipientSecretKey instanceof Uint8Array) ||\n config.recipientSecretKey.length !== 32\n ) {\n throw new SwapHandlerError(\n 'recipientSecretKey must be a 32-byte Uint8Array'\n );\n }\n if (!Array.isArray(config.swapPairs)) {\n throw new SwapHandlerError('swapPairs must be an array');\n }\n if (\n !config.claimIssuer ||\n typeof config.claimIssuer.issueClaim !== 'function'\n ) {\n throw new SwapHandlerError(\n 'claimIssuer must implement issueClaim(params): Promise<IssueClaimResult>'\n );\n }\n\n if (\n config.receiptSecretKey !== undefined &&\n (!(config.receiptSecretKey instanceof Uint8Array) ||\n config.receiptSecretKey.length !== 32)\n ) {\n throw new SwapHandlerError('receiptSecretKey must be a 32-byte Uint8Array');\n }\n\n const logger = config.logger ?? NOOP_LOGGER;\n\n // Issue #84: receipt signing key + per-streamNonce session state. Default\n // signer is the maker identity key (verifiable against `swapPubkey`).\n const receiptSecretKey = config.receiptSecretKey ?? config.recipientSecretKey;\n const receiptSessions: ReceiptSessionStoreLike =\n config.receiptSessions ?? new BoundedReceiptSessions();\n\n // Story 12.8 AC-14: when the operator does not supply a custom set,\n // default to a bounded access-order-LRU set. Operator-supplied sets are\n // used verbatim (we do not second-guess the operator's choice of\n // persistence / bounding).\n const seenPacketIds: SeenPacketIdsLike =\n config.seenPacketIds ?? new BoundedSeenPacketIds();\n\n return async (ctx) => {\n // AC-4: defensive kind guard. HandlerRegistry.dispatch already routes by\n // kind, but a mis-registered handler should fail loudly rather than\n // silently mutate unrelated traffic.\n if (ctx.kind !== 1059) {\n // Generic reject message -- do not leak handler role to the caller.\n // A swap handler registered for non-1059 traffic is a mis-configuration;\n // the caller doesn't need to know which handler fielded the packet.\n return ctx.reject('F02', 'Unreachable');\n }\n\n // Defense-in-depth: reject non-positive amounts eagerly with a dedicated\n // code so the sender gets an unambiguous error (otherwise applyRate would\n // throw and surface as the generic T00 \"Rate conversion error\"). ILP\n // connectors already enforce amount > 0, but we double-check at the\n // protocol boundary.\n if (typeof ctx.amount !== 'bigint' || ctx.amount <= 0n) {\n logger.warn({\n event: 'swap_handler.invalid_amount',\n destination: ctx.destination,\n });\n return ctx.reject('F01', 'Invalid amount');\n }\n\n // AC-4 / AC-5 / AC-6: decode and unwrap. `ctx.toon` is the base64 string\n // lifted verbatim from `ilpPrepare.data` (which `buildIlpPrepare` produces\n // by base64-encoding the raw TOON binary). Single decode -> TOON bytes.\n //\n // NOTE: `ctx.pubkey` is the OUTER ephemeral gift-wrap pubkey, NOT the real\n // sender. The real sender comes from the seal inside\n // unwrapSwapPacketFromToon. Do not use `ctx.pubkey` for sender identity.\n if (typeof ctx.toon !== 'string' || ctx.toon.length === 0) {\n logger.warn({\n event: 'swap_handler.invalid_toon',\n destination: ctx.destination,\n });\n return ctx.reject('F01', 'Invalid gift wrap');\n }\n let rumor: UnsignedEvent;\n let senderPubkey: string;\n try {\n const toonData = new Uint8Array(Buffer.from(ctx.toon, 'base64'));\n ({ rumor, senderPubkey } = unwrapSwapPacketFromToon({\n toonData,\n recipientSecretKey: config.recipientSecretKey,\n }));\n } catch (err) {\n if (err instanceof GiftWrapError) {\n logger.warn({\n event: 'swap_handler.unwrap_failed',\n destination: ctx.destination,\n error: err.message,\n });\n return ctx.reject('F01', 'Invalid gift wrap');\n }\n logger.error({\n event: 'swap_handler.unwrap_unexpected_error',\n destination: ctx.destination,\n error: err instanceof Error ? err.message : String(err),\n });\n return ctx.reject('F01', 'Invalid gift wrap');\n }\n\n // AC-7: pair lookup\n const pair = findSwapPair(rumor, config.swapPairs);\n if (!pair) {\n logger.debug({\n event: 'swap_handler.unsupported_pair',\n destination: ctx.destination,\n });\n return ctx.reject('F06', 'Unsupported swap pair');\n }\n\n // Story 12.9 AC-1 / AC-8: extract and validate the `chain-recipient`\n // tag from the inner rumor. Missing or malformed values are treated as\n // a malformed rumor (T00, consistent with other opaque-internal-error\n // paths) — the sender may retry with a corrected address.\n const chainRecipient = findChainRecipient(rumor, pair.to.chain);\n if (!chainRecipient) {\n logger.debug({\n event: 'swap_handler.malformed_rumor',\n destination: ctx.destination,\n reason: 'missing_or_malformed_chain_recipient',\n chain: pair.to.chain,\n });\n return ctx.reject('T00', 'Internal error');\n }\n\n // AC-11: replay protection check. We RESERVE the packetId synchronously\n // here (before the first `await`) so that two concurrent invocations with\n // an identical packet ID cannot both pass the `has()` gate. Because the\n // JS event loop is cooperative, the check-and-add pair is atomic relative\n // to other microtasks as long as it straddles no `await`. If issuance or\n // encryption later fails, we release the reservation so the sender can\n // legitimately retry (AC-11 requires retries of rejected packets).\n // Story 12.8 AC-14: replay check always runs (against the bounded default\n // when the operator did not supply a custom set).\n const packetId: string = computePacketId(senderPubkey, ctx.amount, rumor);\n if (seenPacketIds.has(packetId)) {\n logger.debug({\n event: 'swap_handler.duplicate_packet',\n packetId,\n });\n return ctx.reject('F04', 'Duplicate packet');\n }\n // Reserve eagerly to close the concurrent check-then-add race.\n seenPacketIds.add(packetId);\n\n // Helper: release the replay reservation on failure so the sender can retry.\n const releaseReservation = (): void => {\n seenPacketIds.delete(packetId);\n };\n\n // AC-9 rate resolution (optional live hook per D12-006).\n //\n // Issue #82 (rolling-swap quote tape): the resolved rate `R_i` and its\n // quote timestamp are captured here and emitted on the accept-metadata.\n // A provider may return the legacy string shape (timestamp = resolution\n // time) or a `RateQuote` carrying its rate source's own tick time.\n let rate: string;\n let rateTimestamp: number;\n try {\n const provided = config.rateProvider\n ? await config.rateProvider(pair)\n : pair.rate;\n if (typeof provided === 'string') {\n rate = provided;\n rateTimestamp = Date.now();\n } else if (\n provided !== null &&\n typeof provided === 'object' &&\n typeof provided.rate === 'string' &&\n typeof provided.rateTimestamp === 'number' &&\n Number.isInteger(provided.rateTimestamp) &&\n provided.rateTimestamp > 0\n ) {\n rate = provided.rate;\n rateTimestamp = provided.rateTimestamp;\n } else {\n throw new SwapHandlerError(\n 'rateProvider must return a decimal-string rate or { rate, rateTimestamp }'\n );\n }\n } catch (err) {\n logger.error({\n event: 'swap_handler.rate_provider_failed',\n error: err instanceof Error ? err.message : String(err),\n });\n releaseReservation();\n return ctx.reject('T00', 'Rate provider error');\n }\n\n // AC-8: apply rate (BigInt throughout).\n let targetAmount: bigint;\n try {\n targetAmount = applyRate({\n sourceAmount: ctx.amount,\n fromScale: pair.from.assetScale,\n toScale: pair.to.assetScale,\n rate,\n });\n logger.debug({\n event: 'swap_handler.rate_applied',\n sourceAmount: ctx.amount.toString(),\n targetAmount: targetAmount.toString(),\n rate,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn({\n event: 'swap_handler.rate_conversion_failed',\n error: msg,\n });\n // Do NOT surface the internal rate string / validation detail to the\n // sender -- return a generic privacy-preserving message.\n releaseReservation();\n return ctx.reject('T00', 'Rate conversion error');\n }\n\n // AC-9: delegate to claim issuer\n let claim: Uint8Array;\n let claimId: string | undefined;\n let settlementChannelId: string | undefined;\n let settlementNonce: bigint | undefined;\n let settlementCumulative: bigint | undefined;\n let settlementRecipient: string | undefined;\n let settlementSwapSigner: string | undefined;\n try {\n const result = await config.claimIssuer.issueClaim({\n sourceAmount: ctx.amount,\n targetAmount,\n pair,\n senderPubkey,\n chainRecipient,\n rumor,\n });\n claim = result.claim;\n claimId = result.claimId;\n settlementChannelId = result.channelId;\n settlementNonce = result.nonce;\n settlementCumulative = result.cumulativeAmount;\n settlementRecipient = result.recipient;\n settlementSwapSigner = result.swapSignerAddress;\n } catch (err) {\n const code = (err as { code?: unknown })?.code;\n const message = err instanceof Error ? err.message : String(err);\n if (code === 'INSUFFICIENT_INVENTORY' || /insufficient/i.test(message)) {\n logger.warn({\n event: 'swap_handler.insufficient_inventory',\n error: message,\n });\n releaseReservation();\n return ctx.reject('T04', 'Insufficient liquidity');\n }\n logger.error({\n event: 'swap_handler.issuer_failed',\n error: message,\n });\n releaseReservation();\n return ctx.reject('T00', 'Internal error');\n }\n\n // AC-10: NIP-44 encrypt the claim (Story 12.2 handles ephemeral-key zeroing).\n let ciphertext: Uint8Array;\n let ephemeralPubkey: string;\n try {\n const enc = encryptFulfillClaim({ claimData: claim, senderPubkey });\n ciphertext = enc.ciphertext;\n ephemeralPubkey = enc.ephemeralPubkey;\n } catch (err) {\n logger.error({\n event: 'swap_handler.encrypt_failed',\n error: err instanceof Error ? err.message : String(err),\n });\n releaseReservation();\n return ctx.reject('T00', 'Internal error');\n }\n\n // AC-11: packetId was reserved pre-issuance to close the concurrent\n // check-then-add race; it remains committed on this success path.\n\n // Issue #84 (rfc-0039 stream receipts, spec §7.2): when the sender\n // advertised a session via the rumor's `stream-nonce` tag, issue the\n // per-fulfill signed receipt. This runs strictly on the ACCEPT path —\n // every reject above returns before this point, so a rejected packet\n // never advances the session (no receipt, no seq, no cumulative).\n // The read-increment-write inside issueSessionReceipt is synchronous,\n // so concurrent packets in one session get gapless distinct seqs.\n // Legacy senders (no tag) get the pre-#84 metadata shape verbatim.\n let receipt: StreamReceipt | undefined;\n const streamNonceTag = findTagValue(rumor, 'stream-nonce');\n if (streamNonceTag !== undefined) {\n if (!isValidStreamNonce(streamNonceTag)) {\n logger.warn({\n event: 'swap_handler.invalid_stream_nonce',\n destination: ctx.destination,\n });\n } else {\n try {\n receipt = issueSessionReceipt({\n sessions: receiptSessions,\n streamNonce: streamNonceTag,\n deliveredAmount: targetAmount,\n rate,\n rateTimestamp,\n secretKey: receiptSecretKey,\n });\n } catch (err) {\n // Value is already committed (claim issued + encrypted): accept\n // WITHOUT a receipt rather than failing the packet. A sender that\n // requires receipts will surface this loudly on its side.\n logger.error({\n event: 'swap_handler.receipt_issue_failed',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n const claimBase64 = Buffer.from(ciphertext).toString('base64');\n logger.info({\n event: 'swap_handler.claim_issued',\n claimId,\n ephemeralPubkey,\n });\n\n // Story 12.5: emit the Swap-computed `targetAmount` (decimal string) so\n // the sender can run an end-to-end rate-deviation check without having to\n // parse the opaque chain-specific `claimBytes`. This is additive and\n // backward compatible — legacy senders that ignore the field get the\n // same {claim, ephemeralPubkey, claimId?} shape they always did.\n // Issue #82 (rolling-swap quote tape, spec §7.1): every FULFILL carries\n // the rate `R_i` actually applied to this packet plus its quote\n // timestamp. Additive and backward compatible — legacy senders ignore\n // the extra fields; rolling senders read the sequence\n // `(R_1, t_1), (R_2, t_2), …` as the price tape.\n const metadata: Record<string, unknown> = {\n claim: claimBase64,\n ephemeralPubkey,\n targetAmount: targetAmount.toString(),\n rate,\n rateTimestamp,\n };\n if (claimId !== undefined) metadata['claimId'] = claimId;\n // Issue #84: per-fulfill signed receipt (additive; only present when the\n // sender advertised a `stream-nonce` and signing succeeded).\n if (receipt !== undefined) metadata['receipt'] = receipt;\n // Story 12.6: thread settlement-context fields through when the claim\n // issuer emits them. All-or-nothing: a Swap that supplies settlement\n // fields MUST supply all five, since the sender's FULFILL decoder\n // enforces all-present-or-all-absent.\n if (\n settlementChannelId !== undefined &&\n settlementNonce !== undefined &&\n settlementCumulative !== undefined &&\n settlementRecipient !== undefined &&\n settlementSwapSigner !== undefined\n ) {\n metadata['channelId'] = settlementChannelId;\n metadata['nonce'] = settlementNonce.toString();\n metadata['cumulativeAmount'] = settlementCumulative.toString();\n metadata['recipient'] = settlementRecipient;\n metadata['swapSignerAddress'] = settlementSwapSigner;\n }\n\n return ctx.accept(metadata);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Replay packet ID hash\n// ---------------------------------------------------------------------------\n\n/**\n * Compute a deterministic packet ID for replay protection.\n *\n * Uses explicit length-prefix delimiters between the three inputs so that\n * `('ab','c12','3')` and `('abc','12','3')` produce distinct digests. Without\n * delimiters, variable-width string concatenation is ambiguous under hashing.\n */\nfunction computePacketId(\n senderPubkey: string,\n sourceAmount: bigint,\n rumor: UnsignedEvent\n): string {\n const rumorId = (rumor as UnsignedEvent & { id?: string }).id ?? '';\n const hash = createHash('sha256');\n const parts = [senderPubkey, sourceAmount.toString(), rumorId];\n for (const p of parts) {\n // 4-byte big-endian length prefix followed by UTF-8 bytes.\n const buf = Buffer.from(p, 'utf8');\n const lenBuf = Buffer.alloc(4);\n lenBuf.writeUInt32BE(buf.length, 0);\n hash.update(lenBuf);\n hash.update(buf);\n }\n return hash.digest('hex');\n}\n","/**\n * Sender-side `streamSwap()` API (Story 12.5).\n *\n * Drives the sender side of the Token Swap Primitive: chunks a total source\n * amount into N sender-chosen packets, wraps each with `wrapSwapPacketToToon()`\n * using a fresh ephemeral gift-wrap key per packet (D12-003 / risk R-006),\n * sends each via `ToonClient.sendSwapPacket(...)`, decrypts each FULFILL's\n * NIP-44-encrypted claim with `decryptFulfillClaim()`, accumulates the\n * decrypted claims into an ordered array, invokes a per-packet rate monitoring\n * callback, and supports pause / resume / stop / AbortSignal so the sender\n * can abort when rate drifts past tolerance.\n *\n * Composition story: consumes Stories 12.1 (SwapPair type), 12.2 (gift wrap\n * primitives), 12.3 (handler wire contract). Produces `AccumulatedClaim[]`\n * consumed by Story 12.6 (`buildSettlementTx()`).\n *\n * Design decisions:\n * - `streamSwap()` does NOT throw on mid-stream failure. Caller gets\n * `StreamSwapResult` with `state` and `abortReason`. Only construction-time\n * validation throws synchronously.\n * - `streamSwap()` does NOT retry individual packets. BTP-level connection\n * retries are handled inside `BtpRuntimeClient`; application-layer retry\n * (e.g., T04 insufficient inventory) is a future-story concern.\n * - Rate deviation math stays BigInt throughout. The `effectiveRate` on\n * `PacketProgress` is a display-only `number` (Epic 11 retro MAX_SAFE_INTEGER\n * guard).\n *\n * @module\n */\n\nimport { getPublicKey } from 'nostr-tools/pure';\nimport type { UnsignedEvent } from 'nostr-tools/pure';\nimport type { SwapPair } from '@toon-protocol/core';\n\nimport { StreamSwapError } from './errors.js';\nimport { wrapSwapPacketToToon, decryptFulfillClaim } from './gift-wrap.js';\nimport { base58Decode } from './identity.js';\nimport { applyRate } from './swap-handler.js';\nimport {\n parseStreamReceipt,\n ReceiptChainTracker,\n type StreamReceipt,\n type StreamReceiptChain,\n} from './stream-receipts.js';\nimport type {\n PacketObservation,\n PacketResolution,\n StreamSwapAdaptiveController,\n} from './adaptive-controller.js';\n\n// ---------------------------------------------------------------------------\n// Minimal structural shapes (avoid cross-package type imports)\n// ---------------------------------------------------------------------------\n\n/** Minimal `IlpSendResult` shape — mirrors `packages/client/src/types.ts`. */\ninterface IlpSendResultLike {\n accepted: boolean;\n data?: string;\n code?: string;\n message?: string;\n}\n\n/**\n * Minimal `SignedBalanceProof` structural type.\n *\n * We avoid importing from `@toon-protocol/client` so the SDK package does not\n * gain a runtime dep on the client package. The sender layer only forwards\n * this opaquely to `ToonClient.sendSwapPacket`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- opaque forwarded type\ntype SignedBalanceProofLike = any;\n\n/**\n * The subset of `ToonClient` that `streamSwap()` requires. This narrow shape\n * exists so that consumers can pass a real `ToonClient` OR a mock without\n * importing the whole class.\n */\nexport interface StreamSwapClient {\n sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n timeout?: number;\n claim?: SignedBalanceProofLike;\n /**\n * Explicit per-packet PREPARE expiry. When set, the transport MUST use\n * exactly this expiry on the wire instead of deriving one from the\n * request timeout. Optional so existing `ToonClient` implementations\n * remain structurally compatible (they ignore the extra field until\n * the client-transport work lands).\n */\n expiresAt?: Date;\n }): Promise<IlpSendResultLike>;\n getPublicKey(): string;\n}\n\n// ---------------------------------------------------------------------------\n// Public interfaces (AC-2, AC-7, AC-8, AC-9, AC-10)\n// ---------------------------------------------------------------------------\n\n/**\n * Parameters for {@link streamSwap} / {@link streamSwapControlled}.\n *\n * @stable — downstream Stories 12.6 and 12.8 depend on this shape.\n */\nexport interface StreamSwapParams {\n /** SDK client with BTP wiring (see {@link StreamSwapClient}). */\n client: StreamSwapClient;\n /** Swap's 64-char lowercase hex pubkey (recipient of gift wrap). */\n swapPubkey: string;\n /** Swap's ILP destination address (e.g., 'g.toon.swap1'). */\n swapIlpAddress: string;\n /** The `SwapPair` being executed (from kind:10032 discovery, Story 12.1). */\n pair: SwapPair;\n /** Sender's 32-byte secp256k1 secret key. Used for seal signing AND FULFILL decryption. */\n senderSecretKey: Uint8Array;\n /**\n * Sender's chain-specific payout address for `pair.to.chain` (Story 12.9 AC-4).\n *\n * REQUIRED. The Nostr `senderPubkey` (identity layer) cannot be used as the\n * on-chain `recipient` in a balance-proof because they are cryptographically\n * independent keys (D12-011). For `evm:*` chains this must be a 20-byte\n * lowercased `0x`-prefixed hex address; for `solana:*` a base58 pubkey that\n * decodes to 32 bytes; for `mina:*` a base58 public-key string. The value\n * is validated at `streamSwapControlled()` entry via `validateChainAddress`\n * and echoed on every rumor as the `chain-recipient` tag (AC-6).\n */\n chainRecipient: string;\n /** Total source-asset amount to swap (source micro-units). */\n totalAmount: bigint;\n /** Even-split packet count. EXACTLY ONE of this, `packetAmounts`, or `controller` is required. */\n packetCount?: number;\n /** Explicit per-packet amounts. EXACTLY ONE of this, `packetCount`, or `controller` is required. */\n packetAmounts?: readonly bigint[];\n /**\n * Adaptive δ/W controller (issue #83, rolling-swap spec §6). EXACTLY ONE\n * of this, `packetCount`, or `packetAmounts` is required.\n *\n * When set, packetization is DYNAMIC: the static even split is replaced by\n * per-packet sizing from `controller.nextDelta(remaining)` (δ, capped by\n * the measured `ε/(v·τ)` bound), and up to `controller.window` (W) packets\n * are kept in flight concurrently. After every packet resolves, the\n * controller receives a {@link PacketObservation} (realized rate, tape\n * entry, RTT, resolution class) so δ and W adapt across the stream —\n * multiplicative shrink on stale/slip/reject, additive widen on clean\n * streaks.\n *\n * Adaptive-mode contract differences (each N/A to the legacy paths):\n * - `PacketProgress.total` is the number of packets scheduled SO FAR (the\n * final count is unknown upfront).\n * - With `W > 1`, `onPacket` fires in packet COMPLETION order, not strict\n * index order.\n * - On a mid-stream halt (floor breach, stop, abort), already-sent\n * in-flight packets are drained and their claims harvested before the\n * result resolves.\n *\n * INVARIANT: the controller only tightens/loosens δ and W. It is consulted\n * strictly AFTER the `minExchangeRate` floor check and can never relax it.\n */\n controller?: StreamSwapAdaptiveController;\n /** Source-asset balance proof claim. Required unless ChannelManager is wired. */\n claim?: SignedBalanceProofLike;\n /** Rate monitoring callback (fires after each accepted FULFILL). */\n onPacket?: RateMonitorCallback;\n /** Rate deviation threshold (decimal, e.g., 0.02 = 2%). */\n rateDeviationThreshold?: number;\n /**\n * Hard floor on the per-packet exchange rate (issue #82, rfc-0029\n * `minExchangeRate` semantics; rolling-swap spec §5). Decimal string in\n * `SwapPair.rate` format (target whole-units per source whole-unit),\n * strictly positive.\n *\n * When set:\n * - The quote tape (`rate` + `rateTimestamp` on each FULFILL's metadata)\n * becomes REQUIRED: a fulfilled packet whose metadata is missing the\n * tape is a loud per-packet `FULFILL_DECODE_FAILED` error, never a\n * silent drop.\n * - Each fulfilled packet is checked BEFORE its claim is decrypted or\n * accumulated: if the maker's tape rate `R_i` is below the floor, OR\n * the delivered `targetAmount` is below `applyRate(sourceAmount,\n * minExchangeRate)`, the packet is recorded as a rejection with code\n * `BELOW_FLOOR` and the stream halts with `abortReason: 'below-floor'`.\n * A violating packet NEVER accumulates into `claims[]`.\n *\n * This is the safety mechanism, deliberately independent of (and never\n * relaxed by) the soft `rateDeviationThreshold` monitor, the `onPacket`\n * callback, or any future adaptive-controller signal: a calm tape must\n * not be able to talk the sender into a worse worst case.\n *\n * When omitted, behavior is unchanged (legacy back-compat): the tape is\n * optional and only the soft deviation monitor applies.\n */\n minExchangeRate?: string;\n /**\n * Maker receipt verification key (issue #84, rfc-0039 stream receipts;\n * rolling-swap spec §7.2): 64-char lowercase hex x-only pubkey each\n * per-fulfill receipt's BIP-340 signature is verified against. Defaults\n * to `swapPubkey` (the maker identity key — the swap handler's default\n * receipt signer). Set when the maker provisioned a dedicated receipt key\n * (`CreateSwapHandlerConfig.receiptSecretKey`, e.g. the swap#47 coupled\n * engine's chain-B claim signer key).\n */\n receiptPubkey?: string;\n /**\n * When true, every fulfilled packet MUST carry a verifiable receipt: a\n * receipt-less FULFILL is recorded as a `RECEIPT_MISSING` rejection and\n * the stream halts with `abortReason: 'receipt-invalid'` (a maker that\n * doesn't attest deliveries cannot be audited, so keep the exposure at\n * zero). When omitted/false, receipt-less fulfills from legacy makers\n * degrade gracefully: the claim still accumulates, `result.receipts`\n * is simply empty. A receipt that is PRESENT but fails verification\n * (tampered, wrong key, wrong session, non-monotone, duplicate seq) is\n * ALWAYS a loud `RECEIPT_INVALID` rejection + halt, regardless of this\n * flag — the packet's claim is never accumulated.\n */\n requireReceipts?: boolean;\n /** Per-packet timeout in ms. Default 30000. */\n packetTimeoutMs?: number;\n /**\n * Per-packet PREPARE expiry window in ms. When set, each packet is sent\n * with `expiresAt = now + packetExpiryMs` (computed at send time) so a\n * stalled packet expires deterministically and releases its in-flight\n * slot (rolling-swap R7). When omitted, behavior is unchanged: the\n * transport derives the expiry from its timeout (back-compat default).\n */\n packetExpiryMs?: number;\n /** Optional AbortSignal. */\n signal?: AbortSignal;\n /**\n * Optional pino-compatible logger. Defaults to a no-op.\n *\n * Note: `streamSwap` calls each method with a single structured-event object\n * (e.g., `logger.warn({ event: 'stream_swap.packet_rejected', code, ... })`).\n * Pino accepts this form (the object is logged as top-level fields and the\n * message is left empty); other loggers that expect `(msg, meta)` should\n * wrap accordingly.\n */\n logger?: {\n debug: (...a: unknown[]) => void;\n info: (...a: unknown[]) => void;\n warn: (...a: unknown[]) => void;\n error: (...a: unknown[]) => void;\n };\n}\n\n/**\n * Rate monitoring callback. Fires after each successful FULFILL decryption,\n * before the next packet is sent. If the callback throws or rejects, the\n * stream is treated as stopped.\n */\nexport type RateMonitorCallback = (\n progress: PacketProgress\n) => void | Promise<void>;\n\n/**\n * Progress payload delivered to {@link RateMonitorCallback}. Frozen to\n * prevent callback mutation.\n */\nexport interface PacketProgress {\n /** 0-indexed packet number within this streamSwap invocation. */\n index: number;\n /**\n * Total number of packets scheduled. In adaptive mode (`controller` set)\n * packet sizing is dynamic, so this is the number scheduled SO FAR.\n */\n total: number;\n /** Source-asset amount sent for this packet (micro-units). */\n sourceAmount: bigint;\n /** Target-asset claim amount derived for this packet (micro-units). */\n targetAmount: bigint;\n /** Rate advertised on the `SwapPair` at swap start (decimal string). */\n advertisedRate: string;\n /** Effective rate for this packet as JS Number (target / source in whole units). Display-only. */\n effectiveRate: number;\n /** Absolute deviation from advertisedRate as a decimal (e.g., 0.0125 = 1.25%). */\n rateDeviation: number;\n /** Cumulative source sent across accepted packets so far (including this one). */\n cumulativeSource: bigint;\n /** Cumulative target received so far (including this one). */\n cumulativeTarget: bigint;\n /**\n * Quote-tape entry (issue #82, rolling-swap spec §7.1): the maker's fresh\n * rate `R_i` actually applied to THIS packet, as reported on the FULFILL\n * metadata. Present iff the maker emitted the tape. The sequence of these\n * values across packets, in `index` order, IS the price tape the adaptive\n * controller (toon#83) consumes.\n */\n rate?: string;\n /** Unix ms timestamp when the maker's rate source produced {@link rate}. Present iff `rate` is. */\n rateTimestamp?: number;\n /**\n * Verified per-fulfill stream receipt (issue #84, rolling-swap spec §7.2).\n * Present iff the maker emitted a receipt AND it verified (signature,\n * session nonce, monotonicity) — an invalid receipt halts the stream\n * before the callback ever fires for that packet.\n */\n receipt?: StreamReceipt;\n /** Controller state at callback time. */\n state: 'running' | 'paused' | 'stopped';\n}\n\n/**\n * An accumulated claim successfully harvested from a single packet.\n *\n * @stable — Story 12.6 (`buildSettlementTx()`) depends on this shape.\n * Breaking changes require a coordinated migration.\n *\n * Story 12.6 ADDITIVE extension: the settlement-context fields\n * `channelId`, `nonce`, `cumulativeAmount`, `recipient`, and\n * `swapSignerAddress` are marked optional (`?:`) for one story-cycle of\n * backward compat but are REQUIRED in practice: Story 12.6's\n * `buildSettlementTx()` throws `MISSING_SETTLEMENT_METADATA` when any of\n * these are absent.\n */\nexport interface AccumulatedClaim {\n /** 0-indexed position in the swap's packet stream. */\n packetIndex: number;\n /** Source-asset amount sent for this packet (micro-units). */\n sourceAmount: bigint;\n /**\n * Target-asset amount claimed (micro-units).\n *\n * **Source of truth caveat:** This is the expected target amount computed\n * by `applyRate(pair.rate)`. The actual signed-claim amount lives inside\n * `claimBytes`; Story 12.6 is responsible for parsing `claimBytes` per\n * chain and verifying the on-wire signed amount equals this expected amount.\n */\n targetAmount: bigint;\n /** Decrypted signed claim bytes. Chain-specific encoding per Story 12.4. */\n claimBytes: Uint8Array;\n /** Swap's ephemeral pubkey from the FULFILL (64-char lowercase hex). */\n swapEphemeralPubkey: string;\n /** Optional Swap-side claim ID (passed through from handler metadata). */\n claimId?: string;\n /** Swap pair this claim was priced against (copy of `pair` for settlement-time routing). */\n pair: SwapPair;\n /** Unix ms timestamp when this claim was accepted. */\n receivedAt: number;\n // --- Story 12.6 settlement-context fields (additive) ---\n /** Channel identifier on the target chain (lowercase hex with 0x prefix for EVM; base58 for Solana). */\n channelId?: string;\n /** Balance-proof nonce (decimal string). Monotonically increasing within a channel. */\n nonce?: string;\n /** Cumulative transferred amount on the channel (target micro-units, decimal string). */\n cumulativeAmount?: string;\n /** Recipient address (the sender's target-asset address). */\n recipient?: string;\n /** Swap's on-chain signer address. */\n swapSignerAddress?: string;\n // --- Issue #82 quote-tape fields (additive) ---\n /** Maker's fresh rate `R_i` applied to this packet (decimal string), from the FULFILL quote tape. */\n rate?: string;\n /** Unix ms timestamp when the maker's rate source produced `rate`. Present iff `rate` is. */\n rateTimestamp?: number;\n // --- Issue #84 stream-receipt field (additive) ---\n /**\n * The VERIFIED signed receipt that rode on this packet's FULFILL\n * (issue #84, rolling-swap spec §7.2) — receipts persist wherever the\n * claim does. Present iff the maker emitted receipts for this session.\n */\n receipt?: StreamReceipt;\n}\n\n/**\n * Aggregate result of a `streamSwap()` invocation.\n *\n * @stable — Stories 12.6 and 12.8 depend on this shape.\n */\nexport interface StreamSwapResult {\n state: 'completed' | 'failed' | 'stopped';\n claims: AccumulatedClaim[];\n rejections: {\n packetIndex: number;\n sourceAmount: bigint;\n code: string;\n message: string;\n }[];\n errors: { packetIndex: number; cause: Error }[];\n abortReason:\n | 'complete'\n | 'aborted'\n | 'stopped'\n | 'callback-stop'\n | 'callback-throw'\n | 'rate-deviation'\n | 'below-floor'\n | 'receipt-invalid'\n | 'all-rejected';\n cumulativeSource: bigint;\n cumulativeTarget: bigint;\n packetsSent: number;\n packetsScheduled: number;\n /**\n * The verified receipt chain for this stream (issue #84, rolling-swap\n * spec §7.2): every receipt that verified against the maker receipt key,\n * sorted by `seq`, plus the superseding `latest`, the signed\n * `totalDelivered`, and any `seq` holes. ALWAYS present — on abort it\n * covers exactly what filled before the halt; against a legacy maker\n * (no receipt support) it is simply empty. Feed to\n * `serializeReceiptChain()` for the portable audit/dispute artifact.\n */\n receipts: StreamReceiptChain;\n}\n\n/** External controller returned by {@link streamSwapControlled}. */\nexport interface StreamSwapController {\n pause(): void;\n resume(): void;\n stop(): void;\n readonly state: 'running' | 'paused' | 'stopped' | 'completed' | 'failed';\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst RATE_REGEX = /^(0|[1-9]\\d*)(\\.\\d+)?$/;\nconst HEX64_REGEX = /^[0-9a-f]{64}$/;\n// Strict base64: only `=` padding at the end, 0/1/2 pad chars. The character\n// class is intentionally restrictive — previous form `/^[A-Za-z0-9+/=]+$/`\n// accepted `=` anywhere and non-multiple-of-4 lengths, which could funnel\n// malformed payloads into `Buffer.from`/`JSON.parse` and surface confusing\n// errors. (Story 12.5 code-review pass #3.)\nconst BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/;\n\n/** True iff `s` is a non-empty, base64-shaped string of length multiple of 4. */\nfunction isBase64(s: string): boolean {\n if (s.length === 0 || s.length % 4 !== 0) return false;\n return BASE64_REGEX.test(s);\n}\n\n/** Length-split `total` into `count` bigints. Remainder absorbed on last element. */\nfunction chunkAmount(total: bigint, count: number): bigint[] {\n if (\n !Number.isInteger(count) ||\n count <= 0 ||\n count > Number.MAX_SAFE_INTEGER\n ) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `packetCount must be a positive integer, got ${count}`\n );\n }\n if (total < BigInt(count)) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `totalAmount (${total}) must be >= packetCount (${count}) so per-packet amount >= 1`\n );\n }\n const base = total / BigInt(count);\n const remainder = total - base * BigInt(count);\n const out: bigint[] = new Array(count);\n for (let i = 0; i < count; i++) out[i] = base;\n const last = out[count - 1] ?? 0n;\n out[count - 1] = last + remainder;\n return out;\n}\n\n/** Build the kind:20032 unsigned \"swap rumor\" event per AC-4.\n *\n * Story 12.9 AC-1/AC-6: the returned rumor MUST include a `chain-recipient`\n * tag carrying the sender-supplied chain-format payout address for\n * `pair.to.chain`. The value is echoed verbatim per packet — no case-folding\n * or transformation beyond what the sender-side `validateChainAddress`\n * accepts.\n *\n * Issue #84 (rfc-0039 stream receipts): when `streamNonce` is provided, the\n * rumor also carries a `stream-nonce` tag — the sender-generated session\n * identifier a receipt-capable maker echoes on every per-fulfill receipt.\n * This is the TOON analogue of rfc-0039's Verifier→Receiver Receipt-Nonce\n * provisioning (in-band, per stream). Legacy makers ignore the unknown tag.\n */\nfunction buildSwapRumor(input: {\n senderPubkey: string;\n pair: SwapPair;\n sourceAmount: bigint;\n packetIndex: number;\n totalPackets: number;\n nonce: Uint8Array;\n createdAt: number;\n chainRecipient: string;\n /** 32-char lowercase hex session nonce (issue #84 stream receipts). */\n streamNonce?: string;\n}): UnsignedEvent {\n const {\n senderPubkey,\n pair,\n sourceAmount,\n packetIndex,\n totalPackets,\n nonce,\n createdAt,\n chainRecipient,\n streamNonce,\n } = input;\n const tags: string[][] = [\n ['swap-from', `${pair.from.assetCode}:${pair.from.chain}`],\n ['swap-to', `${pair.to.assetCode}:${pair.to.chain}`],\n ['amount', sourceAmount.toString()],\n ['seq', String(packetIndex), String(totalPackets)],\n ['nonce', Buffer.from(nonce).toString('hex')],\n ['chain-recipient', chainRecipient],\n ];\n if (streamNonce !== undefined) tags.push(['stream-nonce', streamNonce]);\n return {\n kind: 20032,\n pubkey: senderPubkey,\n content: '',\n created_at: createdAt,\n tags,\n };\n}\n\nconst EVM_CHANNEL_ID_REGEX = /^0x[0-9a-f]{64}$/;\nconst EVM_ADDRESS_REGEX = /^0x[0-9a-f]{40}$/;\nconst DECIMAL_UINT_REGEX = /^(0|[1-9]\\d*)$/;\n// Permissive base58 charset (Bitcoin alphabet — no 0, O, I, l).\nconst BASE58_REGEX = /^[1-9A-HJ-NP-Za-km-z]+$/;\n\n/**\n * Validate a chain-specific address or channelId string. `chain` is the\n * `pair.to.chain` string (e.g., `'evm:base:8453'`, `'solana:mainnet'`).\n *\n * Returns true for valid formats; false otherwise.\n */\nexport function validateChainAddress(\n value: string,\n chain: string,\n kind: 'channelId' | 'address'\n): boolean {\n if (chain.startsWith('evm:')) {\n // #153: viem / EIP-55 emits checksummed (mixed-case) addresses, and\n // callers commonly pass a checksummed `chainRecipient`. Lowercase-normalize\n // before the strict-lowercase-hex regex so a valid checksummed address (or\n // channelId) is accepted instead of being spuriously rejected with\n // INVALID_CHAIN_RECIPIENT / FULFILL_DECODE_FAILED.\n const normalized = value.toLowerCase();\n if (kind === 'channelId') return EVM_CHANNEL_ID_REGEX.test(normalized);\n return EVM_ADDRESS_REGEX.test(normalized);\n }\n if (chain.startsWith('solana:')) {\n // AC-3: base58 decode MUST succeed AND length MUST be 32 bytes. A pure\n // regex + char-length sanity check is too loose — a malformed \"32-char\"\n // base58 string may decode to 22–24 bytes and slip through.\n if (!BASE58_REGEX.test(value)) return false;\n if (value.length < 32 || value.length > 44) return false;\n try {\n return base58Decode(value).length === 32;\n } catch {\n return false;\n }\n }\n if (chain.startsWith('mina:')) {\n return BASE58_REGEX.test(value) && value.length >= 32;\n }\n // Unknown chain — permit; settlement layer will surface UNSUPPORTED_CHAIN.\n return value.length > 0;\n}\n\n/**\n * Decode the FULFILL response `data` (base64-encoded JSON metadata) into\n * the `{ claim, ephemeralPubkey, claimId?, targetAmount?, ...settlement }`\n * shape.\n *\n * Story 12.6 extension: also parses the settlement-context fields\n * (`channelId`, `nonce`, `cumulativeAmount`, `recipient`, `swapSignerAddress`).\n * These are OPTIONAL and best-effort (#153): each is threaded through only\n * when it is a well-formed string for the target chain; an absent or malformed\n * settlement field is silently dropped rather than failing the whole decode,\n * so a fulfilled swap still surfaces its signed `claim` + `ephemeralPubkey`.\n * Only `claim` and `ephemeralPubkey` are strictly required.\n *\n * Issue #82 extension (quote tape, rolling-swap spec §7.1): parses the\n * per-packet quote-tape fields `rate` (decimal string, the maker's fresh\n * `R_i`) and `rateTimestamp` (unix ms). Unlike the best-effort settlement\n * fields, a MALFORMED tape is always a loud `FULFILL_DECODE_FAILED` — a\n * present-but-garbled `rate`/`rateTimestamp`, or one field without the\n * other, fails the decode rather than silently dropping, so a rolling\n * sender can never run blind on a corrupt tape. A wholly ABSENT tape is\n * permitted for legacy makers unless `opts.requireQuoteTape` is set (which\n * `runLoop` does whenever `minExchangeRate` is armed).\n *\n * Issue #84 extension (rfc-0039 stream receipts, spec §7.2): parses the\n * optional `receipt` object into a structurally-validated {@link StreamReceipt}.\n * Like the tape, a PRESENT-but-malformed receipt is a loud\n * `FULFILL_DECODE_FAILED` (a garbled proof must never be silently dropped —\n * the sender would keep streaming while its audit artifact rots); a wholly\n * absent receipt is legacy-maker territory and tolerated here (the\n * `requireReceipts` policy is enforced by the caller, which has the halt\n * machinery). Signature/monotonicity verification is NOT done here — that is\n * `processAcceptedPacket`'s ReceiptChainTracker.\n *\n * @param chain Optional `pair.to.chain` string for per-chain format validation\n * of channelId / recipient / swapSignerAddress. When omitted, format checks\n * fall back to a length-only sanity check.\n * @param opts.requireQuoteTape When true, a missing `rate`/`rateTimestamp`\n * pair is a `FULFILL_DECODE_FAILED` error instead of being tolerated.\n */\nfunction decodeFulfillMetadata(\n data: string | undefined,\n chain?: string,\n opts?: { requireQuoteTape?: boolean }\n): {\n claim: string;\n ephemeralPubkey: string;\n claimId?: string;\n /** Optional Swap-reported actual target amount (decimal string). Used for rate deviation when present. */\n targetAmount?: string;\n /** Quote-tape rate `R_i` (decimal string). Present iff the maker emitted the tape. */\n rate?: string;\n /** Quote-tape timestamp (unix ms). Present iff `rate` is. */\n rateTimestamp?: number;\n /** Per-fulfill stream receipt (issue #84) — structurally validated, NOT yet signature-verified. */\n receipt?: StreamReceipt;\n channelId?: string;\n nonce?: string;\n cumulativeAmount?: string;\n recipient?: string;\n swapSignerAddress?: string;\n} {\n if (data === undefined || data === null || data === '') {\n throw new StreamSwapError('FULFILL_DECODE_FAILED', 'FULFILL data missing');\n }\n // Quick-fail for obvious non-base64 input. Rejects '@' etc. and strings\n // that aren't a multiple-of-4 length.\n if (!isBase64(data)) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL data is not valid base64'\n );\n }\n let jsonBytes: Buffer;\n try {\n jsonBytes = Buffer.from(data, 'base64');\n } catch (err) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n `FULFILL data base64 decode failed: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonBytes.toString('utf8'));\n } catch (err) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n `FULFILL data JSON parse failed: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata is not an object'\n );\n }\n const obj = parsed as Record<string, unknown>;\n const claim = obj['claim'];\n const ephemeralPubkey = obj['ephemeralPubkey'];\n if (typeof claim !== 'string' || !isBase64(claim)) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata.claim is missing or not base64 string'\n );\n }\n if (\n typeof ephemeralPubkey !== 'string' ||\n !HEX64_REGEX.test(ephemeralPubkey)\n ) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata.ephemeralPubkey is missing or not 64-char hex'\n );\n }\n const result: {\n claim: string;\n ephemeralPubkey: string;\n claimId?: string;\n targetAmount?: string;\n rate?: string;\n rateTimestamp?: number;\n receipt?: StreamReceipt;\n channelId?: string;\n nonce?: string;\n cumulativeAmount?: string;\n recipient?: string;\n swapSignerAddress?: string;\n } = {\n claim,\n ephemeralPubkey,\n };\n if (typeof obj['claimId'] === 'string') {\n result.claimId = obj['claimId'] as string;\n }\n // Swap-reported target amount (optional). MUST be a non-negative integer\n // decimal string — reject signed / fractional / non-numeric values so a\n // malicious or buggy Swap cannot poison `cumulativeTarget`, the deviation\n // calc, or the `AccumulatedClaim.targetAmount` surface that Story 12.6\n // settles against. Presence of the field with a malformed value is a\n // FULFILL_DECODE_FAILED — the sender cannot safely consume the metadata.\n if (obj['targetAmount'] !== undefined) {\n const ta = obj['targetAmount'];\n if (typeof ta !== 'string' || !/^(0|[1-9]\\d*)$/.test(ta)) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata.targetAmount must be a non-negative integer decimal string'\n );\n }\n result.targetAmount = ta;\n }\n // Issue #82 quote tape — `rate` + `rateTimestamp` travel together.\n // Malformed-tape handling is deliberately LOUD (unlike the best-effort\n // settlement fields below): a rolling sender that silently dropped a\n // garbled tape entry would keep streaming blind, starving the floor and\n // the adaptive controller. Absence of BOTH fields is legacy-maker\n // territory and tolerated unless the caller requires the tape.\n const tapeRate = obj['rate'];\n const tapeTimestamp = obj['rateTimestamp'];\n const hasRate = tapeRate !== undefined;\n const hasTimestamp = tapeTimestamp !== undefined;\n if (hasRate !== hasTimestamp) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata quote tape is malformed: rate and rateTimestamp must travel together'\n );\n }\n if (hasRate) {\n if (\n typeof tapeRate !== 'string' ||\n !RATE_REGEX.test(tapeRate) ||\n /^0(\\.0+)?$/.test(tapeRate)\n ) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata.rate must be a positive decimal string'\n );\n }\n if (\n typeof tapeTimestamp !== 'number' ||\n !Number.isInteger(tapeTimestamp) ||\n tapeTimestamp <= 0\n ) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata.rateTimestamp must be a positive integer (unix ms)'\n );\n }\n result.rate = tapeRate;\n result.rateTimestamp = tapeTimestamp;\n } else if (opts?.requireQuoteTape === true) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n 'FULFILL metadata is missing the quote tape (rate + rateTimestamp) required when minExchangeRate is set'\n );\n }\n // Issue #84 stream receipt — structural validation only (loud on garble,\n // tolerant on absence; see the function doc). Signature verification and\n // monotonicity are enforced downstream against the session tracker.\n if (obj['receipt'] !== undefined) {\n try {\n result.receipt = parseStreamReceipt(obj['receipt']);\n } catch (err) {\n throw new StreamSwapError(\n 'FULFILL_DECODE_FAILED',\n `FULFILL metadata.receipt is malformed: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n }\n // Story 12.6 settlement-context fields — OPTIONAL, best-effort (#153).\n //\n // These five fields are only consumed downstream by `buildSettlementTx()`\n // (Story 12.6), which performs on-chain target redemption — a flow that is\n // itself #82-bounded (synthetic devnet channels) and independently validates\n // every field it consumes before signing. They are NOT required to surface\n // the swap's signed claim to the caller.\n //\n // The previous contract was all-or-nothing AND hard-failed the entire FULFILL\n // decode (`FULFILL_DECODE_FAILED`) on any partial/malformed settlement field.\n // That rejected otherwise-valid swap FULFILLs whenever the swap's real\n // envelope carried, e.g., a cross-chain channelId (an EVM-style hex channelId\n // echoed on a `solana:`/`mina:` target) or a checksummed address — so a\n // fulfilled swap reported `state: failed` with an empty `claims[]`.\n //\n // New contract: thread each settlement field through ONLY when it is a\n // well-formed string for the target chain; silently drop any field that is\n // absent or malformed. A swap whose swap omits/garbles settlement metadata\n // still completes with the signed claim; `buildSettlementTx()` will then\n // surface `MISSING_SETTLEMENT_METADATA` at settlement time if the caller\n // actually attempts on-chain redemption with an incomplete bundle.\n //\n // `recipient` is special-cased: it is the anti-substitution security check in\n // `runLoop` (the swap must echo the sender-supplied `chainRecipient`). We thread\n // it through whenever it is a non-empty string — even if it fails the chain\n // format check — so the runLoop equality check still fires. The equality\n // comparison there is the real boundary; a format-only mismatch must not be\n // silently dropped (which would skip the check entirely).\n const channelId = obj['channelId'];\n if (\n typeof channelId === 'string' &&\n (!chain || validateChainAddress(channelId, chain, 'channelId'))\n ) {\n result.channelId = channelId;\n }\n const nonce = obj['nonce'];\n if (typeof nonce === 'string' && DECIMAL_UINT_REGEX.test(nonce)) {\n result.nonce = nonce;\n }\n const cumulativeAmount = obj['cumulativeAmount'];\n if (\n typeof cumulativeAmount === 'string' &&\n DECIMAL_UINT_REGEX.test(cumulativeAmount)\n ) {\n result.cumulativeAmount = cumulativeAmount;\n }\n const recipient = obj['recipient'];\n if (typeof recipient === 'string' && recipient.length > 0) {\n // Always thread a present recipient so the runLoop anti-substitution\n // equality check (`metadata.recipient === params.chainRecipient`) runs.\n result.recipient = recipient;\n }\n const swapSignerAddress = obj['swapSignerAddress'];\n if (\n typeof swapSignerAddress === 'string' &&\n (!chain || validateChainAddress(swapSignerAddress, chain, 'address'))\n ) {\n result.swapSignerAddress = swapSignerAddress;\n }\n return result;\n}\n\n/**\n * Compare two decimal-string rates (RATE_REGEX format) exactly, in BigInt —\n * no float coercion. Returns -1 if `a < b`, 0 if equal, 1 if `a > b`.\n *\n * Used by the issue #82 `minExchangeRate` floor to compare the maker's tape\n * rate `R_i` against the floor without precision loss on long fractions.\n */\nfunction compareDecimalRates(a: string, b: string): -1 | 0 | 1 {\n const split = (s: string): { int: string; frac: string } => {\n const dot = s.indexOf('.');\n return dot === -1\n ? { int: s, frac: '' }\n : { int: s.slice(0, dot), frac: s.slice(dot + 1) };\n };\n const pa = split(a);\n const pb = split(b);\n const scale = Math.max(pa.frac.length, pb.frac.length);\n const av = BigInt(pa.int + pa.frac.padEnd(scale, '0'));\n const bv = BigInt(pb.int + pb.frac.padEnd(scale, '0'));\n return av < bv ? -1 : av > bv ? 1 : 0;\n}\n\n/** Simple Deferred for pause/resume gating. */\nclass Deferred<T> {\n readonly promise: Promise<T>;\n resolve!: (v: T) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mirror Promise constructor\n reject!: (e: any) => void;\n constructor() {\n this.promise = new Promise<T>((res, rej) => {\n this.resolve = res;\n this.reject = rej;\n });\n }\n}\n\nconst noop = (): void => undefined;\nconst NOOP_LOGGER: NonNullable<StreamSwapParams['logger']> = {\n debug: noop,\n info: noop,\n warn: noop,\n error: noop,\n};\n\n// ---------------------------------------------------------------------------\n// Validation (AC-2) — synchronous, fires before any packet\n// ---------------------------------------------------------------------------\n\nfunction validateParams(params: StreamSwapParams): void {\n if (typeof params.totalAmount !== 'bigint' || params.totalAmount <= 0n) {\n throw new StreamSwapError(\n 'INVALID_AMOUNT',\n `totalAmount must be a positive bigint, got ${String(params.totalAmount)}`\n );\n }\n\n const hasCount = params.packetCount !== undefined;\n const hasAmounts = params.packetAmounts !== undefined;\n const hasController = params.controller !== undefined;\n const chunkingModes = [hasCount, hasAmounts, hasController].filter(\n Boolean\n ).length;\n if (chunkingModes !== 1) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n 'Exactly one of packetCount, packetAmounts, or controller must be provided'\n );\n }\n\n if (hasController) {\n const c = params.controller as StreamSwapAdaptiveController;\n if (\n typeof c.nextDelta !== 'function' ||\n typeof c.observe !== 'function' ||\n typeof c.window !== 'number'\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'controller must implement nextDelta(remaining), observe(observation), and window'\n );\n }\n } else if (hasCount) {\n const c = params.packetCount as number;\n if (!Number.isInteger(c) || c <= 0) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `packetCount must be a positive integer, got ${c}`\n );\n }\n if (BigInt(c) > params.totalAmount) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `packetCount (${c}) exceeds totalAmount (${params.totalAmount}); per-packet amount would be < 1 micro-unit`\n );\n }\n } else {\n const arr = params.packetAmounts as readonly bigint[];\n if (!Array.isArray(arr) || arr.length === 0) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n 'packetAmounts must be a non-empty array'\n );\n }\n let sum = 0n;\n for (const a of arr) {\n if (typeof a !== 'bigint' || a <= 0n) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `packetAmounts entries must be positive bigint, got ${String(a)}`\n );\n }\n sum += a;\n }\n if (sum !== params.totalAmount) {\n throw new StreamSwapError(\n 'INVALID_CHUNKING',\n `sum(packetAmounts) (${sum}) !== totalAmount (${params.totalAmount})`\n );\n }\n }\n\n if (\n !(params.senderSecretKey instanceof Uint8Array) ||\n params.senderSecretKey.length !== 32\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'senderSecretKey must be a 32-byte Uint8Array'\n );\n }\n\n if (\n typeof params.swapPubkey !== 'string' ||\n !HEX64_REGEX.test(params.swapPubkey)\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'swapPubkey must be a 64-char lowercase hex string'\n );\n }\n\n if (!params.pair || typeof params.pair !== 'object') {\n throw new StreamSwapError('INVALID_PAIR', 'pair is required');\n }\n // Guard the nested `from` / `to` shape before deep-reading `.assetScale` etc.\n // Otherwise malformed input produces a bare TypeError at `applyRate()` or\n // `buildSwapRumor()` instead of a categorized StreamSwapError.\n if (\n !params.pair.from ||\n typeof params.pair.from !== 'object' ||\n typeof params.pair.from.assetCode !== 'string' ||\n typeof params.pair.from.assetScale !== 'number' ||\n typeof params.pair.from.chain !== 'string'\n ) {\n throw new StreamSwapError(\n 'INVALID_PAIR',\n 'pair.from must have { assetCode: string, assetScale: number, chain: string }'\n );\n }\n if (\n !params.pair.to ||\n typeof params.pair.to !== 'object' ||\n typeof params.pair.to.assetCode !== 'string' ||\n typeof params.pair.to.assetScale !== 'number' ||\n typeof params.pair.to.chain !== 'string'\n ) {\n throw new StreamSwapError(\n 'INVALID_PAIR',\n 'pair.to must have { assetCode: string, assetScale: number, chain: string }'\n );\n }\n if (\n typeof params.pair.rate !== 'string' ||\n !RATE_REGEX.test(params.pair.rate)\n ) {\n throw new StreamSwapError(\n 'INVALID_PAIR',\n `pair.rate must match ${RATE_REGEX}, got ${params.pair.rate}`\n );\n }\n try {\n applyRate({\n sourceAmount: 1n,\n fromScale: params.pair.from.assetScale,\n toScale: params.pair.to.assetScale,\n rate: params.pair.rate,\n });\n } catch (err) {\n throw new StreamSwapError(\n 'INVALID_PAIR',\n `pair failed applyRate sanity check: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n\n if (\n params.rateDeviationThreshold !== undefined &&\n (typeof params.rateDeviationThreshold !== 'number' ||\n !Number.isFinite(params.rateDeviationThreshold) ||\n params.rateDeviationThreshold < 0)\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n `rateDeviationThreshold must be a non-negative finite number, got ${params.rateDeviationThreshold}`\n );\n }\n\n // Issue #82: minExchangeRate is a hard floor — decimal string, strictly\n // positive (a zero floor is \"no floor\"; omit the param instead). The\n // format + non-zero constraints here are exactly applyRate's throw\n // conditions, so the per-packet floor computation cannot throw mid-stream.\n if (params.minExchangeRate !== undefined) {\n if (\n typeof params.minExchangeRate !== 'string' ||\n !RATE_REGEX.test(params.minExchangeRate) ||\n /^0(\\.0+)?$/.test(params.minExchangeRate)\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n `minExchangeRate must be a positive decimal string matching ${RATE_REGEX}, got ${String(\n params.minExchangeRate\n )}`\n );\n }\n }\n\n // Issue #84: receipt verification key — same shape as swapPubkey.\n if (\n params.receiptPubkey !== undefined &&\n (typeof params.receiptPubkey !== 'string' ||\n !HEX64_REGEX.test(params.receiptPubkey))\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'receiptPubkey must be a 64-char lowercase hex string'\n );\n }\n if (\n params.requireReceipts !== undefined &&\n typeof params.requireReceipts !== 'boolean'\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n `requireReceipts must be a boolean, got ${String(params.requireReceipts)}`\n );\n }\n\n if (\n params.packetExpiryMs !== undefined &&\n (typeof params.packetExpiryMs !== 'number' ||\n !Number.isInteger(params.packetExpiryMs) ||\n params.packetExpiryMs <= 0)\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n `packetExpiryMs must be a positive integer (ms), got ${params.packetExpiryMs}`\n );\n }\n\n // Story 12.9 AC-4 / AC-5: `chainRecipient` is REQUIRED and MUST validate\n // against `pair.to.chain`. Defense-in-depth for JS callers who bypass the\n // TS interface (the field is declared non-optional on StreamSwapParams).\n if (\n typeof params.chainRecipient !== 'string' ||\n params.chainRecipient.length === 0\n ) {\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'chainRecipient must be a non-empty string (sender payout address for pair.to.chain)'\n );\n }\n if (\n !validateChainAddress(\n params.chainRecipient,\n params.pair.to.chain,\n 'address'\n )\n ) {\n throw new StreamSwapError(\n 'INVALID_CHAIN_RECIPIENT',\n `chainRecipient ${params.chainRecipient} is malformed for chain ${params.pair.to.chain}`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Core: streamSwapControlled (AC-6, AC-7, AC-10)\n// ---------------------------------------------------------------------------\n\n/**\n * Drive a multi-packet swap against a Swap and return a `StreamSwapResult`.\n *\n * `streamSwap()` does NOT throw on mid-stream failure — inspect the result's\n * `state`, `abortReason`, `rejections[]`, and `errors[]` to diagnose.\n *\n * @example\n * ```ts\n * // Discover the SwapPair from the Swap's kind:10032 peer-info event (Story 12.1).\n * const result = await streamSwap({\n * client: toonClient,\n * swapPubkey: swap.pubkey,\n * swapIlpAddress: 'g.toon.swap1',\n * pair,\n * senderSecretKey,\n * totalAmount: 1_000_000n,\n * packetCount: 10,\n * onPacket: (p) => console.log('packet', p.index, 'rate', p.effectiveRate),\n * rateDeviationThreshold: 0.02,\n * });\n * // Feed result.claims into buildSettlementTx() from Story 12.6.\n * ```\n *\n * @throws {StreamSwapError} Only for construction-time validation failures\n * (INVALID_AMOUNT, INVALID_CHUNKING, INVALID_PAIR, INVALID_STATE).\n */\nexport async function streamSwap(\n params: StreamSwapParams\n): Promise<StreamSwapResult> {\n // Note: validation errors are thrown synchronously inside\n // `streamSwapControlled`. We wrap the call so construction-time throws\n // become Promise rejections for ergonomic `await` / `.rejects` handling.\n return streamSwapControlled(params).result;\n}\n\n/**\n * Two-form variant of {@link streamSwap} that additionally returns a\n * {@link StreamSwapController} with `pause()` / `resume()` / `stop()`.\n *\n * @throws {StreamSwapError} Synchronously for construction-time validation.\n */\nexport function streamSwapControlled(params: StreamSwapParams): {\n result: Promise<StreamSwapResult>;\n controller: StreamSwapController;\n} {\n // Construction-time validation — synchronous throw per AC-2 & AC-9.\n validateParams(params);\n\n const logger = params.logger ?? NOOP_LOGGER;\n\n // Derive schedule. In adaptive mode (`controller` set) there is no static\n // schedule — packet sizing is decided per-packet by the controller.\n const schedule: bigint[] = params.packetAmounts\n ? [...params.packetAmounts]\n : params.packetCount !== undefined\n ? chunkAmount(params.totalAmount, params.packetCount)\n : [];\n\n // Freeze a defensive copy of `pair` so callers can't mutate the stored\n // reference on every AccumulatedClaim post-call. (Story 12.5 code-review\n // pass #3.) Shape matches SwapPair (Story 12.1 stable type).\n const frozenPair: SwapPair = Object.freeze({\n from: Object.freeze({ ...params.pair.from }),\n to: Object.freeze({ ...params.pair.to }),\n rate: params.pair.rate,\n }) as SwapPair;\n\n const senderPubkey = getPublicKey(params.senderSecretKey);\n\n // Issue #84 (rfc-0039 stream receipts): one 16-byte session nonce per\n // streamSwap invocation, advertised on every rumor's `stream-nonce` tag.\n // The sender plays rfc-0039's Verifier role — it generates the nonce and\n // verifies each per-fulfill receipt the maker signs against it.\n const streamNonceBytes = new Uint8Array(16);\n getRandomValues(streamNonceBytes);\n const streamNonce = Buffer.from(streamNonceBytes).toString('hex');\n\n // Controller state machine\n type State = 'running' | 'paused' | 'stopped' | 'completed' | 'failed';\n let streamState: State = 'running';\n let resumeDeferred: Deferred<'resume' | 'stop'> | null = null;\n\n const controller: StreamSwapController = {\n pause(): void {\n if (streamState === 'running') {\n streamState = 'paused';\n // resumeDeferred will be created on first await in loop\n }\n },\n resume(): void {\n if (streamState === 'paused') {\n streamState = 'running';\n if (resumeDeferred) {\n resumeDeferred.resolve('resume');\n resumeDeferred = null;\n }\n } else if (streamState === 'running') {\n // no-op\n } else {\n throw new StreamSwapError(\n 'INVALID_STATE',\n `Cannot resume from state \"${streamState}\"`\n );\n }\n },\n stop(): void {\n if (streamState === 'completed' || streamState === 'failed') return;\n const prev = streamState;\n streamState = 'stopped';\n if (prev === 'paused' && resumeDeferred) {\n resumeDeferred.resolve('stop');\n resumeDeferred = null;\n }\n },\n get state(): State {\n return streamState;\n },\n };\n\n const getState = (): State => streamState;\n const setState = (v: State): void => {\n streamState = v;\n };\n const waitForResumeOrStop = (): Promise<'resume' | 'stop'> => {\n if (streamState !== 'paused')\n return Promise.resolve('resume' as 'resume' | 'stop');\n if (!resumeDeferred) resumeDeferred = new Deferred<'resume' | 'stop'>();\n return resumeDeferred.promise;\n };\n\n const result = params.controller\n ? runAdaptiveLoop(\n params,\n frozenPair,\n senderPubkey,\n streamNonce,\n logger,\n getState,\n setState,\n waitForResumeOrStop\n )\n : runLoop(\n params,\n frozenPair,\n schedule,\n senderPubkey,\n streamNonce,\n logger,\n getState,\n setState,\n waitForResumeOrStop\n );\n\n return { result, controller };\n}\n\n// ---------------------------------------------------------------------------\n// Shared per-packet machinery (legacy + adaptive loops)\n// ---------------------------------------------------------------------------\n\n/** Mutable accumulator context shared by the loop and per-packet processing. */\ninterface PacketProcessCtx {\n params: StreamSwapParams;\n pair: SwapPair;\n logger: NonNullable<StreamSwapParams['logger']>;\n getState: () => 'running' | 'paused' | 'stopped' | 'completed' | 'failed';\n claims: AccumulatedClaim[];\n rejections: StreamSwapResult['rejections'];\n errors: StreamSwapResult['errors'];\n cumulative: { source: bigint; target: bigint };\n /** Issue #84: session receipt accumulator/verifier (one per stream). */\n receiptTracker: ReceiptChainTracker;\n}\n\n/**\n * Outcome of processing one ACCEPTED (fulfilled) packet. `'error'`,\n * `'recipient-mismatch'`, `'below-floor'`, and `'callback-throw'` have\n * already pushed their entry onto `ctx.errors` / `ctx.rejections`; the\n * caller only decides continue-vs-halt.\n */\ntype ProcessedPacket =\n | {\n status: 'accepted';\n targetAmount: bigint;\n rateDeviation: number;\n rate?: string;\n rateTimestamp?: number;\n }\n | { status: 'error' }\n | { status: 'recipient-mismatch' }\n | { status: 'below-floor' }\n | { status: 'receipt-invalid' }\n | { status: 'callback-throw' };\n\n/**\n * Build the swap rumor + gift wrap for one packet. Throws on wrap failure\n * (callers record the error and skip the packet).\n */\nfunction buildAndWrapPacket(input: {\n params: StreamSwapParams;\n pair: SwapPair;\n senderPubkey: string;\n sourceAmount: bigint;\n /** 1-based sequence number for the rumor `seq` tag. */\n seq: number;\n /** Total packets for the `seq` tag; `0` = unknown (adaptive mode). */\n totalPackets: number;\n /** Issue #84: session nonce advertised as the rumor `stream-nonce` tag. */\n streamNonce: string;\n}): { toonData: Uint8Array; packetExpiresAt?: Date } {\n const {\n params,\n pair,\n senderPubkey,\n sourceAmount,\n seq,\n totalPackets,\n streamNonce,\n } = input;\n const nonce = new Uint8Array(16);\n getRandomValues(nonce);\n const rumor = buildSwapRumor({\n senderPubkey,\n pair,\n sourceAmount,\n packetIndex: seq,\n totalPackets,\n nonce,\n createdAt: Math.floor(Date.now() / 1000),\n chainRecipient: params.chainRecipient,\n streamNonce,\n });\n\n // Per-packet expiry (issue #81 / rolling-swap R7): computed at send\n // time so a stalled packet expires deterministically. Undefined when\n // packetExpiryMs is not set -> transport keeps its timeout-derived\n // default (back-compat).\n const packetExpiresAt =\n params.packetExpiryMs !== undefined\n ? new Date(Date.now() + params.packetExpiryMs)\n : undefined;\n\n const wrapped = wrapSwapPacketToToon({\n rumor,\n senderSecretKey: params.senderSecretKey,\n recipientPubkey: params.swapPubkey,\n destination: params.swapIlpAddress,\n amount: sourceAmount,\n ...(packetExpiresAt !== undefined && { expiresAt: packetExpiresAt }),\n });\n // `wrapped.ilpPrepare.data` is base64 per buildIlpPrepare. Decode back\n // to raw bytes for the sender API (Uint8Array contract in AC-3).\n const toonData = new Uint8Array(\n Buffer.from(wrapped.ilpPrepare.data, 'base64')\n );\n return {\n toonData,\n ...(packetExpiresAt !== undefined && { packetExpiresAt }),\n };\n}\n\n/**\n * Process one fulfilled packet: decode metadata, run the anti-substitution\n * recipient check, enforce the `minExchangeRate` hard floor, decrypt +\n * accumulate the claim, and fire `onPacket`. Extracted verbatim from the\n * Story 12.5 loop so the legacy and adaptive (issue #83) loops share ONE\n * implementation — the floor semantics cannot drift between the two paths.\n */\nasync function processAcceptedPacket(\n ctx: PacketProcessCtx,\n args: {\n packetIndex: number;\n /** Value surfaced as `PacketProgress.total`. */\n totalForProgress: number;\n sourceAmount: bigint;\n /** FULFILL response `data` (base64 metadata). */\n data: string | undefined;\n }\n): Promise<ProcessedPacket> {\n const { params, pair, logger } = ctx;\n const { packetIndex, totalForProgress, sourceAmount, data } = args;\n\n // --- Decode FULFILL metadata ---\n // Issue #82: when the minExchangeRate floor is armed, the quote tape is\n // REQUIRED on every fulfilled packet — a maker that doesn't emit it (or\n // garbles it) produces a loud per-packet FULFILL_DECODE_FAILED, never a\n // silent drop.\n let metadata: {\n claim: string;\n ephemeralPubkey: string;\n claimId?: string;\n targetAmount?: string;\n rate?: string;\n rateTimestamp?: number;\n receipt?: StreamReceipt;\n channelId?: string;\n nonce?: string;\n cumulativeAmount?: string;\n recipient?: string;\n swapSignerAddress?: string;\n };\n try {\n metadata = decodeFulfillMetadata(data, pair.to.chain, {\n requireQuoteTape: params.minExchangeRate !== undefined,\n });\n } catch (err) {\n logger.error({\n event: 'stream_swap.fulfill_decode_failed',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n return { status: 'error' };\n }\n\n // Story 12.9 AC-7: when the Swap echoes a `recipient` in FULFILL metadata\n // (Story 12.6 settlement context), it MUST equal the sender-supplied\n // `chainRecipient`. A mismatch indicates the Swap is substituting its own\n // address — refuse to accumulate the claim and surface a per-packet\n // rejection with a clear reason code. Missing recipient = legacy\n // (pre-12.6) metadata, permitted.\n //\n // #153: EVM addresses are case-insensitive (EIP-55 checksum casing is\n // purely a typo-detection hint). The swap lowercases its echoed recipient\n // while the sender may pass a checksummed `chainRecipient` (or vice versa);\n // compare case-insensitively on EVM targets so a casing-only difference is\n // NOT flagged as a substitution attack. Non-EVM chains keep the exact\n // (base58 case-sensitive) comparison.\n const isEvmTarget = pair.to.chain.startsWith('evm:');\n const recipientMatches =\n metadata.recipient === undefined ||\n (isEvmTarget\n ? metadata.recipient.toLowerCase() === params.chainRecipient.toLowerCase()\n : metadata.recipient === params.chainRecipient);\n if (!recipientMatches) {\n logger.warn({\n event: 'stream_swap.recipient_mismatch',\n packetIndex,\n expected: params.chainRecipient,\n actual: metadata.recipient,\n });\n ctx.rejections.push({\n packetIndex,\n sourceAmount,\n code: 'SWAP_RECIPIENT_MISMATCH',\n message: `Swap echoed recipient ${metadata.recipient} but sender expected ${params.chainRecipient}`,\n });\n return { status: 'recipient-mismatch' };\n }\n\n // --- Issue #82: minExchangeRate hard floor (rfc-0029 semantics) ---\n // Runs BEFORE claim decryption/accumulation and BEFORE the onPacket\n // callback, and consults NOTHING but the floor itself — not the soft\n // deviation monitor, not the callback, not any controller signal (the\n // issue #83 adaptive controller observes packets strictly AFTER this\n // check and has no seam to weaken it). Two independent breach\n // conditions, either one trips the floor:\n // 1. the maker's tape rate `R_i` is below minExchangeRate (exact\n // BigInt decimal comparison), or\n // 2. the delivered targetAmount is below ⌊sourceAmount·minRate⌋\n // (catches a maker painting a rosy tape while under-delivering).\n // A breach is a hard stop: the packet is recorded as a BELOW_FLOOR\n // rejection (never a claims[] success) and the stream halts — filling\n // further packets against a maker quoting under the floor would keep\n // committing source value below the sender's declared worst case.\n if (params.minExchangeRate !== undefined) {\n // requireQuoteTape guarantees both tape fields are present here.\n const tapeRate = metadata.rate as string;\n const floorTargetAmount = applyRate({\n sourceAmount,\n fromScale: pair.from.assetScale,\n toScale: pair.to.assetScale,\n rate: params.minExchangeRate,\n });\n const deliveredTargetAmount: bigint =\n metadata.targetAmount !== undefined\n ? BigInt(metadata.targetAmount)\n : applyRate({\n sourceAmount,\n fromScale: pair.from.assetScale,\n toScale: pair.to.assetScale,\n rate: tapeRate,\n });\n const tapeBelowFloor =\n compareDecimalRates(tapeRate, params.minExchangeRate) < 0;\n if (tapeBelowFloor || deliveredTargetAmount < floorTargetAmount) {\n logger.warn({\n event: 'stream_swap.below_floor',\n packetIndex,\n rate: tapeRate,\n rateTimestamp: metadata.rateTimestamp,\n minExchangeRate: params.minExchangeRate,\n targetAmount: deliveredTargetAmount.toString(),\n floorTargetAmount: floorTargetAmount.toString(),\n });\n ctx.rejections.push({\n packetIndex,\n sourceAmount,\n code: 'BELOW_FLOOR',\n message: `Packet fill below minExchangeRate floor: rate ${tapeRate}, targetAmount ${deliveredTargetAmount} < floor ${params.minExchangeRate} (${floorTargetAmount})`,\n });\n return { status: 'below-floor' };\n }\n }\n\n // --- Issue #84: stream-receipt verification (rfc-0039 semantics) ---\n // Runs AFTER the floor (a below-floor packet is rejected before its\n // receipt is ever considered — a sender-rejected packet contributes NO\n // receipt to the chain) and BEFORE claim decryption/accumulation: a\n // packet whose proof-of-delivery is forged, replayed from another\n // session, or breaks the monotone cumulative MUST NOT accumulate, and\n // the stream halts — continuing would commit more source value while\n // the audit/dispute artifact is already known-corrupt.\n // - present + verifies → accumulate receipt alongside the claim\n // - absent + !requireReceipts → legacy maker, degrade gracefully\n // - absent + requireReceipts → RECEIPT_MISSING rejection + halt\n // - present + fails verification → RECEIPT_INVALID rejection + halt\n // The receipt's attested tape entry must also match the metadata tape —\n // a maker signing one rate while quoting another is equivocating.\n let verifiedReceipt: StreamReceipt | undefined;\n if (metadata.receipt !== undefined) {\n const receipt = metadata.receipt;\n let failure: string | undefined;\n if (metadata.rate !== undefined && receipt.rate !== metadata.rate) {\n failure = `receipt.rate ${receipt.rate} does not match tape rate ${metadata.rate}`;\n } else if (\n metadata.rateTimestamp !== undefined &&\n receipt.rateTimestamp !== metadata.rateTimestamp\n ) {\n failure = `receipt.rateTimestamp ${receipt.rateTimestamp} does not match tape rateTimestamp ${metadata.rateTimestamp}`;\n } else {\n const added = ctx.receiptTracker.add(receipt);\n if (!added.ok) failure = `${added.code}: ${added.message}`;\n }\n if (failure !== undefined) {\n logger.warn({\n event: 'stream_swap.receipt_invalid',\n packetIndex,\n seq: receipt.seq,\n error: failure,\n });\n ctx.rejections.push({\n packetIndex,\n sourceAmount,\n code: 'RECEIPT_INVALID',\n message: `Stream receipt failed verification: ${failure}`,\n });\n return { status: 'receipt-invalid' };\n }\n verifiedReceipt = receipt;\n } else if (params.requireReceipts === true) {\n logger.warn({\n event: 'stream_swap.receipt_missing',\n packetIndex,\n });\n ctx.rejections.push({\n packetIndex,\n sourceAmount,\n code: 'RECEIPT_MISSING',\n message:\n 'FULFILL carried no stream receipt but requireReceipts is set (legacy maker without rfc-0039 receipt support?)',\n });\n return { status: 'receipt-invalid' };\n }\n\n // --- Decrypt claim ---\n let claimBytes: Uint8Array;\n try {\n const ciphertext = new Uint8Array(Buffer.from(metadata.claim, 'base64'));\n claimBytes = decryptFulfillClaim({\n ciphertext,\n ephemeralPubkey: metadata.ephemeralPubkey,\n recipientSecretKey: params.senderSecretKey,\n });\n } catch (err) {\n logger.error({\n event: 'stream_swap.decrypt_failed',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n return { status: 'error' };\n }\n\n if (claimBytes.length === 0) {\n logger.warn({\n event: 'stream_swap.empty_claim_bytes',\n packetIndex,\n });\n }\n\n // --- Compute expected + actual target + deviation ---\n const expectedTargetAmount = applyRate({\n sourceAmount,\n fromScale: pair.from.assetScale,\n toScale: pair.to.assetScale,\n rate: pair.rate,\n });\n\n // If the Swap includes a `targetAmount` in the FULFILL metadata, use it\n // (chain-specific parsing of `claimBytes` is Story 12.6's job). Otherwise\n // fall back to the advertised-rate expected amount so settlement-time\n // verification still has a baseline.\n // `metadata.targetAmount`, when present, is already validated as a\n // non-negative integer decimal string by `decodeFulfillMetadata`, so\n // `BigInt()` is safe here (no try/catch needed).\n const targetAmount: bigint =\n metadata.targetAmount !== undefined\n ? BigInt(metadata.targetAmount)\n : expectedTargetAmount;\n\n // BigInt-safe deviation: scale up by 1e6 before dividing so we don't lose\n // precision on 18-decimal assets. (Epic 11 retro MAX_SAFE_INTEGER guard.)\n let rateDeviation = 0;\n if (expectedTargetAmount > 0n) {\n const diff =\n targetAmount >= expectedTargetAmount\n ? targetAmount - expectedTargetAmount\n : expectedTargetAmount - targetAmount;\n const scaled = (diff * 1_000_000n) / expectedTargetAmount;\n rateDeviation = Number(scaled) / 1_000_000;\n }\n\n // Display-only effectiveRate for the callback payload. Guard against\n // non-finite results (e.g., advertisedRate=0 + any deviation -> 0;\n // parseFloat surprises) so callback consumers never observe NaN/Infinity.\n const advertisedRate = parseFloat(pair.rate);\n let effectiveRate: number;\n if (targetAmount === expectedTargetAmount) {\n effectiveRate = advertisedRate;\n } else {\n const signedDeviation =\n targetAmount >= expectedTargetAmount ? rateDeviation : -rateDeviation;\n effectiveRate = advertisedRate * (1 + signedDeviation);\n }\n if (!Number.isFinite(effectiveRate)) {\n effectiveRate = advertisedRate;\n }\n\n ctx.cumulative.source += sourceAmount;\n ctx.cumulative.target += targetAmount;\n\n const accumulated: AccumulatedClaim = {\n packetIndex,\n sourceAmount,\n targetAmount,\n claimBytes,\n swapEphemeralPubkey: metadata.ephemeralPubkey,\n pair,\n receivedAt: Date.now(),\n };\n if (metadata.claimId !== undefined) accumulated.claimId = metadata.claimId;\n // Story 12.6: thread settlement-context fields through when present.\n if (metadata.channelId !== undefined)\n accumulated.channelId = metadata.channelId;\n if (metadata.nonce !== undefined) accumulated.nonce = metadata.nonce;\n if (metadata.cumulativeAmount !== undefined)\n accumulated.cumulativeAmount = metadata.cumulativeAmount;\n if (metadata.recipient !== undefined)\n accumulated.recipient = metadata.recipient;\n if (metadata.swapSignerAddress !== undefined)\n accumulated.swapSignerAddress = metadata.swapSignerAddress;\n // Issue #82: persist the quote-tape entry on the accumulated claim so\n // post-hoc consumers (settlement audit, controller replay) retain the\n // per-packet `R_i` sequence. Both-or-neither is enforced by the decoder.\n if (metadata.rate !== undefined) {\n accumulated.rate = metadata.rate;\n accumulated.rateTimestamp = metadata.rateTimestamp as number;\n }\n // Issue #84: the verified receipt persists alongside its claim.\n if (verifiedReceipt !== undefined) accumulated.receipt = verifiedReceipt;\n ctx.claims.push(accumulated);\n\n logger.debug({\n event: 'stream_swap.packet_accepted',\n packetIndex,\n sourceAmount: sourceAmount.toString(),\n targetAmount: targetAmount.toString(),\n });\n\n // --- onPacket callback ---\n if (params.onPacket) {\n const progress: PacketProgress = Object.freeze({\n index: packetIndex,\n total: totalForProgress,\n sourceAmount,\n targetAmount,\n advertisedRate: pair.rate,\n effectiveRate,\n rateDeviation,\n cumulativeSource: ctx.cumulative.source,\n cumulativeTarget: ctx.cumulative.target,\n // Issue #82 quote tape: surface the maker's fresh per-packet quote\n // to the callback (the adaptive-controller seam, toon#83).\n ...(metadata.rate !== undefined && {\n rate: metadata.rate,\n rateTimestamp: metadata.rateTimestamp as number,\n }),\n // Issue #84: surface the verified per-fulfill receipt to the callback.\n ...(verifiedReceipt !== undefined && { receipt: verifiedReceipt }),\n state:\n ctx.getState() === 'paused'\n ? 'paused'\n : ctx.getState() === 'stopped'\n ? 'stopped'\n : 'running',\n });\n\n try {\n const maybePromise = params.onPacket(progress);\n if (\n maybePromise &&\n typeof (maybePromise as Promise<void>).then === 'function'\n ) {\n await maybePromise;\n }\n } catch (err) {\n logger.warn({\n event: 'stream_swap.callback_threw',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n return { status: 'callback-throw' };\n }\n }\n\n return {\n status: 'accepted',\n targetAmount,\n rateDeviation,\n ...(metadata.rate !== undefined && {\n rate: metadata.rate,\n rateTimestamp: metadata.rateTimestamp as number,\n }),\n };\n}\n\n/** Map the accumulated context + abort reason to the terminal result. */\nfunction finalizeResult(input: {\n ctx: PacketProcessCtx;\n abortReason: StreamSwapResult['abortReason'];\n packetsSent: number;\n packetsScheduled: number;\n setState: (\n v: 'running' | 'paused' | 'stopped' | 'completed' | 'failed'\n ) => void;\n}): StreamSwapResult {\n const { claims, rejections, errors, cumulative } = input.ctx;\n let { abortReason } = input;\n\n let finalState: 'completed' | 'failed' | 'stopped';\n if (abortReason === 'aborted' || abortReason === 'stopped') {\n finalState = 'stopped';\n } else if (\n claims.length === 0 &&\n (rejections.length > 0 || errors.length > 0)\n ) {\n finalState = 'failed';\n // If we drained the entire schedule without accepting any claim AND the\n // only failures were Swap rejections, surface that explicitly so callers\n // can distinguish \"all rejected\" from \"loop aborted early with no claims\".\n if (\n abortReason === 'complete' &&\n rejections.length > 0 &&\n errors.length === 0\n ) {\n abortReason = 'all-rejected';\n }\n } else {\n finalState = 'completed';\n }\n\n input.setState(finalState);\n\n return {\n state: finalState,\n claims,\n rejections,\n errors,\n abortReason,\n cumulativeSource: cumulative.source,\n cumulativeTarget: cumulative.target,\n packetsSent: input.packetsSent,\n packetsScheduled: input.packetsScheduled,\n // Issue #84: the verified receipt chain — present on abort too,\n // covering exactly the packets that filled before the halt.\n receipts: input.ctx.receiptTracker.chain(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core loop\n// ---------------------------------------------------------------------------\n\nasync function runLoop(\n params: StreamSwapParams,\n pair: SwapPair,\n schedule: bigint[],\n senderPubkey: string,\n streamNonce: string,\n logger: NonNullable<StreamSwapParams['logger']>,\n getState: () => 'running' | 'paused' | 'stopped' | 'completed' | 'failed',\n setState: (\n v: 'running' | 'paused' | 'stopped' | 'completed' | 'failed'\n ) => void,\n waitForResumeOrStop: () => Promise<'resume' | 'stop'>\n): Promise<StreamSwapResult> {\n const ctx: PacketProcessCtx = {\n params,\n pair,\n logger,\n getState,\n claims: [],\n rejections: [],\n errors: [],\n cumulative: { source: 0n, target: 0n },\n receiptTracker: new ReceiptChainTracker({\n streamNonce,\n makerPubkey: params.receiptPubkey ?? params.swapPubkey,\n }),\n };\n let packetsSent = 0;\n let abortReason: StreamSwapResult['abortReason'] = 'complete';\n\n const totalPackets = schedule.length;\n\n // Snapshot the signal abort state at each check — we don't listen via\n // addEventListener because we check at loop boundaries (between packets).\n const isAborted = (): boolean => params.signal?.aborted === true;\n\n packetLoop: for (\n let packetIndex = 0;\n packetIndex < totalPackets;\n packetIndex++\n ) {\n // --- Abort/stop/pause checks at loop boundary ---\n if (isAborted()) {\n abortReason = 'aborted';\n break;\n }\n if (getState() === 'stopped') {\n abortReason = 'stopped';\n break;\n }\n if (getState() === 'paused') {\n const resumedBy = await waitForResumeOrStop();\n if (resumedBy === 'stop' || getState() === 'stopped') {\n abortReason = 'stopped';\n break;\n }\n // After resume, re-check abort signal\n if (isAborted()) {\n abortReason = 'aborted';\n break;\n }\n }\n\n // Bounds-checked at loop init (`packetIndex < totalPackets === schedule.length`).\n // Narrow out the `undefined` TS widening without silently masking with 0n,\n // which would hide a real bug if the schedule were ever mutated mid-loop.\n // (Story 12.5 code-review pass #3.)\n const sourceAmount = schedule[packetIndex];\n if (sourceAmount === undefined) {\n // Defensive: should be impossible given the bound check. Surface, don't mask.\n throw new StreamSwapError(\n 'INVALID_STATE',\n `schedule[${packetIndex}] is undefined; schedule was mutated mid-stream`\n );\n }\n\n // --- Build + wrap packet ---\n let built: { toonData: Uint8Array; packetExpiresAt?: Date };\n try {\n built = buildAndWrapPacket({\n params,\n pair,\n senderPubkey,\n sourceAmount,\n seq: packetIndex + 1,\n totalPackets,\n streamNonce,\n });\n } catch (err) {\n logger.error({\n event: 'stream_swap.wrap_failed',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n continue;\n }\n const { toonData, packetExpiresAt } = built;\n\n // --- Send packet via client ---\n let sendResult: IlpSendResultLike;\n try {\n sendResult = await params.client.sendSwapPacket({\n destination: params.swapIlpAddress,\n amount: sourceAmount,\n toonData,\n timeout: params.packetTimeoutMs ?? 30000,\n claim: params.claim,\n ...(packetExpiresAt !== undefined && { expiresAt: packetExpiresAt }),\n });\n packetsSent += 1;\n } catch (err) {\n logger.error({\n event: 'stream_swap.send_failed',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n continue;\n }\n\n // --- Rejection path ---\n if (!sendResult.accepted) {\n const code = sendResult.code ?? 'F00';\n const message = sendResult.message ?? 'rejected';\n logger.warn({\n event: 'stream_swap.packet_rejected',\n packetIndex,\n code,\n message,\n });\n ctx.rejections.push({ packetIndex, sourceAmount, code, message });\n continue;\n }\n\n // --- Process the fulfilled packet (decode -> recipient check ->\n // minExchangeRate floor -> decrypt -> accumulate -> onPacket),\n // shared with the adaptive loop (issue #83) ---\n const outcome = await processAcceptedPacket(ctx, {\n packetIndex,\n totalForProgress: totalPackets,\n sourceAmount,\n data: sendResult.data,\n });\n if (outcome.status === 'error' || outcome.status === 'recipient-mismatch') {\n continue;\n }\n if (outcome.status === 'below-floor') {\n abortReason = 'below-floor';\n break packetLoop;\n }\n if (outcome.status === 'receipt-invalid') {\n abortReason = 'receipt-invalid';\n break packetLoop;\n }\n if (outcome.status === 'callback-throw') {\n abortReason = 'callback-throw';\n break packetLoop;\n }\n\n // --- Abort signal / stop check AFTER callback (so tests can abort\n // inside onPacket and we exit the loop on the next iteration's\n // boundary check — but honor stopped/aborted without running\n // additional packets). ---\n if (isAborted()) {\n abortReason = 'aborted';\n break;\n }\n if (getState() === 'stopped') {\n abortReason = 'stopped';\n break;\n }\n\n // --- Rate deviation check (after callback, after accumulation) ---\n if (\n params.rateDeviationThreshold !== undefined &&\n outcome.rateDeviation > params.rateDeviationThreshold\n ) {\n abortReason = 'rate-deviation';\n break;\n }\n }\n\n return finalizeResult({\n ctx,\n abortReason,\n packetsSent,\n packetsScheduled: totalPackets,\n setState,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Adaptive loop (issue #83) — controller-driven δ sizing + W-packet window\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a transport-level send failure into a controller resolution\n * class: timeouts/expiries are TIMING signals (shrink W), everything else\n * is a generic error (shrink δ).\n */\nfunction classifySendError(err: Error): PacketResolution {\n return /timeout|timed out|expire/i.test(err.message) ? 'timeout' : 'error';\n}\n\n/**\n * Classify an ILP reject into a controller resolution class (spec §4/§6):\n * - `T99` — the maker staleness reject (`stale_rate`, swap#48) → 'reject-stale'\n * - `R`-class (expiry) or timeout-shaped messages → 'timeout'\n * - anything else → 'reject'\n */\nfunction classifyReject(code: string, message: string): PacketResolution {\n if (code === 'T99' || /stale[_-]?rate/i.test(message)) return 'reject-stale';\n if (code.startsWith('R') || /timeout|timed out|expire/i.test(message)) {\n return 'timeout';\n }\n return 'reject';\n}\n\n/**\n * Controller-driven variant of {@link runLoop} (issue #83, rolling-swap spec\n * §6). Differences from the legacy loop:\n *\n * - No static schedule: each packet's size δ comes from\n * `controller.nextDelta(remaining)` at send time (already capped by\n * `ε/(v·τ)` inside the controller; defensively clamped to\n * `[1, remaining]` here).\n * - Up to `controller.window` (W) packets are kept in flight concurrently.\n * - Every packet resolution is fed back via `controller.observe(...)` with\n * measured RTT, the quote-tape entry, realized amounts, and a resolution\n * class — the controller applies its one-knob-per-step ramp and persists.\n * - On halt (floor breach, deviation, stop, abort, callback throw) no new\n * packets are scheduled but already-sent in-flight packets are drained so\n * their claims (committed value) are still harvested.\n *\n * The `minExchangeRate` floor is enforced inside the SHARED\n * `processAcceptedPacket` — before the controller observes anything — so\n * controller state can never weaken it.\n *\n * Failed/rejected packet slices are NOT re-scheduled (`streamSwap` does not\n * retry packets); like the legacy loop, a failed packet reduces the filled\n * amount.\n */\nasync function runAdaptiveLoop(\n params: StreamSwapParams,\n pair: SwapPair,\n senderPubkey: string,\n streamNonce: string,\n logger: NonNullable<StreamSwapParams['logger']>,\n getState: () => 'running' | 'paused' | 'stopped' | 'completed' | 'failed',\n setState: (\n v: 'running' | 'paused' | 'stopped' | 'completed' | 'failed'\n ) => void,\n waitForResumeOrStop: () => Promise<'resume' | 'stop'>\n): Promise<StreamSwapResult> {\n const controller = params.controller as StreamSwapAdaptiveController;\n const ctx: PacketProcessCtx = {\n params,\n pair,\n logger,\n getState,\n claims: [],\n rejections: [],\n errors: [],\n cumulative: { source: 0n, target: 0n },\n receiptTracker: new ReceiptChainTracker({\n streamNonce,\n makerPubkey: params.receiptPubkey ?? params.swapPubkey,\n }),\n };\n let packetsSent = 0;\n let abortReason: StreamSwapResult['abortReason'] = 'complete';\n let halted = false;\n let remaining = params.totalAmount;\n let nextIndex = 0;\n\n const isAborted = (): boolean => params.signal?.aborted === true;\n const halt = (reason: StreamSwapResult['abortReason']): void => {\n if (!halted) {\n halted = true;\n abortReason = reason;\n }\n };\n\n /** Feed the controller; a controller/persistence failure never kills the stream. */\n const observeSafe = async (obs: PacketObservation): Promise<void> => {\n try {\n await controller.observe(obs);\n } catch (err) {\n logger.warn({\n event: 'stream_swap.controller_observe_failed',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n };\n\n interface SettledPacket {\n index: number;\n sourceAmount: bigint;\n /** Measured round-trip time, ms. */\n rttMs: number;\n result?: IlpSendResultLike;\n error?: Error;\n }\n const inflight = new Map<number, Promise<SettledPacket>>();\n\n for (;;) {\n // --- Boundary checks (mirror the legacy loop) ---\n if (!halted && isAborted()) halt('aborted');\n if (!halted && getState() === 'stopped') halt('stopped');\n if (!halted && getState() === 'paused' && inflight.size === 0) {\n const resumedBy = await waitForResumeOrStop();\n if (resumedBy === 'stop' || getState() === 'stopped') halt('stopped');\n else if (isAborted()) halt('aborted');\n }\n\n // --- Schedule: fill the in-flight window ---\n if (!halted && getState() === 'running') {\n const rawWindow = controller.window;\n const window =\n Number.isFinite(rawWindow) && rawWindow >= 1\n ? Math.floor(rawWindow)\n : 1;\n while (remaining > 0n && inflight.size < window) {\n let delta: bigint;\n try {\n delta = controller.nextDelta(remaining);\n } catch (err) {\n logger.error({\n event: 'stream_swap.controller_next_delta_failed',\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex: nextIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n halt('callback-throw');\n break;\n }\n // Defensive clamp: δ ∈ [1, remaining] regardless of controller.\n if (typeof delta !== 'bigint' || delta < 1n) delta = 1n;\n if (delta > remaining) delta = remaining;\n\n const packetIndex = nextIndex;\n nextIndex += 1;\n remaining -= delta;\n\n let built: { toonData: Uint8Array; packetExpiresAt?: Date };\n try {\n built = buildAndWrapPacket({\n params,\n pair,\n senderPubkey,\n sourceAmount: delta,\n seq: packetIndex + 1,\n // Adaptive mode: the final packet count is unknown upfront —\n // `0` in the rumor's `seq` tag total position means \"open\".\n totalPackets: 0,\n streamNonce,\n });\n } catch (err) {\n logger.error({\n event: 'stream_swap.wrap_failed',\n packetIndex,\n error: err instanceof Error ? err.message : String(err),\n });\n ctx.errors.push({\n packetIndex,\n cause: err instanceof Error ? err : new Error(String(err)),\n });\n continue;\n }\n const { toonData, packetExpiresAt } = built;\n\n const sentAt = Date.now();\n const promise: Promise<SettledPacket> = (async () => {\n try {\n const result = await params.client.sendSwapPacket({\n destination: params.swapIlpAddress,\n amount: delta,\n toonData,\n timeout: params.packetTimeoutMs ?? 30000,\n claim: params.claim,\n ...(packetExpiresAt !== undefined && {\n expiresAt: packetExpiresAt,\n }),\n });\n return {\n index: packetIndex,\n sourceAmount: delta,\n rttMs: Date.now() - sentAt,\n result,\n };\n } catch (err) {\n return {\n index: packetIndex,\n sourceAmount: delta,\n rttMs: Date.now() - sentAt,\n error: err instanceof Error ? err : new Error(String(err)),\n };\n }\n })();\n inflight.set(packetIndex, promise);\n }\n }\n\n // --- Done? (nothing in flight and either drained or halted) ---\n if (inflight.size === 0) {\n if (halted || remaining <= 0n) break;\n // Paused with an empty window: loop back to the boundary wait.\n if (getState() === 'paused') continue;\n // Running with remaining > 0 and nothing in flight: every slice in\n // this round failed to wrap. Nothing can progress — finish.\n break;\n }\n\n // --- Await the next completion and process it ---\n const settled = await Promise.race(inflight.values());\n inflight.delete(settled.index);\n\n if (settled.error) {\n logger.error({\n event: 'stream_swap.send_failed',\n packetIndex: settled.index,\n error: settled.error.message,\n });\n ctx.errors.push({ packetIndex: settled.index, cause: settled.error });\n await observeSafe({\n resolution: classifySendError(settled.error),\n rttMs: settled.rttMs,\n remaining,\n });\n continue;\n }\n packetsSent += 1;\n const sendResult = settled.result as IlpSendResultLike;\n\n if (!sendResult.accepted) {\n const code = sendResult.code ?? 'F00';\n const message = sendResult.message ?? 'rejected';\n logger.warn({\n event: 'stream_swap.packet_rejected',\n packetIndex: settled.index,\n code,\n message,\n });\n ctx.rejections.push({\n packetIndex: settled.index,\n sourceAmount: settled.sourceAmount,\n code,\n message,\n });\n await observeSafe({\n resolution: classifyReject(code, message),\n rttMs: settled.rttMs,\n remaining,\n });\n continue;\n }\n\n const outcome = await processAcceptedPacket(ctx, {\n packetIndex: settled.index,\n totalForProgress: nextIndex,\n sourceAmount: settled.sourceAmount,\n data: sendResult.data,\n });\n\n switch (outcome.status) {\n case 'error':\n await observeSafe({\n resolution: 'error',\n rttMs: settled.rttMs,\n remaining,\n });\n break;\n case 'recipient-mismatch':\n await observeSafe({\n resolution: 'reject',\n rttMs: settled.rttMs,\n remaining,\n });\n break;\n case 'below-floor':\n // The floor already hard-stopped the packet (shared logic). Feed the\n // shrink signal so the persisted tuple starts cautious next session,\n // then halt the stream. The observation happens strictly AFTER the\n // floor decision — the controller cannot influence it.\n await observeSafe({\n resolution: 'reject',\n rttMs: settled.rttMs,\n remaining,\n });\n halt('below-floor');\n break;\n case 'receipt-invalid':\n // Forged/missing proof-of-delivery (issue #84). Shared logic already\n // recorded the rejection; feed a shrink signal so the persisted\n // tuple starts cautious against this maker, then halt. In-flight\n // packets drain (their claims/receipts are still harvested) but\n // nothing new is scheduled.\n await observeSafe({\n resolution: 'reject',\n rttMs: settled.rttMs,\n remaining,\n });\n halt('receipt-invalid');\n break;\n case 'callback-throw':\n // The fill itself was clean; the sender's own callback threw.\n await observeSafe({\n resolution: 'fulfill',\n rttMs: settled.rttMs,\n remaining,\n });\n halt('callback-throw');\n break;\n case 'accepted': {\n await observeSafe({\n resolution: 'fulfill',\n rttMs: settled.rttMs,\n remaining,\n sourceAmount: settled.sourceAmount,\n targetAmount: outcome.targetAmount,\n ...(outcome.rate !== undefined && {\n rate: outcome.rate,\n rateTimestamp: outcome.rateTimestamp as number,\n }),\n });\n if (isAborted()) halt('aborted');\n else if (getState() === 'stopped') halt('stopped');\n else if (\n params.rateDeviationThreshold !== undefined &&\n outcome.rateDeviation > params.rateDeviationThreshold\n ) {\n halt('rate-deviation');\n }\n break;\n }\n }\n }\n\n return finalizeResult({\n ctx,\n abortReason,\n packetsSent,\n packetsScheduled: nextIndex,\n setState,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Crypto RNG — prefer globalThis.crypto; fall back to node:crypto.webcrypto.\n// ---------------------------------------------------------------------------\n\nfunction getRandomValues(buf: Uint8Array): Uint8Array {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- defensive env probe\n const g: any = globalThis as any;\n if (g.crypto && typeof g.crypto.getRandomValues === 'function') {\n g.crypto.getRandomValues(buf);\n return buf;\n }\n // Node 18/20 fallback\n // eslint-disable-next-line @typescript-eslint/no-require-imports -- fallback only\n const nodeCrypto = require('node:crypto') as {\n webcrypto?: { getRandomValues: (b: Uint8Array) => Uint8Array };\n randomFillSync?: (b: Uint8Array) => Uint8Array;\n };\n if (nodeCrypto.webcrypto?.getRandomValues) {\n nodeCrypto.webcrypto.getRandomValues(buf);\n return buf;\n }\n if (nodeCrypto.randomFillSync) {\n nodeCrypto.randomFillSync(buf);\n return buf;\n }\n throw new StreamSwapError(\n 'INVALID_STATE',\n 'No crypto.getRandomValues available in this environment'\n );\n}\n\n// Re-export `chunkAmount` / `decodeFulfillMetadata` / `buildSwapRumor` via a\n// single-purpose testing surface so unit tests can exercise helpers directly.\n// This surface is NOT part of the public SDK; it is intentionally excluded\n// from `packages/sdk/src/index.ts` per AC-1.\nexport const __testing = {\n chunkAmount,\n decodeFulfillMetadata,\n buildSwapRumor,\n compareDecimalRates,\n};\n"],"mappings":"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","names":["getPublicKey","getPublicKey","bytesToHex","RATE_REGEX","getPublicKey","RATE_REGEX","HEX64_REGEX","DECIMAL_UINT_REGEX","noop","NOOP_LOGGER","getPublicKey"]}
|