@toon-protocol/client-mcp 0.20.3 → 0.20.4
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-LN2OF264.js → chunk-6GXZ4KRO.js} +257 -1552
- package/dist/chunk-6GXZ4KRO.js.map +1 -0
- package/dist/{chunk-UITLRZ7O.js → chunk-F3XAIOGQ.js} +2 -2
- package/dist/chunk-IYPIOSEF.js +3856 -0
- package/dist/chunk-IYPIOSEF.js.map +1 -0
- package/dist/{chunk-LRHUY3PO.js → chunk-JFDVE4IA.js} +473 -4116
- package/dist/chunk-JFDVE4IA.js.map +1 -0
- package/dist/{chunk-R3KZIPBQ.js → chunk-TGIKIMXO.js} +8 -6
- package/dist/{chunk-R3KZIPBQ.js.map → chunk-TGIKIMXO.js.map} +1 -1
- package/dist/chunk-V7D5HJBT.js +1363 -0
- package/dist/chunk-V7D5HJBT.js.map +1 -0
- package/dist/daemon.js +5 -3
- package/dist/daemon.js.map +1 -1
- package/dist/{ed25519-VBPL32VX.js → ed25519-U6LNJH4K.js} +3 -2
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +5 -3
- package/dist/mcp.js.map +1 -1
- package/dist/mina-channel-deploy-CO2LMPPB-H2N5FX4G.js +12 -0
- package/dist/mina-channel-deploy-CO2LMPPB-H2N5FX4G.js.map +1 -0
- package/package.json +4 -4
- package/dist/chunk-LN2OF264.js.map +0 -1
- package/dist/chunk-LRHUY3PO.js.map +0 -1
- /package/dist/{chunk-UITLRZ7O.js.map → chunk-F3XAIOGQ.js.map} +0 -0
- /package/dist/{ed25519-VBPL32VX.js.map → ed25519-U6LNJH4K.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../client/src/render/constants.ts","../../client/src/render/swap-defense.ts","../../client/src/render/dispatch.ts","../../client/src/render/consent.ts","../../client/src/render/KindRegistry.ts","../../client/src/render/resolveRenderer.ts","../../client/src/render/index.ts","../../client/src/render/generative.ts","../../client/src/ToonClient.ts","../../client/src/config.ts","../../client/src/errors.ts","../../client/src/keys/KeyDerivation.ts","../../client/src/utils/binary.ts","../../client/src/utils/store-envelope.ts","../../client/src/utils/fulfill-http.ts","../../client/src/modes/http.ts","../../client/src/utils/retry.ts","../../client/src/adapters/HttpRuntimeClient.ts","../../client/src/btp/protocol.ts","../../client/src/btp/IsomorphicBtpClient.ts","../../client/src/utils/condition.ts","../../client/src/adapters/ilp-send.ts","../../client/src/adapters/BtpRuntimeClient.ts","../../client/src/adapters/HttpIlpClient.ts","../../client/src/adapters/selectIlpTransport.ts","../../client/src/channel/OnChainChannelClient.ts","../../client/src/channel/solana-payment-channel.ts","../../client/src/signing/evm-signer.ts","../../client/src/signing/solana-signer.ts","../../client/src/signing/mina-signer.ts","../../client/src/channel/mina-payment-channel.ts","../../client/src/channel/mina-deposit.ts","../../client/src/channel/ChannelManager.ts","../../client/src/channel/ChannelStore.ts","../../client/src/balance/WalletBalanceReader.ts","../../client/src/blob-storage.ts","../../client/src/adapters/Http402Client.ts","../../client/src/swap/settle-received-claims.ts","../../client/src/swap/mina-settlement.ts","../../client/src/adapters/HttpConnectorAdmin.ts","../../client/src/channel/ReceivedClaimStore.ts","../../client/src/swap/received-claims.ts","../../client/src/swap/evm-claim-digest.ts","../../client/src/swap/preimage-retention.ts","../../client/src/swap/atomic-reveal.ts","../../client/src/faucet.ts","../../client/src/keys/KeyManager.ts","../../client/src/keys/PasskeyAuth.ts","../../client/src/keys/encoding.ts","../../client/src/keys/KeyVault.ts","../../client/src/keys/BackupService.ts","../../client/src/keys/keystore-node.ts","../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/pbkdf2.ts","../../../node_modules/.pnpm/@scure+bip39@1.6.0/node_modules/@scure/bip39/esm/index.js","../../../node_modules/.pnpm/@scure+bip39@1.6.0/node_modules/@scure/bip39/esm/wordlists/english.js","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/utils.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/modular.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/curve.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/weierstrass.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/_shortw_utils.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/secp256k1.ts","../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/legacy.ts","../../../node_modules/.pnpm/@scure+bip32@1.7.0/node_modules/@scure/bip32/index.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/errors.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/identity.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/gift-wrap.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/stream-receipts.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/swap-handler.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/stream-swap.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/handler-context.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/handler-registry.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/pricing-validator.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/verification-pipeline.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/payment-handler-bridge.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/event-storage-handler.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/create-node.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/skill-descriptor.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/workflow-orchestrator.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/swarm-coordinator.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/prefix-claim-handler.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/arweave/arweave-dvm-handler.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/arweave/turbo-adapter.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/arweave/chunk-manager.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/arweave/chunked-upload.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/adaptive-controller.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/settlement/evm.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/settlement/hashes.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/settlement/mina.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/settlement/solana.ts","../../../node_modules/.pnpm/@toon-protocol+sdk@3.1.2_mina-signer@3.1.0_typescript@5.9.3/node_modules/@toon-protocol/sdk/src/settlement/build-settlement-tx.ts","../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/sha3.ts","../../arweave/src/gateways.ts","../src/daemon/config.ts","../src/control-client.ts","../src/daemon/lifecycle.ts"],"sourcesContent":["/**\n * Render-side protocol constants for NIP-on-TOON.\n *\n * The canonical homes for the renderer kind, the `ui` tag, and the\n * `UiCoordinate` helpers are `@toon-protocol/core` (published in `1.6.0`):\n * {@link UI_RENDERER_KIND} (31036), {@link UI_TAG}, and `parseUiCoordinate` /\n * `buildUiCoordinate` / `getUiCoordinate` / `selectLatestAddressable`. They are\n * re-exported here so the render module has a single import surface; only the\n * mime-type selectors below are owned locally (core does not export them).\n */\n\n// `UI_RENDERER_KIND`, `UI_TAG`, and the `UiCoordinate` helpers now come from\n// core — re-exported, not mirrored.\nexport {\n UI_RENDERER_KIND,\n UI_TAG,\n buildUiCoordinate,\n parseUiCoordinate,\n getUiCoordinate,\n selectLatestAddressable,\n type UiCoordinate,\n} from '@toon-protocol/core';\n\n/**\n * The `m` (mimeType) tag value selecting **branch 2** — A2UI, medium trust.\n *\n * Owned locally: core does not export the render-branch mime selectors.\n */\nexport const MIME_A2UI = 'application/a2ui+json';\n\n/**\n * The `m` (mimeType) tag value selecting **branch 3** — sandboxed mcp-ui iframe,\n * low trust.\n *\n * Owned locally: core does not export the render-branch mime selectors.\n */\nexport const MIME_MCP_APP = 'text/html;profile=mcp-app';\n","/**\n * Renderer-swap defense — the security guard layer around render dispatch\n * (toon-protocol/toon-client#91, part of toon-protocol/toon-meta#58).\n *\n * ── THREAT: \"renderer swap\" ───────────────────────────────────────────────────\n * A `kind:31036` renderer is an *addressable* event: the coordinate\n * `31036:<author-pubkey>:<targetKind>` can later resolve to a *different* event\n * (different `id`, different content) by publishing a newer-`created_at`\n * revision. Because resolving the `ui` tag yields a renderer that selects the\n * render strategy *and* the trust tier, an attacker who gets a malicious 31036\n * selected can attack the user:\n *\n * V1. Cross-author substitution — serve a 31036 authored by someone *other*\n * than the authoritative renderer author, hoping the client renders it.\n * V2. Forged / tampered renderer — serve a 31036 whose signature does not\n * verify (mutated tags/content, or no signature at all).\n * V3. Resolution race / nondeterminism — feed candidate revisions in an order\n * that makes some clients pick the attacker's revision.\n * V4. Silent mid-session swap — after a renderer has been pinned for an event,\n * publish a newer revision (new `id`) to swap the active renderer out from\n * under an already-decided render, especially to *downgrade trust* (e.g.\n * push a benign event into a hostile low-trust widget).\n *\n * Per toon#36 decisions: the renderer-author pubkey is the **event author** (the\n * `pubkey` of the event being rendered), and clients MUST re-verify the 31036\n * signature before it can select a render strategy.\n *\n * ── DEFENSE (this module) ─────────────────────────────────────────────────────\n * {@link verifyRendererTrust} is a guard placed *between* renderer resolution and\n * {@link renderDispatch}. It **fails closed**: on any violation it returns a\n * rejection and the caller drops to the safe branch (native for known kinds,\n * generative for unknown kinds) — it never renders the suspect renderer.\n *\n * - **Author binding (closes V1):** the resolved 31036's `pubkey` MUST equal\n * the authoritative renderer author (the rendered event's `pubkey`). The\n * coordinate's `pubkey` segment is also checked, so a coordinate pointing at\n * a third-party author is refused before any fetch is trusted.\n * - **Signature verification (closes V2):** the 31036 event signature is\n * re-verified with {@link verifyEvent}; a tampered/unsigned renderer is\n * refused.\n * - **Deterministic selection (closes V3):** candidates are collapsed with\n * {@link selectLatestAddressable} (latest `created_at`, lowest-`id`\n * tiebreak), so selection is not attacker-race-controllable.\n * - **Anti-swap pinning + downgrade detection (closes V4):** the chosen\n * renderer `id` and its trust tier are pinned per coordinate in a\n * {@link RendererPinStore}. A later revision with a *different* `id` is a\n * detected swap; if it would *lower* the trust tier the swap is refused\n * (fail closed). High-trust kinds use the issue's stricter rule: any `id`\n * change at all → refuse and fall back to the native component.\n *\n * ── RELATION TO {@link import('./resolveRenderer.js').resolveUiRenderer} ───────\n * `resolveRenderer.ts` (#97) is the plain, stateless `ui`→`kind:31036` resolver:\n * author-bound coordinate, latest-addressable selection, signature re-verify,\n * returning the renderer or `undefined`. This guard shares the SAME core\n * primitives it builds on — `getUiCoordinate` / `selectLatestAddressable` (now\n * from `@toon-protocol/core`) and `verifyEvent` — so the two agree bit-for-bit\n * on which revision a coordinate selects and on signature acceptance. The guard\n * adds what the plain resolver deliberately omits: a stateful anti-swap pin\n * store, trust-downgrade / high-trust id-change detection, and *granular*\n * fail-closed {@link SwapRejectionReason}s (the resolver collapses every failure\n * to `undefined`). It is therefore a strict superset, not a parallel copy.\n */\n\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\n// The `ui` coordinate primitives now live in `@toon-protocol/core@1.6.0` (#97\n// dropped the local `constants.ts` mirror). The pure resolution/selection\n// helpers (`getUiCoordinate` / `selectLatestAddressable`) are shared verbatim\n// with `./resolveRenderer.js`, so the swap-defense guard and the plain resolver\n// agree bit-for-bit on which `kind:31036` revision a coordinate selects — the\n// guard layers anti-swap pinning + fail-closed *granular* rejection reasons on\n// top of that same selection, rather than re-deriving it.\nimport {\n UI_RENDERER_KIND,\n getUiCoordinate,\n selectLatestAddressable,\n type UiCoordinate,\n} from '@toon-protocol/core';\nimport type { KindRegistry } from './KindRegistry.js';\nimport type { RenderTrust } from './types.js';\n\n/** Ordering of the trust tiers; higher number = more trusted. */\nconst TRUST_RANK: Record<RenderTrust, number> = { low: 0, medium: 1, full: 2 };\n\n/** Whether trust tier `next` is strictly lower than `prev` (a downgrade). */\nexport function isTrustDowngrade(\n prev: RenderTrust,\n next: RenderTrust\n): boolean {\n return TRUST_RANK[next] < TRUST_RANK[prev];\n}\n\n/** Why a renderer was refused. Stable string values, safe to log. */\nexport type SwapRejectionReason =\n /** No `ui` coordinate on the event, or it was malformed. */\n | 'no-coordinate'\n /** The coordinate's author segment is not the rendered event's author. */\n | 'coordinate-author-mismatch'\n /** No candidate `kind:31036` renderer resolved for the coordinate. */\n | 'no-renderer'\n /** The resolved event is not a `kind:31036` renderer. */\n | 'not-a-renderer'\n /** The resolved renderer's `pubkey` is not the authoritative author. */\n | 'author-mismatch'\n /** The renderer's `d` tag does not match the coordinate's target kind. */\n | 'target-kind-mismatch'\n /** The renderer signature did not verify (tampered / unsigned). */\n | 'bad-signature'\n /** A high-trust kind's pinned renderer `id` changed (issue rule: refuse). */\n | 'high-trust-id-changed'\n /** The swap would lower the trust tier from the pinned tier. */\n | 'trust-downgrade';\n\n/** A renderer refused by the guard. The caller must fall back, not render. */\nexport interface SwapRejection {\n ok: false;\n reason: SwapRejectionReason;\n /** Human-readable detail for logging. */\n detail: string;\n /** The coordinate involved, when one was resolvable. */\n coordinate?: UiCoordinate;\n}\n\n/** A renderer the guard approved for dispatch. */\nexport interface SwapApproval {\n ok: true;\n /** The verified, author-bound, deterministically-selected renderer. */\n renderer: NostrEvent;\n /** The coordinate the renderer was selected for. */\n coordinate: UiCoordinate;\n /** Whether this approval newly pinned the coordinate (first sighting). */\n pinned: boolean;\n /** Set when an `id` swap was observed but allowed (non-downgrading). */\n swapObserved?: boolean;\n}\n\nexport type SwapDecision = SwapApproval | SwapRejection;\n\n/**\n * A pinned renderer decision for one coordinate: the `id` we committed to and\n * the trust tier it implied. Used to detect swaps and downgrades.\n */\nexport interface RendererPin {\n /** The pinned `kind:31036` event id. */\n id: string;\n /** The trust tier the pinned renderer selected. */\n trust: RenderTrust;\n}\n\n/**\n * Pins the chosen renderer per coordinate so a later replaceable `kind:31036`\n * cannot silently swap the active renderer mid-session. Keyed by the canonical\n * coordinate string `31036:<pubkey>:<targetKind>`.\n *\n * In-memory by default; a host may seed pins from config (the issue's\n * \"allowlist high-trust renderers by event id\" — pre-populate the expected `id`\n * for a known kind) via {@link pin}.\n */\nexport class RendererPinStore {\n private readonly byCoord = new Map<string, RendererPin>();\n\n private static key(coord: UiCoordinate): string {\n return `${coord.kind}:${coord.pubkey}:${coord.targetKind}`;\n }\n\n /** The pin for `coord`, or `undefined` if not yet pinned. */\n get(coord: UiCoordinate): RendererPin | undefined {\n return this.byCoord.get(RendererPinStore.key(coord));\n }\n\n /** Pin (or overwrite) the renderer decision for `coord`. */\n pin(coord: UiCoordinate, decision: RendererPin): this {\n this.byCoord.set(RendererPinStore.key(coord), { ...decision });\n return this;\n }\n\n /** Whether `coord` is pinned. */\n has(coord: UiCoordinate): boolean {\n return this.byCoord.has(RendererPinStore.key(coord));\n }\n\n /** Number of pinned coordinates. */\n get size(): number {\n return this.byCoord.size;\n }\n}\n\n/** The trust tier a renderer's `m` (mimeType) tag selects, or `undefined`. */\nfunction rendererTrust(renderer: NostrEvent): RenderTrust | undefined {\n const mime = renderer.tags.find((t) => t[0] === 'm')?.[1];\n if (mime === 'application/a2ui+json') return 'medium';\n if (mime === 'text/html;profile=mcp-app') return 'low';\n return undefined;\n}\n\n/** The renderer's `d` (target kind) tag, parsed, or `undefined`. */\nfunction rendererTargetKind(renderer: NostrEvent): number | undefined {\n const d = renderer.tags.find((t) => t[0] === 'd')?.[1];\n if (d === undefined || !/^\\d+$/.test(d)) return undefined;\n return Number(d);\n}\n\n/** Input to {@link verifyRendererTrust}. */\nexport interface VerifyRendererInput<C> {\n /** The event whose renderer is being resolved. Its `pubkey` is authoritative. */\n event: NostrEvent;\n /**\n * The candidate `kind:31036` renderer(s) fetched for the event's `ui`\n * coordinate. May contain multiple revisions; the guard picks the winner\n * deterministically. The caller does not pre-select.\n */\n candidates: readonly NostrEvent[];\n /** The branch-1 native registry; used to decide which kinds are \"high trust\". */\n registry: KindRegistry<C>;\n /** The pin store enforcing stable, anti-swap selection across resolutions. */\n pins: RendererPinStore;\n /**\n * Signature verifier (defaults to nostr-tools `verifyEvent`). Injectable so\n * tests can exercise the fail-closed path deterministically.\n */\n verify?: (event: NostrEvent) => boolean;\n /**\n * Treat the event's kind as a \"high-trust\" kind subject to the issue's strict\n * id-allowlist rule (any `id` change → refuse, fall back to native). Defaults\n * to \"the registry has a native component for this kind\", matching the spec:\n * branch-1 known kinds are the high-trust set. A host may override.\n */\n isHighTrustKind?: (kind: number) => boolean;\n}\n\n/**\n * Guard a renderer before it can select a render strategy. Runs author binding,\n * signature verification, deterministic selection, and anti-swap / downgrade\n * detection. **Fails closed**: any violation returns a {@link SwapRejection} and\n * the caller must drop to the safe branch rather than render.\n *\n * On approval, the chosen renderer is pinned for its coordinate so a subsequent\n * resolution that yields a different `id` is detected (and, if it would downgrade\n * trust, refused).\n */\nexport function verifyRendererTrust<C>(\n input: VerifyRendererInput<C>\n): SwapDecision {\n const { event, candidates, registry, pins } = input;\n const verify = input.verify ?? verifyEvent;\n const isHighTrustKind =\n input.isHighTrustKind ?? ((kind: number) => registry.has(kind));\n\n // The authoritative renderer author is the rendered EVENT's author (toon#36).\n const authorPubkey = event.pubkey;\n\n // 0. The event must carry a well-formed `ui` coordinate.\n const coordinate = getUiCoordinate(event);\n if (coordinate === null) {\n return {\n ok: false,\n reason: 'no-coordinate',\n detail: 'event has no parseable ui tag',\n };\n }\n\n // Author binding (V1), checked at the coordinate level first: a coordinate\n // pointing at a third party is refused before we trust any fetched event.\n if (coordinate.pubkey !== authorPubkey) {\n return {\n ok: false,\n reason: 'coordinate-author-mismatch',\n detail: `ui coordinate author ${coordinate.pubkey} != event author ${authorPubkey}`,\n coordinate,\n };\n }\n\n // 1. Deterministic selection (V3): collapse all candidates to the single\n // winning revision — latest created_at, lowest-id tiebreak — but only among\n // candidates that actually match this coordinate (right kind + author +\n // target kind), so a foreign revision cannot influence the pick.\n const matching = candidates.filter(\n (c) =>\n c.kind === UI_RENDERER_KIND &&\n c.pubkey === authorPubkey &&\n rendererTargetKind(c) === coordinate.targetKind\n );\n const renderer = selectLatestAddressable([...matching]);\n if (renderer === undefined) {\n return {\n ok: false,\n reason: 'no-renderer',\n detail: 'no candidate matched the coordinate',\n coordinate,\n };\n }\n\n // 2. Structural checks on the selected renderer (defence in depth — the filter\n // above already enforces these, but assert them explicitly so a future\n // refactor cannot quietly weaken the guarantee).\n if (renderer.kind !== UI_RENDERER_KIND) {\n return {\n ok: false,\n reason: 'not-a-renderer',\n detail: `kind ${renderer.kind} != ${UI_RENDERER_KIND}`,\n coordinate,\n };\n }\n if (renderer.pubkey !== authorPubkey) {\n return {\n ok: false,\n reason: 'author-mismatch',\n detail: `renderer author ${renderer.pubkey} != event author ${authorPubkey}`,\n coordinate,\n };\n }\n if (rendererTargetKind(renderer) !== coordinate.targetKind) {\n return {\n ok: false,\n reason: 'target-kind-mismatch',\n detail: `renderer d != coordinate target kind ${coordinate.targetKind}`,\n coordinate,\n };\n }\n\n // 3. Signature verification (V2): re-verify before the renderer can select a\n // strategy. A tampered or unsigned renderer fails closed.\n let signatureOk: boolean;\n try {\n signatureOk = verify(renderer);\n } catch {\n signatureOk = false;\n }\n if (!signatureOk) {\n return {\n ok: false,\n reason: 'bad-signature',\n detail: `renderer ${renderer.id} signature did not verify`,\n coordinate,\n };\n }\n\n // 4. Anti-swap pinning + downgrade detection (V4).\n const trust = rendererTrust(renderer);\n // Renderers with an unrecognised `m` tag select no branch; treat as low trust\n // for downgrade math so they can never be used to *raise* a later pin's floor.\n const effectiveTrust: RenderTrust = trust ?? 'low';\n const existing = pins.get(coordinate);\n const highTrust = isHighTrustKind(event.kind);\n\n if (existing === undefined) {\n // First sighting for this coordinate — establish the pin.\n pins.pin(coordinate, { id: renderer.id, trust: effectiveTrust });\n return { ok: true, renderer, coordinate, pinned: true };\n }\n\n if (existing.id === renderer.id) {\n // Same revision as pinned — stable, no swap.\n return { ok: true, renderer, coordinate, pinned: false };\n }\n\n // The `id` changed under a stable coordinate: this is a swap.\n if (highTrust) {\n // Issue rule for high-trust (branch-1 known) kinds: never silently render a\n // new id. Refuse so the caller falls back to the native component.\n return {\n ok: false,\n reason: 'high-trust-id-changed',\n detail: `high-trust kind ${event.kind}: pinned renderer ${existing.id} swapped to ${renderer.id}`,\n coordinate,\n };\n }\n\n if (isTrustDowngrade(existing.trust, effectiveTrust)) {\n // A trust-lowering swap (e.g. medium A2UI → low sandboxed/unknown). Refuse:\n // the user already saw content at the higher tier; do not silently demote.\n return {\n ok: false,\n reason: 'trust-downgrade',\n detail: `swap would downgrade trust ${existing.trust} → ${effectiveTrust} for coordinate ${coordinate.pubkey}:${coordinate.targetKind}`,\n coordinate,\n };\n }\n\n // A non-downgrading swap of a low-trust kind: allowed, but re-pin and flag so\n // the host can surface \"renderer updated\" through the trust gradient again.\n pins.pin(coordinate, { id: renderer.id, trust: effectiveTrust });\n return { ok: true, renderer, coordinate, pinned: false, swapObserved: true };\n}\n","/**\n * Kind-keyed render dispatch — the skeleton the four render branches plug into.\n *\n * Implements §\"Client dispatch algorithm\" of the NIP-on-TOON render-side spec\n * (`skills/nip-on-toon-discovery/SKILL.md` in toon-meta):\n *\n * 1. Is this kind known? → **branch 1** (native registry). Done.\n * 2. Otherwise resolve the event's `ui` tag to a `kind:31036` renderer.\n * 3. If a renderer is found, read its `m` (mimeType) tag:\n * - `application/a2ui+json` → **branch 2** (A2UI, medium trust)\n * - `text/html;profile=mcp-app` → **branch 3** (sandboxed mcp-ui, low)\n * 4. If no renderer is found → **branch 4** (generative fallback, low trust).\n *\n * SCOPE (#88 — skeleton + branch 1 only): branch 1 is wired through the\n * {@link KindRegistry}; branches 2/3/4 are returned as clearly-marked decisions\n * for the sibling tickets to consume (#89 A2UI, #90 mcp-ui + consent, #92\n * generative). This module does NOT render — it returns a {@link RenderDecision}.\n *\n * The `ui`-tag → `kind:31036` *resolution* lives outside this function — see\n * {@link resolveUiRenderer} in `./resolveRenderer.js`, which parses the `ui`\n * coordinate (via core's `getUiCoordinate` / `parseUiCoordinate`), picks the\n * latest addressable `kind:31036` (`selectLatestAddressable`), and re-verifies\n * its signature before trusting it. The dispatch takes the already-resolved\n * renderer event via {@link DispatchInput.renderer}.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { MIME_A2UI, MIME_MCP_APP, UI_RENDERER_KIND } from './constants.js';\nimport type { KindRegistry } from './KindRegistry.js';\nimport type { RenderDecision } from './types.js';\nimport {\n verifyRendererTrust,\n type RendererPinStore,\n type SwapRejection,\n} from './swap-defense.js';\n\n/** Read the first value of the named tag, or `undefined`. */\nfunction tagValue(event: NostrEvent, name: string): string | undefined {\n const tag = event.tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/**\n * The `m` (mimeType) tag value of a resolved `kind:31036` renderer, or\n * `undefined` if the event is not a renderer or carries no `m` tag.\n *\n * The `m` tag is the format selector that picks the branch + trust tier.\n */\nexport function resolveRendererMime(\n renderer: NostrEvent | undefined\n): string | undefined {\n if (!renderer || renderer.kind !== UI_RENDERER_KIND) return undefined;\n return tagValue(renderer, 'm');\n}\n\n/** Input to {@link renderDispatch}. */\nexport interface DispatchInput {\n /** The decoded event the client wants to render. */\n event: NostrEvent;\n /**\n * The `kind:31036` renderer resolved from the event's `ui` tag, if any.\n *\n * Resolution (parse the `ui` coordinate, fetch + pick the latest addressable\n * `kind:31036`) is performed by the caller — see the toon#36 spike. Only\n * consulted when the event's kind is unknown (branches 2–4).\n */\n renderer?: NostrEvent;\n}\n\n/**\n * Route an event to one of the four render branches.\n *\n * @param input The event + (optionally) its resolved `kind:31036` renderer.\n * @param registry The branch-1 native-component registry to consult first.\n * @returns A {@link RenderDecision} naming the branch, trust tier, and payload.\n */\nexport function renderDispatch<C>(\n input: DispatchInput,\n registry: KindRegistry<C>\n): RenderDecision<C> {\n const { event, renderer } = input;\n\n // Branch 1 — known kind → native component, full trust.\n const component = registry.lookup(event.kind);\n if (component !== undefined) {\n return { branch: 'native', trust: 'full', event, component };\n }\n\n // Unknown kind: the `m` tag of the resolved renderer picks the branch.\n const mime = resolveRendererMime(renderer);\n\n // Branch 2 — A2UI, medium trust. [STUB: renderer in #89]\n if (mime === MIME_A2UI && renderer) {\n return { branch: 'a2ui', trust: 'medium', event, renderer };\n }\n\n // Branch 3 — sandboxed mcp-ui iframe, low trust. [STUB: renderer + consent in #90]\n if (mime === MIME_MCP_APP && renderer) {\n return { branch: 'mcp-ui', trust: 'low', event, renderer };\n }\n\n // Branch 4 — no (recognised) renderer → generative fallback, low trust.\n // [STUB: generative fallback + kind:31036 publish-back in #92]\n return { branch: 'generative', trust: 'low', event };\n}\n\n/** Input to {@link guardedRenderDispatch}. */\nexport interface GuardedDispatchInput {\n /** The decoded event the client wants to render. */\n event: NostrEvent;\n /**\n * The candidate `kind:31036` renderer(s) fetched for the event's `ui`\n * coordinate, *unfiltered*. The swap-defense guard selects the winner\n * deterministically and verifies it; the caller does NOT pre-select. Pass an\n * empty array (or omit) when no renderer was resolved.\n */\n candidates?: readonly NostrEvent[];\n}\n\n/** Why {@link guardedRenderDispatch} fell back to a safe branch. */\nexport interface DispatchGuardInfo {\n /** A renderer was refused by the swap-defense guard. */\n rejected: SwapRejection;\n}\n\n/**\n * Dispatch with the **renderer-swap defense** (toon-client#91) interposed.\n *\n * This is the secure entry point: it runs {@link verifyRendererTrust} over the\n * raw candidate renderers *before* {@link renderDispatch} can pick a strategy,\n * and **fails closed** on any violation (wrong-author, bad signature,\n * trust-downgrading swap, high-trust id change):\n *\n * - Known kind → branch 1 (native) regardless of renderers. The guard still\n * runs so a *high-trust* renderer swap is detected, but a known kind always\n * has the native component to fall back to, so the result is branch 1.\n * - Unknown kind, renderer **approved** → normal {@link renderDispatch} with the\n * single verified renderer (branches 2/3).\n * - Unknown kind, renderer **refused** or none → branch 4 (generative). We do\n * NOT pass the suspect renderer through; the user gets the safe fallback.\n *\n * @returns the {@link RenderDecision} plus, when a renderer was refused, the\n * {@link DispatchGuardInfo} describing why (for logging / UX \"renderer refused\").\n */\nexport function guardedRenderDispatch<C>(\n input: GuardedDispatchInput,\n registry: KindRegistry<C>,\n pins: RendererPinStore\n): { decision: RenderDecision<C>; guard?: DispatchGuardInfo } {\n const { event } = input;\n const candidates = input.candidates ?? [];\n\n // Known kind: branch 1 wins. Still run the guard so a high-trust swap is\n // observed/pinned, but the safe fall-back for a known kind is its native\n // component, so the decision is branch 1 either way.\n if (registry.has(event.kind)) {\n const guarded = verifyRendererTrust({ event, candidates, registry, pins });\n const decision = renderDispatch({ event }, registry);\n return guarded.ok\n ? { decision }\n : { decision, guard: { rejected: guarded } };\n }\n\n // Unknown kind with no candidates: nothing to guard → generative fallback.\n if (candidates.length === 0) {\n return { decision: renderDispatch({ event }, registry) };\n }\n\n // Unknown kind with candidates: the guard must approve a renderer before it\n // can select a strategy. Fail closed to generative on refusal.\n const guarded = verifyRendererTrust({ event, candidates, registry, pins });\n if (!guarded.ok) {\n return {\n decision: renderDispatch({ event }, registry),\n guard: { rejected: guarded },\n };\n }\n\n // Approved: dispatch with ONLY the verified, author-bound renderer.\n return {\n decision: renderDispatch({ event, renderer: guarded.renderer }, registry),\n };\n}\n","/**\n * The consent invariant — the load-bearing security property of branch 3\n * (sandboxed mcp-ui, low trust) of the NIP-on-TOON render trust gradient\n * (toon-meta#58, toon-client#90; spec §\"Branch 3 — sandboxed mcp-ui & the\n * consent invariant\").\n *\n * ── The invariant (verbatim from the spec) ──────────────────────────────────\n * A sandboxed widget may only *request* an action. The authorization surface is\n * rendered by the trusted client outside the iframe and is non-themeable. The\n * sandboxed widget can never draw, style, or spoof the consent/authorization UI.\n * A widget that can paint the authorization UI collapses the entire trust\n * gradient to its lowest tier.\n * ────────────────────────────────────────────────────────────────────────────\n *\n * This module is the framework-agnostic half of branch 3. It carries the\n * *decision* and the *policy*, not any React tree — mirroring how `@toon-protocol/client`'s\n * {@link ./dispatch} carries the render decision and `@toon-protocol/views`\n * carries the rendered component. The React side (the sandboxed `AppRenderer`\n * iframe + the host-rendered, non-themeable `ConsentPrompt`) lives in\n * `@toon-protocol/views`; it consumes the types and functions defined here.\n *\n * Why the policy lives here, away from React:\n * - The classification of \"is this a state-changing action that needs explicit\n * authorization?\" is a pure, auditable function with no DOM dependency.\n * - The widget supplies ZERO inputs to this function that can influence the\n * *appearance* of the prompt: it supplies only the requested tool name and\n * arguments. Everything the prompt renders is derived by the trusted client.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { MIME_MCP_APP, UI_RENDERER_KIND } from './constants.js';\n\n/**\n * The widget payload handed to the sandboxed iframe. The `m`-tagged\n * `text/html;profile=mcp-app` renderer ships a raw HTML widget as a UIResource;\n * we pass the HTML through to the iframe untouched, but everything the host\n * renders around it is client-controlled.\n *\n * Deliberately minimal: the host needs only the HTML to feed the iframe and the\n * mimeType to assert the branch. No widget-supplied styling, theme, chrome, or\n * \"trusted\" hints are carried — by construction the widget cannot pass any.\n */\nexport interface UiResource {\n /** The raw widget HTML to render inside the sandboxed iframe. */\n html: string;\n /** Always `text/html;profile=mcp-app` for branch 3 (asserted on extract). */\n mimeType: string;\n /** The `ui://…` resource URI, if the renderer declared one (host metadata only). */\n uri?: string;\n}\n\n/**\n * Extract the branch-3 {@link UiResource} from a resolved `kind:31036` renderer\n * event whose `m` tag is `text/html;profile=mcp-app`.\n *\n * The renderer's `content` is either the raw widget HTML, or a JSON-encoded\n * MCP `UIResource` embedded-resource block (`{ type: 'resource', resource: {\n * uri, mimeType, text } }`) as produced by mcp-ui servers. Both are accepted;\n * the HTML is returned verbatim for iframe passthrough.\n *\n * Returns `undefined` (never throws) when the event is not a usable branch-3\n * renderer, so the caller can fall through to branch 4 rather than render\n * something unexpected.\n */\nexport function extractUiResource(renderer: NostrEvent | undefined): UiResource | undefined {\n if (!renderer || renderer.kind !== UI_RENDERER_KIND) return undefined;\n const mime = renderer.tags.find((t) => t[0] === 'm')?.[1];\n if (mime !== MIME_MCP_APP) return undefined;\n\n const raw = renderer.content;\n if (typeof raw !== 'string' || raw.length === 0) return undefined;\n\n // Try the embedded-resource JSON shape first; fall back to raw HTML.\n const trimmed = raw.trimStart();\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(raw) as {\n type?: string;\n resource?: { uri?: unknown; mimeType?: unknown; text?: unknown };\n };\n const res = parsed.resource;\n if (parsed.type === 'resource' && res && typeof res.text === 'string') {\n return {\n html: res.text,\n mimeType: MIME_MCP_APP,\n ...(typeof res.uri === 'string' ? { uri: res.uri } : {}),\n };\n }\n } catch {\n // Not JSON — treat the whole content as raw HTML below.\n }\n }\n return { html: raw, mimeType: MIME_MCP_APP };\n}\n\n/**\n * An action a sandboxed widget *requested* (never performed). This is the only\n * thing that crosses the iframe → host boundary, and it carries no presentation\n * data — only the tool name and arguments the widget wants to invoke.\n */\nexport interface WidgetIntent {\n /** The tool/action name the widget asked the host to invoke. */\n toolName: string;\n /** The arguments the widget supplied for that tool. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * The classification of a {@link WidgetIntent}: does it need an explicit, host-\n * rendered authorization decision before the host may act on it?\n *\n * - `requires-consent` — a state-changing / spendy / outbound action. The host\n * MUST render the {@link ConsentRequest} prompt (outside the iframe,\n * non-themeable) and only proceed on an explicit user grant.\n * - `auto` — a read-only / inert request the host may forward without a prompt.\n *\n * Default-deny: anything not provably inert is treated as `requires-consent`.\n */\nexport type IntentClassification = 'requires-consent' | 'auto';\n\n/**\n * Tool-name prefixes / names that are read-only on the TOON client and therefore\n * safe to forward without an authorization prompt. Everything else (publishing,\n * paying, swapping, channel ops, media upload, link opening, anything unknown)\n * requires explicit, host-rendered consent.\n *\n * This allowlist is intentionally tiny and lives in trusted client code; a\n * widget cannot extend it.\n */\nconst AUTO_FORWARD_TOOLS: ReadonlySet<string> = new Set([\n 'toon_read',\n 'toon_query',\n 'toon_status',\n 'toon_identity',\n 'toon_channels',\n 'toon_targets',\n 'toon_atoms',\n]);\n\n/**\n * Classify a widget intent. Pure and default-deny: only an exact match against\n * the trusted read-only allowlist is auto-forwarded; everything else requires a\n * host-rendered consent prompt.\n */\nexport function classifyIntent(intent: WidgetIntent): IntentClassification {\n return AUTO_FORWARD_TOOLS.has(intent.toolName) ? 'auto' : 'requires-consent';\n}\n\n/**\n * The data the trusted host needs to render an authorization prompt for a\n * widget-requested action. EVERY field here is either a fixed, client-owned\n * constant or a plain machine value copied out of the intent — there is NO\n * styling, theme, color, label-override, HTML, or markup field the widget could\n * supply. This is what makes the prompt non-themeable by construction: the type\n * simply has nowhere to put presentation input.\n */\nexport interface ConsentRequest {\n /** Stable id for correlating the prompt with its resolution. */\n readonly id: string;\n /** The tool the widget asked to invoke (rendered as plain text by the host). */\n readonly toolName: string;\n /** The arguments the widget supplied (rendered as inspectable data, not HTML). */\n readonly arguments: Record<string, unknown>;\n /**\n * The trust tier of the requesting surface — always `'low'` for a branch-3\n * sandboxed widget. Carried so the host can render the appropriate warning\n * chrome; the widget cannot change it.\n */\n readonly trust: 'low';\n}\n\n/** The user's decision on a {@link ConsentRequest}. */\nexport type ConsentDecision = 'grant' | 'deny';\n\n/** Monotonic counter so generated consent ids are unique within a session. */\nlet consentSeq = 0;\n\n/**\n * Build a {@link ConsentRequest} from a widget intent. The host calls this when\n * {@link classifyIntent} returns `requires-consent`. It copies ONLY the tool\n * name and arguments out of the widget's request; it fixes `trust: 'low'` and\n * generates the id itself. The widget contributes nothing to how the prompt\n * looks.\n */\nexport function buildConsentRequest(intent: WidgetIntent): ConsentRequest {\n consentSeq += 1;\n return {\n id: `consent-${consentSeq}`,\n toolName: intent.toolName,\n arguments: intent.arguments,\n trust: 'low',\n };\n}\n","/**\n * Branch 1 — the native-component registry.\n *\n * A `kind → native component` map for the kinds the client knows natively. This\n * is the registry abstraction that {@link renderDispatch} consults first: a hit\n * is branch 1 (full trust), a miss falls through to the unknown-kind branches.\n *\n * The component type `C` is generic so the rendering package\n * (`@toon-protocol/views`) instantiates it with its own component contract (e.g.\n * an `Atom`) — this keeps `@toon-protocol/client` free of any React dependency\n * while still owning the dispatch + registry abstraction (per the epic split:\n * dispatch in `client`, branch-1 components in `views`).\n *\n * Replaces ad-hoc per-kind conditionals with a single register/lookup seam.\n */\n\n/** A native component registered for one or more event kinds. */\nexport class KindRegistry<C> {\n private readonly byKind = new Map<number, C>();\n\n /**\n * Register a native `component` as the renderer for one or more event\n * `kinds`. Registering an already-registered kind overwrites it (last write\n * wins) so a host can override a default; pass {@link register} per kind to\n * keep the first registration explicit.\n */\n register(kinds: number | readonly number[], component: C): this {\n const list = typeof kinds === 'number' ? [kinds] : kinds;\n for (const kind of list) {\n this.byKind.set(kind, component);\n }\n return this;\n }\n\n /** The native component for `kind`, or `undefined` if the kind is unknown. */\n lookup(kind: number): C | undefined {\n return this.byKind.get(kind);\n }\n\n /** Whether a native component is registered for `kind` (branch 1 applies). */\n has(kind: number): boolean {\n return this.byKind.has(kind);\n }\n\n /** Every kind with a registered native component. */\n kinds(): number[] {\n return [...this.byKind.keys()];\n }\n\n /** Number of registered kinds. */\n get size(): number {\n return this.byKind.size;\n }\n}\n","/**\n * `ui`-tag → `kind:31036` renderer resolution (toon#36).\n *\n * This is the resolution seam the {@link renderDispatch} skeleton (#88)\n * deliberately left out: dispatch consumes an *already-resolved* renderer, and\n * this module produces it. It is split out from dispatch so the relay query +\n * cache (which is IO, and lives in the daemon / {@link import('../ToonClient.js')})\n * stays separate from the pure selection logic — mirroring core's own\n * \"helpers are pure, resolution is client-local\" split.\n *\n * Algorithm, per the toon#36 decisions:\n *\n * 1. Read the rendered event's `ui` tag and parse it into a target coordinate.\n * The coordinate convention is `31036:<renderer-author-pubkey>:<targetKind>`.\n * Per toon#36 the **renderer-author pubkey is the EVENT AUTHOR**, so the\n * `ui` tag MAY carry just the bare target kind (e.g. `42`); the author is\n * taken from `event.pubkey`. A full `31036:<pubkey>:<kind>` coordinate is\n * also accepted (via core's `getUiCoordinate`), but its pubkey MUST equal\n * the event author — a coordinate naming a different author is rejected, so\n * an event cannot point at a third party's renderer.\n * 2. Filter the caller-supplied `kind:31036` candidates to that coordinate\n * (author === event author, `d` tag === target kind) and pick the latest\n * addressable one (NIP-33 latest-wins) via `selectLatestAddressable`.\n * 3. **Re-verify the signature** of the chosen renderer with `verifyEvent`\n * before trusting it. A renderer that fails verification is dropped (the\n * resolution returns `undefined`) — the client never feeds an unverified\n * renderer to the dispatch.\n *\n * The relay query that produces `candidates` is the caller's responsibility:\n * query `kind:31036`, `authors: [event.pubkey]`, `#d: [String(targetKind)]`.\n */\n\nimport { verifyEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport {\n UI_RENDERER_KIND,\n getUiCoordinate,\n parseUiCoordinate,\n selectLatestAddressable,\n} from '@toon-protocol/core';\n\n/** Read the first value of the named tag, or `undefined`. */\nfunction tagValue(event: NostrEvent, name: string): string | undefined {\n return event.tags.find((t) => t[0] === name)?.[1];\n}\n\n/**\n * The renderer coordinate a rendered event points at: a `kind:31036` event\n * authored by the rendered event's author, targeting `targetKind`.\n */\nexport interface ResolvedCoordinate {\n /** Always {@link UI_RENDERER_KIND} (31036). */\n kind: typeof UI_RENDERER_KIND;\n /** The renderer author pubkey — per toon#36, the EVENT AUTHOR's pubkey. */\n pubkey: string;\n /** The kind of event the renderer targets (the renderer's `d` value). */\n targetKind: number;\n}\n\n/**\n * Compute the renderer coordinate a rendered event points at, anchoring the\n * renderer-author pubkey to the **event author** per toon#36.\n *\n * Accepts two `ui` tag shapes:\n * - a bare target kind, e.g. `[\"ui\", \"42\"]` → author = `event.pubkey`;\n * - a full coordinate, e.g. `[\"ui\", \"31036:<pubkey>:42\"]` → accepted only if\n * `<pubkey>` equals `event.pubkey` (else `null`).\n *\n * Pure: no IO.\n *\n * @param event - The rendered event that may carry a `ui` tag.\n * @returns The resolved coordinate, or `null` if there is no usable `ui` tag.\n */\nexport function resolveUiCoordinate(event: NostrEvent): ResolvedCoordinate | null {\n const raw = tagValue(event, 'ui');\n if (raw === undefined) return null;\n\n // Full `31036:<pubkey>:<kind>` coordinate form.\n const full = getUiCoordinate(event) ?? parseUiCoordinate(raw);\n if (full) {\n // The coordinate must name the event author — no third-party renderers.\n if (full.pubkey !== event.pubkey) return null;\n return { kind: UI_RENDERER_KIND, pubkey: event.pubkey, targetKind: full.targetKind };\n }\n\n // Bare target-kind form: the author is the event author.\n const targetKind = Number(raw);\n if (!Number.isInteger(targetKind) || targetKind < 0) return null;\n return { kind: UI_RENDERER_KIND, pubkey: event.pubkey, targetKind };\n}\n\n/**\n * Resolve a rendered event's `ui` tag to a verified `kind:31036` renderer.\n *\n * Filters `candidates` to the coordinate computed by {@link resolveUiCoordinate},\n * picks the latest addressable match, and **re-verifies its signature** before\n * returning it. The result feeds {@link renderDispatch} as `DispatchInput.renderer`.\n *\n * @param event - The rendered event carrying the `ui` tag.\n * @param candidates - `kind:31036` events the caller fetched for this coordinate\n * (the relay query is the caller's responsibility).\n * @returns The latest verified renderer, or `undefined` if none resolves /\n * verifies.\n */\nexport function resolveUiRenderer(\n event: NostrEvent,\n candidates: readonly NostrEvent[]\n): NostrEvent | undefined {\n const coord = resolveUiCoordinate(event);\n if (!coord) return undefined;\n\n const matches = candidates.filter(\n (c) =>\n c.kind === UI_RENDERER_KIND &&\n c.pubkey === coord.pubkey &&\n tagValue(c, 'd') === String(coord.targetKind)\n );\n\n const latest = selectLatestAddressable(matches);\n if (!latest) return undefined;\n\n // Re-verify the resolved renderer's signature before trusting it (toon#36).\n if (!verifyEvent(latest)) return undefined;\n\n return latest;\n}\n","/**\n * NIP-on-TOON render dispatch (toon-protocol/toon-meta#58).\n *\n * The kind-keyed dispatch skeleton + branch-1 native-component registry. Branches\n * 2/4 are routed to as clearly-marked decisions for the sibling tickets to\n * implement (#89 A2UI, #92 generative fallback); branch 3 (#90) adds the\n * framework-agnostic consent invariant consumed by the `@toon-protocol/views`\n * sandboxed renderer.\n */\n\nexport {\n renderDispatch,\n resolveRendererMime,\n guardedRenderDispatch,\n} from './dispatch.js';\nexport type {\n DispatchInput,\n GuardedDispatchInput,\n DispatchGuardInfo,\n} from './dispatch.js';\nexport {\n extractUiResource,\n classifyIntent,\n buildConsentRequest,\n} from './consent.js';\nexport type {\n UiResource,\n WidgetIntent,\n IntentClassification,\n ConsentRequest,\n ConsentDecision,\n} from './consent.js';\nexport { KindRegistry } from './KindRegistry.js';\nexport { resolveUiCoordinate, resolveUiRenderer } from './resolveRenderer.js';\nexport type { ResolvedCoordinate } from './resolveRenderer.js';\nexport { UI_RENDERER_KIND, UI_TAG, MIME_A2UI, MIME_MCP_APP } from './constants.js';\n// The `ui` coordinate helpers + type now live in `@toon-protocol/core@1.6.0`\n// (#97 dropped the local mirror); re-exported here so the render module keeps a\n// single import surface for them.\nexport {\n parseUiCoordinate,\n getUiCoordinate,\n buildUiCoordinate,\n selectLatestAddressable,\n} from '@toon-protocol/core';\nexport type { UiCoordinate } from '@toon-protocol/core';\nexport {\n verifyRendererTrust,\n isTrustDowngrade,\n RendererPinStore,\n} from './swap-defense.js';\nexport type {\n SwapDecision,\n SwapApproval,\n SwapRejection,\n SwapRejectionReason,\n RendererPin,\n VerifyRendererInput,\n} from './swap-defense.js';\nexport type {\n RenderBranch,\n RenderTrust,\n RenderDecision,\n NativeDecision,\n A2uiDecision,\n McpUiDecision,\n GenerativeDecision,\n} from './types.js';\n\n// Branch 4 — generative fallback + optional kind:31036 publish-back (#92).\nexport {\n GenerativeFallbackRenderer,\n deterministicGenerator,\n renderDeterministicHtml,\n buildRendererEventTemplate,\n publishBackCoordinate,\n} from './generative.js';\nexport type {\n GeneratedRenderer,\n GenerateContext,\n RendererGenerator,\n RendererSigner,\n RendererPublisher,\n PublishBackOptions,\n GenerativeFallbackOptions,\n GenerativeFallbackResult,\n} from './generative.js';\n","/**\n * Branch 4 — generative fallback + optional `kind:31036` publish-back.\n *\n * Implements §\"branch 4 — generative fallback\" of the NIP-on-TOON render-side\n * spec (`skills/nip-on-toon-discovery/SKILL.md` in toon-meta) and\n * toon-protocol/toon-client#92.\n *\n * Branch 4 is reached by {@link renderDispatch} when a kind is **unknown** *and*\n * no resolvable `kind:31036` renderer exists (no `ui` tag, or nothing resolves,\n * or the resolved renderer carries no recognised `m` tag). With nothing else to\n * go on, the client generates a best-effort rendering for the unknown event's\n * shape at **low trust**.\n *\n * Design seams (per the issue's `needs:human` open questions — the model, the\n * curation policy, and the publish-back opt-in semantics are product decisions\n * the host owns, so this module hardcodes none of them):\n *\n * - **Generator** ({@link RendererGenerator}) — the actual model call is\n * abstracted behind an interface the host injects. The host wires its own\n * provider/keys/prompt; this module never imports an LLM SDK. A deterministic\n * non-LLM generator ({@link deterministicGenerator}) is provided as the\n * default and for tests, so branch 4 always produces *something* renderable\n * even with no model configured.\n * - **Publish-back** — optionally republish the generated renderer as a\n * `kind:31036` addressable event so the next client has a \"known\" renderer\n * for that kind (branch 4 slowly feeds branch 1). This is a **guarded,\n * off-by-default** capability: it only fires when the host explicitly passes\n * `publish: { enabled: true, ... }`. The published renderer is clearly\n * low-trust / curation-pending; the namespacing & curation policy for\n * community-published renderers is an open question in the epic (toon#58) and\n * is intentionally *not* built here.\n */\n\nimport type { NostrEvent, EventTemplate } from 'nostr-tools/pure';\nimport { MIME_MCP_APP, UI_RENDERER_KIND, buildUiCoordinate } from './constants.js';\n\n/**\n * A generated renderer for an unknown event kind: an HTML `UIResource`-style\n * document plus the `m` (mimeType) tag that classifies it.\n *\n * The HTML is the raw widget body; if published back as a `kind:31036` event it\n * is rendered through branch 3 (sandboxed mcp-ui iframe) by a downstream client,\n * which is why {@link mimeType} defaults to the branch-3 selector.\n */\nexport interface GeneratedRenderer {\n /** The generated HTML document (the `UIResource` body). */\n html: string;\n /**\n * The `m` (mimeType) tag for the generated renderer. Defaults to\n * {@link MIME_MCP_APP} (`text/html;profile=mcp-app`) so a published renderer\n * routes through branch 3 (sandboxed, low trust) on the next client.\n */\n mimeType: string;\n /**\n * Whether this rendering came from a model (`'model'`) or the built-in\n * deterministic fallback (`'deterministic'`). Surfaced so the host can label\n * trust/provenance in the UI.\n */\n source: 'model' | 'deterministic';\n}\n\n/** Context handed to a {@link RendererGenerator}. */\nexport interface GenerateContext {\n /** The unknown event the client wants to render. */\n event: NostrEvent;\n}\n\n/**\n * The pluggable generator seam. A host injects its own implementation (wired to\n * whatever model endpoint, key, and prompt it has chosen — all `needs:human`\n * product decisions this module deliberately does not own).\n *\n * Implementations should be best-effort and resilient: a failed model call\n * should reject so {@link GenerativeFallbackRenderer} can fall back to the\n * deterministic generator rather than render nothing.\n */\nexport interface RendererGenerator {\n generate(ctx: GenerateContext): Promise<GeneratedRenderer>;\n}\n\n/** Minimal HTML-escape for embedding event data in the deterministic template. */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * A deterministic, non-LLM generator: renders a best-effort, dependency-free\n * \"unknown kind\" card from the event's shape (kind, author, tags, content). It\n * never calls a network or a model, so it is the safe default and the basis for\n * tests — given the same event it always produces the same HTML.\n *\n * The output is intentionally generic and clearly marked as a low-trust\n * fallback; it makes no claim to understand the kind's semantics.\n */\nexport const deterministicGenerator: RendererGenerator = {\n async generate({ event }: GenerateContext): Promise<GeneratedRenderer> {\n return { html: renderDeterministicHtml(event), mimeType: MIME_MCP_APP, source: 'deterministic' };\n },\n};\n\n/**\n * The pure HTML projection used by {@link deterministicGenerator}. Exported so\n * tests (and hosts wanting the fallback body without the Promise wrapper) can\n * assert on it directly.\n */\nexport function renderDeterministicHtml(event: NostrEvent): string {\n const rows = event.tags\n .filter((t) => t[0] !== undefined)\n .map(\n (t) =>\n `<tr><th>${escapeHtml(String(t[0]))}</th><td>${escapeHtml(t.slice(1).join(' '))}</td></tr>`\n )\n .join('');\n const content = event.content ? escapeHtml(event.content) : '<em>(no content)</em>';\n return [\n '<!doctype html>',\n '<section data-toon-fallback=\"generative\" data-trust=\"low\">',\n `<header><h1>Unknown event</h1><p>kind <code>${event.kind}</code></p></header>`,\n `<p class=\"toon-fallback-note\">No renderer was found for this kind. This is a best-effort, low-trust fallback rendering of the event's raw shape.</p>`,\n `<dl><dt>author</dt><dd><code>${escapeHtml(event.pubkey)}</code></dd>`,\n `<dt>id</dt><dd><code>${escapeHtml(event.id)}</code></dd></dl>`,\n `<div class=\"toon-fallback-content\">${content}</div>`,\n rows ? `<table class=\"toon-fallback-tags\"><tbody>${rows}</tbody></table>` : '',\n '</section>',\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\n/** Host-supplied signer seam used to finalize the publish-back event. */\nexport interface RendererSigner {\n /** The author pubkey (hex) the renderer is published under (the coordinate author). */\n getPublicKey(): string;\n /** Finalize an unsigned event template into a signed `NostrEvent`. */\n signEvent(template: EventTemplate): NostrEvent;\n}\n\n/** Host-supplied publisher seam used to broadcast the publish-back event. */\nexport interface RendererPublisher {\n publishEvent(event: NostrEvent): Promise<unknown>;\n}\n\n/**\n * Publish-back configuration. **Off by default**: publish-back never happens\n * unless the host passes this object with `enabled: true` *and* supplies a\n * signer and publisher. This is the explicit-enablement gate the issue requires\n * — there is no implicit / always-on publish path.\n */\nexport interface PublishBackOptions {\n /** Master switch. Must be `true` for any publish to occur. */\n enabled: boolean;\n /** Signs the `kind:31036` event (also supplies the coordinate author pubkey). */\n signer: RendererSigner;\n /** Broadcasts the signed event. */\n publisher: RendererPublisher;\n}\n\n/** Options for {@link GenerativeFallbackRenderer}. */\nexport interface GenerativeFallbackOptions {\n /**\n * The generator to use. Defaults to {@link deterministicGenerator}. A host\n * injects its model-backed generator here.\n */\n generator?: RendererGenerator;\n /**\n * Publish-back config. Omit (or pass `enabled: false`) to keep publish-back\n * off — the default. See {@link PublishBackOptions}.\n */\n publish?: PublishBackOptions;\n}\n\n/** The outcome of {@link GenerativeFallbackRenderer.render}. */\nexport interface GenerativeFallbackResult {\n /** The generated renderer (model output, or the deterministic fallback). */\n rendered: GeneratedRenderer;\n /** Always `'low'` — branch 4 is a low-trust path. */\n trust: 'low';\n /**\n * The signed `kind:31036` event that was published back, or `undefined` when\n * publish-back was disabled (the default) or could not run.\n */\n published?: NostrEvent;\n}\n\n/**\n * Build the unsigned `kind:31036` renderer event for a generated renderer. The\n * `d` tag is the target kind; the `m` tag is the renderer's mimeType; the body\n * is the generated HTML. The signed event's coordinate is\n * `31036:<author-pubkey>:<targetKind>` (see {@link buildUiCoordinate}).\n *\n * Exported for tests / hosts that want to inspect the event before signing.\n */\nexport function buildRendererEventTemplate(\n targetKind: number,\n rendered: GeneratedRenderer\n): EventTemplate {\n return {\n kind: UI_RENDERER_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', String(targetKind)],\n ['m', rendered.mimeType],\n // Self-describing, curation-pending marker. The full curation/namespacing\n // policy for community-published renderers is an open epic question\n // (toon#58) and is intentionally not modelled here.\n ['t', 'generative-fallback'],\n ],\n content: rendered.html,\n };\n}\n\n/**\n * Branch 4 renderer: generate a best-effort rendering for an unknown event and,\n * if explicitly enabled, publish it back as a `kind:31036` renderer.\n *\n * Generation is resilient: if the injected model generator throws, the renderer\n * transparently falls back to {@link deterministicGenerator} so a rendering is\n * always produced.\n */\nexport class GenerativeFallbackRenderer {\n private readonly generator: RendererGenerator;\n private readonly publish?: PublishBackOptions;\n\n constructor(options: GenerativeFallbackOptions = {}) {\n this.generator = options.generator ?? deterministicGenerator;\n this.publish = options.publish;\n }\n\n /**\n * Generate a fallback rendering for `event` (low trust), optionally publishing\n * the result back as a `kind:31036` renderer when publish-back is enabled.\n */\n async render(event: NostrEvent): Promise<GenerativeFallbackResult> {\n let rendered: GeneratedRenderer;\n try {\n rendered = await this.generator.generate({ event });\n } catch {\n // Model call failed → never render nothing; use the deterministic fallback.\n rendered = await deterministicGenerator.generate({ event });\n }\n\n const result: GenerativeFallbackResult = { rendered, trust: 'low' };\n\n // Publish-back gate: only when explicitly enabled with a signer + publisher.\n if (this.publish?.enabled && this.publish.signer && this.publish.publisher) {\n const template = buildRendererEventTemplate(event.kind, rendered);\n const signed = this.publish.signer.signEvent(template);\n await this.publish.publisher.publishEvent(signed);\n result.published = signed;\n }\n\n return result;\n }\n\n /** Whether publish-back is currently enabled (for host introspection/UI). */\n get publishBackEnabled(): boolean {\n return this.publish?.enabled === true;\n }\n}\n\n/**\n * The coordinate (`31036:<author-pubkey>:<targetKind>`) a publish-back will/did\n * use for `targetKind` under `signer`'s identity. Convenience for hosts that\n * want to show or pre-resolve the coordinate.\n *\n * Throws if the signer's pubkey or `targetKind` is malformed — core's\n * {@link buildUiCoordinate} returns `null` for invalid inputs, which here can\n * only mean the host wired a bad signer.\n */\nexport function publishBackCoordinate(signer: RendererSigner, targetKind: number): string {\n const coord = buildUiCoordinate({ pubkey: signer.getPublicKey(), targetKind });\n if (coord === null) {\n throw new Error(\n `publishBackCoordinate: invalid renderer coordinate for pubkey=${signer.getPublicKey()} targetKind=${targetKind}`\n );\n }\n return coord;\n}\n","import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent, EventTemplate } from 'nostr-tools/pure';\nimport type {\n BootstrapService,\n DiscoveryTracker,\n IlpSendResult,\n IlpClient,\n} from '@toon-protocol/core';\nimport type { NetworkFamilyStatus } from '@toon-protocol/core';\nimport { validateConfig, applyDefaults, getNetworkStatus } from './config.js';\nimport { toBase64 } from './utils/binary.js';\nimport { buildStoreWriteEnvelope } from './utils/store-envelope.js';\nimport { parseFulfillHttp } from './utils/fulfill-http.js';\nimport type { ResolvedConfig } from './config.js';\nimport { initializeHttpMode } from './modes/http.js';\nimport { ToonClientError } from './errors.js';\nimport { EvmSigner } from './signing/evm-signer.js';\nimport { SolanaSigner } from './signing/solana-signer.js';\nimport { MinaSigner } from './signing/mina-signer.js';\nimport { deriveFullIdentity } from './keys/KeyDerivation.js';\nimport {\n ChannelManager,\n type PeerNegotiation,\n} from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { OnChainChannelClient } from './channel/OnChainChannelClient.js';\nimport {\n readEvmTokenBalance,\n readSolanaTokenBalance,\n readMinaBalance,\n readWalletBalances,\n type WalletBalance,\n type WalletBalanceSources,\n type WalletChainBalances,\n} from './balance/WalletBalanceReader.js';\nimport {\n requestBlobStorage,\n type RequestBlobStorageResult,\n} from './blob-storage.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.js';\nimport type { IlpSendParams } from './adapters/ilp-send.js';\nimport {\n Http402Client,\n type H402FetchOptions,\n} from './adapters/Http402Client.js';\nimport type { SettlementBundle } from '@toon-protocol/sdk';\nimport {\n submitEvmSettlement,\n type SubmitEvmSettlementResult,\n} from './swap/settle-received-claims.js';\nimport {\n submitMinaSettlement,\n type MinaSignaturePair,\n} from './swap/mina-settlement.js';\nimport type {\n ToonClientConfig,\n ToonStartResult,\n PublishEventResult,\n SignedBalanceProof,\n} from './types.js';\n\n/**\n * Validate an operator-supplied maker Mina co-signature (`{ r, s }` decimal\n * Field strings). Returns undefined for an absent/malformed entry so the\n * settlement path fails closed with `MINA_MAKER_COSIGN_REQUIRED` rather than\n * forwarding garbage into o1js.\n */\nfunction parseMakerMinaSignature(\n raw: { r: string; s: string } | undefined\n): MinaSignaturePair | undefined {\n if (\n raw &&\n typeof raw.r === 'string' &&\n raw.r.length > 0 &&\n typeof raw.s === 'string' &&\n raw.s.length > 0\n ) {\n return { r: raw.r, s: raw.s };\n }\n return undefined;\n}\n\n/**\n * Internal state for ToonClient after initialization.\n */\ninterface ToonClientState {\n bootstrapService: BootstrapService;\n discoveryTracker: DiscoveryTracker;\n runtimeClient: IlpClient;\n peersDiscovered: number;\n btpClient?: BtpRuntimeClient;\n}\n\n/**\n * ToonClient - High-level client for interacting with TOON network.\n *\n * This story implements HTTP mode only. Embedded mode will be added in a future epic.\n *\n * @example HTTP Mode\n * ```typescript\n * import { ToonClient } from '@toon-protocol/client';\n * import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\n * import { encodeEvent, decodeEvent } from '@toon-protocol/relay';\n *\n * const secretKey = generateSecretKey();\n * const pubkey = getPublicKey(secretKey);\n *\n * const client = new ToonClient({\n * connectorUrl: 'http://localhost:8080',\n * secretKey,\n * ilpInfo: {\n * pubkey,\n * ilpAddress: `g.toon.${pubkey.slice(0, 8)}`,\n * btpEndpoint: 'ws://localhost:3000',\n * },\n * toonEncoder: encodeEvent,\n * toonDecoder: decodeEvent,\n * });\n *\n * await client.start(); // Bootstrap peers, start monitoring\n *\n * // Publish to default destination (from config)\n * await client.publishEvent(signedEvent);\n *\n * // Publish to specific destination (multi-hop routing)\n * await client.publishEvent(signedEvent, { destination: 'g.toon.peer1' });\n *\n * await client.stop(); // Cleanup\n * ```\n */\nexport class ToonClient {\n private readonly config: ResolvedConfig;\n private state: ToonClientState | null = null;\n private readonly evmSigner?: EvmSigner;\n private solanaSigner?: SolanaSigner;\n /**\n * Ed25519 signing seed (32 bytes) derived from the mnemonic for the Solana\n * identity. Retained so `start()` can inject it into the on-chain channel\n * client's Solana config (same key as `solanaSigner`).\n */\n private solanaSeed?: Uint8Array;\n private minaSigner?: MinaSigner;\n /**\n * Mina private key (big-endian hex scalar, as `deriveFullIdentity` emits)\n * derived from the mnemonic. Retained so `start()` can inject it into the\n * on-chain channel client's Mina config (same key as `minaSigner`).\n */\n private minaPrivateKey?: string;\n private channelManager?: ChannelManager;\n /** Concrete on-chain client, kept so deposit/withdraw can reach chain methods. */\n private onChainChannelClient?: OnChainChannelClient;\n private readonly peerNegotiations = new Map<string, PeerNegotiation>();\n\n /**\n * Creates a new ToonClient instance.\n *\n * @param config - Client configuration\n * @throws {ValidationError} If configuration is invalid\n */\n constructor(config: ToonClientConfig) {\n // Validate config (will reject embedded mode, require connectorUrl)\n validateConfig(config);\n\n // Apply defaults to optional fields (auto-generates secretKey if needed)\n this.config = applyDefaults(config);\n\n // Create EVM signer if private key provided\n if (this.config.evmPrivateKey) {\n this.evmSigner = new EvmSigner(this.config.evmPrivateKey);\n }\n }\n\n /**\n * Generates a new Nostr keypair.\n *\n * @returns Object with secretKey (Uint8Array) and pubkey (hex string)\n */\n static generateKeypair(): { secretKey: Uint8Array; pubkey: string } {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n }\n\n /**\n * Gets the Nostr public key derived from the secret key.\n * Works before start() is called.\n */\n getPublicKey(): string {\n return getPublicKey(this.config.secretKey);\n }\n\n /**\n * Sign an unsigned Nostr event template with the client's Nostr secret key,\n * returning a fully-signed event (id + pubkey + sig).\n *\n * This is the key primitive behind the daemon's sign-and-publish path: a UI\n * or agent supplies only `{ kind, content, tags, created_at }` and never holds\n * the private key — signing happens here, inside the key owner.\n */\n signEvent(template: EventTemplate): NostrEvent {\n return finalizeEvent(template, this.config.secretKey);\n }\n\n /**\n * Upload bytes to Arweave via the kind:5094 blob-storage DVM (single-packet),\n * signing the request with this client's Nostr key and paying through its\n * existing channel. Returns the Arweave tx id on success.\n *\n * Backs the daemon's `upload-media` path: the key and claim/channel plumbing\n * stay inside the client; callers pass only the bytes.\n */\n async uploadBlob(params: {\n blobData: Uint8Array;\n contentType?: string;\n bid?: string;\n destination?: string;\n ilpAmount?: bigint;\n }): Promise<RequestBlobStorageResult> {\n return requestBlobStorage(this, this.config.secretKey, params);\n }\n\n /**\n * Per-chain settlement readiness for the configured `network` tier, mirroring\n * the relay node's status. Returns `undefined` when no named `network` is\n * set (or `network: 'custom'`), since there is no preset tier to report on.\n */\n getNetworkStatus(): NetworkFamilyStatus | undefined {\n return getNetworkStatus(this.config);\n }\n\n /**\n * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).\n */\n getEvmAddress(): string | undefined {\n return this.evmSigner?.address;\n }\n\n /**\n * Gets the Solana (base58) address, when the client was constructed from a\n * `mnemonic`. Available only AFTER `start()` (Solana keys are derived\n * asynchronously). Returns undefined otherwise.\n */\n getSolanaAddress(): string | undefined {\n return this.solanaSigner?.signerIdentifier;\n }\n\n /**\n * Gets the Mina (base58) address, when the client was constructed from a\n * `mnemonic` AND `mina-signer` is installed. Available only AFTER `start()`.\n * Returns undefined otherwise.\n */\n getMinaAddress(): string | undefined {\n return this.minaSigner?.signerIdentifier;\n }\n\n /**\n * Derive the Solana/Mina keys from the mnemonic and register their signers on\n * the ChannelManager. Mirrors how the EVM signer is wired, but for the\n * non-secp256k1 chains. Skips any chain whose optional dependency is missing.\n */\n private async registerMnemonicChainSigners(\n mnemonic: string,\n accountIndex = 0\n ): Promise<void> {\n if (!this.channelManager) return;\n const identity = await deriveFullIdentity(mnemonic, accountIndex);\n\n // Solana: @noble/curves Ed25519 expects a 32-byte seed; deriveFullIdentity\n // returns a 64-byte keypair (seed||pubkey).\n if (identity.solana.publicKey) {\n const seed = identity.solana.secretKey.slice(0, 32);\n this.solanaSeed = seed;\n this.solanaSigner = new SolanaSigner(seed, identity.solana.publicKey);\n this.channelManager.registerChainSigner('solana', this.solanaSigner);\n }\n\n // Mina: only present when mina-signer is installed (optional dep).\n if (identity.mina.publicKey) {\n this.minaPrivateKey = identity.mina.privateKey;\n // Pass the configured GraphQL URL so the signer can read the channel's\n // on-chain `depositTotal` and bind the conserved `balanceB = depositTotal\n // − balanceA` commitment that a funded zkApp requires (connector#133);\n // without it Mina claims use the legacy balanceB=0 form and a funded zkApp\n // rejects them (F06 - Invalid zk-SNARK proof on claim).\n this.minaSigner = new MinaSigner(\n identity.mina.privateKey,\n identity.mina.publicKey,\n this.config.minaChannel?.graphqlUrl\n ? { graphqlUrl: this.config.minaChannel.graphqlUrl }\n : undefined\n );\n this.channelManager.registerChainSigner('mina', this.minaSigner);\n }\n }\n\n /**\n * Starts the ToonClient.\n *\n * This will:\n * 1. Initialize HTTP mode components (runtime client, admin client, bootstrap, monitor)\n * 2. Bootstrap the network (discover peers, register, and open channels)\n * 3. Start monitoring relay for new peers (kind:10032 events)\n *\n * @returns Result with number of peers discovered and mode\n * @throws {ToonClientError} If client is already started\n * @throws {ToonClientError} If initialization fails\n */\n async start(): Promise<ToonStartResult> {\n if (this.state !== null) {\n throw new ToonClientError('Client already started', 'INVALID_STATE');\n }\n\n try {\n // Create channel manager FIRST (before bootstrap) so it can sign claims during settlement\n if (this.evmSigner) {\n const store = this.config.channelStorePath\n ? new JsonFileChannelStore(this.config.channelStorePath)\n : undefined;\n this.channelManager = new ChannelManager(this.evmSigner, store);\n\n // When constructed from a mnemonic, derive the non-secp256k1 keys\n // (Solana Ed25519, Mina Pallas) and register their signers so the\n // client can settle on those chains too. Derivation is async (dynamic\n // imports + optional deps), hence done here rather than in the\n // synchronous constructor. Gracefully skips a chain whose optional dep\n // is absent (e.g. mina-signer) — deriveFullIdentity leaves it empty.\n if (this.config.mnemonic) {\n await this.registerMnemonicChainSigners(\n this.config.mnemonic,\n this.config.mnemonicAccountIndex ?? 0\n );\n }\n }\n\n // Initialize HTTP mode components\n const initialization = await initializeHttpMode(this.config);\n\n const { bootstrapService, discoveryTracker, runtimeClient, btpClient } =\n initialization;\n\n // Wire claim signer to bootstrap service if we have channel manager\n if (this.channelManager) {\n const cm = this.channelManager;\n const nostrPubkey = this.getPublicKey();\n // Derive default chain context from config (first supported chain)\n const defaultChainCtx = this.getDefaultChainContext();\n bootstrapService.setClaimSigner(\n async (channelId: string, amount: bigint) => {\n // Track the channel if not already tracked\n if (!cm.isTracking(channelId)) {\n cm.trackChannel(channelId, defaultChainCtx);\n }\n // Sign balance proof and build full claim message with the\n // chain-appropriate signer (the channel is tracked above, so a\n // non-EVM channel yields its correct envelope, not an EVM claim).\n const proof = await cm.signBalanceProof(channelId, amount);\n const signer = cm.getSignerForChannel(channelId);\n return signer.buildClaimMessage(proof, nostrPubkey);\n }\n );\n }\n\n // Start bootstrap process (discover peers, register with settlement, announce)\n const bootstrapResults = await bootstrapService.bootstrap();\n\n // Store negotiation metadata from bootstrap results for lazy channel opening\n for (const result of bootstrapResults) {\n if (result.negotiatedChain && result.settlementAddress) {\n const chainType = result.negotiatedChain.split(':')[0] ?? 'evm';\n const parts = result.negotiatedChain.split(':');\n // Accept 3-part `evm:{network}:{chainId}` and 2-part `evm:{chainId}`.\n const chainId =\n parts.length >= 3\n ? parseInt(parts[2] ?? '0', 10)\n : parts.length >= 2\n ? parseInt(parts[1] ?? '0', 10)\n : 0;\n const r = result as typeof result & {\n tokenAddress?: string;\n tokenNetwork?: string;\n };\n this.peerNegotiations.set(result.registeredPeerId, {\n chain: result.negotiatedChain,\n chainType,\n chainId: isNaN(chainId) ? 0 : chainId,\n settlementAddress: result.settlementAddress,\n tokenAddress: r.tokenAddress,\n tokenNetwork: r.tokenNetwork,\n });\n } else if (\n result.registeredPeerId &&\n !this.peerNegotiations.has(result.registeredPeerId)\n ) {\n // Lightweight client fallback: bootstrap discovered the peer but didn't\n // negotiate a chain (no connector admin to register with). Extract the\n // peer's settlement info from their kind:10032 event data and match\n // against our supported chains.\n const peerInfo = result.peerInfo as typeof result.peerInfo & {\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n };\n const peerChains = peerInfo.supportedChains ?? [];\n const ourChains = this.config.supportedChains ?? [];\n // Find the first chain both sides support\n const matchedChain =\n ourChains.find((c) => peerChains.includes(c)) ?? ourChains[0];\n if (matchedChain) {\n const peerAddr = peerInfo.settlementAddresses?.[matchedChain];\n const parts = matchedChain.split(':');\n const chainId =\n parts.length >= 3\n ? parseInt(parts[2] ?? '0', 10)\n : parts.length >= 2\n ? parseInt(parts[1] ?? '0', 10)\n : 0;\n if (peerAddr) {\n this.peerNegotiations.set(result.registeredPeerId, {\n chain: matchedChain,\n chainType: parts[0] ?? 'evm',\n chainId: isNaN(chainId) ? 0 : chainId,\n settlementAddress: peerAddr,\n tokenAddress:\n peerInfo.preferredTokens?.[matchedChain] ??\n this.config.preferredTokens?.[matchedChain],\n tokenNetwork:\n peerInfo.tokenNetworks?.[matchedChain] ??\n this.config.tokenNetworks?.[matchedChain],\n });\n }\n }\n }\n // Track any pre-opened channels (backwards compat)\n if (\n this.channelManager &&\n result.channelId &&\n !this.channelManager.isTracking(result.channelId)\n ) {\n const chainCtx = this.getChainContext(result.negotiatedChain);\n this.channelManager.trackChannel(result.channelId, chainCtx);\n }\n }\n\n // Wire on-chain channel client into ChannelManager for lazy opens\n if (this.channelManager && initialization.onChainChannelClient) {\n this.onChainChannelClient = initialization.onChainChannelClient;\n this.channelManager.setChannelClient(\n initialization.onChainChannelClient\n );\n\n // Late-bind the Solana channel config: the program/RPC/token come from\n // config, the Ed25519 keypair from the mnemonic-derived Solana seed.\n // Requires both a Solana seed (mnemonic-derived) and explicit\n // solanaChannel config — otherwise the on-chain Solana opener has no\n // program/RPC and would throw at openChannel time.\n if (this.config.solanaChannel && this.solanaSeed) {\n initialization.onChainChannelClient.setSolanaConfig({\n rpcUrl: this.config.solanaChannel.rpcUrl,\n programId: this.config.solanaChannel.programId,\n tokenMint: this.config.solanaChannel.tokenMint,\n challengeDuration: this.config.solanaChannel.challengeDuration,\n deposit: this.config.solanaChannel.deposit,\n keypair: this.solanaSeed,\n });\n }\n\n // Late-bind the Mina channel config (parallel to Solana). The\n // graphqlUrl + zkAppAddress come from config; the Mina private key from\n // the mnemonic-derived Mina identity (same key as the registered Mina\n // signer). Requires both a Mina private key (mnemonic-derived, present\n // only when `mina-signer` is installed) and explicit minaChannel config.\n //\n // openMinaChannel now performs a REAL on-chain channel open\n // (initialize + optional deposit) on the deployed zkApp so the\n // connector's getChannelState reports `opened` and the claim verifies +\n // stores (parity with Solana). Full on-chain Mina SETTLE remains gated by\n // the connector-side settlement-executor (same blocker as Solana).\n if (this.config.minaChannel && this.minaPrivateKey) {\n initialization.onChainChannelClient.setMinaConfig({\n graphqlUrl: this.config.minaChannel.graphqlUrl,\n ...(this.config.minaChannel.zkAppAddress !== undefined\n ? { zkAppAddress: this.config.minaChannel.zkAppAddress }\n : {}),\n ...(this.config.minaChannel.autoDeploy !== undefined\n ? { autoDeploy: this.config.minaChannel.autoDeploy }\n : {}),\n privateKey: this.minaPrivateKey,\n ...(this.config.minaChannel.challengeDuration !== undefined\n ? { challengeDuration: this.config.minaChannel.challengeDuration }\n : {}),\n ...(this.config.minaChannel.tokenId !== undefined\n ? { tokenId: this.config.minaChannel.tokenId }\n : {}),\n ...(this.config.minaChannel.deposit !== undefined\n ? { deposit: this.config.minaChannel.deposit }\n : {}),\n ...(this.config.minaChannel.networkId !== undefined\n ? { networkId: this.config.minaChannel.networkId }\n : {}),\n });\n }\n }\n\n // Store state\n this.state = {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n peersDiscovered: bootstrapResults.length,\n btpClient: btpClient ?? undefined,\n };\n\n return {\n peersDiscovered: bootstrapResults.length,\n mode: 'http',\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to start client',\n 'INITIALIZATION_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Publishes a Nostr event to the relay via ILP payment.\n *\n * The event must already be finalized (signed with id, pubkey, sig).\n *\n * @param event - Signed Nostr event to publish\n * @param options - Optional options including destination and signed balance proof claim\n * @returns Result with success status and event ID\n * @throws {ToonClientError} If client is not started\n * @throws {ToonClientError} If event publishing fails\n */\n async publishEvent(\n event: NostrEvent,\n options?: {\n destination?: string;\n claim?: SignedBalanceProof;\n ilpAmount?: bigint;\n /** HTTP request-target the payment-proxy replays (default '/write', the\n * relay; use '/store' for the Arweave store/DVM backend). */\n proxyPath?: string;\n }\n ): Promise<PublishEventResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n try {\n // Encode event to TOON format. This is used ONLY to PRICE the write\n // (basePricePerByte * encoded size); the bytes sent on the wire are the\n // HTTP store-write envelope built below.\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size.\n // Callers may override via options.ilpAmount (e.g. 0n for free relays).\n const basePricePerByte = 10n;\n const amount =\n options?.ilpAmount !== undefined\n ? String(options.ilpAmount)\n : String(BigInt(toonData.length) * basePricePerByte);\n\n // The deployed connector is a payment-proxy: it terminates the paid write\n // as HTTP-in-ILP, decoding the ILP PREPARE `data` as a literal HTTP/1.1\n // request and reverse-proxying it to the relay store's `POST /write`. The\n // wire data must therefore be a full HTTP request envelope carrying the\n // signed event as `{\"event\": <event object>}` JSON — NOT the bare TOON\n // bytes (those make the proxy reject with F01 - malformed request-line).\n // See utils/store-envelope.ts. `sendSwapPacket` (swap peer swaps) is a separate\n // surface with a raw-TOON contract and is intentionally NOT wrapped here.\n const writeData = buildStoreWriteEnvelope(event, options?.proxyPath);\n\n // Use provided destination or fall back to config default\n const destination =\n options?.destination ?? this.config.destinationAddress;\n\n // Resolve the active paid-write transport (proxy ILP-over-HTTP or BTP).\n const transport = this.getClaimTransport();\n\n const claimMessage = await this.resolveClaimForDestination(\n destination,\n BigInt(amount),\n options?.claim\n );\n\n const response = await transport.sendIlpPacketWithClaim(\n {\n destination,\n amount,\n data: toBase64(writeData),\n },\n claimMessage\n );\n\n if (!response.accepted) {\n return {\n success: false,\n error: `Event rejected: ${response.code} - ${response.message}`,\n };\n }\n\n // The connector is a payment-proxy: an ACCEPTED ILP FULFILL only means\n // the PAYMENT cleared, not that the relay STORE persisted the event. The\n // FULFILL `data` carries the relay's verbatim HTTP/1.1 response, so a\n // write can FAIL (e.g. `HTTP/1.1 404 Not Found`) inside a successful\n // FULFILL. Parse the envelope and fail the publish on a non-2xx status so\n // we never report a fake `eventId` for a write that did not persist.\n //\n // DEFENSIVE: if the FULFILL data is not HTTP-enveloped (legacy / non-proxy\n // relays may return bare data), `isHttp` is false and we preserve the\n // prior behavior (treat an accepted FULFILL as success).\n if (response.data) {\n const httpResult = parseFulfillHttp(response.data);\n if (httpResult.isHttp && (httpResult.status < 200 || httpResult.status >= 300)) {\n const detail = httpResult.body ? ` - ${httpResult.body}` : '';\n return {\n success: false,\n error: `Write failed: relay returned HTTP ${httpResult.status} ${httpResult.statusText}`.trimEnd() + detail,\n };\n }\n }\n\n return {\n success: true,\n eventId: event.id,\n data: response.data,\n };\n } catch (error) {\n console.error(\n '[ToonClient.publishEvent] ROOT CAUSE:',\n String(error),\n error instanceof Error ? error.stack : ''\n );\n throw new ToonClientError(\n 'Failed to publish event',\n 'PUBLISH_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Payment-aware HTTP fetch over TOON (issue #50). A `fetch()`-like method that\n * makes paying for an HTTP resource transparent:\n *\n * 1. Issues the HTTP request to `url`.\n * 2. On `402`, parses the x402 `accepts` array and selects the\n * `toon-channel` entry (see {@link Http402Client} for the wire shape).\n * 3. Opens/reuses a payment channel for the entry's ILP destination (via\n * ChannelManager), signs a balance proof for the demanded price, and\n * re-sends the SAME HTTP request as a transparent HTTP-in-ILP packet to\n * the connector's `POST /ilp` (via {@link HttpIlpClient}), with the claim\n * in the `ILP-Payment-Channel-Claim` header.\n * 4. Reconstructs and returns a standard Web `Response` from the FULFILL\n * `data`. The caller never sees ILP.\n *\n * If the origin offers no `toon-channel` entry, the original `402` Response is\n * returned unchanged (the caller sees the vanilla x402 challenge).\n *\n * The channel/claim plumbing is wired to the live ChannelManager + per-chain\n * signer via `resolveClaimForDestination` — identical to `publishEvent`. The\n * `amount` paid comes from the selected x402 entry (the resource's price).\n *\n * @throws {ToonClientError} If the client is not started.\n * @throws {ConnectorError} If the connector rejects the payment or returns no\n * HTTP payload.\n */\n async h402Fetch(url: string, opts?: H402FetchOptions): Promise<Response> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n // Pay only when a channel manager is configured; otherwise the engine still\n // probes and transparently surfaces the vanilla 402 (no resolveClaim hook).\n const client = new Http402Client({\n ...(this.channelManager\n ? {\n resolveClaim: (destination: string, amount: bigint) =>\n this.resolveClaimForDestination(destination, amount),\n }\n : {}),\n });\n\n return client.fetch(url, opts);\n }\n\n /**\n * Sends a raw swap ILP packet (Story 12.5) to a swap peer with an attached\n * balance-proof claim. This is a lower-level surface than `publishEvent`:\n * it forwards the raw `IlpSendResult` so the sender (`streamSwap()`) can\n * decode FULFILL metadata itself.\n *\n * Claim resolution mirrors `publishEvent`:\n * (a) explicit `params.claim` -> use it,\n * (b) `channelManager` present -> auto-open + auto-sign for the peer\n * matching `destination`,\n * (c) neither -> throw MISSING_CLAIM.\n *\n * A caller may supply a sender-chosen 32-byte `executionCondition`\n * (`C = sha256(P)`, one fresh preimage per packet — toon-client#350,\n * rolling-swap spec §3 R1/R2) and an explicit `expiresAt` (R7). Both are\n * set on the wire by either transport (HTTP `POST /ilp` and BTP), and on\n * FULFILL the transport verifies `sha256(fulfillment) == condition` —\n * a mismatch comes back as `accepted: false` (code F99), never a silent\n * accept. Omitting them keeps today's zero-condition legacy packet.\n *\n * @throws {ToonClientError} INVALID_STATE / NO_ILP_TRANSPORT / MISSING_CLAIM\n */\n async sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n timeout?: number;\n claim?: SignedBalanceProof;\n /** Sender-chosen 32-byte execution condition; absent/zero = legacy. */\n executionCondition?: Uint8Array;\n /** Explicit ILP expiry; defaults to `now + timeout` in the transport. */\n expiresAt?: Date;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n const transport = this.getClaimTransport();\n\n const claimMessage = await this.resolveClaimForDestination(\n params.destination,\n params.amount,\n params.claim\n );\n\n return transport.sendIlpPacketWithClaim(\n {\n destination: params.destination,\n amount: String(params.amount),\n data: toBase64(params.toonData),\n timeout: params.timeout ?? 30000,\n ...(params.executionCondition\n ? { executionCondition: params.executionCondition }\n : {}),\n ...(params.expiresAt ? { expiresAt: params.expiresAt } : {}),\n },\n claimMessage as unknown as Record<string, unknown>\n );\n }\n\n /**\n * Build a BTP claim message from a pre-signed balance proof using the\n * CHAIN-APPROPRIATE signer.\n *\n * The explicit-claim path (caller signs the balance proof, then passes\n * `{ claim }`) must wrap the proof with the signer matching the channel's\n * chain. Hardcoding `EvmSigner.buildClaimMessage` here produced an EVM\n * `BTPClaimMessage` for a Solana/Mina balance proof — no `blockchain`\n * discriminator and the base58 channel account placed in the EVM\n * `channelId` field — which the connector's inbound validator classifies\n * as EVM and rejects with F06 (`Invalid channelId format`).\n *\n * When the proof's `channelId` is tracked we use\n * `getSignerForChannel(channelId).buildClaimMessage`, which emits the\n * correct per-chain envelope (e.g. `blockchain:'solana'` + base58\n * `channelAccount`). When it is not tracked we fall back to the EVM signer\n * to preserve prior behavior for lightweight/EVM-only callers.\n *\n * EVM output is byte-identical to the previous hardcoded path (the EVM\n * adapter in `getSignerForChannel` delegates to the same\n * `EvmSigner.buildClaimMessage`).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- claim message is opaque forwarded type\n private buildClaimMessageForProof(claim: SignedBalanceProof): any {\n if (this.channelManager?.isTracking(claim.channelId)) {\n const signer = this.channelManager.getSignerForChannel(claim.channelId);\n return signer.buildClaimMessage(claim, this.getPublicKey());\n }\n return EvmSigner.buildClaimMessage(claim, this.getPublicKey());\n }\n\n /**\n * Resolve the ILP transport for a paid (claim-bearing) write.\n *\n * The connector is a payment-proxy: paid writes carry an ILP PREPARE plus the\n * signed payment-channel claim. Either transport speaks the SAME claim\n * contract — the BTP `payment-channel-claim` protocolData entry and the\n * ILP-over-HTTP `ILP-Payment-Channel-Claim` header serialize the same claim\n * JSON — so we route through whichever transport is ACTIVE rather than\n * hard-requiring BTP.\n *\n * Selection (mirrors `modes/http.ts` runtime-client precedence):\n * 1. `runtimeClient` when it implements `sendIlpPacketWithClaim` — this is\n * the HttpIlpClient (proxy `POST /ilp`) when a `proxyUrl`/\n * `connectorHttpEndpoint` is configured, else the BtpRuntimeClient.\n * 2. `btpClient` as an explicit fallback (always present when `btpUrl` is set).\n *\n * The level-3 `HttpRuntimeClient` (connector-admin HTTP, no `btpUrl` AND no\n * proxy) does NOT implement `sendIlpPacketWithClaim`; in that case there is no\n * paid-write transport and we throw a clear, actionable error.\n *\n * @throws {ToonClientError} NO_ILP_TRANSPORT when no active transport can send\n * a packet+claim.\n */\n private getClaimTransport(): {\n // IlpSendParams: both built-in transports (HttpIlpClient / BtpRuntimeClient)\n // accept the sender-chosen `executionCondition` + explicit `expiresAt`\n // extensions (toon-client#350) and enforce FULFILL preimage verification.\n sendIlpPacketWithClaim(\n params: IlpSendParams,\n claim: unknown\n ): Promise<IlpSendResult>;\n } {\n const state = this.state;\n if (!state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n const candidates: (IlpClient | BtpRuntimeClient | undefined)[] = [\n state.runtimeClient,\n state.btpClient,\n ];\n for (const candidate of candidates) {\n if (\n candidate &&\n typeof (candidate as IlpClient).sendIlpPacketWithClaim === 'function'\n ) {\n return candidate as ReturnType<ToonClient['getClaimTransport']>;\n }\n }\n throw new ToonClientError(\n 'No ILP transport for paid writes. Configure `proxyUrl`/`connectorHttpEndpoint` ' +\n '(route through the connector proxy over ILP-over-HTTP) or `btpUrl` (BTP socket).',\n 'NO_ILP_TRANSPORT'\n );\n }\n\n /**\n * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.\n */\n private async resolveClaimForDestination(\n destination: string,\n amount: bigint,\n explicitClaim?: SignedBalanceProof\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- claim message is opaque forwarded type\n ): Promise<any> {\n if (explicitClaim) {\n return this.buildClaimMessageForProof(explicitClaim);\n }\n if (this.channelManager) {\n const peerId = this.resolvePeerId(destination);\n const negotiation = this.peerNegotiations.get(peerId);\n if (!negotiation) {\n throw new ToonClientError(\n `No negotiation metadata for peer \"${peerId}\" — was bootstrap completed?`,\n 'PEER_NOT_NEGOTIATED'\n );\n }\n const channelId = await this.channelManager.ensureChannel(\n peerId,\n negotiation\n );\n const proof = await this.channelManager.signBalanceProof(\n channelId,\n amount\n );\n const signer = this.channelManager.getSignerForChannel(channelId);\n return signer.buildClaimMessage(proof, this.getPublicKey());\n }\n throw new ToonClientError(\n 'No claim provided and no channel manager configured',\n 'MISSING_CLAIM'\n );\n }\n\n /**\n * Signs a balance proof for the given channel with the specified amount.\n * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param amount - Additional amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws {ToonClientError} If no EVM signer configured or channel not tracked\n */\n async signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof> {\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No EVM signer configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n return this.channelManager.signBalanceProof(channelId, amount);\n }\n\n /**\n * Eagerly open (or return existing) payment channel for the given destination.\n *\n * Channels are normally opened lazily on the first `publishEvent()` /\n * `sendSwapPacket()` call. This method exposes the lazy-open path so\n * callers (and E2E tests) that need a tracked `channelId` BEFORE publishing\n * can force the open. Idempotent — returns the existing channel ID for the\n * peer if one is already open.\n *\n * @param destination - Optional ILP destination address. Defaults to\n * `config.destinationAddress`.\n * @returns The channel ID of the (now) open channel.\n * @throws {ToonClientError} If client not started, no channel manager\n * configured, or peer negotiation metadata missing.\n */\n async openChannel(destination?: string): Promise<string> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No channel manager configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n const dest = destination ?? this.config.destinationAddress;\n if (!dest) {\n throw new ToonClientError(\n 'No destination provided and no default destinationAddress configured.',\n 'NO_DESTINATION'\n );\n }\n const peerId = this.resolvePeerId(dest);\n const negotiation = this.peerNegotiations.get(peerId);\n if (!negotiation) {\n throw new ToonClientError(\n `No negotiation metadata for peer \"${peerId}\" — was bootstrap completed?`,\n 'PEER_NOT_NEGOTIATED'\n );\n }\n return this.channelManager.ensureChannel(peerId, negotiation);\n }\n\n /**\n * Gets list of tracked payment channel IDs.\n */\n getTrackedChannels(): string[] {\n return this.channelManager?.getTrackedChannels() ?? [];\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getChannelNonce(channelId: string): number {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getNonce(channelId);\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getChannelCumulativeAmount(channelId: string): bigint {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getCumulativeAmount(channelId);\n }\n\n /**\n * Gets the on-chain deposit total (locked collateral) for a tracked channel.\n * The available (spendable) balance is this minus the cumulative spent amount.\n */\n getChannelDepositTotal(channelId: string): bigint {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getDepositTotal(channelId);\n }\n\n /**\n * Deposit additional collateral into an open channel. `amount` is the delta to\n * add (base units, decimal string or bigint). The daemon signs its own tx; no\n * key material leaves the client. Reads the current tracked deposit, performs\n * the on-chain deposit, updates the tracked total, and returns the new total.\n * EVM is live; Solana/Mina deposit lands in a follow-up.\n */\n async depositToChannel(\n channelId: string,\n amount: string | bigint\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }> {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n if (!this.onChainChannelClient) {\n throw new Error('On-chain channel client not configured (no chainRpcUrls).');\n }\n const delta = BigInt(amount);\n if (delta <= 0n) throw new Error('Deposit amount must be positive.');\n const currentDeposit = this.channelManager.getDepositTotal(channelId);\n const result = await this.onChainChannelClient.depositToChannel(channelId, delta, {\n currentDeposit,\n });\n this.channelManager.setDepositTotal(channelId, result.depositTotal);\n return {\n channelId,\n ...(result.txHash ? { txHash: result.txHash } : {}),\n depositTotal: result.depositTotal.toString(),\n };\n }\n\n /**\n * Close a channel to begin the settlement grace period (first half of\n * withdraw). Records `closedAt`/`settleableAt` (unix seconds) on the tracked\n * channel — persisted, so the grace timer survives a daemon restart. Spends\n * on-chain. EVM today; Solana/Mina are follow-ups.\n */\n async closeChannel(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string; closedAt: string; settleableAt: string }> {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n if (!this.onChainChannelClient) {\n throw new Error('On-chain channel client not configured (no chainRpcUrls).');\n }\n const r = await this.onChainChannelClient.closeChannel(channelId);\n this.channelManager.setChannelClosed(channelId, r.closedAt, r.settleableAt);\n return {\n channelId,\n ...(r.txHash ? { txHash: r.txHash } : {}),\n closedAt: r.closedAt.toString(),\n settleableAt: r.settleableAt.toString(),\n };\n }\n\n /**\n * Settle a closed channel to release collateral (second half of withdraw).\n * THE time guard: never settle before `settleableAt`. A too-early call throws\n * a retryable error (carrying the remaining seconds) BEFORE spending gas — the\n * contract would revert anyway. Spends on-chain. EVM today.\n */\n async settleChannel(channelId: string): Promise<{ channelId: string; txHash?: string }> {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n if (!this.onChainChannelClient) {\n throw new Error('On-chain channel client not configured (no chainRpcUrls).');\n }\n const settleableAt = this.channelManager.getSettleableAt(channelId);\n if (settleableAt === undefined) {\n throw new Error(`Channel \"${channelId}\" is not closed; call closeChannel first.`);\n }\n const nowSec = BigInt(Math.floor(Date.now() / 1000));\n if (nowSec < settleableAt) {\n const remaining = settleableAt - nowSec;\n throw Object.assign(\n new Error(\n `Channel \"${channelId}\" is not settleable yet — ${remaining}s remain (settleable at ${settleableAt}).`\n ),\n { name: 'SettleTooEarlyError', retryable: true, settleableAt: settleableAt.toString() }\n );\n }\n const r = await this.onChainChannelClient.settleChannel(channelId);\n this.channelManager.setChannelSettled(channelId, nowSec);\n return { channelId, ...(r.txHash ? { txHash: r.txHash } : {}) };\n }\n\n /**\n * Submit a receive-side swap settlement bundle on-chain (toon-client#352).\n * The bundle comes from the sdk's `buildSettlementTx` over persisted,\n * verified chain-B claims (`buildSwapSettlements`); this signs it with the\n * client's EVM account (the claim recipient) and broadcasts it.\n *\n * Env-gated seam: EVM only, and only when `chainRpcUrls[bundle.chain]` is\n * configured — otherwise this throws a clear config error and callers\n * surface a built-not-submitted result. Solana submission and the Mina\n * receive-side co-sign path are explicit follow-ups (see\n * swap/settle-received-claims.ts module doc).\n */\n async settleSwapBundle(\n bundle: SettlementBundle\n ): Promise<SubmitEvmSettlementResult> {\n if (bundle.chainKind === 'mina') {\n // Mina receive-side redemption (#357): reads the live channel state,\n // produces the recipient's co-signature, and drives the dual-party\n // `claimFromChannel` (o1js proving). Config-gated on minaChannel.graphqlUrl\n // + a derived Mina key; fails closed (never a silent pass) with a stable\n // MinaSettlementError code that the daemon surfaces.\n if (!this.minaPrivateKey) {\n throw new Error(\n 'Mina signer not configured (no mnemonic / mina-signer) — cannot co-sign the receive-side claim.'\n );\n }\n const makerSignature = parseMakerMinaSignature(\n this.config.swapMinaMakerSignatures?.[bundle.channelId]\n );\n const { txHash } = await submitMinaSettlement(bundle, {\n recipientPrivateKey: this.minaPrivateKey,\n ...(this.config.minaChannel?.graphqlUrl\n ? { graphqlUrl: this.config.minaChannel.graphqlUrl }\n : {}),\n ...(makerSignature ? { makerSignature } : {}),\n });\n return { txHash };\n }\n if (bundle.chainKind !== 'evm') {\n throw new Error(\n `Swap settlement submission for ${bundle.chainKind} (${bundle.chain}) is not wired yet — EVM only today.`\n );\n }\n const rpcUrl = this.config.chainRpcUrls?.[bundle.chain];\n if (!rpcUrl) {\n throw new Error(\n `No RPC URL configured for chain \"${bundle.chain}\" — add it to chainRpcUrls to enable swap settlement submission.`\n );\n }\n if (!this.evmSigner) {\n throw new Error('EVM signer not configured (no evmPrivateKey/mnemonic).');\n }\n return submitEvmSettlement(bundle, {\n rpcUrl,\n account: this.evmSigner.account,\n });\n }\n\n /** Where a tracked channel sits in the withdraw journey. */\n getChannelCloseState(channelId: string): 'open' | 'closing' | 'settleable' | 'settled' {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getChannelCloseState(channelId);\n }\n\n getSettleableAt(channelId: string): bigint | undefined {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getSettleableAt(channelId);\n }\n\n /**\n * Re-hydrate a RESUMED channel's on-chain deposit. Persisted channel state\n * omits `depositTotal`, so after a daemon restart the tracked deposit is `0`\n * and the wallet shows 0 spendable even though real collateral is locked\n * on-chain. Read the participant's `deposit` from the `participants` mapping\n * and update the tracked total so `depositTotal - cumulativeAmount` is right.\n * Best-effort by caller (await + catch); returns the on-chain deposit, or\n * `undefined` when it can't be read (no channel manager / on-chain client /\n * EVM address).\n */\n async rehydrateChannelDeposit(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined> {\n if (!this.channelManager || !this.onChainChannelClient) return undefined;\n const participant = this.getEvmAddress();\n if (!participant) return undefined;\n const { deposit } = await this.onChainChannelClient.readEvmParticipantState({\n chain: opts.chain,\n tokenNetworkAddress: opts.tokenNetworkAddress,\n channelId,\n participant,\n });\n this.channelManager.setDepositTotal(channelId, deposit);\n return deposit;\n }\n\n /**\n * Read the on-chain settlement-token balance of this client's OWN wallet on\n * each configured chain (EVM token, Solana SPL, native MINA). A free read — no\n * signing, no payment. Best-effort per chain: a chain whose config is absent or\n * whose RPC read fails is omitted rather than failing the whole result, so the\n * wallet view degrades gracefully. Available after `start()` (Solana/Mina keys\n * are derived there).\n */\n async getBalances(): Promise<WalletBalance[]> {\n const out: WalletBalance[] = [];\n\n // EVM: read the settlement token (preferredTokens) for the client's\n // settlement chain. `supportedChains` is a union with the network PRESET\n // first (see applyDefaults), so the preset's primary EVM chain (e.g.\n // base-sepolia on devnet) sorts ahead of an explicitly-configured chain\n // like `evm:anvil:31337`. Picking the FIRST evm key would read the preset\n // chain's token — a different contract with a 0 balance — even though the\n // faucet funds, and channels live on, the settlement chain. So prefer the\n // evm key that is the actual settlement chain (present in\n // `settlementAddresses`), falling back to the first usable evm chain.\n const evmAddress = this.getEvmAddress();\n const rpcUrls = this.config.chainRpcUrls;\n const tokens = this.config.preferredTokens;\n if (evmAddress && rpcUrls && tokens) {\n const chainKeys = this.config.supportedChains ?? Object.keys(rpcUrls);\n const usableEvm = (c: string): boolean =>\n c.startsWith('evm') && Boolean(rpcUrls[c]) && Boolean(tokens[c]);\n const settlementKeys = Object.keys(this.config.settlementAddresses ?? {});\n const chainKey =\n settlementKeys.find((c) => usableEvm(c)) ?? chainKeys.find(usableEvm);\n const rpcUrl = chainKey ? rpcUrls[chainKey] : undefined;\n const tokenAddress = chainKey ? tokens[chainKey] : undefined;\n if (chainKey && rpcUrl && tokenAddress) {\n try {\n out.push(await readEvmTokenBalance({ rpcUrl, chainKey, tokenAddress, owner: evmAddress }));\n } catch {\n /* best-effort: drop EVM on read failure */\n }\n }\n }\n\n // Solana: SPL balance of the negotiated token mint.\n const solAddress = this.getSolanaAddress();\n const sol = this.config.solanaChannel;\n if (solAddress && sol?.rpcUrl && sol.tokenMint) {\n try {\n out.push(\n await readSolanaTokenBalance({ rpcUrl: sol.rpcUrl, mint: sol.tokenMint, owner: solAddress })\n );\n } catch {\n /* best-effort */\n }\n }\n\n // Mina: native MINA balance via GraphQL.\n const minaAddress = this.getMinaAddress();\n const mina = this.config.minaChannel;\n if (minaAddress && mina?.graphqlUrl) {\n try {\n out.push(await readMinaBalance({ graphqlUrl: mina.graphqlUrl, owner: minaAddress }));\n } catch {\n /* best-effort */\n }\n }\n\n return out;\n }\n\n /**\n * The FULL multi-chain wallet view (#299): for every chain the identity is\n * configured for, the native coin (ETH / SOL / MINA) AND every configured\n * token (USDC), grouped per chain with the identity's address on that chain.\n * A superset of {@link getBalances} — which stays scoped to the channel's\n * settlement token — kept as a separate reader so channel-settlement callers\n * are unaffected.\n *\n * FREE: read-only RPC, no signing, no payment. Works on an UNSTARTED client:\n * the Solana/Mina addresses (which the signers only register during\n * `start()`) are derived on demand from the retained mnemonic — the SAME keys\n * `start()` would register and that `rig fund` prints — so all configured\n * chains appear even before a start. Best-effort per chain: an unreachable\n * RPC yields `{ unreadable: true }` for that chain, never failing the others.\n */\n async getWalletBalances(): Promise<WalletChainBalances[]> {\n const sources: WalletBalanceSources = {};\n\n // Solana/Mina keys are only registered as signers during start(); derive\n // them from the retained mnemonic on demand so an unstarted client (e.g.\n // `rig balance`) still reports every configured chain. Derived once, lazily.\n let derived: Awaited<ReturnType<typeof deriveFullIdentity>> | undefined;\n let derivedTried = false;\n const ensureDerived = async (): Promise<typeof derived> => {\n if (derivedTried) return derived;\n derivedTried = true;\n if (this.config.mnemonic) {\n derived = await deriveFullIdentity(\n this.config.mnemonic,\n this.config.mnemonicAccountIndex ?? 0\n );\n }\n return derived;\n };\n\n // EVM: native ETH + settlement USDC. Pick the settlement chain key the same\n // way getBalances does (settlement chain wins over the preset primary).\n const evmAddress = this.getEvmAddress();\n const rpcUrls = this.config.chainRpcUrls;\n const tokens = this.config.preferredTokens;\n if (evmAddress && rpcUrls) {\n const usableEvm = (c: string): boolean => c.startsWith('evm') && Boolean(rpcUrls[c]);\n const settlementKeys = Object.keys(this.config.settlementAddresses ?? {});\n const chainKeys = this.config.supportedChains ?? Object.keys(rpcUrls);\n const chainKey = settlementKeys.find(usableEvm) ?? chainKeys.find(usableEvm);\n if (chainKey && rpcUrls[chainKey]) {\n sources.evm = {\n chainKey,\n rpcUrl: rpcUrls[chainKey],\n owner: evmAddress,\n ...(tokens?.[chainKey] ? { tokenAddress: tokens[chainKey] } : {}),\n };\n }\n }\n\n // Solana: native SOL + SPL USDC (the negotiated mint).\n const sol = this.config.solanaChannel;\n if (sol?.rpcUrl) {\n const solAddress = this.getSolanaAddress() ?? (await ensureDerived())?.solana.publicKey;\n if (solAddress) {\n sources.solana = {\n chainKey: 'solana',\n rpcUrl: sol.rpcUrl,\n owner: solAddress,\n ...(sol.tokenMint ? { tokenMint: sol.tokenMint } : {}),\n };\n }\n }\n\n // Mina: native MINA plus, when the deployment settles a custom token, that\n // token's balance (USDC) — read via the derived/explicit `minaChannel.tokenId`\n // (core preset or announce, see resolveNetworkTopology/deriveMinaChannel). A\n // fresh client with no explicit `config.minaChannel` still gets both.\n const mina = this.config.minaChannel;\n if (mina?.graphqlUrl) {\n const minaAddress = this.getMinaAddress() ?? (await ensureDerived())?.mina.publicKey;\n if (minaAddress) {\n sources.mina = {\n chainKey: 'mina',\n graphqlUrl: mina.graphqlUrl,\n owner: minaAddress,\n ...(mina.tokenId ? { tokenId: mina.tokenId } : {}),\n };\n }\n }\n\n return readWalletBalances(sources);\n }\n\n /**\n * Resolves an ILP destination address to a peer ID.\n * Convention: destination \"g.toon.peer1\" → peerId \"peer1\" (last segment).\n * Falls back to first known peer if no match.\n */\n private resolvePeerId(destination: string): string {\n // Check if destination matches a known peer's ILP address pattern\n const segments = destination.split('.');\n const lastSegment = segments[segments.length - 1] ?? '';\n\n // Direct match against peerNegotiations keys\n if (lastSegment && this.peerNegotiations.has(lastSegment)) {\n return lastSegment;\n }\n\n // Try \"nostr-\" prefixed peer IDs (convention: nostr-{pubkey_prefix})\n for (const peerId of this.peerNegotiations.keys()) {\n if (\n destination.endsWith(`.${peerId}`) ||\n destination.endsWith(`.${peerId.replace('nostr-', '')}`)\n ) {\n return peerId;\n }\n }\n\n // Fallback: return first peer\n const firstPeerResult = this.peerNegotiations.keys().next();\n if (!firstPeerResult.done && firstPeerResult.value)\n return firstPeerResult.value;\n\n throw new ToonClientError(\n `Cannot resolve peer for destination: ${destination}`,\n 'PEER_NOT_FOUND'\n );\n }\n\n /**\n * Extracts chain context (chainId + tokenNetworkAddress) from a chain key like 'evm:base:421614'.\n */\n private getChainContext(\n negotiatedChain?: string\n ):\n | { chainId: number; tokenNetworkAddress: string; tokenAddress?: string }\n | undefined {\n if (!negotiatedChain) return undefined;\n const parts = negotiatedChain.split(':');\n // Accept 3-part `evm:{network}:{chainId}` and 2-part `evm:{chainId}`.\n const chainIdPart =\n parts.length >= 3 ? parts[2] : parts.length >= 2 ? parts[1] : undefined;\n const numericChainId =\n chainIdPart !== undefined ? parseInt(chainIdPart, 10) : NaN;\n if (isNaN(numericChainId)) return undefined;\n const tokenNetworkAddress = this.config.tokenNetworks?.[negotiatedChain];\n if (!tokenNetworkAddress) return undefined;\n const tokenAddress = this.config.preferredTokens?.[negotiatedChain];\n return { chainId: numericChainId, tokenNetworkAddress, tokenAddress };\n }\n\n /**\n * Gets the default chain context from the first supported chain in config.\n */\n private getDefaultChainContext():\n | { chainId: number; tokenNetworkAddress: string; tokenAddress?: string }\n | undefined {\n const chains = this.config.supportedChains;\n if (!chains?.length) return undefined;\n return this.getChainContext(chains[0]);\n }\n\n /**\n * Sends an ILP payment, optionally with a balance proof claim via BTP.\n *\n * @param params - Payment parameters\n * @returns ILP send result\n * @throws {ToonClientError} If client is not started\n */\n async sendPayment(params: {\n destination: string;\n amount: string;\n data?: string;\n claim?: SignedBalanceProof;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n const ilpParams = {\n destination: params.destination,\n amount: params.amount,\n data: params.data ?? '',\n };\n\n // Require claim + BTP — plain sendIlpPacket is only valid for\n // node-to-node forwarding (relay.ts), not client-to-node.\n if (!params.claim) {\n throw new ToonClientError(\n 'Signed balance proof required. Call signBalanceProof() first.',\n 'MISSING_CLAIM'\n );\n }\n const transport = this.getClaimTransport();\n\n const claimMessage = this.buildClaimMessageForProof(params.claim);\n return transport.sendIlpPacketWithClaim(\n ilpParams,\n claimMessage as unknown as Record<string, unknown>\n );\n }\n\n /**\n * Stops the ToonClient and cleans up resources.\n *\n * This will:\n * 1. Disconnect BTP client if connected\n * 2. Clear internal state\n *\n * @throws {ToonClientError} If client is not started\n */\n async stop(): Promise<void> {\n if (!this.state) {\n throw new ToonClientError('Client not started', 'INVALID_STATE');\n }\n\n try {\n // Disconnect BTP client if connected\n if (this.state.btpClient) {\n await this.state.btpClient.disconnect();\n }\n\n // Clear state\n this.state = null;\n } catch (error) {\n throw new ToonClientError(\n 'Failed to stop client',\n 'STOP_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Returns true if the client is currently started.\n */\n isStarted(): boolean {\n return this.state !== null;\n }\n\n /**\n * Gets the number of peers discovered during bootstrap.\n *\n * @returns Number of peers discovered\n * @throws {ToonClientError} If client is not started\n */\n getPeersCount(): number {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.peersDiscovered;\n }\n\n /**\n * Gets the list of peers discovered by the relay monitor.\n *\n * @returns Array of discovered peer objects\n * @throws {ToonClientError} If client is not started\n */\n getDiscoveredPeers() {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.discoveryTracker.getDiscoveredPeers();\n }\n}\n","import { generateSecretKey } from 'nostr-tools/pure';\nimport {\n resolveClientNetwork,\n type NetworkFamilyStatus,\n} from '@toon-protocol/core';\nimport { ValidationError } from './errors.js';\nimport {\n validateMnemonic,\n deriveNostrKeyFromMnemonic,\n} from './keys/KeyDerivation.js';\nimport type { ToonClientConfig } from './types.js';\n\n/**\n * Settlement info produced by buildSettlementInfo().\n * Extends the core SettlementConfig shape with ilpAddress for client use.\n */\nexport interface ClientSettlementInfo {\n ilpAddress?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Applies named-network preset defaults to a client config.\n *\n * When `config.network` is set and != `'custom'`, the settlement-related\n * fields are defaulted from the shared core presets (`resolveClientNetwork`):\n * RPC/GraphQL URLs, supported chain identifiers, preferred tokens, EVM\n * TokenNetwork addresses, and the Solana/Mina channel params. Any explicit\n * per-chain field on `config` OVERRIDES the preset (explicit always wins).\n *\n * `'custom'` and the unset case both pass `config` through untouched, keeping\n * the fully-manual path and full backward compatibility.\n *\n * @returns A shallow copy with preset defaults merged in (never mutates input).\n */\nexport function applyNetworkPresets(\n config: ToonClientConfig\n): ToonClientConfig {\n const { network } = config;\n if (!network || network === 'custom') return config;\n\n const presets = resolveClientNetwork(network);\n\n // Merge a preset record under an explicit one (explicit keys win).\n const mergeRecord = (\n explicit: Record<string, string> | undefined,\n preset: Record<string, string>\n ): Record<string, string> => ({ ...preset, ...explicit });\n\n // supportedChains: union (preset first), preserving any explicit extras.\n const supportedChains = config.supportedChains\n ? Array.from(\n new Set([...presets.supportedChains, ...config.supportedChains])\n )\n : presets.supportedChains;\n\n return {\n ...config,\n supportedChains,\n chainRpcUrls: mergeRecord(config.chainRpcUrls, presets.chainRpcUrls),\n preferredTokens: mergeRecord(\n config.preferredTokens,\n presets.preferredTokens\n ),\n tokenNetworks: mergeRecord(config.tokenNetworks, presets.tokenNetworks),\n // settlementAddresses are identity-derived (per-client), so they have no\n // preset; pass any explicit value through unchanged.\n ...(config.settlementAddresses && {\n settlementAddresses: config.settlementAddresses,\n }),\n // Channel params: preset fills the deployed programId/zkApp + URLs unless\n // the caller supplied their own (explicit object wins wholesale).\n ...(presets.solanaChannel && {\n solanaChannel: config.solanaChannel ?? presets.solanaChannel,\n }),\n ...(presets.minaChannel && {\n minaChannel: config.minaChannel ?? presets.minaChannel,\n }),\n };\n}\n\n/**\n * Returns per-chain settlement readiness for the configured `network` tier,\n * mirroring the relay node's status. Returns `undefined` when `network` is\n * unset or `'custom'` (no preset tier to report on).\n */\nexport function getNetworkStatus(\n config: ToonClientConfig\n): NetworkFamilyStatus | undefined {\n const { network } = config;\n if (!network || network === 'custom') return undefined;\n return resolveClientNetwork(network).status;\n}\n\n/**\n * Normalize a connector-proxy base URL into its `POST /ilp` endpoint.\n *\n * `https://proxy.devnet.toonprotocol.dev` → `https://proxy.devnet.toonprotocol.dev/ilp`\n * `https://proxy.devnet.toonprotocol.dev/ilp` → unchanged (idempotent)\n * `https://proxy.devnet.toonprotocol.dev/` → `https://proxy.devnet.toonprotocol.dev/ilp`\n *\n * Returns `undefined` for an empty/falsy input so callers can `??`-chain it.\n */\nexport function proxyIlpEndpoint(proxyUrl: string | undefined): string | undefined {\n if (!proxyUrl) return undefined;\n const trimmed = proxyUrl.replace(/\\/+$/, '');\n return /\\/ilp$/i.test(trimmed) ? trimmed : `${trimmed}/ilp`;\n}\n\n/**\n * Validates ToonClient configuration.\n *\n * This story implements HTTP mode only. Embedded mode validation will be added in a future epic.\n *\n * @throws {ValidationError} If configuration is invalid\n */\nexport function validateConfig(config: ToonClientConfig): void {\n // Reject embedded mode (not implemented in this story)\n if (config.connector !== undefined) {\n throw new ValidationError(\n 'Embedded mode not yet implemented in ToonClient. Use connectorUrl for HTTP mode.'\n );\n }\n\n // Require an HTTP edge for HTTP mode — either an explicit `connectorUrl` or a\n // connector-`proxyUrl` (the devnet payment-proxy). `applyDefaults` derives\n // `connectorUrl` from `proxyUrl` when only the proxy is given.\n if (!config.connectorUrl && !config.proxyUrl) {\n throw new ValidationError(\n 'connectorUrl (or proxyUrl) is required for HTTP mode. Example: \"http://localhost:8080\"'\n );\n }\n\n // Validate connectorUrl format\n if (config.connectorUrl) {\n try {\n const url = new URL(config.connectorUrl);\n if (!url.protocol.startsWith('http')) {\n throw new Error('Must be HTTP or HTTPS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid connectorUrl: must be a valid HTTP/HTTPS URL (e.g., \"http://localhost:8080\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate proxyUrl / faucetUrl format when provided (both are HTTP(S) URLs).\n for (const [field, value] of [\n ['proxyUrl', config.proxyUrl],\n ['faucetUrl', config.faucetUrl],\n ] as const) {\n if (value === undefined) continue;\n try {\n const url = new URL(value);\n if (!url.protocol.startsWith('http')) {\n throw new Error('Must be HTTP or HTTPS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid ${field}: must be a valid HTTP/HTTPS URL. ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate secretKey only when provided\n if (config.secretKey !== undefined) {\n if (!config.secretKey || config.secretKey.length !== 32) {\n throw new ValidationError(\n 'secretKey must be 32 bytes (Nostr private key)'\n );\n }\n }\n\n // Validate mnemonic when provided. The mnemonic derives the Nostr/EVM +\n // Solana/Mina identity, so it cannot coexist with an explicit secretKey\n // (that would split the Nostr identity from the Solana/Mina identity). An\n // explicit evmPrivateKey override IS allowed (documented hardware-wallet case).\n if (config.mnemonic !== undefined) {\n if (config.secretKey !== undefined) {\n throw new ValidationError(\n 'Provide either `mnemonic` or `secretKey`, not both — the mnemonic ' +\n 'derives the Nostr key, so a separate secretKey would yield an ' +\n 'inconsistent cross-chain identity. (An `evmPrivateKey` override is allowed.)'\n );\n }\n if (\n typeof config.mnemonic !== 'string' ||\n !validateMnemonic(config.mnemonic)\n ) {\n throw new ValidationError('mnemonic must be a valid BIP-39 phrase');\n }\n }\n\n // Validate mnemonicAccountIndex when provided (must be a non-negative\n // integer within the BIP-32 non-hardened range, matching the SDK guard).\n if (config.mnemonicAccountIndex !== undefined) {\n const idx = config.mnemonicAccountIndex;\n if (!Number.isInteger(idx) || idx < 0 || idx > 0x7fffffff) {\n throw new ValidationError(\n 'mnemonicAccountIndex must be a non-negative integer (0 to 2147483647)'\n );\n }\n }\n\n if (!config.ilpInfo?.ilpAddress) {\n throw new ValidationError('ilpInfo.ilpAddress is required');\n }\n\n if (!config.toonEncoder || typeof config.toonEncoder !== 'function') {\n throw new ValidationError('toonEncoder function is required');\n }\n\n if (!config.toonDecoder || typeof config.toonDecoder !== 'function') {\n throw new ValidationError('toonDecoder function is required');\n }\n\n // Validate evmPrivateKey format when provided\n if (config.evmPrivateKey !== undefined) {\n if (config.evmPrivateKey instanceof Uint8Array) {\n if (config.evmPrivateKey.length !== 32) {\n throw new ValidationError('evmPrivateKey must be 32 bytes');\n }\n } else if (typeof config.evmPrivateKey === 'string') {\n const hex = config.evmPrivateKey.startsWith('0x')\n ? config.evmPrivateKey.slice(2)\n : config.evmPrivateKey;\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new ValidationError('evmPrivateKey must be a 32-byte hex string');\n }\n } else {\n throw new ValidationError(\n 'evmPrivateKey must be a hex string or Uint8Array'\n );\n }\n }\n\n // Validate btpUrl when provided\n if (config.btpUrl !== undefined) {\n try {\n const url = new URL(config.btpUrl);\n if (!url.protocol.startsWith('ws')) {\n throw new Error('Must be WS or WSS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid btpUrl: must be a valid WebSocket URL (e.g., \"ws://localhost:3000\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate chainRpcUrls keys match supportedChains when both present\n if (config.chainRpcUrls && config.supportedChains) {\n for (const chain of Object.keys(config.chainRpcUrls)) {\n if (!config.supportedChains.includes(chain)) {\n throw new ValidationError(\n `chainRpcUrls key \"${chain}\" is not in supportedChains`\n );\n }\n }\n }\n}\n\n/**\n * The resolved config type after defaults are applied.\n * secretKey is guaranteed to be present (auto-generated if omitted).\n */\nexport type ResolvedConfig = Required<\n Omit<\n ToonClientConfig,\n | 'connector'\n | 'mnemonic'\n | 'mnemonicAccountIndex'\n | 'evmPrivateKey'\n | 'network'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'connectorHttpEndpoint'\n | 'proxyUrl'\n | 'faucetUrl'\n | 'connectorSupportsUpgrade'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\n | 'solanaChannel'\n | 'minaChannel'\n | 'swapMinaMakerSignatures'\n | 'channelStorePath'\n | 'knownPeers'\n | 'destinationAddress'\n >\n> & {\n connector?: unknown;\n /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */\n evmPrivateKey: string | Uint8Array;\n /**\n * BIP-39 phrase retained so `ToonClient.start()` can derive the Solana/Mina\n * keys asynchronously and register the corresponding signers. The Nostr/EVM\n * keys are already resolved synchronously into `secretKey`/`evmPrivateKey`.\n */\n mnemonic?: string;\n /**\n * BIP-44 account index for mnemonic-based derivation (defaults to 0).\n * Retained so `ToonClient.start()` derives the Solana/Mina signers at the\n * same index as the synchronously-resolved Nostr/EVM keys.\n */\n mnemonicAccountIndex?: number;\n /** Named network tier, retained for `getNetworkStatus()`. */\n network?: ToonClientConfig['network'];\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n btpUrl?: string;\n btpAuthToken?: string;\n btpPeerId?: string;\n connectorHttpEndpoint?: string;\n proxyUrl?: string;\n faucetUrl?: string;\n connectorSupportsUpgrade?: boolean;\n chainRpcUrls?: Record<string, string>;\n initialDeposit?: string;\n settlementTimeout?: number;\n solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n swapMinaMakerSignatures?: ToonClientConfig['swapMinaMakerSignatures'];\n channelStorePath?: string;\n knownPeers?: {\n pubkey: string;\n relayUrl: string;\n btpEndpoint?: string;\n }[];\n destinationAddress: string;\n};\n\n/**\n * Applies default values to optional configuration fields.\n * Auto-generates a Nostr keypair when secretKey is omitted.\n * Derives btpUrl from connectorUrl when not provided.\n */\nexport function applyDefaults(rawConfig: ToonClientConfig): ResolvedConfig {\n // Fill settlement-related defaults from the named-network presets first\n // (explicit per-chain fields always win). No-op for unset/`custom`.\n const config = applyNetworkPresets(rawConfig);\n\n // Resolve the Nostr secret key. Precedence:\n // 1. explicit secretKey\n // 2. derived from mnemonic (Nostr/EVM only — Solana/Mina are derived\n // asynchronously in start(), which is why the mnemonic is retained)\n // 3. auto-generated ephemeral key\n const secretKey =\n config.secretKey ??\n (config.mnemonic\n ? deriveNostrKeyFromMnemonic(\n config.mnemonic,\n config.mnemonicAccountIndex ?? 0\n ).secretKey\n : generateSecretKey());\n\n // Derive the connector-proxy `POST /ilp` endpoint from `proxyUrl` (unless an\n // explicit `connectorHttpEndpoint` was given — explicit always wins). When set\n // this makes `selectIlpTransport` prefer the stateless HttpIlpClient transport\n // for one-shot writes, routing through the devnet payment-proxy.\n const connectorHttpEndpoint =\n config.connectorHttpEndpoint ?? proxyIlpEndpoint(config.proxyUrl);\n\n // `connectorUrl` is required by validateConfig but can be satisfied by\n // `proxyUrl`; fall back to the proxy base so downstream code (HttpRuntimeClient\n // fallback, destination derivation) always has an HTTP edge URL.\n const connectorUrl =\n config.connectorUrl ?? config.proxyUrl?.replace(/\\/+$/, '');\n\n // Derive btpUrl from connectorUrl when not explicitly provided\n // http://host:8080 → ws://host:3000.\n // SKIP this auto-derivation when an HTTP/proxy transport is configured: the\n // proxy edge serves ILP-over-HTTP and may not expose BTP, so we must not open\n // (or require) a BTP socket. Writes route through HttpIlpClient instead.\n let btpUrl = config.btpUrl;\n if (!btpUrl && connectorUrl && !connectorHttpEndpoint) {\n try {\n const url = new URL(connectorUrl);\n const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n btpUrl = `${wsProtocol}//${url.hostname}:3000`;\n } catch {\n // connectorUrl already validated, this shouldn't happen\n }\n }\n\n // Derive destinationAddress from connectorUrl port when not explicitly provided\n // This provides sensible defaults for local development:\n // - http://localhost:8080 → g.toon.genesis (genesis node)\n // - http://localhost:8090 → g.toon.peer1 (peer1 node)\n // For production, explicitly set destinationAddress in config\n let destinationAddress = config.destinationAddress;\n if (!destinationAddress && connectorUrl) {\n try {\n const url = new URL(connectorUrl);\n if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {\n // Map common local ports to known nodes\n if (url.port === '8080') {\n destinationAddress = 'g.toon.genesis';\n } else if (url.port === '8090') {\n destinationAddress = 'g.toon.peer1';\n } else if (url.port === '8100') {\n destinationAddress = 'g.toon.peer2';\n } else {\n // Fallback: use ilpInfo.ilpAddress if available\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } else {\n // Production: default to ilpInfo.ilpAddress\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n } catch {\n destinationAddress = config.ilpInfo?.ilpAddress || 'g.toon.relay';\n }\n }\n\n // Derive EVM private key from Nostr secret key when not explicitly provided.\n // Both Nostr and EVM use secp256k1, so a single 32-byte key works for both.\n const evmPrivateKey = config.evmPrivateKey ?? secretKey;\n\n return {\n ...config,\n secretKey,\n evmPrivateKey,\n // Satisfied by connectorUrl OR proxyUrl (validateConfig enforced one of them).\n connectorUrl: connectorUrl as string,\n // Surface the derived `POST /ilp` endpoint so HTTP mode selects HttpIlpClient.\n ...(connectorHttpEndpoint ? { connectorHttpEndpoint } : {}),\n // Last-resort relay default. An explicit `config.relayUrl` always wins;\n // `applyNetworkPresets` does not set a per-network relay, so this fallback\n // points at the public devnet relay rather than a dead localhost socket.\n relayUrl: config.relayUrl ?? 'wss://relay-ws.devnet.toonprotocol.dev',\n queryTimeout: config.queryTimeout ?? 30000,\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n btpUrl,\n destinationAddress: destinationAddress as string, // Always set by logic above\n };\n}\n\n/**\n * Builds SettlementConfig from client config.\n * Returns undefined if no settlement-related config is present.\n */\nexport function buildSettlementInfo(\n rawConfig: ToonClientConfig\n): ClientSettlementInfo | undefined {\n // Resolve named-network preset defaults so a `network`-only config still\n // produces settlement info (explicit fields win; no-op for unset/`custom`).\n const config = applyNetworkPresets(rawConfig);\n\n if (\n !config.supportedChains?.length &&\n !config.settlementAddresses &&\n !config.preferredTokens &&\n !config.tokenNetworks\n ) {\n return undefined;\n }\n\n return {\n ilpAddress: config.ilpInfo?.ilpAddress,\n supportedChains: config.supportedChains,\n settlementAddresses: config.settlementAddresses,\n preferredTokens: config.preferredTokens,\n tokenNetworks: config.tokenNetworks,\n };\n}\n","/**\n * Base error class for all TOON client errors.\n */\nexport class ToonClientError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n cause?: Error\n ) {\n super(message, { cause });\n this.name = 'ToonClientError';\n }\n}\n\n/**\n * Network error for connection failures (ECONNREFUSED, ETIMEDOUT).\n * These errors trigger retry logic with exponential backoff.\n */\nexport class NetworkError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Connector error for 5xx server errors.\n * These errors indicate the connector is unavailable or malfunctioning.\n */\nexport class ConnectorError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONNECTOR_ERROR', cause);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Validation error for invalid input parameters.\n * These errors are thrown before making any HTTP requests.\n */\nexport class ValidationError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'VALIDATION_ERROR', cause);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Unauthorized error for 401 responses from connector admin API.\n * Indicates missing or invalid authentication credentials.\n */\nexport class UnauthorizedError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'UNAUTHORIZED', cause);\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Peer not found error for 404 responses when removing a peer.\n * Indicates the specified peer ID does not exist in the connector.\n */\nexport class PeerNotFoundError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_NOT_FOUND', cause);\n this.name = 'PeerNotFoundError';\n }\n}\n\n/**\n * Peer already exists error for 409 responses when adding a peer.\n * Indicates a peer with the same ID already exists in the connector.\n */\nexport class PeerAlreadyExistsError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_ALREADY_EXISTS', cause);\n this.name = 'PeerAlreadyExistsError';\n }\n}\n\n/**\n * Thrown when the one-time on-chain payment-channel OPEN reverts because the\n * local settlement wallet has no native gas to pay for its own\n * approve/openChannel/setTotalDeposit transactions. This is the channel OPEN\n * only — per-write settlement rides ILP-over-HTTP and never spends gas. We\n * remap ONLY this case so callers get an actionable message (fund the wallet)\n * instead of the raw viem \"...exceeds the balance of the account\" string\n * (toon-meta#65). Retryable once the wallet is funded; the underlying viem/RPC\n * error is preserved as `cause`.\n */\nexport class ChannelFundingError extends ToonClientError {\n readonly retryable = true;\n constructor(message: string, cause?: Error) {\n super(message, 'CHANNEL_FUNDING', cause);\n this.name = 'ChannelFundingError';\n }\n}\n\n/**\n * Substrings that mark an insufficient-native-gas revert from an on-chain\n * channel-open tx. viem surfaces the node's message verbatim and the exact\n * phrasing varies by RPC (anvil/geth/hardhat), so we match a set of known\n * markers case-insensitively.\n */\nconst INSUFFICIENT_GAS_MARKERS = [\n 'exceeds the balance of the account',\n 'insufficient funds for gas',\n 'insufficient funds for intrinsic transaction cost',\n 'insufficient funds for transfer',\n];\n\n/**\n * True when `err` (or any error in its nested `cause` chain) is an\n * insufficient-native-gas revert. viem wraps the node error one or more levels\n * deep, so the whole chain is flattened and scanned.\n */\nexport function isInsufficientGasError(err: unknown): boolean {\n const parts: string[] = [];\n let cur: unknown = err;\n for (let i = 0; i < 10 && cur != null; i++) {\n parts.push(cur instanceof Error ? cur.message : String(cur));\n cur = cur instanceof Error ? (cur as { cause?: unknown }).cause : undefined;\n }\n const text = parts.join(' | ').toLowerCase();\n return INSUFFICIENT_GAS_MARKERS.some((m) => text.includes(m));\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { toHex } from 'viem';\nimport {\n generateMnemonic as _genMnemonic,\n validateMnemonic as _validateMnemonic,\n mnemonicToSeedSync,\n} from '@scure/bip39';\nimport { wordlist as english } from '@scure/bip39/wordlists/english';\nimport { HDKey } from '@scure/bip32';\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport type { ToonIdentity } from './types.js';\n\n/**\n * Generate a new 12-word BIP-39 mnemonic.\n */\nexport function generateMnemonic(): string {\n return _genMnemonic(english, 128);\n}\n\n/**\n * Validate a BIP-39 mnemonic phrase.\n */\nexport function validateMnemonic(mnemonic: string): boolean {\n return _validateMnemonic(mnemonic, english);\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 * Validate a BIP-44 account index. Mirrors the SDK's `fromMnemonic` guard so\n * the client and SDK reject the same out-of-range indices.\n */\nfunction assertValidAccountIndex(accountIndex: number): void {\n if (\n !Number.isInteger(accountIndex) ||\n accountIndex < 0 ||\n accountIndex > MAX_BIP32_INDEX\n ) {\n throw new Error(\n `Invalid accountIndex: expected a non-negative integer (0 to ${MAX_BIP32_INDEX}), got ${String(accountIndex)}`\n );\n }\n}\n\n/**\n * Derive the Nostr secp256k1 key from mnemonic using NIP-06 path:\n * m/44'/1237'/0'/0/{accountIndex}. At accountIndex 0 this is the canonical\n * m/44'/1237'/0'/0/0, matching the SDK's `fromMnemonic`/`fromMnemonicFull`.\n */\nfunction deriveNostrKey(\n seed: Uint8Array,\n accountIndex = 0\n): {\n secretKey: Uint8Array;\n pubkey: string;\n} {\n const master = HDKey.fromMasterSeed(seed);\n const child = master.derive(`m/44'/1237'/0'/0/${accountIndex}`);\n if (!child.privateKey) {\n throw new Error('Failed to derive Nostr private key from seed');\n }\n const secretKey = new Uint8Array(child.privateKey);\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n}\n\n/**\n * Derive the EVM address from the same secp256k1 key (shares curve with Nostr).\n */\nfunction deriveEvmIdentity(secretKey: Uint8Array): {\n privateKey: Uint8Array;\n address: string;\n} {\n const account = privateKeyToAccount(toHex(secretKey));\n return {\n privateKey: secretKey,\n address: account.address,\n };\n}\n\n/**\n * Derive Solana Ed25519 keypair using SLIP-0010 path:\n * m/44'/501'/{accountIndex}'/0' (all hardened). At accountIndex 0 this is the\n * canonical m/44'/501'/0'/0', matching the SDK's `deriveSolanaIdentity`.\n * Dynamically imports @noble/curves for Ed25519 operations.\n */\nasync function deriveSolanaKey(\n seed: Uint8Array,\n accountIndex = 0\n): Promise<{\n secretKey: Uint8Array;\n publicKey: string;\n}> {\n // SLIP-0010 Ed25519 derivation (hardened only)\n // Uses HMAC-SHA512 with \"ed25519 seed\" as key\n const { hmac } = await import('@noble/hashes/hmac');\n const { sha512 } = await import('@noble/hashes/sha512');\n const { ed25519 } = await import('@noble/curves/ed25519.js');\n\n // SLIP-0010 master key derivation for ed25519\n const encoder = new TextEncoder();\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 // Derive path: m/44'/501'/{accountIndex}'/0' (all hardened)\n const indices = [\n 0x8000002c, // 44'\n 0x800001f5, // 501'\n (0x80000000 + accountIndex) >>> 0, // {accountIndex}'\n 0x80000000, // 0'\n ];\n\n for (const index of indices) {\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 const publicKeyBytes: Uint8Array = ed25519.getPublicKey(key);\n\n // Solana keypair = 32-byte private key + 32-byte public key = 64 bytes\n const keypair = new Uint8Array(64);\n keypair.set(key, 0);\n keypair.set(publicKeyBytes, 32);\n\n // Base58 encode the public key\n const publicKey = toBase58(publicKeyBytes);\n\n return { secretKey: keypair, publicKey };\n}\n\n/**\n * Derive Mina Pallas key using path: m/44'/12586'/{accountIndex}'/0/0. At\n * accountIndex 0 this is the canonical m/44'/12586'/0'/0/0, matching the SDK's\n * `deriveMinaIdentity`.\n * Dynamically imports mina-signer.\n */\nasync function deriveMinaKey(\n seed: Uint8Array,\n accountIndex = 0\n): Promise<{\n privateKey: string;\n publicKey: string;\n}> {\n const master = HDKey.fromMasterSeed(seed);\n // Mina coin type = 12586 (0x312A)\n const child = master.derive(`m/44'/12586'/${accountIndex}'/0/0`);\n if (!child.privateKey) {\n throw new Error('Failed to derive Mina private key from seed');\n }\n const keyBytes = new Uint8Array(child.privateKey);\n\n // Clamp the top 2 bits so the scalar is within the Pallas base-field order\n // (matches @toon-protocol/swap's `deriveSwapKeys`). Without this, the raw\n // BIP-32 child scalar can exceed the field order and mina-signer rejects it.\n keyBytes[0] = (keyBytes[0] ?? 0) & 0x3f;\n\n // mina-signer needs the Mina base58check (`EK…`) private-key format, NOT a\n // raw hex scalar (raw hex fails with \"invalid checksum\"). Convert via the\n // shared @toon-protocol/core helper before deriving the public key.\n try {\n const MinaSignerLib = await import('mina-signer');\n const Client =\n 'default' in MinaSignerLib ? MinaSignerLib.default : MinaSignerLib;\n const client = new Client({ network: 'mainnet' });\n\n const hexKey = Array.from(keyBytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n const minaPrivateKey = hexToMinaBase58PrivateKey(hexKey);\n const publicKey = client.derivePublicKey(minaPrivateKey);\n return {\n // Store the clamped big-endian hex scalar; consumers (e.g. the client's\n // MinaSigner) re-convert to base58check via hexToMinaBase58PrivateKey.\n privateKey: hexKey,\n publicKey,\n };\n } catch {\n throw new Error(\n 'mina-signer is required for Mina key derivation. Install it as an optional dependency.'\n );\n }\n}\n\n/**\n * Synchronously derive ONLY the Nostr secp256k1 key (NIP-06) from a mnemonic.\n *\n * The EVM key shares this same secp256k1 key. Solana (Ed25519) and Mina\n * (Pallas) require async dynamic imports — use {@link deriveFullIdentity} for\n * those. This sync subset exists so `ToonClient`'s synchronous constructor can\n * resolve the Nostr/EVM identity from a `mnemonic` config field without an\n * async factory; the client derives Solana/Mina lazily in `start()`.\n */\nexport function deriveNostrKeyFromMnemonic(\n mnemonic: string,\n accountIndex = 0\n): {\n secretKey: Uint8Array;\n pubkey: string;\n} {\n assertValidAccountIndex(accountIndex);\n const seed = mnemonicToSeedSync(mnemonic);\n const result = deriveNostrKey(seed, accountIndex);\n seed.fill(0); // Zero seed after derivation (F7 fix)\n return result;\n}\n\n/**\n * Derive a full multi-chain ToonIdentity from a BIP-39 mnemonic.\n *\n * All four chains vary by `accountIndex` (default 0), matching the SDK's\n * {@link https://www.npmjs.com/package/@toon-protocol/sdk `fromMnemonicFull`}\n * path-per-index scheme so a non-zero index produces the SAME addresses as\n * `fromMnemonicFull(mnemonic, { accountIndex })`. Index 0 is unchanged from the\n * historical fixed paths (back-compat).\n *\n * Chains derived:\n * - Nostr (secp256k1): m/44'/1237'/0'/0/{accountIndex}\n * - EVM (secp256k1): same key as Nostr\n * - Solana (Ed25519): m/44'/501'/{accountIndex}'/0' (SLIP-0010)\n * - Mina (Pallas): m/44'/12586'/{accountIndex}'/0/0\n *\n * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).\n * @param accountIndex - BIP-44 account index (default 0).\n */\nexport async function deriveFullIdentity(\n mnemonic: string,\n accountIndex = 0\n): Promise<ToonIdentity> {\n assertValidAccountIndex(accountIndex);\n const seed = mnemonicToSeedSync(mnemonic);\n\n const nostr = deriveNostrKey(seed, accountIndex);\n const evm = deriveEvmIdentity(nostr.secretKey);\n\n // Solana and Mina can fail if optional deps are missing — derive independently\n let solana: ToonIdentity['solana'];\n try {\n solana = await deriveSolanaKey(seed, accountIndex);\n } catch {\n solana = { secretKey: new Uint8Array(64), publicKey: '' };\n }\n\n let mina: ToonIdentity['mina'];\n try {\n mina = await deriveMinaKey(seed, accountIndex);\n } catch {\n mina = { privateKey: '', publicKey: '' };\n }\n\n // Zero seed after derivation (F7 fix)\n seed.fill(0);\n\n return { nostr, evm, solana, mina };\n}\n\n/**\n * Derive a partial identity from an nsec (Nostr-only private key).\n * Nostr + EVM share the same secp256k1 key.\n * Solana and Mina get empty placeholders (not deterministically linked).\n */\nexport function deriveFromNsec(secretKey: Uint8Array): ToonIdentity {\n // Copy input to avoid caller mutation corrupting identity keys\n const keyCopy = new Uint8Array(secretKey);\n const pubkey = getPublicKey(keyCopy);\n const evm = deriveEvmIdentity(keyCopy);\n\n return {\n nostr: { secretKey: keyCopy, pubkey },\n evm,\n solana: { secretKey: new Uint8Array(64), publicKey: '' },\n mina: { privateKey: '', publicKey: '' },\n };\n}\n\n/**\n * Generate a random identity (no mnemonic — for testing or ephemeral use).\n */\nexport function generateRandomIdentity(): ToonIdentity {\n const secretKey = generateSecretKey();\n return deriveFromNsec(secretKey);\n}\n\n// --- Utility ---\n\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\nfunction toBase58(bytes: Uint8Array): string {\n let num = BigInt(0);\n for (const b of bytes) num = num * 256n + BigInt(b);\n let result = '';\n while (num > 0n) {\n result = BASE58_ALPHABET[Number(num % 58n)] + result;\n num = num / 58n;\n }\n for (const b of bytes) {\n if (b === 0) result = '1' + result;\n else break;\n }\n return result;\n}\n","/**\n * Isomorphic binary helpers — works in both Node.js and browser\n * without requiring Buffer polyfills.\n *\n * These replace Buffer usage throughout the client package so that\n * browser consumers (e.g. ditto) don't need the `buffer` npm polyfill.\n */\n\n/** Convert a Uint8Array to a base64 string (browser + Node compatible). */\nexport function toBase64(bytes: Uint8Array): string {\n // Node.js Buffer is a Uint8Array subclass and has toString('base64')\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(bytes)) {\n return (bytes as Buffer).toString('base64');\n }\n // Browser path: use btoa with binary string\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\n/** Convert a base64 string to a Uint8Array (browser + Node compatible). */\nexport function fromBase64(base64: string): Uint8Array {\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return new Uint8Array(Buffer.from(base64, 'base64'));\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/** Convert a Uint8Array to a hex string. */\nexport function toHex(bytes: Uint8Array): string {\n let hex = '';\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert a hex string to a Uint8Array. */\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.startsWith('0x') ? hex.slice(2) : hex;\n const bytes = new Uint8Array(clean.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/** Convert a UTF-8 string to Uint8Array. */\nexport function encodeUtf8(str: string): Uint8Array {\n return new TextEncoder().encode(str);\n}\n\n/** Convert a Uint8Array to a UTF-8 string. */\nexport function decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\n/** Check if a string is valid base64. */\nexport function isBase64(str: string): boolean {\n return /^[A-Za-z0-9+/]*={0,2}$/.test(str);\n}\n","/**\n * Store-write HTTP envelope for the payment-proxy (HTTP-in-ILP) path.\n *\n * The deployed connector is a payment-proxy: it terminates a paid write by\n * decoding the ILP PREPARE `data` as a literal RFC 7230 HTTP/1.1 request and\n * reverse-proxying it to the relay store's `POST /write` (see the connector's\n * `HttpProxyHandler.decodeHttpRequest`). The ILP `data` MUST therefore be a\n * full HTTP request envelope:\n *\n * POST /write HTTP/1.1\\r\\n\n * Host: relay\\r\\n\n * Content-Type: application/json\\r\\n\n * \\r\\n\n * {\"event\": <signed nostr event object>}\n *\n * Sending the bare TOON-encoded event (no request-line) makes the proxy reject\n * with `F01 - Invalid HTTP envelope: malformed request-line`. The relay's\n * `/write` handler parses the body as JSON and reads `body.event` as a full\n * signed Nostr event OBJECT (it runs `verifyEvent(event)` + `store(event)`), so\n * the body carries the event object verbatim — NOT the TOON string.\n *\n * This helper is the single source of truth for that envelope so the proxy\n * paid-write path (`ToonClient.publishEvent`) and any future caller stay\n * byte-compatible with the deployed store. It is isomorphic (Node + browser):\n * `JSON.stringify` escapes non-ASCII to `\\uXXXX`, so the serialized envelope is\n * pure ASCII and `encodeUtf8` matches the bytes the store expects.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { encodeUtf8 } from './binary.js';\n\n/** Default request-target + headers the deployed store accepts (proven against\n * devnet). The relay's paid-write surface is `POST /write`; the Arweave store\n * (DVM) backend is `POST /store` — same `{ event }` body, different target. */\nconst DEFAULT_REQUEST_TARGET = '/write';\nconst HEADERS = ['Host: relay', 'Content-Type: application/json'];\n\n/**\n * Wrap a signed Nostr event in the HTTP envelope the deployed payment-proxy\n * reverse-proxies to its backend. Defaults to the relay's `POST /write`; pass\n * `requestTarget: '/store'` for the Arweave store/DVM backend.\n *\n * @param event - A finalized (signed) Nostr event — passed through to the store\n * as the JSON `event` field verbatim (the store re-verifies the signature).\n * @param requestTarget - HTTP request-target the proxy replays (default `/write`).\n * @returns The envelope bytes to use as the ILP PREPARE `data`.\n */\nexport function buildStoreWriteEnvelope(\n event: NostrEvent,\n requestTarget: string = DEFAULT_REQUEST_TARGET\n): Uint8Array {\n const body = JSON.stringify({ event });\n const head = [`POST ${requestTarget} HTTP/1.1`, ...HEADERS].join('\\r\\n');\n return encodeUtf8(head + '\\r\\n\\r\\n' + body);\n}\n","/**\n * Shared parser for the HTTP-over-ILP response carried in an ILP **FULFILL**\n * packet's `data` field.\n *\n * The deployed connector is a payment-proxy (HTTP-in-ILP): a paid write/upload\n * is reverse-proxied to the relay/DVM origin and the origin's reply is returned\n * **verbatim as a full HTTP/1.1 response message** inside the FULFILL `data`:\n *\n * HTTP/1.1 200 OK\\r\\n\n * content-length: 189\\r\\n\n * \\r\\n\n * {\"accept\":true,\"txId\":\"4QcRav...\",\"data\":\"<base64-txid>\",...}\n *\n * Callers (`ToonClient.publishEvent`, `requestBlobStorage`) previously treated\n * this `data` as opaque success bytes — `publishEvent` reported success on ANY\n * FULFILL even when the embedded HTTP status was `404 Not Found`, and\n * `requestBlobStorage` base64-decoded the WHOLE response as if it were the bare\n * Arweave tx id. This module makes the HTTP envelope first-class so both paths\n * can read the real status and body.\n *\n * The full Web-`Response` reconstruction used by the h402 fetch path lives in\n * `adapters/Http402Client.ts` (`parseHttpResponse`); this is a smaller,\n * dependency-free helper that returns the status code + raw body string, which\n * is all the publish/upload paths need.\n *\n * DEFENSIVE: not every FULFILL is HTTP-enveloped (e.g. swap peer raw-TOON\n * FULFILLs go through `sendSwapPacket`, not these paths). If the decoded data\n * does not begin with an `HTTP/<v>` status line, `isHttp` is `false` and the\n * caller should fall back to its prior (non-HTTP) interpretation rather than\n * fail. This keeps non-HTTP FULFILLs from regressing.\n */\n\nimport { fromBase64, decodeUtf8 } from './binary.js';\n\nconst CRLF = '\\r\\n';\n\n/** Result of parsing FULFILL `data` as an HTTP/1.1 response. */\nexport interface ParsedFulfillHttp {\n /** Whether the data looked like an HTTP/1.1 response (status line present). */\n isHttp: boolean;\n /** HTTP status code (e.g. 200, 404). Only meaningful when `isHttp` is true. */\n status: number;\n /** Reason phrase from the status line (may be empty). */\n statusText: string;\n /** Decoded response body as a UTF-8 string (empty when none). */\n body: string;\n}\n\n/** Find the index just past the first `\\r\\n\\r\\n` (header/body boundary). */\nfunction findHeaderEnd(bytes: Uint8Array): number {\n for (let i = 0; i + 3 < bytes.length; i++) {\n if (\n bytes[i] === 0x0d &&\n bytes[i + 1] === 0x0a &&\n bytes[i + 2] === 0x0d &&\n bytes[i + 3] === 0x0a\n ) {\n return i + 4;\n }\n }\n return -1;\n}\n\n/**\n * Parse FULFILL `data` bytes as an HTTP/1.1 response.\n *\n * Returns `{ isHttp: false, ... }` (without throwing) when the payload does not\n * start with an `HTTP/<v>` status line, so callers can fall back to their\n * legacy non-HTTP interpretation. When it IS an HTTP response, the status code\n * and body are extracted; a present-but-malformed status line yields\n * `isHttp: false` as well (treated as non-HTTP rather than throwing).\n */\nexport function parseFulfillHttpBytes(bytes: Uint8Array): ParsedFulfillHttp {\n const notHttp: ParsedFulfillHttp = {\n isHttp: false,\n status: 0,\n statusText: '',\n body: '',\n };\n\n const headerEnd = findHeaderEnd(bytes);\n const headBytes = headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);\n const bodyBytes =\n headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);\n\n const headText = decodeUtf8(headBytes);\n const lines = headText.split(CRLF).filter((l) => l.length > 0);\n const statusLine = lines.shift();\n if (!statusLine) return notHttp;\n\n // Cheap guard before the regex: must look like an HTTP status line.\n if (!statusLine.trimStart().startsWith('HTTP/')) return notHttp;\n\n // `HTTP/1.1 200 OK` — tolerate a missing reason phrase.\n const match = /^HTTP\\/\\d\\.\\d\\s+(\\d{3})(?:\\s+(.*))?$/.exec(statusLine.trim());\n if (!match) return notHttp;\n\n return {\n isHttp: true,\n status: parseInt(match[1] as string, 10),\n statusText: match[2] ?? '',\n body: decodeUtf8(bodyBytes),\n };\n}\n\n/**\n * Convenience wrapper: decode a base64 FULFILL `data` string (the shape carried\n * on `IlpSendResult.data`) and parse it as an HTTP/1.1 response.\n */\nexport function parseFulfillHttp(base64Data: string): ParsedFulfillHttp {\n return parseFulfillHttpBytes(fromBase64(base64Data));\n}\n","import { BootstrapService, createDiscoveryTracker } from '@toon-protocol/core';\nimport type { BootstrapServiceConfig } from '@toon-protocol/core';\nimport { HttpRuntimeClient } from '../adapters/HttpRuntimeClient.js';\nimport { BtpRuntimeClient } from '../adapters/BtpRuntimeClient.js';\nimport { HttpIlpClient } from '../adapters/HttpIlpClient.js';\nimport {\n selectIlpTransport,\n readDiscoveredIlpPeer,\n} from '../adapters/selectIlpTransport.js';\nimport { OnChainChannelClient } from '../channel/OnChainChannelClient.js';\nimport { EvmSigner } from '../signing/evm-signer.js';\nimport { buildSettlementInfo } from '../config.js';\nimport type { ResolvedConfig } from '../config.js';\nimport type { HttpModeInitialization } from './types.js';\n\n/**\n * Initializes HTTP mode for ToonClient.\n *\n * HTTP mode uses external connector service via HTTP/WebSocket.\n * This function creates all necessary clients and services for operating in HTTP mode.\n *\n * @param config - ToonClient configuration (must have connectorUrl)\n * @returns Initialized HTTP mode components\n */\nexport async function initializeHttpMode(\n config: ResolvedConfig\n): Promise<HttpModeInitialization> {\n const effectiveBtpUrl = config.btpUrl;\n const effectiveConnectorUrl = config.connectorUrl;\n\n // Build settlement info from config\n const settlementInfo = buildSettlementInfo(config);\n\n // Select the ILP transport for the uplink connector.\n //\n // The connector serves ILP-over-HTTP (`POST /ilp`) and BTP on the SAME port\n // (connector PR #181). A peer advertises the `POST /ilp` URL in discovery via\n // `IlpPeerInfo.httpEndpoint` (+ `supportsUpgrade`) added in toon PR #29.\n //\n // The client SDK's only \"peer\" at init time is its uplink connector, so we\n // build a DiscoveredIlpPeer from the connector endpoints we know: the derived\n // BTP WebSocket URL plus an optional explicit `connectorHttpEndpoint` (which\n // mirrors the on-wire `httpEndpoint`). `readDiscoveredIlpPeer` reads those\n // fields defensively so this stays compatible with the installed\n // @toon-protocol/core 1.4.2 (pre-PR-#29).\n //\n // NOTE(dep): once a @toon-protocol/core release with the PR-#29\n // `httpEndpoint`/`supportsUpgrade` IlpPeerInfo fields is published (latest on\n // npm is still 1.4.2), bump the dep and feed real discovered peer info here\n // instead of (or in addition to) the explicit config field.\n //\n // FALLBACK GUARANTEE: when no `httpEndpoint` is present (the default — no\n // connector advertises one yet, and `connectorHttpEndpoint` is unset),\n // `selectIlpTransport` returns `{ kind: 'btp' }`, which is exactly the prior\n // behavior. Existing BTP-only flows are unchanged.\n const discoveredPeer = readDiscoveredIlpPeer({\n btpEndpoint: effectiveBtpUrl,\n httpEndpoint: config.connectorHttpEndpoint,\n supportsUpgrade: config.connectorSupportsUpgrade,\n });\n\n // The client SDK's runtime client is a one-shot consumer (it sends ILP\n // packets and reads the synchronous FULFILL/REJECT). It does not need a duplex\n // session at the transport-selection layer, so `needsDuplex` stays false and\n // HTTP is preferred when advertised. A `btpEndpoint` is always present when\n // `btpUrl` is configured, so HTTP is only ever selected when an httpEndpoint\n // is explicitly available.\n const transportChoice =\n discoveredPeer.httpEndpoint || discoveredPeer.btpEndpoint\n ? selectIlpTransport(discoveredPeer, { needsDuplex: false })\n : null;\n\n // Create BTP runtime client — the duplex transport for the client SDK.\n // The client connects to the connector via BTP WebSocket to send ILP packets\n // AND to receive server-initiated packets / act as a peer. We always open it\n // when a btpUrl is configured (publishing, swaps and payments require it), so\n // ToonClient's `btpClient`-gated paths keep working even when one-shot writes\n // go over HTTP.\n let btpClient: BtpRuntimeClient | null = null;\n if (effectiveBtpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: effectiveBtpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n });\n await btpClient.connect();\n }\n\n // Build the HTTP one-shot client when the transport policy selected it.\n let httpIlpClient: HttpIlpClient | null = null;\n if (\n transportChoice &&\n (transportChoice.kind === 'http' ||\n transportChoice.kind === 'http-upgradable')\n ) {\n httpIlpClient = new HttpIlpClient({\n httpEndpoint: transportChoice.httpEndpoint,\n ...(config.btpPeerId !== undefined ? { peerId: config.btpPeerId } : {}),\n ...(config.btpAuthToken !== undefined\n ? { authToken: config.btpAuthToken }\n : {}),\n timeout: config.queryTimeout,\n maxRetries: config.maxRetries,\n retryDelay: config.retryDelay,\n });\n }\n\n // Runtime client precedence for sending ILP packets:\n // 1. HttpIlpClient — when the connector advertises an httpEndpoint (PR #29).\n // 2. BtpRuntimeClient — the BTP WebSocket (existing default; FALLBACK).\n // 3. HttpRuntimeClient — connector-admin-style HTTP when no btpUrl at all.\n const runtimeClient =\n httpIlpClient ??\n btpClient ??\n new HttpRuntimeClient({\n connectorUrl: effectiveConnectorUrl,\n timeout: config.queryTimeout,\n maxRetries: config.maxRetries,\n retryDelay: config.retryDelay,\n });\n\n // Create on-chain channel client when chain RPC URLs are configured.\n // evmPrivateKey is always present (derived from secretKey by default).\n let onChainChannelClient: OnChainChannelClient | null = null;\n if (config.chainRpcUrls) {\n const evmSigner = new EvmSigner(config.evmPrivateKey);\n onChainChannelClient = new OnChainChannelClient({\n evmSigner,\n chainRpcUrls: config.chainRpcUrls,\n });\n }\n\n // Create BootstrapService\n const bootstrapConfig: BootstrapServiceConfig = {\n knownPeers: (config.knownPeers || []).map((p) => ({\n pubkey: p.pubkey,\n relayUrl: p.relayUrl,\n btpEndpoint: p.btpEndpoint ?? '',\n })),\n queryTimeout: config.queryTimeout,\n ardriveEnabled: true,\n defaultRelayUrl: config.relayUrl,\n settlementInfo,\n ownIlpAddress: config.ilpInfo.ilpAddress,\n toonEncoder: config.toonEncoder,\n toonDecoder: config.toonDecoder,\n basePricePerByte: 10n, // Match network default (10 micro-USDC per byte)\n };\n\n const bootstrapService = new BootstrapService(\n bootstrapConfig,\n config.secretKey,\n config.ilpInfo\n );\n\n // Wire ILP client into bootstrap service\n bootstrapService.setIlpClient(runtimeClient);\n\n // Wire on-chain channel client if available\n if (onChainChannelClient) {\n bootstrapService.setChannelClient(onChainChannelClient);\n }\n\n // Do NOT wire ConnectorAdmin — addPeer() at line 472 is skipped when connectorAdmin is null\n // This is intentional: the client is a standalone peer, not an admin interface\n\n // Create DiscoveryTracker\n const discoveryTracker = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo,\n });\n\n return {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n adminClient: null,\n btpClient,\n onChainChannelClient,\n };\n}\n","/**\n * Configuration options for retry behavior with exponential backoff.\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Initial delay in milliseconds between retries (default: 1000) */\n retryDelay: number;\n /** Use exponential backoff for delays (default: true) */\n exponentialBackoff?: boolean;\n /** Maximum delay cap in milliseconds (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to determine if an error should trigger a retry */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Executes an async operation with retry logic and exponential backoff.\n *\n * @param operation - The async function to execute\n * @param options - Retry configuration options\n * @returns The result of the successful operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await withRetry(\n * async () => fetchData(),\n * {\n * maxRetries: 3,\n * retryDelay: 1000,\n * shouldRetry: (err) => err.name === 'NetworkError'\n * }\n * );\n * ```\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n options: RetryOptions\n): Promise<T> {\n const {\n maxRetries,\n retryDelay,\n exponentialBackoff = true,\n maxDelay = 30000,\n shouldRetry,\n } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry this error\n if (shouldRetry && !shouldRetry(lastError)) {\n throw lastError;\n }\n\n // If this was the last attempt, throw the error\n if (attempt === maxRetries) {\n throw lastError;\n }\n\n // Calculate delay with exponential backoff\n const currentDelay = exponentialBackoff\n ? Math.min(retryDelay * Math.pow(2, attempt), maxDelay)\n : retryDelay;\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, currentDelay));\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError ?? new Error('Unknown error during retry');\n}\n","import type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { NetworkError, ConnectorError, ValidationError } from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\nimport { isBase64 } from '../utils/binary.js';\n\n/**\n * Configuration options for HttpRuntimeClient.\n */\nexport interface HttpRuntimeClientConfig {\n /** Connector runtime API base URL (e.g., 'http://localhost:8080') */\n connectorUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client implementation (for testing) */\n httpClient?: typeof fetch;\n}\n\n/**\n * HTTP client for sending ILP packets to an external connector runtime API.\n *\n * Implements the IlpClient interface for use with TOON agents\n * that need to send ILP packets without embedding a full connector.\n *\n * Features:\n * - Request validation (destination, amount, data)\n * - Retry logic with exponential backoff for transient network failures\n * - Typed error handling (NetworkError, ConnectorError, ValidationError)\n * - Connection pooling and keep-alive (via Node.js fetch)\n *\n * @example\n * ```typescript\n * const client = new HttpRuntimeClient({\n * connectorUrl: 'http://localhost:8080'\n * });\n *\n * const result = await client.sendIlpPacket({\n * destination: 'g.toon.alice',\n * amount: '1000',\n * data: 'base64EncodedToonData==',\n * });\n *\n * if (result.accepted) {\n * console.log('Payment accepted');\n * } else {\n * console.error('Payment rejected:', result.code, result.message);\n * }\n * ```\n */\nexport class HttpRuntimeClient implements IlpClient {\n private readonly connectorUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpRuntimeClientConfig) {\n // Normalize connector URL (remove trailing slash)\n this.connectorUrl = config.connectorUrl.replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Send an ILP packet to the connector runtime API.\n *\n * @param params - ILP packet parameters\n * @returns ILP packet response with acceptance status\n * @throws {ValidationError} If request parameters are invalid\n * @throws {NetworkError} If network connection fails after retries\n * @throws {ConnectorError} If connector returns 5xx server error\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n // Validate request parameters\n this.validateRequest(params);\n\n // Wrap HTTP request with retry logic\n return withRetry(async () => this.sendHttpRequest(params), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Validate ILP packet request parameters.\n *\n * @throws {ValidationError} If any parameter is invalid\n */\n private validateRequest(params: {\n destination: string;\n amount: string;\n data: string;\n }): void {\n // Validate destination: non-empty, valid ILP address format\n if (!params.destination || params.destination.trim() === '') {\n throw new ValidationError('Destination cannot be empty');\n }\n if (!params.destination.startsWith('g.')) {\n throw new ValidationError(\n `Invalid ILP address format: \"${params.destination}\" (must start with \"g.\")`\n );\n }\n\n // Validate amount: non-empty, parseable as bigint, positive\n if (!params.amount || params.amount.trim() === '') {\n throw new ValidationError('Amount cannot be empty');\n }\n try {\n const amountBigInt = BigInt(params.amount);\n if (amountBigInt <= 0n) {\n throw new ValidationError(\n `Amount must be positive: \"${params.amount}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Amount must be a valid integer: \"${params.amount}\"`,\n error instanceof Error ? error : undefined\n );\n }\n\n // Validate data: non-empty, valid Base64 encoding\n if (!params.data || params.data.trim() === '') {\n throw new ValidationError('Data cannot be empty');\n }\n try {\n if (!isBase64(params.data)) {\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Send HTTP POST request to connector runtime API.\n *\n * @throws {NetworkError} On connection failures (ECONNREFUSED, ETIMEDOUT)\n * @throws {ConnectorError} On 5xx server errors\n * @returns IlpSendResult with acceptance status\n */\n private async sendHttpRequest(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeout);\n\n try {\n // NOTE: Using admin endpoint /admin/ilp/send since connector doesn't have public /ilp endpoint yet\n const response = await this.httpClient(\n `${this.connectorUrl}/admin/ilp/send`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n }),\n signal: controller.signal,\n }\n );\n\n clearTimeout(timeoutId);\n\n // Handle response by status code\n if (response.ok) {\n // 200 OK: Parse response as IlpSendResult\n const result = (await response.json()) as Record<string, unknown>;\n return {\n accepted: (result['accepted'] as boolean) ?? false,\n data: result['data'] as string | undefined,\n code: result['code'] as string | undefined,\n message: result['message'] as string | undefined,\n };\n } else if (response.status >= 400 && response.status < 500) {\n // 4xx: Client error - return as failed ILP response (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n return {\n accepted: false,\n code: `HTTP_${response.status}`,\n message:\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText,\n };\n } else if (response.status >= 500 && response.status < 600) {\n // 5xx: Server error - throw ConnectorError (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n throw new ConnectorError(\n `Connector server error (${response.status}): ${\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText\n }`\n );\n }\n\n // Unexpected status code (not 2xx, 4xx, or 5xx)\n throw new ConnectorError(\n `Unexpected HTTP status: ${response.status} ${response.statusText}`\n );\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle AbortController timeout\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request timeout after ${requestTimeout}ms`,\n error\n );\n }\n\n // Handle network errors (ECONNREFUSED, ETIMEDOUT, etc.)\n if (\n error instanceof TypeError &&\n (error.message.includes('fetch failed') ||\n error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('network'))\n ) {\n throw new NetworkError(\n `Network connection failed: ${error.message}`,\n error\n );\n }\n\n // Re-throw known error types\n if (\n error instanceof NetworkError ||\n error instanceof ConnectorError ||\n error instanceof ValidationError\n ) {\n throw error;\n }\n\n // Unknown error\n throw new ConnectorError(\n `Unexpected error during HTTP request: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}\n","/* eslint-disable @typescript-eslint/no-non-null-assertion -- bounds-checked binary parsing */\n/**\n * Isomorphic BTP + ILP binary protocol.\n * Uses Uint8Array and DataView — no Buffer, no Node.js dependencies.\n *\n * BTP (Bilateral Transfer Protocol) message format:\n * 1 byte — message type\n * 4 bytes — request ID (uint32 BE)\n * variable — payload (MESSAGE data or ERROR data)\n *\n * ILP (Interledger Protocol) OER-encoded packets:\n * PREPARE (type 12), FULFILL (type 13), REJECT (type 14)\n */\n\n// ─── Text codec (isomorphic) ────────────────────────────────────────────────\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\n// ─── BTP types ──────────────────────────────────────────────────────────────\n\nexport const BTPMessageType = {\n RESPONSE: 1,\n ERROR: 2,\n MESSAGE: 6,\n} as const;\n\nexport const ILPPacketType = {\n PREPARE: 12,\n FULFILL: 13,\n REJECT: 14,\n} as const;\n\nexport interface BTPProtocolData {\n protocolName: string;\n contentType: number;\n data: Uint8Array;\n}\n\nexport interface BTPMessageData {\n protocolData: BTPProtocolData[];\n ilpPacket?: Uint8Array;\n}\n\nexport interface BTPMessage {\n type: number;\n requestId: number;\n data: BTPMessageData | BTPErrorData;\n}\n\nexport interface BTPErrorData {\n code: string;\n name: string;\n triggeredAt: string;\n data: Uint8Array;\n}\n\nexport interface ILPPreparePacket {\n type: typeof ILPPacketType.PREPARE;\n amount: bigint;\n destination: string;\n executionCondition: Uint8Array;\n expiresAt: Date;\n data: Uint8Array;\n}\n\nexport interface ILPFulfillPacket {\n type: typeof ILPPacketType.FULFILL;\n /**\n * The 32-byte fulfillment preimage from the wire. All-zero on legacy\n * (zero-condition) packets; on sender-chosen conditions (toon-client#350)\n * the transport verifies `sha256(fulfillment) == executionCondition`.\n */\n fulfillment: Uint8Array;\n data: Uint8Array;\n}\n\nexport interface ILPRejectPacket {\n type: typeof ILPPacketType.REJECT;\n code: string;\n message: string;\n data: Uint8Array;\n}\n\nexport type ILPResponsePacket = ILPFulfillPacket | ILPRejectPacket;\n\n// ─── Byte helpers ───────────────────────────────────────────────────────────\n\nfunction concat(...arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, a) => sum + a.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const a of arrays) {\n result.set(a, offset);\n offset += a.length;\n }\n return result;\n}\n\nfunction readUint8(buf: Uint8Array, offset: number): number {\n if (offset >= buf.length) throw new Error('Buffer underflow reading uint8');\n return buf[offset]!;\n}\n\nfunction readUint16BE(buf: Uint8Array, offset: number): number {\n if (offset + 2 > buf.length)\n throw new Error('Buffer underflow reading uint16');\n return (buf[offset]! << 8) | buf[offset + 1]!;\n}\n\nfunction readUint32BE(buf: Uint8Array, offset: number): number {\n if (offset + 4 > buf.length)\n throw new Error('Buffer underflow reading uint32');\n return (\n ((buf[offset]! << 24) |\n (buf[offset + 1]! << 16) |\n (buf[offset + 2]! << 8) |\n buf[offset + 3]!) >>>\n 0\n );\n}\n\nfunction writeUint8(value: number): Uint8Array {\n return new Uint8Array([value]);\n}\n\nfunction writeUint16BE(value: number): Uint8Array {\n return new Uint8Array([(value >> 8) & 0xff, value & 0xff]);\n}\n\nfunction writeUint32BE(value: number): Uint8Array {\n return new Uint8Array([\n (value >> 24) & 0xff,\n (value >> 16) & 0xff,\n (value >> 8) & 0xff,\n value & 0xff,\n ]);\n}\n\nfunction sliceUtf8(buf: Uint8Array, offset: number, length: number): string {\n return textDecoder.decode(buf.slice(offset, offset + length));\n}\n\n// ─── OER encoding (ILP wire format) ─────────────────────────────────────────\n\nfunction encodeVarUInt(value: bigint): Uint8Array {\n if (value >= 0n && value <= 127n) {\n return new Uint8Array([Number(value)]);\n }\n const bytes: number[] = [];\n let remaining = value;\n while (remaining > 0n) {\n bytes.unshift(Number(remaining & 0xffn));\n remaining = remaining >> 8n;\n }\n return new Uint8Array([0x80 | bytes.length, ...bytes]);\n}\n\nfunction decodeVarUInt(\n buf: Uint8Array,\n offset: number\n): { value: bigint; bytesRead: number } {\n const firstByte = readUint8(buf, offset);\n if (firstByte <= 127) {\n return { value: BigInt(firstByte), bytesRead: 1 };\n }\n const length = firstByte & 0x7f;\n if (offset + 1 + length > buf.length)\n throw new Error('VarUInt buffer underflow');\n let value = 0n;\n for (let i = 0; i < length; i++) {\n value = (value << 8n) | BigInt(buf[offset + 1 + i]!);\n }\n return { value, bytesRead: 1 + length };\n}\n\nfunction encodeVarOctetString(data: Uint8Array): Uint8Array {\n return concat(encodeVarUInt(BigInt(data.length)), data);\n}\n\nfunction decodeVarOctetString(\n buf: Uint8Array,\n offset: number\n): { value: Uint8Array; bytesRead: number } {\n const { value: length, bytesRead: lenBytes } = decodeVarUInt(buf, offset);\n const dataLen = Number(length);\n const start = offset + lenBytes;\n if (start + dataLen > buf.length)\n throw new Error('VarOctetString buffer underflow');\n return {\n value: buf.slice(start, start + dataLen),\n bytesRead: lenBytes + dataLen,\n };\n}\n\nfunction encodeGeneralizedTime(date: Date): Uint8Array {\n const y = date.getUTCFullYear().toString().padStart(4, '0');\n const mo = (date.getUTCMonth() + 1).toString().padStart(2, '0');\n const d = date.getUTCDate().toString().padStart(2, '0');\n const h = date.getUTCHours().toString().padStart(2, '0');\n const mi = date.getUTCMinutes().toString().padStart(2, '0');\n const s = date.getUTCSeconds().toString().padStart(2, '0');\n const ms = date.getUTCMilliseconds().toString().padStart(3, '0');\n return textEncoder.encode(`${y}${mo}${d}${h}${mi}${s}.${ms}Z`);\n}\n\n// ─── ILP packet serialization ───────────────────────────────────────────────\n\nexport function serializeIlpPrepare(packet: ILPPreparePacket): Uint8Array {\n const condition =\n packet.executionCondition.length === 32\n ? packet.executionCondition\n : new Uint8Array(32);\n return concat(\n writeUint8(ILPPacketType.PREPARE),\n encodeVarUInt(packet.amount),\n encodeGeneralizedTime(packet.expiresAt),\n condition,\n encodeVarOctetString(textEncoder.encode(packet.destination)),\n encodeVarOctetString(packet.data)\n );\n}\n\nexport function deserializeIlpPacket(buf: Uint8Array): ILPResponsePacket {\n if (buf.length === 0) throw new Error('Empty ILP packet');\n const type = buf[0]!;\n if (type === ILPPacketType.FULFILL) return deserializeIlpFulfill(buf);\n if (type === ILPPacketType.REJECT) return deserializeIlpReject(buf);\n throw new Error(`Unknown ILP packet type: ${type}`);\n}\n\nfunction deserializeIlpFulfill(buf: Uint8Array): ILPFulfillPacket {\n let offset = 1; // skip type byte\n // 32-byte fulfillment preimage. Was skipped (\"unused in TOON\") before\n // sender-chosen execution conditions (toon-client#350) made it load-bearing.\n if (buf.length < offset + 32) {\n throw new Error('Buffer underflow reading FULFILL fulfillment');\n }\n const fulfillment = buf.slice(offset, offset + 32);\n offset += 32;\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.FULFILL, fulfillment, data };\n}\n\nfunction deserializeIlpReject(buf: Uint8Array): ILPRejectPacket {\n let offset = 1; // skip type byte\n // 3-byte error code\n const code = sliceUtf8(buf, offset, 3);\n offset += 3;\n // triggeredBy (skip)\n const { bytesRead: tbBytes } = decodeVarOctetString(buf, offset);\n offset += tbBytes;\n // message\n const { value: msgBuf, bytesRead: msgBytes } = decodeVarOctetString(\n buf,\n offset\n );\n offset += msgBytes;\n const message = textDecoder.decode(msgBuf);\n // data\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.REJECT, code, message, data };\n}\n\n// ─── BTP message serialization ──────────────────────────────────────────────\n\nexport function serializeBtpMessage(message: BTPMessage): Uint8Array {\n const parts: Uint8Array[] = [\n writeUint8(message.type),\n writeUint32BE(message.requestId),\n ];\n\n const data = message.data as BTPMessageData;\n const protocolData = data.protocolData ?? [];\n\n // Protocol data count\n parts.push(writeUint8(protocolData.length));\n\n // Each protocol data entry\n for (const pd of protocolData) {\n const nameBytes = textEncoder.encode(pd.protocolName);\n parts.push(writeUint8(nameBytes.length));\n parts.push(nameBytes);\n parts.push(writeUint16BE(pd.contentType));\n parts.push(writeUint32BE(pd.data.length));\n if (pd.data.length > 0) parts.push(pd.data);\n }\n\n // ILP packet\n const ilpPacket = data.ilpPacket ?? new Uint8Array(0);\n parts.push(writeUint32BE(ilpPacket.length));\n if (ilpPacket.length > 0) parts.push(ilpPacket);\n\n return concat(...parts);\n}\n\nexport function parseBtpMessage(buf: Uint8Array): BTPMessage {\n if (buf.length < 5) throw new Error('BTP message too short');\n let offset = 0;\n\n const type = readUint8(buf, offset);\n offset += 1;\n const requestId = readUint32BE(buf, offset);\n offset += 4;\n\n if (type === BTPMessageType.ERROR) {\n // code\n const codeLen = readUint8(buf, offset);\n offset += 1;\n const code = sliceUtf8(buf, offset, codeLen);\n offset += codeLen;\n // name\n const nameLen = readUint8(buf, offset);\n offset += 1;\n const name = sliceUtf8(buf, offset, nameLen);\n offset += nameLen;\n // triggeredAt\n const taLen = readUint8(buf, offset);\n offset += 1;\n const triggeredAt = sliceUtf8(buf, offset, taLen);\n offset += taLen;\n // data\n const dataLen = readUint32BE(buf, offset);\n offset += 4;\n const data = buf.slice(offset, offset + dataLen);\n return { type, requestId, data: { code, name, triggeredAt, data } };\n }\n\n // MESSAGE or RESPONSE\n const pdCount = readUint8(buf, offset);\n offset += 1;\n const protocolData: BTPProtocolData[] = [];\n for (let i = 0; i < pdCount; i++) {\n const nameLen = readUint8(buf, offset);\n offset += 1;\n const protocolName = sliceUtf8(buf, offset, nameLen);\n offset += nameLen;\n const contentType = readUint16BE(buf, offset);\n offset += 2;\n const dataLen = readUint32BE(buf, offset);\n offset += 4;\n const data = buf.slice(offset, offset + dataLen);\n offset += dataLen;\n protocolData.push({ protocolName, contentType, data });\n }\n\n let ilpPacket: Uint8Array | undefined;\n if (offset + 4 <= buf.length) {\n const ilpLen = readUint32BE(buf, offset);\n offset += 4;\n if (ilpLen > 0 && offset + ilpLen <= buf.length) {\n ilpPacket = buf.slice(offset, offset + ilpLen);\n }\n }\n\n return { type, requestId, data: { protocolData, ilpPacket } };\n}\n","/* eslint-disable @typescript-eslint/no-non-null-assertion -- ws is guaranteed non-null when _isConnected */\n/**\n * Isomorphic BTP client — works in both browser and Node.js.\n * Uses native WebSocket (browser) or globalThis.WebSocket (Node 21+).\n * No dependency on `ws`, `events`, or `Buffer`.\n *\n * Replaces the @toon-protocol/connector BTPClient for the client SDK.\n */\n\nimport {\n BTPMessageType,\n ILPPacketType,\n serializeBtpMessage,\n serializeIlpPrepare,\n parseBtpMessage,\n deserializeIlpPacket,\n type BTPProtocolData,\n type BTPMessageData,\n type BTPErrorData,\n type ILPPreparePacket,\n type ILPResponsePacket,\n} from './protocol.js';\n\nconst textEncoder = new TextEncoder();\n\nexport interface IsomorphicBtpClientConfig {\n url: string;\n peerId: string;\n authToken: string;\n sendTimeoutMs?: number;\n authTimeoutMs?: number;\n /** Custom WebSocket constructor (e.g., the Node `ws` package, or for testing). */\n createWebSocket?: (url: string) => WebSocket;\n}\n\ninterface PendingRequest {\n resolve: (packet: ILPResponsePacket) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\nexport class BtpConnectionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BtpConnectionError';\n }\n}\n\nexport class BtpAuthError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BtpAuthError';\n }\n}\n\n/**\n * Lightweight BTP client that speaks the BTP binary protocol over WebSocket.\n * Handles: connect → authenticate → send ILP packets → receive responses.\n */\nexport class IsomorphicBtpClient {\n private ws: WebSocket | null = null;\n private _isConnected = false;\n private requestIdCounter = 0;\n private readonly pendingRequests = new Map<number, PendingRequest>();\n private readonly config: Required<\n Omit<IsomorphicBtpClientConfig, 'createWebSocket'>\n > &\n Pick<IsomorphicBtpClientConfig, 'createWebSocket'>;\n\n constructor(config: IsomorphicBtpClientConfig) {\n this.config = {\n sendTimeoutMs: 30_000,\n authTimeoutMs: 5_000,\n ...config,\n };\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n async connect(): Promise<void> {\n if (this._isConnected) return;\n\n return new Promise<void>((resolve, reject) => {\n try {\n this.ws = this.config.createWebSocket\n ? this.config.createWebSocket(this.config.url)\n : new WebSocket(this.config.url);\n this.ws.binaryType = 'arraybuffer';\n } catch (err) {\n reject(\n new BtpConnectionError(\n `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n return;\n }\n\n this.ws.onopen = async () => {\n try {\n await this.authenticate();\n this._isConnected = true;\n resolve();\n } catch (err) {\n this._isConnected = false;\n this.ws?.close();\n reject(err);\n }\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n this.handleMessage(event.data);\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.ws.onerror = (event: any) => {\n const underlying = event?.error ?? event?.message;\n const detail =\n underlying instanceof Error\n ? underlying.message\n : typeof underlying === 'string'\n ? underlying\n : null;\n reject(\n new BtpConnectionError(\n detail\n ? `WebSocket connection error: ${detail}`\n : 'WebSocket connection error'\n )\n );\n };\n\n this.ws.onclose = () => {\n this._isConnected = false;\n // Reject all pending requests\n for (const [id, pending] of this.pendingRequests) {\n clearTimeout(pending.timeoutId);\n pending.reject(new BtpConnectionError('Connection closed'));\n this.pendingRequests.delete(id);\n }\n };\n });\n }\n\n async disconnect(): Promise<void> {\n this._isConnected = false;\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n for (const [id, pending] of this.pendingRequests) {\n clearTimeout(pending.timeoutId);\n pending.reject(new BtpConnectionError('Disconnected'));\n this.pendingRequests.delete(id);\n }\n }\n\n /**\n * Send an ILP PREPARE packet, optionally with protocol data (e.g. payment channel claim).\n * Returns the ILP response (FULFILL or REJECT).\n */\n async sendPacket(\n packet: ILPPreparePacket,\n protocolData?: BTPProtocolData[]\n ): Promise<ILPResponsePacket> {\n if (!this._isConnected || !this.ws) {\n throw new BtpConnectionError('Not connected');\n }\n\n const serializedIlp = serializeIlpPrepare(packet);\n const requestId = this.nextRequestId();\n\n const btpMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: protocolData ?? [],\n ilpPacket: serializedIlp,\n },\n });\n\n this.ws.send(btpMessage);\n\n // Calculate timeout from packet expiry or default\n let timeoutMs = this.config.sendTimeoutMs;\n if (packet.expiresAt) {\n const remaining = packet.expiresAt.getTime() - Date.now();\n timeoutMs = Math.max(remaining - 500, 1000);\n }\n\n return new Promise<ILPResponsePacket>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new BtpConnectionError(`Packet send timeout (${timeoutMs}ms)`));\n }, timeoutMs);\n\n this.pendingRequests.set(requestId, { resolve, reject, timeoutId });\n });\n }\n\n /**\n * Send a fire-and-forget BTP MESSAGE carrying only protocol data (no ILP\n * packet). Used for out-of-band claim notifications that the connector's\n * ClaimReceiver consumes via `handleClaimMessage` — there is no RESPONSE\n * frame, so we resolve immediately after the WebSocket buffers the bytes.\n *\n * Mirrors `sendPacket` wire-format construction but uses an empty ILP\n * payload and does not enroll a pending request.\n */\n async sendProtocolData(\n protocolName: string,\n contentType: number,\n data: Uint8Array\n ): Promise<void> {\n if (!this._isConnected || !this.ws) {\n throw new BtpConnectionError('Not connected');\n }\n\n const requestId = this.nextRequestId();\n const btpMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: [{ protocolName, contentType, data }],\n ilpPacket: new Uint8Array(0),\n },\n });\n\n this.ws.send(btpMessage);\n }\n\n // ─── Private ────────────────────────────────────────────────────────────\n\n private async authenticate(): Promise<void> {\n if (!this.ws) throw new BtpAuthError('WebSocket not connected');\n\n const authData = JSON.stringify({\n peerId: this.config.peerId,\n secret: this.config.authToken,\n });\n\n const requestId = this.nextRequestId();\n const authMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: [\n {\n protocolName: 'auth',\n contentType: 0,\n data: textEncoder.encode(authData),\n },\n ],\n ilpPacket: new Uint8Array(0),\n },\n });\n\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new BtpAuthError('Authentication timeout'));\n }, this.config.authTimeoutMs);\n\n // Temporarily intercept messages for auth response\n const originalHandler = this.ws!.onmessage;\n this.ws!.onmessage = (event: MessageEvent) => {\n try {\n const data = this.toUint8Array(event.data);\n\n // Try JSON parse first (server may respond with JSON)\n try {\n const jsonStr = new TextDecoder().decode(data);\n if (jsonStr.startsWith('{')) {\n // JSON response — not a BTP binary auth response, ignore\n }\n } catch {\n /* not JSON */\n }\n\n // Parse as BTP binary\n const message = parseBtpMessage(data);\n if (message.requestId === requestId) {\n clearTimeout(timeout);\n this.ws!.onmessage = originalHandler;\n\n if (message.type === BTPMessageType.ERROR) {\n const errData = message.data as BTPErrorData;\n reject(\n new BtpAuthError(\n `Authentication failed: ${errData.code} msg=${errData.message ?? ''} trigger=${errData.triggeredBy ?? ''}`\n )\n );\n } else if (message.type === BTPMessageType.RESPONSE) {\n resolve();\n }\n }\n } catch (err) {\n clearTimeout(timeout);\n this.ws!.onmessage = originalHandler;\n reject(\n new BtpAuthError(err instanceof Error ? err.message : String(err))\n );\n }\n };\n\n this.ws!.send(authMessage);\n });\n }\n\n private handleMessage(raw: unknown): void {\n // Try JSON first (server can send JSON FULFILL/REJECT responses)\n try {\n const data = this.toUint8Array(raw);\n const jsonStr = new TextDecoder().decode(data);\n if (jsonStr.startsWith('{')) {\n const json = JSON.parse(jsonStr) as Record<string, unknown>;\n if (json['type'] === 'FULFILL' || json['type'] === 'REJECT') {\n const first = this.pendingRequests.entries().next();\n if (!first.done) {\n const [id, pending] = first.value;\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(id);\n\n if (json['type'] === 'FULFILL') {\n const responseData = json['data']\n ? this.base64ToUint8Array(json['data'] as string)\n : new Uint8Array(0);\n // JSON FULFILLs may carry the fulfillment preimage as base64;\n // absent means all-zero (legacy). A sender-chosen condition\n // (toon-client#350) then fails verification — fail-closed.\n const fulfillment = json['fulfillment']\n ? this.base64ToUint8Array(json['fulfillment'] as string)\n : new Uint8Array(32);\n pending.resolve({\n type: ILPPacketType.FULFILL,\n fulfillment,\n data: responseData,\n });\n } else {\n pending.resolve({\n type: ILPPacketType.REJECT,\n code: (json['code'] as string) || 'F00',\n message: (json['message'] as string) || 'Unknown error',\n data: json['data']\n ? this.base64ToUint8Array(json['data'] as string)\n : new Uint8Array(0),\n });\n }\n }\n return;\n }\n }\n } catch {\n /* not JSON, try BTP binary */\n }\n\n // BTP binary response\n try {\n const data = this.toUint8Array(raw);\n const message = parseBtpMessage(data);\n\n if (\n message.type === BTPMessageType.RESPONSE ||\n message.type === BTPMessageType.ERROR\n ) {\n const pending = this.pendingRequests.get(message.requestId);\n if (!pending) return;\n\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(message.requestId);\n\n if (message.type === BTPMessageType.ERROR) {\n const errData = message.data as BTPErrorData;\n pending.reject(\n new BtpConnectionError(`BTP error: ${errData.code} ${errData.name}`)\n );\n return;\n }\n\n const msgData = message.data as BTPMessageData;\n if (msgData.ilpPacket && msgData.ilpPacket.length > 0) {\n const ilpResponse = deserializeIlpPacket(msgData.ilpPacket);\n pending.resolve(ilpResponse);\n }\n }\n } catch {\n // Unparseable message — ignore\n }\n }\n\n private toUint8Array(data: unknown): Uint8Array {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (data instanceof Uint8Array) return data;\n if (typeof data === 'string') return textEncoder.encode(data);\n throw new Error(`Unexpected WebSocket data type: ${typeof data}`);\n }\n\n private base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n }\n\n private nextRequestId(): number {\n this.requestIdCounter = (this.requestIdCounter + 1) & 0xffffffff;\n return this.requestIdCounter;\n }\n}\n","/**\n * Sender-chosen ILP execution conditions (toon-client#350, rolling-swap\n * prerequisite — toon-meta#145 §3 R1/R2).\n *\n * Contract (normative: connector `docs/local-delivery-fulfillment-contract.md`,\n * connector#309):\n * - An ABSENT or ALL-ZERO (32×0x00) `executionCondition` is the LEGACY\n * class: no hashlock, no verification — today's publish/upload writes.\n * - Any NON-ZERO condition is SENDER-CHOSEN: the sender mints a fresh\n * random 32-byte preimage `P` per packet, sets `C = sha256(P)` on the\n * PREPARE, and on FULFILL MUST verify that the returned fulfillment\n * hashes back to `C` — a missing/malformed/mismatching fulfillment is a\n * hard failure (the packet is counted failed, never silently accepted).\n *\n * Isomorphic: @noble/hashes only (no Buffer, no node:crypto).\n */\n\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { randomBytes } from '@noble/hashes/utils.js';\n\n/** Exact byte length of an ILP execution condition / fulfillment preimage. */\nexport const CONDITION_LENGTH = 32;\n\n/** A freshly minted per-packet hashlock: `condition = sha256(preimage)`. */\nexport interface ExecutionConditionPair {\n /** Random 32-byte preimage `P` — reveal is the commit act; never reuse. */\n preimage: Uint8Array;\n /** `C = sha256(P)` — goes on the PREPARE's `executionCondition`. */\n condition: Uint8Array;\n}\n\n/**\n * Mint a fresh random 32-byte preimage and its sha256 condition (spec R1).\n * Mint one PER PACKET: a reused preimage lets any observer of packet *i*\n * fulfill packet *i+1* without the sender's consent.\n */\nexport function mintExecutionCondition(): ExecutionConditionPair {\n const preimage = randomBytes(CONDITION_LENGTH);\n return { preimage, condition: sha256(preimage) };\n}\n\n/**\n * True when `condition` selects the LEGACY (unverified) class: absent or\n * all-zero. The OER wire treats absent and 32×0x00 identically.\n */\nexport function isZeroCondition(condition: Uint8Array | undefined): boolean {\n if (condition === undefined) return true;\n for (const byte of condition) {\n if (byte !== 0) return false;\n }\n return true;\n}\n\n/**\n * Validate a caller-supplied execution condition before it goes on the wire.\n * @throws {Error} When the condition is not exactly 32 bytes — the OER\n * serializer would otherwise silently zero-fill it, downgrading a\n * sender-chosen packet to the legacy unverified class.\n */\nexport function assertValidCondition(condition: Uint8Array): void {\n if (condition.length !== CONDITION_LENGTH) {\n throw new Error(\n `executionCondition must be exactly ${CONDITION_LENGTH} bytes, got ${condition.length}`\n );\n }\n}\n\n/**\n * True iff `fulfillment` is exactly 32 bytes and `sha256(fulfillment)`\n * equals `condition` — the sender-side FULFILL check (spec R6's mirror).\n * Fail-closed: an undefined/short/long fulfillment never matches.\n */\nexport function fulfillmentMatchesCondition(\n fulfillment: Uint8Array | undefined,\n condition: Uint8Array\n): boolean {\n if (fulfillment === undefined || fulfillment.length !== CONDITION_LENGTH) {\n return false;\n }\n const digest = sha256(fulfillment);\n // Plain comparison is fine: both operands are locally computed/received\n // values on the sender; there is no secret to protect from timing here.\n for (let i = 0; i < CONDITION_LENGTH; i++) {\n if (digest[i] !== condition[i]) return false;\n }\n return true;\n}\n","/**\n * Shared sender-side plumbing for sender-chosen execution conditions\n * (toon-client#350; contract: connector `docs/local-delivery-fulfillment-contract.md`,\n * connector#309; spec: toon-meta `docs/rolling-swap.md` §3).\n *\n * Both ILP transports (HTTP `POST /ilp` and BTP) take the SAME extended send\n * params and map FULFILL/REJECT responses through the SAME verifier so the\n * two paths cannot drift:\n *\n * - absent/all-zero `executionCondition` → legacy class: today's behavior,\n * byte-for-byte (zero condition on the wire, no FULFILL verification);\n * - non-zero `executionCondition` → sender-chosen: the condition goes on\n * the PREPARE verbatim and the FULFILL's 32-byte fulfillment MUST hash\n * back to it — a missing/malformed/mismatching preimage is surfaced as a\n * FAILED result (never a silent accept, never retried).\n */\n\nimport type { IlpSendResult } from '@toon-protocol/core';\nimport { ILPPacketType, type ILPResponsePacket } from '../btp/protocol.js';\nimport { toBase64 } from '../utils/binary.js';\nimport {\n fulfillmentMatchesCondition,\n isZeroCondition,\n} from '../utils/condition.js';\n\n/**\n * Send parameters accepted by both ILP transports. Extends the\n * `@toon-protocol/core` `IlpClient` param shape with the sender-chosen\n * condition and an explicit expiry — plain `IlpClient` callers keep working\n * unchanged (both extras are optional; omitting them is the legacy path).\n */\nexport interface IlpSendParams {\n destination: string;\n amount: string;\n /** Base64 ILP `data` payload. */\n data: string;\n /** Transport timeout in ms; also the default expiry window. */\n timeout?: number;\n /**\n * Sender-chosen 32-byte execution condition `C = sha256(P)` (spec R2).\n * Absent or all-zero = legacy unverified packet (default — ordinary\n * publish/upload writes MUST keep this default).\n */\n executionCondition?: Uint8Array;\n /**\n * Explicit PREPARE `expiresAt` (spec R7). Defaults to `now + timeout`,\n * preserving pre-#350 behavior. Accepts a `Date` or an ISO 8601 string —\n * `@toon-protocol/core` ≥2.1.0's `IlpClient` passes the string form.\n */\n expiresAt?: Date | string;\n}\n\n/** Normalize an `IlpSendParams.expiresAt` to a `Date` (default: now + timeout). */\nexport function resolveExpiresAt(\n expiresAt: Date | string | undefined,\n timeoutMs: number\n): Date {\n if (expiresAt === undefined) return new Date(Date.now() + timeoutMs);\n return expiresAt instanceof Date ? new Date(expiresAt.getTime()) : new Date(expiresAt);\n}\n\n/**\n * `IlpSendResult` plus the FULFILL preimage when a sender-chosen condition\n * was verified. Only populated on the sender-chosen path so legacy result\n * objects stay byte-identical.\n */\nexport interface IlpSendResultWithFulfillment extends IlpSendResult {\n /** Base64 32-byte fulfillment preimage (verified: sha256 == condition). */\n fulfillment?: string;\n}\n\n/** ILP code used for a client-side fulfillment-verification failure. */\nexport const FULFILLMENT_MISMATCH_CODE = 'F99';\n\n/** Message for a client-side fulfillment-verification failure. */\nexport const FULFILLMENT_MISMATCH_MESSAGE =\n 'FULFILL fulfillment does not match execution condition ' +\n '(sha256(fulfillment) != executionCondition) — packet counted failed';\n\n/**\n * Map a parsed ILP response packet to an `IlpSendResult`, enforcing the\n * sender-chosen condition when one was sent.\n *\n * Verification is fail-closed: when `sentCondition` is non-zero, a FULFILL\n * whose fulfillment is absent, not exactly 32 bytes, or does not sha256-hash\n * to the condition yields `accepted: false` (code F99). The result shape —\n * not a thrown error — is deliberate: transports only retry thrown\n * `NetworkError`s, so a forged/wrong FULFILL is never retried (re-sending\n * would re-spend the attached claim), and packet counters (`streamSwap`\n * rejections) count it failed.\n */\nexport function mapIlpResponse(\n packet: ILPResponsePacket,\n sentCondition?: Uint8Array\n): IlpSendResultWithFulfillment {\n if (packet.type === ILPPacketType.FULFILL) {\n const dataField =\n packet.data.length > 0 ? { data: toBase64(packet.data) } : {};\n\n // Legacy class: absent/all-zero condition → accept without verification\n // (pre-#350 behavior, byte-for-byte).\n if (sentCondition === undefined || isZeroCondition(sentCondition)) {\n return { accepted: true, ...dataField };\n }\n\n // Sender-chosen class: the FULFILL preimage MUST hash to the condition.\n if (!fulfillmentMatchesCondition(packet.fulfillment, sentCondition)) {\n return {\n accepted: false,\n code: FULFILLMENT_MISMATCH_CODE,\n message: FULFILLMENT_MISMATCH_MESSAGE,\n ...dataField,\n };\n }\n\n return {\n accepted: true,\n fulfillment: toBase64(packet.fulfillment),\n ...dataField,\n };\n }\n\n // REJECT\n return {\n accepted: false,\n code: packet.code,\n message: packet.message,\n ...(packet.data.length > 0 ? { data: toBase64(packet.data) } : {}),\n };\n}\n","import {\n IsomorphicBtpClient,\n BtpConnectionError,\n} from '../btp/IsomorphicBtpClient.js';\nimport { type BTPProtocolData } from '../btp/protocol.js';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { withRetry } from '../utils/retry.js';\nimport { fromBase64, encodeUtf8 } from '../utils/binary.js';\nimport { mapIlpResponse, resolveExpiresAt, type IlpSendParams } from './ilp-send.js';\nimport { assertValidCondition, isZeroCondition } from '../utils/condition.js';\n\nexport interface BtpRuntimeClientConfig {\n btpUrl: string;\n peerId: string;\n authToken: string;\n /** Max reconnection attempts on send failure (default: 3) */\n maxRetries?: number;\n /** Delay between reconnection attempts in ms (default: 1000) */\n retryDelay?: number;\n /** Custom WebSocket constructor (for testing / custom transports). */\n createWebSocket?: (url: string) => WebSocket;\n}\n\n/**\n * Returns true if the error is a connection-level error worth retrying.\n * ILP application-level rejects (F02, T01, etc.) are NOT retried.\n */\nfunction isConnectionError(error: Error): boolean {\n const msg = error.message.toLowerCase();\n return (\n msg.includes('not connected') ||\n msg.includes('connection') ||\n msg.includes('websocket') ||\n msg.includes('econnrefused') ||\n msg.includes('econnreset') ||\n msg.includes('socket hang up') ||\n msg.includes('timeout')\n );\n}\n\n/**\n * BTP transport implementing IlpClient.\n * Uses IsomorphicBtpClient (browser-native, no Node.js dependencies).\n */\nexport class BtpRuntimeClient implements IlpClient {\n private btpClient: IsomorphicBtpClient | null = null;\n private readonly config: BtpRuntimeClientConfig;\n private _isConnected = false;\n\n constructor(config: BtpRuntimeClientConfig) {\n this.config = config;\n }\n\n /**\n * Connects to the BTP peer via WebSocket.\n */\n async connect(): Promise<void> {\n this.btpClient = new IsomorphicBtpClient({\n url: this.config.btpUrl,\n peerId: this.config.peerId,\n authToken: this.config.authToken,\n createWebSocket: this.config.createWebSocket,\n });\n\n await this.btpClient.connect();\n this._isConnected = true;\n }\n\n /**\n * Attempts to reconnect by creating a fresh client and connecting.\n */\n async reconnect(): Promise<void> {\n if (this.btpClient) {\n try {\n await this.btpClient.disconnect();\n } catch {\n // Ignore disconnect errors during reconnect\n }\n this.btpClient = null;\n this._isConnected = false;\n }\n\n await this.connect();\n }\n\n /**\n * Disconnects from the BTP peer.\n */\n async disconnect(): Promise<void> {\n if (this.btpClient) {\n await this.btpClient.disconnect();\n this._isConnected = false;\n this.btpClient = null;\n }\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Sends an ILP packet via BTP with auto-reconnect on connection errors.\n * Satisfies IlpClient interface.\n *\n * `params` may carry a sender-chosen `executionCondition` and an explicit\n * `expiresAt` (toon-client#350); omitting both is the legacy zero-condition\n * path, unchanged. With a non-zero condition the FULFILL preimage is\n * verified (`sha256(fulfillment) == condition`) and a mismatch is surfaced\n * as a failed result — see `mapIlpResponse`.\n */\n async sendIlpPacket(params: IlpSendParams): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketOnce(params), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.\n * Auto-reconnects on connection errors.\n *\n * Sender-chosen `executionCondition` / explicit `expiresAt` semantics are\n * identical to {@link sendIlpPacket}.\n */\n async sendIlpPacketWithClaim(\n params: IlpSendParams,\n claim: Record<string, unknown>\n ): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketWithClaimOnce(params, claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Send a standalone `payment-channel-claim` BTP MESSAGE (no ILP packet\n * attached). The connector's ClaimReceiver consumes this fire-and-forget\n * to register cumulative claim state independently of the per-packet\n * forwarding path. Auto-reconnects on connection errors.\n */\n async sendClaimMessage(claim: Record<string, unknown>): Promise<void> {\n return withRetry(() => this._sendClaimMessageOnce(claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n private async _sendClaimMessageOnce(\n claim: Record<string, unknown>\n ): Promise<void> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new BtpConnectionError('BTP client not connected');\n }\n\n await this.btpClient.sendProtocolData(\n 'payment-channel-claim',\n 1,\n encodeUtf8(JSON.stringify(claim))\n );\n }\n\n /**\n * Build the ILP PREPARE for a send, applying the sender-chosen condition /\n * explicit expiry when provided (toon-client#350) and validating that a\n * non-zero condition is exactly 32 bytes (the OER serializer would\n * otherwise silently zero-fill it, downgrading the packet to the legacy\n * unverified class).\n */\n private buildPrepare(params: IlpSendParams): {\n prepare: {\n type: 12;\n amount: bigint;\n destination: string;\n executionCondition: Uint8Array;\n expiresAt: Date;\n data: Uint8Array;\n };\n condition: Uint8Array | undefined;\n } {\n const condition = params.executionCondition;\n if (condition !== undefined && !isZeroCondition(condition)) {\n assertValidCondition(condition);\n }\n return {\n prepare: {\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: condition ?? new Uint8Array(32),\n expiresAt: resolveExpiresAt(params.expiresAt, params.timeout ?? 30000),\n data: fromBase64(params.data),\n },\n condition,\n };\n }\n\n /**\n * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(\n params: IlpSendParams\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n const { prepare, condition } = this.buildPrepare(params);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed by reconnect() above\n const response = await this.btpClient!.sendPacket(prepare);\n\n return mapIlpResponse(response, condition);\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: IlpSendParams,\n claim: Record<string, unknown>\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new BtpConnectionError('BTP client not connected');\n }\n\n const protocolData: BTPProtocolData[] = [\n {\n protocolName: 'payment-channel-claim',\n contentType: 1,\n data: encodeUtf8(JSON.stringify(claim)),\n },\n ];\n\n const { prepare, condition } = this.buildPrepare(params);\n const response = await this.btpClient.sendPacket(prepare, protocolData);\n\n return mapIlpResponse(response, condition);\n }\n}\n","/**\n * ILP-over-HTTP (RFC-0035) transport for the TOON client.\n *\n * The connector now serves ILP-over-HTTP on the SAME port as BTP (connector\n * PR #181). This adapter lets a client do stateless one-shot writes over HTTP\n * (`POST /ilp`) and upgrade to a duplex BTP session when it needs to receive\n * server-initiated packets or act as a peer.\n *\n * Wire contract (targets connector PR #181's `/ilp`):\n * - One-shot write: `POST /ilp`\n * body: OER-encoded ILP PREPARE (`application/octet-stream`)\n * header: `ILP-Payment-Channel-Claim: base64(JSON of the claim)` — the\n * SAME claim JSON the BTP path attaches as the\n * `payment-channel-claim` protocolData entry.\n * optional: `ILP-Peer-Id` + `Authorization: Bearer <secret>` identity.\n * response: `200 OK` with an OER FULFILL or REJECT body. HTTP non-2xx is\n * reserved for TRANSPORT errors (400/401/413/5xx); ILP-level\n * rejects come back as a 200 + REJECT body.\n * - Upgrade to BTP: standard HTTP `Upgrade` with `Sec-WebSocket-Protocol: btp`\n * plus the same `ILP-Peer-Id` + `Authorization` headers. The connector\n * pre-authenticates the BTP session from those headers (continuity), so\n * after `101` we send BTP frames WITHOUT a separate in-band auth frame.\n * Omitting the auth headers falls back to the normal BTP auth-frame flow.\n *\n * Reuses `serializeIlpPrepare`/`deserializeIlpPacket` from `btp/protocol.ts` —\n * the SAME OER codec the BTP path uses. Claim signing/construction is owned by\n * the caller (BootstrapService); this transport never builds or signs claims.\n */\n\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport type WSModule from 'ws';\nimport {\n ILPPacketType,\n serializeIlpPrepare,\n deserializeIlpPacket,\n} from '../btp/protocol.js';\nimport { BtpRuntimeClient } from './BtpRuntimeClient.js';\nimport { NetworkError, ConnectorError } from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\nimport { toBase64, fromBase64, encodeUtf8 } from '../utils/binary.js';\nimport { mapIlpResponse, resolveExpiresAt, type IlpSendParams } from './ilp-send.js';\nimport { assertValidCondition, isZeroCondition } from '../utils/condition.js';\n\n/** Header carrying the base64(JSON) payment-channel claim. */\nexport const ILP_CLAIM_HEADER = 'ILP-Payment-Channel-Claim';\n/** Header carrying a NIP-59 wrapped (gift-wrapped) claim. */\nexport const ILP_CLAIM_WRAPPED_HEADER = 'ILP-Payment-Channel-Claim-Wrapped';\n/** Header carrying the peer identity. */\nexport const ILP_PEER_ID_HEADER = 'ILP-Peer-Id';\n\nexport interface HttpIlpClientConfig {\n /** The peer's `POST /ilp` URL (the `httpEndpoint` from discovery). */\n httpEndpoint: string;\n /**\n * Optional peer identity. With no `peerId`/`authToken` the connector treats\n * the request as an anonymous no-auth peer (permissionless default) and\n * derives an ephemeral id from the claim signer.\n */\n peerId?: string;\n /** Bearer secret for `Authorization`. Omit for the no-auth peer path. */\n authToken?: string;\n /** Request timeout in milliseconds (default: 30000). */\n timeout?: number;\n /** Max retry attempts for transport-level network failures (default: 3). */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000). */\n retryDelay?: number;\n /** Custom fetch implementation (for testing / custom transports). */\n httpClient?: typeof fetch;\n /**\n * Custom WebSocket constructor for the BTP upgrade path (for testing /\n * custom transports). Forwarded to the underlying BtpRuntimeClient.\n */\n createWebSocket?: (url: string) => WebSocket;\n}\n\n/**\n * Stateless ILP-over-HTTP transport implementing `IlpClient`.\n *\n * Use this for pure one-shot consumers (publish-and-forget writes). When the\n * client needs a duplex session — to receive server-initiated packets or to act\n * as a peer — call {@link upgradeToBtp} to obtain a connected BtpRuntimeClient\n * that reuses the existing BTP code path.\n */\nexport class HttpIlpClient implements IlpClient {\n private readonly httpEndpoint: string;\n private readonly peerId: string | undefined;\n private readonly authToken: string | undefined;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n private readonly createWebSocket: ((url: string) => WebSocket) | undefined;\n\n constructor(config: HttpIlpClientConfig) {\n this.httpEndpoint = config.httpEndpoint;\n this.peerId = config.peerId;\n this.authToken = config.authToken;\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n this.createWebSocket = config.createWebSocket;\n }\n\n /**\n * Send an ILP PREPARE via `POST /ilp` WITHOUT a claim. The connector accepts\n * this only on free/zero-amount routes; paid writes must use\n * {@link sendIlpPacketWithClaim}. Satisfies the IlpClient interface.\n *\n * `params` may carry a sender-chosen `executionCondition` and an explicit\n * `expiresAt` (toon-client#350); omitting both is the legacy zero-condition\n * path, unchanged. With a non-zero condition the FULFILL preimage is\n * verified (`sha256(fulfillment) == condition`) and a mismatch is surfaced\n * as a failed result — see {@link mapIlpResponse}.\n */\n async sendIlpPacket(params: IlpSendParams): Promise<IlpSendResult> {\n return withRetry(() => this.postPrepare(params), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => error instanceof NetworkError,\n });\n }\n\n /**\n * Send an ILP PREPARE via `POST /ilp` with the payment-channel claim attached\n * as the `ILP-Payment-Channel-Claim` header. `claim` is the SAME JSON object\n * the BTP path attaches as the `payment-channel-claim` protocolData entry —\n * we base64(JSON.stringify(claim)) it, byte-for-byte identical to BTP.\n *\n * Sender-chosen `executionCondition` / explicit `expiresAt` semantics are\n * identical to {@link sendIlpPacket}.\n */\n async sendIlpPacketWithClaim(\n params: IlpSendParams,\n claim: unknown\n ): Promise<IlpSendResult> {\n return withRetry(() => this.postPrepare(params, claim), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => error instanceof NetworkError,\n });\n }\n\n /**\n * Upgrade to a duplex BTP session over the SAME endpoint.\n *\n * Derives the `ws(s)://` URL from `httpEndpoint`, opens a WebSocket with\n * `Sec-WebSocket-Protocol: btp` and the same `ILP-Peer-Id` + `Authorization`\n * headers, and returns a connected {@link BtpRuntimeClient}. When auth headers\n * are present the connector pre-authenticates the session (no in-band auth\n * frame); without them the BtpRuntimeClient falls back to the normal BTP\n * auth-frame flow.\n *\n * NOTE: passing per-connection headers + a subprotocol to a WebSocket is\n * Node-only (the `ws` package). Browsers cannot set arbitrary request headers\n * on a WebSocket handshake, so a browser consumer must use the gateway\n * transport or BTP-with-auth-frame instead.\n */\n async upgradeToBtp(): Promise<BtpRuntimeClient> {\n const btpUrl = httpEndpointToBtpUrl(this.httpEndpoint);\n\n // Default WS factory negotiates `btp` + carries the auth headers so the\n // connector pre-authenticates. Built lazily (Node-only) — browsers must\n // pass an explicit `createWebSocket` (they can't set handshake headers).\n const createWebSocket =\n this.createWebSocket ?? (await makeBtpWebSocketFactory(this.authHeaders()));\n\n const client = new BtpRuntimeClient({\n btpUrl,\n // BtpRuntimeClient sends an auth frame using these; when the connector\n // pre-authenticated via Upgrade headers it accepts the (redundant) frame.\n peerId: this.peerId ?? 'client',\n authToken: this.authToken ?? '',\n createWebSocket,\n });\n await client.connect();\n return client;\n }\n\n // ─── Private ──────────────────────────────────────────────────────────────\n\n private authHeaders(): Record<string, string> {\n const headers: Record<string, string> = {};\n if (this.peerId) headers[ILP_PEER_ID_HEADER] = this.peerId;\n if (this.authToken) headers['Authorization'] = `Bearer ${this.authToken}`;\n return headers;\n }\n\n /**\n * Single attempt: serialize the PREPARE, POST it, and map the response.\n * @throws {NetworkError} On connection/timeout failures (retried).\n * @throws {ConnectorError} On non-retryable transport errors (5xx / unexpected).\n */\n private async postPrepare(\n params: IlpSendParams,\n claim?: unknown\n ): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n\n // Sender-chosen condition (toon-client#350): validate length up front so\n // the OER serializer can never silently zero-fill a malformed condition\n // and downgrade the packet to the legacy unverified class.\n const condition = params.executionCondition;\n if (condition !== undefined && !isZeroCondition(condition)) {\n assertValidCondition(condition);\n }\n\n const prepare = serializeIlpPrepare({\n type: ILPPacketType.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: condition ?? new Uint8Array(32),\n expiresAt: resolveExpiresAt(params.expiresAt, requestTimeout),\n data: fromBase64(params.data),\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n ...this.authHeaders(),\n };\n if (claim !== undefined) {\n headers[ILP_CLAIM_HEADER] = toBase64(\n encodeUtf8(JSON.stringify(claim))\n );\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeout);\n\n try {\n const response = await this.httpClient(this.httpEndpoint, {\n method: 'POST',\n headers,\n // Copy into a fresh ArrayBuffer so fetch sees a clean body, not a view.\n body: prepare.slice(),\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n return await this.mapResponse(response, condition);\n } catch (error) {\n clearTimeout(timeoutId);\n throw this.mapTransportError(error, requestTimeout);\n }\n }\n\n /**\n * Map a `200 OK` body (OER FULFILL/REJECT) to an IlpSendResult; map a non-2xx\n * to a transport error. Per the wire contract, ILP-level rejects arrive as a\n * 200 + REJECT body — only HTTP non-2xx means a transport-layer failure.\n *\n * When `sentCondition` is non-zero the FULFILL preimage is verified against\n * it; a mismatch yields `accepted: false` (shared `mapIlpResponse` logic,\n * identical to the BTP path).\n */\n private async mapResponse(\n response: Response,\n sentCondition?: Uint8Array\n ): Promise<IlpSendResult> {\n if (response.ok) {\n const buf = new Uint8Array(await response.arrayBuffer());\n if (buf.length === 0) {\n throw new ConnectorError('Empty 200 body from /ilp (expected OER ILP response)');\n }\n return mapIlpResponse(deserializeIlpPacket(buf), sentCondition);\n }\n\n // Transport-level error (400 malformed, 401 auth, 413 too large, 5xx).\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body}` : '';\n if (response.status >= 500) {\n throw new ConnectorError(\n `Connector transport error (${response.status} ${response.statusText})${detail}`\n );\n }\n // 4xx — non-retryable client/transport error.\n throw new ConnectorError(\n `ILP-over-HTTP request rejected (${response.status} ${response.statusText})${detail}`\n );\n }\n\n private mapTransportError(error: unknown, requestTimeout: number): Error {\n if (error instanceof ConnectorError || error instanceof NetworkError) {\n return error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n return new NetworkError(`Request timeout after ${requestTimeout}ms`, error);\n }\n if (\n error instanceof TypeError &&\n (error.message.includes('fetch failed') ||\n error.message.includes('ECONNREFUSED') ||\n error.message.includes('ECONNRESET') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('network'))\n ) {\n return new NetworkError(`Network connection failed: ${error.message}`, error);\n }\n return new ConnectorError(\n `Unexpected error during ILP-over-HTTP request: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Derive the BTP WebSocket URL from a `POST /ilp` HTTP endpoint. The connector\n * serves BTP on the SAME path, so we only swap the scheme (http→ws, https→wss).\n */\nexport function httpEndpointToBtpUrl(httpEndpoint: string): string {\n return httpEndpoint\n .replace(/^https:\\/\\//i, 'wss://')\n .replace(/^http:\\/\\//i, 'ws://');\n}\n\n/**\n * Build a WebSocket factory that opens the BTP upgrade with a `btp` subprotocol\n * and the given handshake headers. Node-only — lazily loads the `ws` package via\n * a dynamically-imported `createRequire` so the node-only `node:module`/`ws`\n * deps never enter a browser bundle (per-connection headers aren't settable on\n * the browser WebSocket anyway).\n */\nasync function makeBtpWebSocketFactory(\n headers: Record<string, string>\n): Promise<(url: string) => WebSocket> {\n const { createRequire } = await import('node:module');\n const require = createRequire(import.meta.url);\n const WS = require('ws') as typeof WSModule;\n\n // CJS/ESM interop: walk the constructor ladder (class / .default / .WebSocket)\n // so this works under any loader.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const ws = WS as any;\n const WSClass = (typeof ws === 'function'\n ? ws\n : typeof ws.default === 'function'\n ? ws.default\n : typeof ws.WebSocket === 'function'\n ? ws.WebSocket\n : null) as unknown as typeof WSModule.prototype.constructor;\n if (WSClass === null) {\n throw new Error(\n \"makeBtpWebSocketFactory: require('ws') did not yield a constructor on .default, .WebSocket, or the module root.\"\n );\n }\n\n // `ws` accepts (url, protocols, options); the connector negotiates `btp` and\n // pre-authenticates the session from the `ILP-Peer-Id`/`Authorization` headers.\n return (url: string): WebSocket =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new (WSClass as any)(url, 'btp', { headers }) as unknown as WebSocket;\n}\n","/**\n * Transport selection policy for the client ILP layer.\n *\n * The connector serves ILP-over-HTTP (`POST /ilp`) and BTP on the SAME port\n * (connector PR #181). A peer advertises this in discovery via the toon-core\n * `IlpPeerInfo` fields added in toon PR #29:\n * - `httpEndpoint?: string` — the `POST /ilp` URL.\n * - `supportsUpgrade?: boolean` — whether the host accepts the BTP upgrade.\n *\n * Those fields may not yet exist on the installed `@toon-protocol/core`\n * `IlpPeerInfo` type, so we read them defensively here (see `DiscoveredIlpPeer`).\n *\n * Policy:\n * - Pure one-shot consumers (`needsDuplex: false`) prefer HTTP when the peer\n * advertises an `httpEndpoint` — stateless, no persistent socket.\n * - Clients that must receive server-initiated packets or act as a peer\n * (`needsDuplex: true`) use BTP. If the peer only exposes `httpEndpoint`\n * and `supportsUpgrade` is true, we go HTTP-then-upgrade; otherwise we\n * connect to the BTP endpoint directly.\n */\n\n/**\n * The subset of discovery fields this policy reads. Structurally compatible with\n * core's `IlpPeerInfo`; `httpEndpoint`/`supportsUpgrade` are optional so a\n * pre-PR-#29 `IlpPeerInfo` can be passed through a cast.\n */\nexport interface DiscoveredIlpPeer {\n /** BTP WebSocket endpoint (always present on a TOON peer). */\n btpEndpoint?: string;\n /** `POST /ilp` URL (toon PR #29). */\n httpEndpoint?: string;\n /** Whether the host accepts the BTP upgrade over the HTTP endpoint (toon PR #29). */\n supportsUpgrade?: boolean;\n}\n\nexport type IlpTransportChoice =\n /** Stateless one-shot writes via `POST /ilp`. */\n | { kind: 'http'; httpEndpoint: string; canUpgrade: boolean }\n /** Duplex BTP session via the WebSocket endpoint. */\n | { kind: 'btp'; btpEndpoint: string }\n /**\n * Open HTTP first (one-shot writes) but upgrade to BTP when duplex is needed.\n * Only chosen when the peer exposes `httpEndpoint` + `supportsUpgrade` and no\n * separate `btpEndpoint`.\n */\n | { kind: 'http-upgradable'; httpEndpoint: string };\n\nexport interface SelectIlpTransportOptions {\n /**\n * Whether the client needs a duplex session (receive server-initiated\n * packets / act as a peer). Default: false (pure one-shot consumer).\n */\n needsDuplex?: boolean;\n}\n\n/**\n * Read discovery fields defensively from a (possibly pre-PR-#29) peer info\n * object. Accepts core's `IlpPeerInfo` or any structurally-compatible shape.\n */\nexport function readDiscoveredIlpPeer(peer: unknown): DiscoveredIlpPeer {\n const p = (peer ?? {}) as Record<string, unknown>;\n return {\n btpEndpoint:\n typeof p['btpEndpoint'] === 'string' ? (p['btpEndpoint'] as string) : undefined,\n httpEndpoint:\n typeof p['httpEndpoint'] === 'string'\n ? (p['httpEndpoint'] as string)\n : undefined,\n supportsUpgrade:\n typeof p['supportsUpgrade'] === 'boolean'\n ? (p['supportsUpgrade'] as boolean)\n : undefined,\n };\n}\n\n/**\n * Choose the ILP transport for a discovered peer given the consumer's needs.\n *\n * @throws {Error} If the peer advertises no usable endpoint at all.\n */\nexport function selectIlpTransport(\n peer: DiscoveredIlpPeer,\n options: SelectIlpTransportOptions = {}\n): IlpTransportChoice {\n const needsDuplex = options.needsDuplex ?? false;\n const http = peer.httpEndpoint?.trim() || undefined;\n const btp = peer.btpEndpoint?.trim() || undefined;\n const canUpgrade = peer.supportsUpgrade === true;\n\n if (needsDuplex) {\n // Duplex consumers prefer a real BTP endpoint; fall back to HTTP-upgrade\n // only when the host advertises it.\n if (btp) return { kind: 'btp', btpEndpoint: btp };\n if (http && canUpgrade) return { kind: 'http-upgradable', httpEndpoint: http };\n throw new Error(\n 'Duplex transport required but peer exposes neither a btpEndpoint nor an upgradable httpEndpoint'\n );\n }\n\n // One-shot consumers prefer stateless HTTP when available.\n if (http) return { kind: 'http', httpEndpoint: http, canUpgrade };\n if (btp) return { kind: 'btp', btpEndpoint: btp };\n throw new Error('Peer exposes neither an httpEndpoint nor a btpEndpoint');\n}\n","import {\n createPublicClient,\n createWalletClient,\n http,\n maxUint256,\n decodeEventLog,\n defineChain,\n type Hex,\n type TransactionReceipt,\n} from 'viem';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '@toon-protocol/core';\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { base58Encode } from '@toon-protocol/core';\nimport type { EvmSigner } from '../signing/evm-signer.js';\nimport { ChannelFundingError, isInsufficientGasError } from '../errors.js';\nimport {\n openSolanaChannel as openSolanaChannelOnChain,\n getChannelAccountState as getSolanaChannelAccountState,\n depositSolanaChannel,\n deriveAssociatedTokenAccount,\n} from './solana-payment-channel.js';\nimport { openMinaChannelOnChain } from './mina-channel-open.js';\n\n// TokenNetwork ABI — only the functions we need\nconst TOKEN_NETWORK_ABI = [\n {\n name: 'openChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'participant2', type: 'address' },\n { name: 'settlementTimeout', type: 'uint256' },\n ],\n outputs: [{ type: 'bytes32' }],\n },\n {\n name: 'setTotalDeposit',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'participant', type: 'address' },\n { name: 'totalDeposit', type: 'uint256' },\n ],\n outputs: [],\n },\n {\n name: 'closeChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [{ name: 'channelId', type: 'bytes32' }],\n outputs: [],\n },\n {\n name: 'settleChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [{ name: 'channelId', type: 'bytes32' }],\n outputs: [],\n },\n {\n name: 'channels',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ type: 'bytes32' }],\n outputs: [\n { name: 'settlementTimeout', type: 'uint256' },\n { name: 'state', type: 'uint8' },\n { name: 'closedAt', type: 'uint256' },\n { name: 'openedAt', type: 'uint256' },\n { name: 'participant1', type: 'address' },\n { name: 'participant2', type: 'address' },\n ],\n },\n {\n name: 'participants',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ type: 'bytes32' }, { type: 'address' }],\n outputs: [\n { name: 'deposit', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n { name: 'transferredAmount', type: 'uint256' },\n ],\n },\n {\n name: 'ChannelOpened',\n type: 'event',\n inputs: [\n { name: 'channelId', type: 'bytes32', indexed: true },\n { name: 'participant1', type: 'address', indexed: true },\n { name: 'participant2', type: 'address', indexed: true },\n { name: 'settlementTimeout', type: 'uint256', indexed: false },\n ],\n },\n] as const;\n\n// ERC20 ABI — only approve and allowance\nconst ERC20_ABI = [\n {\n name: 'approve',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'spender', type: 'address' },\n { name: 'amount', type: 'uint256' },\n ],\n outputs: [{ type: 'bool' }],\n },\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n ],\n outputs: [{ type: 'uint256' }],\n },\n] as const;\n\n/** Maps on-chain state uint8 to ChannelState status */\nconst STATE_MAP: Record<number, ChannelState['status']> = {\n 0: 'settled',\n 1: 'open',\n 2: 'closed',\n 3: 'settled',\n};\n\nexport interface SolanaChannelConfig {\n rpcUrl: string;\n /**\n * Ed25519 keypair material. Accepts either a 32-byte seed or a 64-byte\n * `secretKey` (seed || pubkey, as produced by `deriveFullIdentity`). The first\n * 32 bytes are the signing seed; the public key is derived from it.\n */\n keypair: Uint8Array;\n programId: string;\n /**\n * SPL token mint (base58) for PDA derivation. Optional — the per-channel\n * negotiated token (`OpenChannelParams.token`) takes precedence when present.\n */\n tokenMint?: string;\n /**\n * Challenge-period duration in seconds for `initialize_channel`. Defaults to\n * `OpenChannelParams.settlementTimeout` or 86400.\n */\n challengeDuration?: number;\n /**\n * Optional deposit amount (base units, string) + the payer's funded SPL token\n * account (ATA, base58). When omitted, the channel is opened (initialized)\n * without an on-chain deposit — the connector accepts the claim on channel\n * `opened` status + participant membership; deposit is only consumed at\n * on-chain claim/settle time.\n */\n deposit?: { amount: string; payerTokenAccount: string };\n}\n\nexport interface MinaChannelConfig {\n graphqlUrl: string;\n privateKey: string;\n /**\n * Deployed payment-channel zkApp address (B62). Optional when `autoDeploy`\n * is set — the open path then resolves (or deploys) a pair-owned zkApp\n * itself; see `mina-channel-deploy.ts`.\n */\n zkAppAddress?: string;\n /**\n * Per-pair zkApp auto-deploy (the Mina `PaymentChannel` zkApp is\n * single-pair: one deployment serves exactly one client↔connector pair).\n * When set, `openMinaChannel` first resolves a zkApp that is provably OURS\n * — the recorded `deployed` one, or `zkAppAddress` when its on-chain\n * channelHash matches this pair — and deploys a fresh one otherwise\n * (compile ≈1-3 min; inclusion ≈3-6 min; costs ~1.1 MINA + fees).\n * Without it, behavior is exactly the pre-autoDeploy contract: the\n * configured `zkAppAddress` is required and used verbatim.\n */\n autoDeploy?: {\n /** A previously recorded own deployment for this identity, if any. */\n deployed?: { zkAppAddress: string; zkAppPrivateKey: string };\n /** Persist hook — called BEFORE the open proceeds on a fresh deploy. */\n onDeployed?: (record: {\n zkAppAddress: string;\n zkAppPrivateKey: string;\n feePayer: string;\n deployTxHash?: string;\n vkHash?: string;\n }) => void | Promise<void>;\n /** Progress lines (compile/deploy/inclusion phases take minutes). */\n onProgress?: (line: string) => void;\n };\n /**\n * Channel settlement timeout in slots for `initializeChannel`. Defaults to\n * `OpenChannelParams.settlementTimeout` or 86400.\n */\n challengeDuration?: number;\n /**\n * Mina token id field (decimal string) for `initializeChannel`. Default '1'\n * (native MINA). The connector reads this only as on-chain channel metadata.\n */\n tokenId?: string;\n /**\n * Optional on-chain deposit (base units, string) submitted after the channel\n * is initialized. When omitted, the channel is opened (OPEN state) without a\n * deposit — the connector accepts the claim on `opened` status; deposit is\n * only consumed at on-chain settle time.\n */\n deposit?: { amount: string };\n /** Mina network id for the account/Schnorr prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n}\n\nexport interface OnChainChannelClientConfig {\n evmSigner: EvmSigner;\n chainRpcUrls: Record<string, string>;\n solanaConfig?: SolanaChannelConfig;\n minaConfig?: MinaChannelConfig;\n}\n\n/**\n * Implements ConnectorChannelClient using viem for direct on-chain\n * interaction with TokenNetwork smart contract.\n *\n * Fully non-custodial — the client deposits its own funds on-chain.\n */\nexport class OnChainChannelClient implements ConnectorChannelClient {\n private readonly evmSigner: EvmSigner;\n private readonly chainRpcUrls: Record<string, string>;\n private solanaConfig?: SolanaChannelConfig;\n private minaConfig?: MinaChannelConfig;\n private readonly channelContext = new Map<\n string,\n { chain: string; tokenNetworkAddress: string; tokenAddress?: string }\n >();\n\n constructor(config: OnChainChannelClientConfig) {\n this.evmSigner = config.evmSigner;\n this.chainRpcUrls = config.chainRpcUrls;\n this.solanaConfig = config.solanaConfig;\n this.minaConfig = config.minaConfig;\n }\n\n /**\n * Late-binds the Solana channel config.\n *\n * `ToonClient.start()` derives the Solana Ed25519 keypair from the client's\n * mnemonic asynchronously (after this client is constructed), so the keypair\n * is injected here rather than at construction. Same keypair as the\n * registered Solana signer — guarantees the channel-open key and the\n * claim-signing key match.\n */\n setSolanaConfig(config: SolanaChannelConfig): void {\n this.solanaConfig = config;\n }\n\n /**\n * Late-binds the Mina channel config.\n *\n * Parallel to `setSolanaConfig`: `ToonClient.start()` derives the Mina private\n * key from the client's mnemonic asynchronously (after this client is\n * constructed), so the key is injected here rather than at construction. Same\n * key as the registered Mina signer.\n */\n setMinaConfig(config: MinaChannelConfig): void {\n this.minaConfig = config;\n }\n\n /**\n * Parse chain identifier to extract chainId.\n * Format: \"evm:{network}:{chainId}\" e.g., \"evm:anvil:31337\"\n */\n private parseChainId(chain: string): number {\n const parts = chain.split(':');\n if (parts.length < 2) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\" or \"evm:{chainId}\".`\n );\n }\n // Accept both the canonical 3-part `evm:{network}:{chainId}` and the 2-part\n // `evm:{chainId}` form some connectors advertise (e.g. `evm:31337`).\n const chainIdStr = parts.length >= 3 ? parts[2] : parts[1];\n if (!chainIdStr) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainId = parseInt(chainIdStr, 10);\n if (isNaN(chainId)) {\n throw new Error(`Invalid chainId in chain \"${chain}\".`);\n }\n return chainId;\n }\n\n /**\n * Create viem clients for a given chain.\n */\n private createClients(chain: string) {\n const rpcUrl = this.chainRpcUrls[chain];\n if (!rpcUrl) {\n throw new Error(\n `No RPC URL configured for chain \"${chain}\". Available: ${Object.keys(this.chainRpcUrls).join(', ')}`\n );\n }\n\n const chainId = this.parseChainId(chain);\n\n const viemChain = defineChain({\n id: chainId,\n name: chain,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n });\n\n const publicClient = createPublicClient({\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n const walletClient = createWalletClient({\n account: this.evmSigner.account,\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n return { publicClient, walletClient };\n }\n\n /**\n * Opens a new payment channel on-chain.\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n const chainPrefix = params.chain.split(':')[0];\n\n // Dispatch to chain-specific opener\n if (chainPrefix === 'solana') return this.openSolanaChannel(params);\n if (chainPrefix === 'mina') return this.openMinaChannel(params);\n\n // EVM path (default)\n return this.openEvmChannel(params);\n }\n\n /**\n * Deposit additional collateral into an already-open channel. `amount` is the\n * DELTA to add (base units). Dispatches by the channel's cached chain context;\n * `currentDeposit` is the channel's current locked total (tracked off-chain by\n * the caller) — required for EVM, whose `setTotalDeposit` takes the new\n * cumulative total, not a delta. Returns the new on-chain deposit total.\n *\n * Non-custodial: the client deposits its OWN funds and signs its OWN tx.\n * EVM is live; Solana/Mina deposit extraction is a follow-up (PR B.1).\n */\n async depositToChannel(\n channelId: string,\n amount: bigint,\n opts: { currentDeposit: bigint }\n ): Promise<{ txHash?: string; depositTotal: bigint }> {\n if (amount <= 0n) throw new Error('Deposit amount must be positive.');\n const ctx = this.channelContext.get(channelId);\n if (!ctx) {\n throw new Error(\n `No on-chain context for channel \"${channelId}\" — it must be opened by this client first.`\n );\n }\n const chainPrefix = ctx.chain.split(':')[0];\n if (chainPrefix === 'solana') {\n return this.depositSolana(channelId, amount, opts.currentDeposit);\n }\n if (chainPrefix === 'mina') {\n throw new Error('Deposit on mina is not yet supported (EVM + Solana today; Mina follow-up).');\n }\n return this.depositEvm(channelId, amount, opts.currentDeposit, ctx);\n }\n\n /**\n * Solana deposit: fire the standalone `deposit` instruction against the channel\n * vault. Incremental on-chain (the program adds `amount`), so the new total is\n * the caller-tracked current plus the delta. Requires the funded payer token\n * account (the funded ATA) from the Solana channel config.\n */\n private async depositSolana(\n channelId: string,\n amount: bigint,\n currentDeposit: bigint\n ): Promise<{ txHash: string; depositTotal: bigint }> {\n if (!this.solanaConfig) {\n throw new Error('Solana channel config not set — cannot deposit.');\n }\n const cfg = this.solanaConfig;\n const payerSeed = cfg.keypair.slice(0, 32);\n const payerPubkey = base58Encode(new Uint8Array(ed25519.getPublicKey(payerSeed)));\n // The funded token account is deterministically the payer's ATA for the\n // channel mint, so derive it when the caller didn't pass one explicitly\n // (config need not carry payerTokenAccount — it's owner+mint, both known here).\n let payerTokenAccount = cfg.deposit?.payerTokenAccount;\n if (!payerTokenAccount) {\n if (!cfg.tokenMint) {\n throw new Error(\n 'Solana deposit requires solanaConfig.deposit.payerTokenAccount or solanaConfig.tokenMint to derive the payer ATA.'\n );\n }\n payerTokenAccount = deriveAssociatedTokenAccount(payerPubkey, cfg.tokenMint);\n }\n const { depositTxSignature } = await depositSolanaChannel({\n rpcUrl: cfg.rpcUrl,\n programId: cfg.programId,\n channelPDA: channelId,\n payerSeed,\n payerPubkey,\n payerTokenAccount,\n amount,\n });\n return { txHash: depositTxSignature, depositTotal: currentDeposit + amount };\n }\n\n /**\n * EVM deposit: approve the token-network for the delta if the allowance is\n * short, then `setTotalDeposit(channelId, participant, current + delta)` —\n * the contract takes the new cumulative total, so we add the delta to the\n * caller-supplied current locked amount.\n */\n private async depositEvm(\n channelId: string,\n amount: bigint,\n currentDeposit: bigint,\n ctx: { chain: string; tokenNetworkAddress: string; tokenAddress?: string }\n ): Promise<{ txHash: string; depositTotal: bigint }> {\n const { publicClient, walletClient } = this.createClients(ctx.chain);\n const tokenNetworkAddr = ctx.tokenNetworkAddress as Hex;\n const myAddress = this.evmSigner.address as Hex;\n const newTotal = currentDeposit + amount;\n\n // Approve the additional collateral if the current allowance can't cover it.\n if (ctx.tokenAddress) {\n const tokenAddr = ctx.tokenAddress as Hex;\n const allowance = await publicClient.readContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'allowance',\n args: [myAddress, tokenNetworkAddr],\n });\n if ((allowance as bigint) < amount) {\n const approveHash = await walletClient.writeContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'approve',\n args: [tokenNetworkAddr, maxUint256],\n });\n await publicClient.waitForTransactionReceipt({ hash: approveHash });\n }\n }\n\n const depositHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'setTotalDeposit',\n args: [channelId as Hex, myAddress, newTotal],\n });\n await publicClient.waitForTransactionReceipt({ hash: depositHash });\n return { txHash: depositHash, depositTotal: newTotal };\n }\n\n /**\n * Close a channel to begin the settlement grace period. Dispatches by the\n * channel's cached chain context. EVM `closeChannel` is unilateral (channelId\n * only); after it confirms we read the `channels()` view for the AUTHORITATIVE\n * `closedAt` + `settlementTimeout` (block-timestamp seconds) and compute\n * `settleableAt = closedAt + settlementTimeout`. Solana/Mina are follow-ups.\n */\n async closeChannel(\n channelId: string\n ): Promise<{ txHash?: string; closedAt: bigint; settlementTimeout: bigint; settleableAt: bigint }> {\n const ctx = this.channelContext.get(channelId);\n if (!ctx) {\n throw new Error(\n `No on-chain context for channel \"${channelId}\" — it must be opened by this client first.`\n );\n }\n const chainPrefix = ctx.chain.split(':')[0];\n if (chainPrefix === 'solana' || chainPrefix === 'mina') {\n throw new Error(\n `Close on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`\n );\n }\n const { publicClient, walletClient } = this.createClients(ctx.chain);\n const tokenNetworkAddr = ctx.tokenNetworkAddress as Hex;\n const closeHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'closeChannel',\n args: [channelId as Hex],\n });\n await publicClient.waitForTransactionReceipt({ hash: closeHash });\n\n const info = await this.readEvmChannel(publicClient, tokenNetworkAddr, channelId);\n return {\n txHash: closeHash,\n closedAt: info.closedAt,\n settlementTimeout: info.settlementTimeout,\n settleableAt: info.closedAt + info.settlementTimeout,\n };\n }\n\n /**\n * Settle a closed channel after its grace period to release collateral. EVM\n * `settleChannel` is unilateral (channelId only); the contract itself reverts\n * before `closedAt + settlementTimeout`, so an early call surfaces as a tx\n * revert here — but the caller (ToonClient/daemon) enforces the time guard\n * BEFORE spending gas. Solana/Mina are follow-ups.\n */\n async settleChannel(channelId: string): Promise<{ txHash?: string }> {\n const ctx = this.channelContext.get(channelId);\n if (!ctx) {\n throw new Error(\n `No on-chain context for channel \"${channelId}\" — it must be opened by this client first.`\n );\n }\n const chainPrefix = ctx.chain.split(':')[0];\n if (chainPrefix === 'solana' || chainPrefix === 'mina') {\n throw new Error(\n `Settle on ${chainPrefix} is not yet supported (EVM today; Solana/Mina follow-up).`\n );\n }\n const { publicClient, walletClient } = this.createClients(ctx.chain);\n const settleHash = await walletClient.writeContract({\n address: ctx.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'settleChannel',\n args: [channelId as Hex],\n });\n await publicClient.waitForTransactionReceipt({ hash: settleHash });\n return { txHash: settleHash };\n }\n\n /**\n * Read the EVM channel's close-relevant fields so a restarted daemon can\n * recompute the grace timer from chain (chain is authoritative). EVM-only.\n */\n async getChannelCloseInfo(channelId: string): Promise<{\n status: ChannelState['status'];\n closedAt: bigint;\n settlementTimeout: bigint;\n settleableAt: bigint;\n }> {\n const ctx = this.channelContext.get(channelId);\n if (!ctx) throw new Error(`No on-chain context for channel \"${channelId}\".`);\n const chainPrefix = ctx.chain.split(':')[0];\n if (chainPrefix === 'solana' || chainPrefix === 'mina') {\n throw new Error(`getChannelCloseInfo on ${chainPrefix} is not supported.`);\n }\n const { publicClient } = this.createClients(ctx.chain);\n const info = await this.readEvmChannel(publicClient, ctx.tokenNetworkAddress as Hex, channelId);\n return {\n status: STATE_MAP[info.state] ?? 'open',\n closedAt: info.closedAt,\n settlementTimeout: info.settlementTimeout,\n settleableAt: info.closedAt + info.settlementTimeout,\n };\n }\n\n /** Read + destructure the EVM `channels(bytes32)` view. */\n private async readEvmChannel(\n publicClient: ReturnType<OnChainChannelClient['createClients']>['publicClient'],\n tokenNetworkAddr: Hex,\n channelId: string\n ): Promise<{ settlementTimeout: bigint; state: number; closedAt: bigint }> {\n const res = (await publicClient.readContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'channels',\n args: [channelId as Hex],\n })) as readonly [bigint, number, bigint, bigint, string, string];\n return { settlementTimeout: res[0], state: Number(res[1]), closedAt: res[2] };\n }\n\n /**\n * Read a participant's on-chain channel state — `deposit` (locked collateral),\n * `nonce`, and `transferredAmount` — straight from the `participants` mapping.\n * Takes the chain + token-network explicitly so it works for a channel that\n * was RESUMED from disk (no in-memory `channelContext` yet), which is exactly\n * when the daemon needs to re-hydrate the deposit it doesn't persist.\n */\n async readEvmParticipantState(opts: {\n chain: string;\n tokenNetworkAddress: string;\n channelId: string;\n participant: string;\n }): Promise<{ deposit: bigint; nonce: bigint; transferredAmount: bigint }> {\n const { publicClient } = this.createClients(opts.chain);\n const res = (await publicClient.readContract({\n address: opts.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'participants',\n args: [opts.channelId as Hex, opts.participant as Hex],\n })) as readonly [bigint, bigint, bigint];\n return { deposit: res[0], nonce: res[1], transferredAmount: res[2] };\n }\n\n /**\n * Opens a REAL on-chain Solana payment channel.\n *\n * Derives the connector-parity channel PDA\n * (`[b\"channel\", min_pubkey, max_pubkey, token_mint]`), submits the\n * `initialize_channel` instruction (+ optional `deposit`) to the deployed\n * payment-channel program, and returns the base58 PDA as the channel id. That\n * PDA is what the claim carries as `channelAccount`, and the on-chain channel\n * is what the connector's `verifySolanaClaim` reads via\n * `provider.getChannelState` before accepting the claim.\n *\n * Mirrors `openEvmChannel`'s open(+deposit) structure. Idempotent: if the\n * channel account already exists on-chain, returns its PDA without\n * re-initializing.\n */\n private async openSolanaChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n if (!this.solanaConfig) {\n throw new Error(\n 'Solana channel config not provided — cannot open Solana channel'\n );\n }\n\n const cfg = this.solanaConfig;\n // First 32 bytes are the Ed25519 signing seed (config may pass a 64-byte\n // secretKey of seed||pubkey, or a bare 32-byte seed).\n const payerSeed = cfg.keypair.slice(0, 32);\n const payerPubkey = base58Encode(\n new Uint8Array(ed25519.getPublicKey(payerSeed))\n );\n\n // PDA mint: per-channel negotiated token takes precedence over config default.\n const tokenMint = params.token ?? cfg.tokenMint;\n if (!tokenMint) {\n throw new Error(\n 'Solana channel requires a token mint (OpenChannelParams.token or solanaConfig.tokenMint)'\n );\n }\n if (!params.peerAddress) {\n throw new Error(\n 'Solana channel requires peerAddress (apex settlement pubkey, base58)'\n );\n }\n\n const challengeDuration = BigInt(\n cfg.challengeDuration ?? params.settlementTimeout ?? 86400\n );\n\n const deposit = cfg.deposit\n ? {\n amount: BigInt(cfg.deposit.amount),\n // Derive the payer ATA (owner + channel mint) when not supplied — it is\n // deterministic, so the caller need not thread payerTokenAccount through.\n payerTokenAccount:\n cfg.deposit.payerTokenAccount ||\n deriveAssociatedTokenAccount(payerPubkey, tokenMint),\n }\n : undefined;\n\n const { channelPDA } = await openSolanaChannelOnChain({\n rpcUrl: cfg.rpcUrl,\n programId: cfg.programId,\n tokenMint,\n payerSeed,\n payerPubkey,\n peerPubkey: params.peerAddress,\n challengeDuration,\n deposit,\n });\n\n // Cache context (PDA is the channel id / channelAccount).\n this.channelContext.set(channelPDA, {\n chain: params.chain,\n tokenNetworkAddress: cfg.programId,\n });\n\n return { channelId: channelPDA, status: 'opening' };\n }\n\n /**\n * Opens a REAL on-chain Mina payment channel on the deployed `PaymentChannel`\n * zkApp.\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address). This client then calls\n * `initializeChannel` on that zkApp so its on-chain `channelState` becomes\n * `OPEN` — which is what the connector's `MinaPaymentChannelSDK.getChannelState`\n * reads to return status `'opened'` (claim verification otherwise fails with\n * `mina_claim_verification_failed`). The deployed zkApp address IS the channel\n * id: `MinaClaimMessage.zkAppAddress` is both the claim's channel identifier\n * AND the channel-hash preimage the off-chain proof binds to (see\n * `mina-payment-channel.ts`), so the channel-open id and the claim's channel id\n * are guaranteed identical.\n *\n * This is the Mina analog of `openSolanaChannel` (connector#105): the client\n * opens its own per-channel on-chain state (initialize + optional deposit). The\n * heavyweight o1js + `@toon-protocol/mina-zkapp` proof work is lazily imported\n * inside `openMinaChannelOnChain` so npm consumers who never open a Mina\n * channel don't pay the o1js cost.\n *\n * Idempotent: if the on-chain channel is already `OPEN`, the opener returns\n * without re-initializing.\n *\n * NOTE: full on-chain Mina SETTLE remains gated by the connector-side\n * settlement-executor (the same blocker that stops the Solana SETTLE); reaching\n * `opened` + a stored claim is parity with Solana.\n */\n private async openMinaChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n if (!this.minaConfig) {\n throw new Error(\n 'Mina channel config not provided — cannot open Mina channel'\n );\n }\n // The apex's Mina settlement B62 (participantB) is REQUIRED so the channel is\n // opened TWO-party. The off-chain claim is signed in participant form\n // (`Poseidon([client.x, apex.x, 0])`); without participantB the on-chain\n // channel records empty/duplicate participants and the connector's\n // participant-form verification fails on settle (`Invalid balance proof\n // signature`, `participants:[\"\",\"\"]`). Mirrors the Solana peerAddress guard.\n // (Checked before auto-deploy too — the pair hash needs participantB.)\n if (!params.peerAddress) {\n throw new Error(\n 'Mina channel requires peerAddress (apex Mina settlement B62) so the ' +\n 'on-chain channel is opened two-party — the participant-form claim ' +\n 'cannot settle against a single-party channel'\n );\n }\n // The deployed zkApp address IS the channel id (claim `zkAppAddress`).\n // With autoDeploy, resolve a zkApp that is provably OURS for this pair\n // (reusing the recorded/configured one when its on-chain channelHash\n // matches; deploying a dedicated one otherwise) — the Mina PaymentChannel\n // zkApp is single-pair, so a shared/announced address can never serve a\n // second identity. Lazily imported: only autoDeploy users pay the cost.\n let zkAppAddress = this.minaConfig.zkAppAddress;\n if (this.minaConfig.autoDeploy) {\n const { ensureOwnedMinaZkApp } = await import(\n './mina-channel-deploy.js'\n );\n const ensured = await ensureOwnedMinaZkApp({\n graphqlUrl: this.minaConfig.graphqlUrl,\n payerPrivateKey: this.minaConfig.privateKey,\n peerPublicKey: params.peerAddress,\n ...(this.minaConfig.autoDeploy.deployed\n ? { deployed: this.minaConfig.autoDeploy.deployed }\n : {}),\n ...(zkAppAddress ? { candidateZkAppAddress: zkAppAddress } : {}),\n ...(this.minaConfig.autoDeploy.onDeployed\n ? { onDeployed: this.minaConfig.autoDeploy.onDeployed }\n : {}),\n ...(this.minaConfig.autoDeploy.onProgress\n ? { onProgress: this.minaConfig.autoDeploy.onProgress }\n : {}),\n });\n zkAppAddress = ensured.zkAppAddress;\n }\n if (!zkAppAddress) {\n throw new Error(\n 'Mina channel requires a deployed zkAppAddress (minaConfig.zkAppAddress)'\n );\n }\n\n const timeout = BigInt(\n this.minaConfig.challengeDuration ?? params.settlementTimeout ?? 86400\n );\n const deposit = this.minaConfig.deposit\n ? { amount: BigInt(this.minaConfig.deposit.amount) }\n : undefined;\n\n const openResult = await openMinaChannelOnChain({\n graphqlUrl: this.minaConfig.graphqlUrl,\n zkAppAddress,\n payerPrivateKey: this.minaConfig.privateKey,\n // params.peerAddress is the apex Mina settlement B62 pubkey (participantB).\n peerPublicKey: params.peerAddress,\n timeout,\n tokenId: this.minaConfig.tokenId,\n deposit,\n networkId: this.minaConfig.networkId,\n });\n\n // The deployed zkApp address IS the channel id (claim `zkAppAddress`).\n this.channelContext.set(zkAppAddress, {\n chain: params.chain,\n tokenNetworkAddress: zkAppAddress,\n });\n\n // Surface the CURRENT on-chain depositTotal so the Mina signer can bind\n // `balanceB = depositTotal − balanceA` (connector#133). Read at open time so\n // a re-deposited channel signs against the live value, not a stale config.\n return {\n channelId: zkAppAddress,\n status: 'opening',\n depositTotal: openResult.depositTotal,\n };\n }\n\n /**\n * Opens an EVM payment channel on-chain, remapping the one-time\n * insufficient-native-gas revert into an actionable {@link ChannelFundingError}\n * so callers surface \"fund the wallet\" instead of the raw viem\n * \"...exceeds the balance of the account\" string (toon-meta#65). Only the gas\n * case is remapped; every other error propagates unchanged.\n */\n private async openEvmChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n try {\n return await this.openEvmChannelUnchecked(params);\n } catch (err) {\n if (!isInsufficientGasError(err)) throw err;\n // `chain` is a CAIP-ish id (e.g. \"evm:anvil:31337\"); surface just the\n // family so the remedy reads \"no gas on evm\", not the full slug.\n const chainFamily = params.chain.split(':')[0] || params.chain;\n throw new ChannelFundingError(\n `Settlement wallet ${this.evmSigner.address} has no gas on ` +\n `${chainFamily} to open a payment channel. Run toon_fund_wallet ` +\n `(or fund the wallet) and retry.`,\n err instanceof Error ? err : undefined\n );\n }\n }\n\n /**\n * Raw EVM channel-open (no gas-error remapping — see {@link openEvmChannel}).\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n private async openEvmChannelUnchecked(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n const {\n chain,\n tokenNetwork,\n peerAddress,\n initialDeposit,\n settlementTimeout,\n } = params;\n\n if (!tokenNetwork) {\n throw new Error(\n 'tokenNetwork address is required for on-chain channel opening'\n );\n }\n\n const { publicClient, walletClient } = this.createClients(chain);\n const tokenNetworkAddr = tokenNetwork as Hex;\n const deposit = initialDeposit ? BigInt(initialDeposit) : 0n;\n\n // If deposit > 0, ensure token approval\n if (deposit > 0n && params.token) {\n const tokenAddr = params.token as Hex;\n const myAddress = this.evmSigner.address as Hex;\n\n const currentAllowance = await publicClient.readContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'allowance',\n args: [myAddress, tokenNetworkAddr],\n });\n\n if ((currentAllowance as bigint) < deposit) {\n const approveHash = await walletClient.writeContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'approve',\n args: [tokenNetworkAddr, maxUint256],\n });\n await publicClient.waitForTransactionReceipt({ hash: approveHash });\n }\n }\n\n // Open channel\n const timeout = BigInt(settlementTimeout ?? 86400);\n const openHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'openChannel',\n args: [peerAddress as Hex, timeout],\n });\n\n const receipt: TransactionReceipt =\n await publicClient.waitForTransactionReceipt({ hash: openHash });\n\n // Extract channelId from ChannelOpened event\n let channelId: string | undefined;\n for (const log of receipt.logs) {\n try {\n const decoded = decodeEventLog({\n abi: TOKEN_NETWORK_ABI,\n data: log.data,\n topics: log.topics,\n });\n if (decoded.eventName === 'ChannelOpened') {\n channelId = (decoded.args as Record<string, unknown>)[\n 'channelId'\n ] as string;\n break;\n }\n } catch {\n // Not our event, skip\n }\n }\n\n if (!channelId) {\n throw new Error('Failed to extract channelId from ChannelOpened event');\n }\n\n // Cache context for getChannelState + later deposits (the token address is\n // needed to approve additional collateral on a standalone deposit).\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\n ...(params.token ? { tokenAddress: params.token } : {}),\n });\n\n // Deposit initial funds if specified\n if (deposit > 0n) {\n const depositHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'setTotalDeposit',\n args: [channelId as Hex, this.evmSigner.address as Hex, deposit],\n });\n await publicClient.waitForTransactionReceipt({ hash: depositHash });\n }\n\n return { channelId, status: 'opening' };\n }\n\n /**\n * Gets the current state of a payment channel from on-chain data.\n */\n async getChannelState(channelId: string): Promise<ChannelState> {\n const context = this.channelContext.get(channelId);\n if (!context) {\n throw new Error(\n `No context for channel \"${channelId}\". Channel must be opened via this client first.`\n );\n }\n\n // Mina channels are opened/deployed out-of-band; the connector performs the\n // authoritative on-chain `getChannelState(zkAppAddress)` check at claim\n // verification. Reading zkApp state client-side would require the o1js WASM\n // runtime, which the lightweight client intentionally avoids. Report `open`\n // for the configured deployed zkApp.\n if (context.chain.split(':')[0] === 'mina') {\n return { channelId, status: 'open', chain: context.chain };\n }\n\n // Solana channels read on-chain state from the PDA account, not an EVM contract.\n if (context.chain.split(':')[0] === 'solana' && this.solanaConfig) {\n const account = await getSolanaChannelAccountState(\n this.solanaConfig.rpcUrl,\n channelId\n );\n const status: ChannelState['status'] = !account.exists\n ? 'settled'\n : account.state === 'opened'\n ? 'open'\n : account.state === 'closed'\n ? 'closed'\n : 'settled';\n return { channelId, status, chain: context.chain };\n }\n\n const { publicClient } = this.createClients(context.chain);\n\n const result = await publicClient.readContract({\n address: context.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'channels',\n args: [channelId as Hex],\n });\n\n const [, state] = result as [\n bigint,\n number,\n bigint,\n bigint,\n string,\n string,\n ];\n const status = STATE_MAP[state] ?? 'settled';\n\n return {\n channelId,\n status,\n chain: context.chain,\n };\n }\n}\n","/**\n * Solana payment-channel primitives — connector-parity.\n *\n * Pure, dependency-light helpers that reproduce the EXACT on-chain contract the\n * connector's `SolanaPaymentChannelSDK` (`@toon-protocol/connector`\n * `settlement/solana-payment-channel-sdk.ts`) implements, so a client-issued\n * Solana payment-channel claim is accepted by connector 3.9.0's\n * `verifySolanaClaim` path:\n *\n * 1. PDA derivation — `[b\"channel\", min_pubkey, max_pubkey, token_mint]` sorted\n * lexicographically by raw 32-byte pubkey, derived against the program id.\n * This base58 PDA is the claim's `channelAccount` and the channel-state\n * account the connector reads via `provider.getChannelState`.\n * 2. The 48-byte balance-proof message the connector verifies the Ed25519\n * signature over: `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)`.\n * NOTE: this is NOT the swap-claim `balanceProofHashSolana` shape used by the\n * swap peer ↔ sender wire / SDK `verifyEd25519Signature`; the connector's on-chain\n * payment-channel verifier (`solana-payment-channel-provider.verifyBalanceProof`)\n * verifies this raw 48-byte message, un-hashed.\n * 3. The `initialize_channel` (+ `deposit`) instructions, built and submitted\n * over raw Solana JSON-RPC (no `@solana/web3.js` / `@solana/kit` runtime\n * dependency — only `@noble/curves` + `@noble/hashes`, already client deps).\n *\n * Every byte layout / discriminator / account-meta order here is mirrored from\n * the connector SDK and the SDK reference E2E\n * (`packages/sdk/tests/e2e/docker-solana-settlement-e2e.test.ts`). Keep them in\n * lock-step; a mismatch makes the connector reject the claim (ON_CHAIN_VERIFICATION_FAILED\n * or INVALID_SIGNATURE).\n */\n\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { base58Encode, base58Decode } from '@toon-protocol/core';\n\n// ---------------------------------------------------------------------------\n// Constants (must match the Rust program + connector SDK exactly)\n// ---------------------------------------------------------------------------\n\n/** Well-known Solana program addresses (base58). */\nconst TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\nconst SYSTEM_PROGRAM_ID = '11111111111111111111111111111111';\nconst RENT_SYSVAR_ID = 'SysvarRent111111111111111111111111111111111';\n\n/** Instruction discriminators — first byte of an 8-byte LE tag. */\nconst IX_INITIALIZE_CHANNEL = new Uint8Array([0x01, 0, 0, 0, 0, 0, 0, 0]);\nconst IX_DEPOSIT = new Uint8Array([0x02, 0, 0, 0, 0, 0, 0, 0]);\n\n/** On-chain channel-account discriminator: ASCII \"pchannel\". */\nconst CHANNEL_DISCRIMINATOR = new Uint8Array([\n 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,\n]);\n\n/** Channel-state account size in bytes. */\nconst CHANNEL_ACCOUNT_SIZE = 178;\n\n// ---------------------------------------------------------------------------\n// Byte helpers\n// ---------------------------------------------------------------------------\n\nconst MAX_U64 = (1n << 64n) - 1n;\n\nfunction writeU64LE(buf: Uint8Array, offset: number, value: bigint): void {\n if (value < 0n || value > MAX_U64) {\n throw new RangeError(`Value ${value} outside u64 range [0, 2^64-1]`);\n }\n for (let i = 0; i < 8; i++) {\n buf[offset + i] = Number((value >> BigInt(i * 8)) & 0xffn);\n }\n}\n\n/** Left-pad / trim a byte array to exactly 32 bytes. */\nfunction padTo32(bytes: Uint8Array): Uint8Array {\n if (bytes.length === 32) return bytes;\n if (bytes.length > 32) return bytes.slice(bytes.length - 32);\n const padded = new Uint8Array(32);\n padded.set(bytes, 32 - bytes.length);\n return padded;\n}\n\n/** Sort two 32-byte pubkeys lexicographically by raw bytes (matches Rust). */\nfunction sortPubkeys(a: Uint8Array, b: Uint8Array): [Uint8Array, Uint8Array] {\n for (let i = 0; i < 32; i++) {\n const ai = a[i] ?? 0;\n const bi = b[i] ?? 0;\n if (ai < bi) return [a, b];\n if (ai > bi) return [b, a];\n }\n return [a, b];\n}\n\n// ---------------------------------------------------------------------------\n// Ed25519 curve check + PDA derivation (matches Solana find_program_address)\n// ---------------------------------------------------------------------------\n\nfunction modPow(base: bigint, exp: bigint, mod: bigint): bigint {\n let result = 1n;\n base = ((base % mod) + mod) % mod;\n while (exp > 0n) {\n if (exp & 1n) result = (result * base) % mod;\n exp >>= 1n;\n base = (base * base) % mod;\n }\n return result;\n}\n\nfunction modInverse(a: bigint, m: bigint): bigint {\n return modPow(((a % m) + m) % m, m - 2n, m);\n}\n\n/** True if 32 bytes lie on the Ed25519 curve. A valid PDA must NOT be on-curve. */\nfunction isOnCurve(bytes: Uint8Array): boolean {\n const P = (1n << 255n) - 19n;\n const yBytes = new Uint8Array(32);\n yBytes.set(bytes);\n yBytes[31] = (yBytes[31] ?? 0) & 0x7f;\n\n let y = 0n;\n for (let i = 0; i < 32; i++) {\n y |= BigInt(yBytes[i] ?? 0) << BigInt(i * 8);\n }\n if (y >= P) return true;\n\n const y2 = (y * y) % P;\n const D = (P - ((121665n * modInverse(121666n, P)) % P) + P) % P;\n const numerator = (y2 - 1n + P) % P;\n const denominator = (D * y2 + 1n) % P;\n const x2 = (numerator * modInverse(denominator, P)) % P;\n if (x2 === 0n) return true;\n return modPow(x2, (P - 1n) / 2n, P) === 1n;\n}\n\nfunction findProgramAddress(\n seeds: Uint8Array[],\n programId: Uint8Array\n): { pda: Uint8Array; bump: number } {\n const PDA_MARKER = new TextEncoder().encode('ProgramDerivedAddress');\n for (let bump = 255; bump >= 0; bump--) {\n const allSeeds = [...seeds, new Uint8Array([bump])];\n let totalLen = programId.length + PDA_MARKER.length;\n for (const s of allSeeds) totalLen += s.length;\n\n const input = new Uint8Array(totalLen);\n let offset = 0;\n for (const s of allSeeds) {\n input.set(s, offset);\n offset += s.length;\n }\n input.set(programId, offset);\n offset += programId.length;\n input.set(PDA_MARKER, offset);\n\n const hash = sha256(input);\n if (!isOnCurve(hash)) return { pda: hash, bump };\n }\n throw new Error('Could not find a viable PDA bump seed');\n}\n\n/**\n * Derive the channel PDA — connector-parity.\n * Seeds: `[b\"channel\", min_pubkey, max_pubkey, token_mint]` (participants sorted).\n *\n * @returns base58 PDA + bump.\n */\nexport function deriveChannelPDA(\n participantA: string,\n participantB: string,\n tokenMint: string,\n programId: string\n): { pda: string; bump: number } {\n const a = padTo32(base58Decode(participantA));\n const b = padTo32(base58Decode(participantB));\n const mint = padTo32(base58Decode(tokenMint));\n const program = padTo32(base58Decode(programId));\n const [min, max] = sortPubkeys(a, b);\n const seeds = [new TextEncoder().encode('channel'), min, max, mint];\n const { pda, bump } = findProgramAddress(seeds, program);\n return { pda: base58Encode(pda), bump };\n}\n\n/**\n * Derive the Associated Token Account (ATA) for an owner + SPL mint — the\n * standard SPL ATA PDA over seeds `[owner, TOKEN_PROGRAM_ID, mint]` under the\n * Associated-Token-Account program. Deterministic from `(owner, mint)`, so\n * callers (e.g. a Solana channel deposit) need not supply the funded token\n * account explicitly — it is always the owner's ATA for the channel's mint.\n *\n * @param owner - base58 wallet pubkey that owns the token account.\n * @param tokenMint - base58 SPL mint.\n * @returns base58 ATA address.\n */\nexport function deriveAssociatedTokenAccount(owner: string, tokenMint: string): string {\n // Canonical mainnet/devnet SPL program ids (same on every cluster).\n const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\n const ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';\n const seeds = [\n padTo32(base58Decode(owner)),\n padTo32(base58Decode(TOKEN_PROGRAM_ID)),\n padTo32(base58Decode(tokenMint)),\n ];\n const { pda } = findProgramAddress(seeds, padTo32(base58Decode(ASSOCIATED_TOKEN_PROGRAM_ID)));\n return base58Encode(pda);\n}\n\n/**\n * Derive the vault PDA for a channel — connector-parity.\n * Seeds: `[b\"vault\", channel_pda]`.\n */\nexport function deriveVaultPDA(\n channelPDA: string,\n programId: string\n): { pda: string; bump: number } {\n const channel = padTo32(base58Decode(channelPDA));\n const program = padTo32(base58Decode(programId));\n const seeds = [new TextEncoder().encode('vault'), channel];\n const { pda, bump } = findProgramAddress(seeds, program);\n return { pda: base58Encode(pda), bump };\n}\n\n// ---------------------------------------------------------------------------\n// Balance-proof message + signing (connector-parity)\n// ---------------------------------------------------------------------------\n\n/**\n * Build the connector's canonical 48-byte balance-proof message:\n * `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)`.\n *\n * Mirrors `SolanaPaymentChannelSDK._buildBalanceProofMessage`. This is the EXACT\n * message the connector's `solana-payment-channel-provider.verifyBalanceProof`\n * reconstructs and Ed25519-verifies (un-hashed).\n */\nexport function buildBalanceProofMessage(\n channelPDA: string,\n nonce: bigint,\n transferredAmount: bigint\n): Uint8Array {\n const message = new Uint8Array(48);\n message.set(padTo32(base58Decode(channelPDA)), 0);\n writeU64LE(message, 32, nonce);\n writeU64LE(message, 40, transferredAmount);\n return message;\n}\n\n/** Sign the 48-byte balance-proof message with a 32-byte Ed25519 seed. */\nexport function signBalanceProofMessage(\n channelPDA: string,\n nonce: bigint,\n transferredAmount: bigint,\n seed: Uint8Array\n): Uint8Array {\n const message = buildBalanceProofMessage(\n channelPDA,\n nonce,\n transferredAmount\n );\n return ed25519.sign(message, seed);\n}\n\n// ---------------------------------------------------------------------------\n// On-chain channel open (initialize_channel + deposit) over raw JSON-RPC\n// ---------------------------------------------------------------------------\n\ninterface InstructionKey {\n pubkey: string;\n isSigner: boolean;\n isWritable: boolean;\n}\n\ninterface RawInstruction {\n programId: string;\n keys: InstructionKey[];\n data: Uint8Array;\n}\n\ninterface Signer {\n publicKey: Uint8Array;\n privateKey: Uint8Array;\n}\n\nlet rpcIdCounter = 1;\n\nasync function solanaRpc(\n rpcUrl: string,\n method: string,\n params: unknown[] = []\n): Promise<unknown> {\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method,\n params,\n id: rpcIdCounter++,\n }),\n signal: AbortSignal.timeout(30000),\n });\n const json = (await res.json()) as {\n result?: unknown;\n error?: { message: string; code: number };\n };\n if (json.error) {\n throw new Error(\n `Solana RPC error [${method}]: ${json.error.message} (code ${json.error.code})`\n );\n }\n return json.result;\n}\n\nasync function getLatestBlockhash(rpcUrl: string): Promise<string> {\n const result = (await solanaRpc(rpcUrl, 'getLatestBlockhash', [\n { commitment: 'confirmed' },\n ])) as { value: { blockhash: string } };\n return result.value.blockhash;\n}\n\ninterface AccountInfo {\n data: [string, string];\n owner: string;\n lamports: number;\n}\n\nasync function getAccountInfo(\n rpcUrl: string,\n pubkey: string\n): Promise<AccountInfo | null> {\n const result = (await solanaRpc(rpcUrl, 'getAccountInfo', [\n pubkey,\n { encoding: 'base64', commitment: 'confirmed' },\n ])) as { value: AccountInfo | null };\n return result.value;\n}\n\nasync function waitForConfirmation(\n rpcUrl: string,\n signature: string,\n timeoutMs = 30000\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const result = (await solanaRpc(rpcUrl, 'getSignatureStatuses', [\n [signature],\n ])) as {\n value: ({ confirmationStatus: string; err?: unknown } | null)[];\n };\n const status = result.value[0];\n if (\n status?.confirmationStatus === 'confirmed' ||\n status?.confirmationStatus === 'finalized'\n ) {\n if (status.err) {\n throw new Error(\n `Transaction ${signature} failed: ${JSON.stringify(status.err)}`\n );\n }\n return;\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n throw new Error(\n `Transaction ${signature} not confirmed within ${timeoutMs}ms`\n );\n}\n\nfunction compactU16Size(value: number): number {\n if (value > 0xffff) {\n throw new RangeError(`compact-u16 value ${value} exceeds u16 max (0xFFFF)`);\n }\n return value < 0x80 ? 1 : value < 0x4000 ? 2 : 3;\n}\n\nfunction writeCompactU16(\n buf: Uint8Array,\n offset: number,\n value: number\n): number {\n if (value < 0x80) {\n buf[offset++] = value;\n } else if (value < 0x4000) {\n buf[offset++] = (value & 0x7f) | 0x80;\n buf[offset++] = value >> 7;\n } else {\n buf[offset++] = (value & 0x7f) | 0x80;\n buf[offset++] = ((value >> 7) & 0x7f) | 0x80;\n buf[offset++] = value >> 14;\n }\n return offset;\n}\n\ninterface AccountEntry {\n pubkey: string;\n isSigner: boolean;\n isWritable: boolean;\n}\n\n/**\n * Build, sign, and send a Solana legacy transaction over raw JSON-RPC, then wait\n * for confirmation. Mirrors the SDK reference E2E's `buildAndSendTransaction`.\n */\nasync function buildAndSendTransaction(\n rpcUrl: string,\n feePayer: Signer,\n instructions: RawInstruction[],\n additionalSigners: Signer[] = []\n): Promise<string> {\n const blockhash = await getLatestBlockhash(rpcUrl);\n const feePayerPubkey = base58Encode(feePayer.publicKey);\n\n const accountMap = new Map<string, AccountEntry>();\n accountMap.set(feePayerPubkey, {\n pubkey: feePayerPubkey,\n isSigner: true,\n isWritable: true,\n });\n for (const ix of instructions) {\n for (const key of ix.keys) {\n const existing = accountMap.get(key.pubkey);\n if (existing) {\n existing.isSigner = existing.isSigner || key.isSigner;\n existing.isWritable = existing.isWritable || key.isWritable;\n } else {\n accountMap.set(key.pubkey, { ...key });\n }\n }\n if (!accountMap.has(ix.programId)) {\n accountMap.set(ix.programId, {\n pubkey: ix.programId,\n isSigner: false,\n isWritable: false,\n });\n }\n }\n\n const accounts = [...accountMap.values()].sort((a, b) => {\n if (a.pubkey === feePayerPubkey) return -1;\n if (b.pubkey === feePayerPubkey) return 1;\n const aScore = (a.isSigner ? 2 : 0) + (a.isWritable ? 1 : 0);\n const bScore = (b.isSigner ? 2 : 0) + (b.isWritable ? 1 : 0);\n return bScore - aScore;\n });\n\n const numSigners = accounts.filter((a) => a.isSigner).length;\n const numReadonlySigners = accounts.filter(\n (a) => a.isSigner && !a.isWritable\n ).length;\n const numReadonlyNonSigners = accounts.filter(\n (a) => !a.isSigner && !a.isWritable\n ).length;\n\n const accountIndexMap = new Map<string, number>();\n accounts.forEach((a, i) => accountIndexMap.set(a.pubkey, i));\n\n const compiled = instructions.map((ix) => ({\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- programId added to accountMap above\n programIdIndex: accountIndexMap.get(ix.programId)!,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every key added to accountMap above\n accountIndices: ix.keys.map((k) => accountIndexMap.get(k.pubkey)!),\n data: ix.data,\n }));\n\n const blockhashBytes = base58Decode(blockhash);\n\n let instructionSize = compactU16Size(compiled.length);\n for (const ix of compiled) {\n instructionSize += 1;\n instructionSize +=\n compactU16Size(ix.accountIndices.length) + ix.accountIndices.length;\n instructionSize += compactU16Size(ix.data.length) + ix.data.length;\n }\n\n const messageSize =\n 3 +\n compactU16Size(accounts.length) +\n 32 * accounts.length +\n 32 +\n instructionSize;\n const message = new Uint8Array(messageSize);\n let offset = 0;\n\n message[offset++] = numSigners;\n message[offset++] = numReadonlySigners;\n message[offset++] = numReadonlyNonSigners;\n\n offset = writeCompactU16(message, offset, accounts.length);\n for (const acct of accounts) {\n message.set(padTo32(base58Decode(acct.pubkey)), offset);\n offset += 32;\n }\n\n message.set(padTo32(blockhashBytes), offset);\n offset += 32;\n\n offset = writeCompactU16(message, offset, compiled.length);\n for (const ix of compiled) {\n message[offset++] = ix.programIdIndex;\n offset = writeCompactU16(message, offset, ix.accountIndices.length);\n for (const idx of ix.accountIndices) message[offset++] = idx;\n offset = writeCompactU16(message, offset, ix.data.length);\n message.set(ix.data, offset);\n offset += ix.data.length;\n }\n\n const finalMessage = message.slice(0, offset);\n\n const allSigners = [feePayer, ...additionalSigners];\n const signerPubkeys = accounts.filter((a) => a.isSigner).map((a) => a.pubkey);\n const signatures: Uint8Array[] = [];\n for (const signerPubkey of signerPubkeys) {\n const signer = allSigners.find(\n (s) => base58Encode(s.publicKey) === signerPubkey\n );\n if (!signer) throw new Error(`Missing signer for ${signerPubkey}`);\n signatures.push(ed25519.sign(finalMessage, signer.privateKey));\n }\n\n const txSize =\n compactU16Size(signatures.length) +\n signatures.length * 64 +\n finalMessage.length;\n const tx = new Uint8Array(txSize);\n let txOffset = 0;\n txOffset = writeCompactU16(tx, txOffset, signatures.length);\n for (const sig of signatures) {\n tx.set(sig, txOffset);\n txOffset += 64;\n }\n tx.set(finalMessage, txOffset);\n\n const txBase64 = Buffer.from(tx).toString('base64');\n const txSig = (await solanaRpc(rpcUrl, 'sendTransaction', [\n txBase64,\n {\n encoding: 'base64',\n skipPreflight: false,\n preflightCommitment: 'confirmed',\n },\n ])) as string;\n await waitForConfirmation(rpcUrl, txSig);\n return txSig;\n}\n\n/** Parsed status of an on-chain channel account. */\nexport interface SolanaChannelAccountState {\n exists: boolean;\n /** 'opened' | 'closed' | 'settled' when the account exists with valid data. */\n state?: 'opened' | 'closed' | 'settled';\n participantA?: string;\n participantB?: string;\n}\n\nconst STATE_MAP = ['opened', 'closed', 'settled'] as const;\n\n/** Fetch + minimally parse the on-chain channel account at a PDA. */\nexport async function getChannelAccountState(\n rpcUrl: string,\n channelPDA: string\n): Promise<SolanaChannelAccountState> {\n const info = await getAccountInfo(rpcUrl, channelPDA);\n if (!info) return { exists: false };\n const data = new Uint8Array(Buffer.from(info.data[0], 'base64'));\n if (data.length < CHANNEL_ACCOUNT_SIZE) return { exists: false };\n for (let i = 0; i < 8; i++) {\n if (data[i] !== CHANNEL_DISCRIMINATOR[i]) return { exists: false };\n }\n return {\n exists: true,\n state: STATE_MAP[data[160] ?? 0] ?? 'opened',\n participantA: base58Encode(data.slice(8, 40)),\n participantB: base58Encode(data.slice(40, 72)),\n };\n}\n\nexport interface OpenSolanaChannelParams {\n rpcUrl: string;\n programId: string;\n tokenMint: string;\n /** Client's 32-byte Ed25519 seed (participant A + fee payer). */\n payerSeed: Uint8Array;\n /** Client's base58 pubkey (participant A). */\n payerPubkey: string;\n /** Apex's base58 settlement pubkey (participant B). */\n peerPubkey: string;\n /** Challenge-period duration in seconds. */\n challengeDuration: bigint;\n /** Optional deposit amount + funded SPL token account (ATA) of the payer. */\n deposit?: { amount: bigint; payerTokenAccount: string };\n}\n\nexport interface OpenSolanaChannelResult {\n channelPDA: string;\n /** True if a fresh on-chain initialize_channel tx was submitted. */\n opened: boolean;\n initTxSignature?: string;\n depositTxSignature?: string;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Solana\n * payment channel at the connector-parity PDA. Idempotent: if the channel\n * account already exists on-chain, returns the PDA without re-initializing.\n *\n * The Ed25519 keypair derives both the participant-A identity and the fee\n * payer; the apex pubkey is participant B. The returned `channelPDA` (base58) is\n * the value carried in the claim's `channelAccount`.\n */\nexport async function openSolanaChannel(\n params: OpenSolanaChannelParams\n): Promise<OpenSolanaChannelResult> {\n const {\n rpcUrl,\n programId,\n tokenMint,\n payerSeed,\n payerPubkey,\n peerPubkey,\n challengeDuration,\n } = params;\n\n const { pda: channelPDA } = deriveChannelPDA(\n payerPubkey,\n peerPubkey,\n tokenMint,\n programId\n );\n\n // Idempotent: skip initialize if the channel account already exists.\n const existing = await getChannelAccountState(rpcUrl, channelPDA);\n if (existing.exists) {\n return { channelPDA, opened: false };\n }\n\n const payerPublicKey = padTo32(base58Decode(payerPubkey));\n const payer: Signer = { publicKey: payerPublicKey, privateKey: payerSeed };\n\n const { pda: vaultPDA } = deriveVaultPDA(channelPDA, programId);\n\n // initialize_channel: discriminator(8) + challenge_duration(8 LE)\n const initData = new Uint8Array(16);\n initData.set(IX_INITIALIZE_CHANNEL, 0);\n writeU64LE(initData, 8, challengeDuration);\n\n const initTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: true },\n { pubkey: payerPubkey, isSigner: false, isWritable: false }, // participant A\n { pubkey: peerPubkey, isSigner: false, isWritable: false }, // participant B\n { pubkey: tokenMint, isSigner: false, isWritable: false },\n { pubkey: channelPDA, isSigner: false, isWritable: true },\n { pubkey: vaultPDA, isSigner: false, isWritable: true },\n { pubkey: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },\n { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n { pubkey: RENT_SYSVAR_ID, isSigner: false, isWritable: false },\n ],\n data: initData,\n },\n ]);\n\n let depositTxSignature: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n ({ depositTxSignature } = await depositSolanaChannel({\n rpcUrl,\n programId,\n channelPDA,\n payerSeed,\n payerPubkey,\n payerTokenAccount: params.deposit.payerTokenAccount,\n amount: params.deposit.amount,\n }));\n }\n\n return { channelPDA, opened: true, initTxSignature, depositTxSignature };\n}\n\nexport interface DepositSolanaChannelParams {\n rpcUrl: string;\n programId: string;\n /** The channel PDA (base58) — the Solana channel id. */\n channelPDA: string;\n /** Ed25519 signing seed (32 bytes) of the payer. */\n payerSeed: Uint8Array;\n /** Payer public key (base58). */\n payerPubkey: string;\n /** Funded SPL token account (ATA, base58) the collateral is pulled from. */\n payerTokenAccount: string;\n /** Delta to deposit (base units). The on-chain `deposit` ix adds this amount. */\n amount: bigint;\n}\n\n/**\n * Deposit additional collateral into an existing on-chain Solana channel — the\n * standalone `deposit` instruction (discriminator + amount LE), the same one the\n * open flow fires post-init. Incremental: the program adds `amount` to the\n * channel vault. Returns the deposit tx signature.\n */\nexport async function depositSolanaChannel(\n params: DepositSolanaChannelParams\n): Promise<{ depositTxSignature: string }> {\n const { rpcUrl, programId, channelPDA, payerSeed, payerPubkey, payerTokenAccount, amount } =\n params;\n if (amount <= 0n) throw new Error('Solana deposit amount must be positive.');\n\n const payer: Signer = {\n publicKey: padTo32(base58Decode(payerPubkey)),\n privateKey: payerSeed,\n };\n const { pda: vaultPDA } = deriveVaultPDA(channelPDA, programId);\n\n // deposit: discriminator(8) + amount(8 LE)\n const depositData = new Uint8Array(16);\n depositData.set(IX_DEPOSIT, 0);\n writeU64LE(depositData, 8, amount);\n\n const depositTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: false },\n { pubkey: payerTokenAccount, isSigner: false, isWritable: true },\n { pubkey: vaultPDA, isSigner: false, isWritable: true },\n { pubkey: channelPDA, isSigner: false, isWritable: true },\n { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n ],\n data: depositData,\n },\n ]);\n return { depositTxSignature };\n}\n\n// Internal helpers exported for unit tests (parity assertions).\nexport const __testing = { padTo32, sortPubkeys, isOnCurve };\n","import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport { type Hex, toHex } from 'viem';\nimport type { BalanceProofParams, SignedBalanceProof } from '../types.js';\n// Types re-exported for convenience\nexport type { ClaimMessage } from './types.js';\n\n/**\n * EVM claim message for BTP protocol data.\n * Matches @toon-protocol/connector's EVMClaimMessage interface.\n *\n * The connector's validateClaimMessage() requires envelope fields\n * (version, messageId, timestamp) plus EVM claim fields, and optionally\n * chainId + tokenNetworkAddress for self-describing signature verification.\n */\nexport interface EVMClaimMessage {\n version: '1.0';\n blockchain: 'evm';\n messageId: string;\n timestamp: string;\n senderId: string;\n channelId: string;\n nonce: number;\n transferredAmount: string;\n lockedAmount: string;\n locksRoot: string;\n signature: string;\n signerAddress: string;\n /** Chain ID for self-describing EIP-712 verification */\n chainId: number;\n /** TokenNetwork address for self-describing EIP-712 verification */\n tokenNetworkAddress: string;\n /** ERC-20 token address for self-describing claim verification */\n tokenAddress?: string;\n}\n\n/**\n * EIP-712 domain for TokenNetwork balance proofs.\n * Must match connector's eip712-helper.js getDomainSeparator().\n */\nfunction getBalanceProofDomain(chainId: number, tokenNetworkAddress: string) {\n return {\n name: 'TokenNetwork' as const,\n version: '1' as const,\n chainId,\n verifyingContract: tokenNetworkAddress as Hex,\n };\n}\n\n/**\n * EIP-712 types for balance proofs.\n * Must match connector's eip712-helper.js getBalanceProofTypes().\n */\nconst BALANCE_PROOF_TYPES = {\n BalanceProof: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'nonce', type: 'uint256' },\n { name: 'transferredAmount', type: 'uint256' },\n { name: 'lockedAmount', type: 'uint256' },\n { name: 'locksRoot', type: 'bytes32' },\n ],\n} as const;\n\n/**\n * EVM signer for EIP-712 balance proofs and on-chain transactions.\n *\n * Encapsulates the private key — no getPrivateKey() method is exposed.\n */\nexport class EvmSigner {\n readonly chainType = 'evm' as const;\n private readonly _account: PrivateKeyAccount;\n\n /**\n * @param privateKey - EVM private key as hex string (with or without 0x prefix) or Uint8Array\n */\n constructor(privateKey: string | Uint8Array) {\n let hexKey: Hex;\n if (privateKey instanceof Uint8Array) {\n hexKey = toHex(privateKey);\n } else {\n hexKey = (\n privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`\n ) as Hex;\n }\n this._account = privateKeyToAccount(hexKey);\n }\n\n /** Derived 0x EVM address */\n get address(): string {\n return this._account.address;\n }\n\n /** ChainSigner identifier — EVM address */\n get signerIdentifier(): string {\n return this._account.address;\n }\n\n /** Viem PrivateKeyAccount — usable with walletClient for on-chain transactions */\n get account(): PrivateKeyAccount {\n return this._account;\n }\n\n /**\n * Signs a balance proof using EIP-712 typed data.\n *\n * @param params - Balance proof parameters plus chain context\n * @returns Signed balance proof with signature\n */\n async signBalanceProof(\n params: BalanceProofParams & {\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n }\n ): Promise<SignedBalanceProof> {\n const domain = getBalanceProofDomain(\n params.chainId,\n params.tokenNetworkAddress\n );\n\n const signature = await this._account.signTypedData({\n domain,\n types: BALANCE_PROOF_TYPES,\n primaryType: 'BalanceProof',\n message: {\n channelId: params.channelId as Hex,\n nonce: BigInt(params.nonce),\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot as Hex,\n },\n });\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature,\n signerAddress: this._account.address,\n chainId: params.chainId,\n tokenNetworkAddress: params.tokenNetworkAddress,\n ...(params.tokenAddress && { tokenAddress: params.tokenAddress }),\n };\n }\n\n /**\n * Builds an EVMClaimMessage from a signed balance proof.\n * Static so it can be called without an EvmSigner instance.\n *\n * @param proof - Signed balance proof (includes chainId and tokenNetworkAddress)\n * @param senderId - Nostr pubkey or identifier of the sender\n * @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL\n */\n static buildClaimMessage(\n proof: SignedBalanceProof,\n senderId: string\n ): EVMClaimMessage {\n return {\n version: '1.0',\n blockchain: 'evm',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n channelId: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n lockedAmount: proof.lockedAmount.toString(),\n locksRoot: proof.locksRoot,\n signature: proof.signature,\n signerAddress: proof.signerAddress,\n chainId: proof.chainId,\n tokenNetworkAddress: proof.tokenNetworkAddress,\n ...(proof.tokenAddress && { tokenAddress: proof.tokenAddress }),\n };\n }\n}\n","import { ed25519 } from '@noble/curves/ed25519.js';\nimport { base58Encode } from '@toon-protocol/core';\nimport type { SignedBalanceProof } from '../types.js';\nimport type {\n ChainSigner,\n ChainMetadata,\n ClaimMessage,\n SolanaClaimMessage,\n} from './types.js';\nimport { toHex as bytesToHex } from '../utils/binary.js';\nimport { buildBalanceProofMessage } from '../channel/solana-payment-channel.js';\n\n/**\n * Solana signer for the connector payment-channel claim path.\n *\n * Signs the connector's on-chain payment-channel balance-proof message — the\n * raw 48-byte `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)` (see\n * `@toon-protocol/connector` `SolanaPaymentChannelSDK._buildBalanceProofMessage`\n * + `solana-payment-channel-provider.verifyBalanceProof`). The produced 64-byte\n * Ed25519 signature verifies on the connector's `verifySolanaClaim` path, which\n * is what makes a client-issued Solana payment-channel claim (paying the apex\n * to write) acceptable on connector 3.9.0.\n *\n * NOTE: this is a DIFFERENT message from the swap peer ↔ sender swap-claim wire\n * contract (`balanceProofHashSolana`, SDK `verifyEd25519Signature`). The client\n * here is paying a payment-channel claim to the apex, not issuing a swap claim,\n * so it must sign the connector's on-chain payment-channel message. `channelId`\n * MUST be the base58 channel PDA (produced by `OnChainChannelClient.openChannel`).\n */\nexport class SolanaSigner implements ChainSigner {\n readonly chainType = 'solana' as const;\n /** 32-byte Ed25519 seed. */\n private readonly privateKey: Uint8Array;\n private pubkeyBase58Cache?: string;\n\n /**\n * @param privateKey - 32-byte Ed25519 seed (e.g. `identity.solana.secretKey.slice(0, 32)`).\n * @param publicKeyBase58 - Optional base58 public key (e.g. `identity.solana.publicKey`).\n * When omitted it is derived lazily from `privateKey`.\n */\n constructor(privateKey: Uint8Array, publicKeyBase58?: string) {\n if (privateKey.length !== 32) {\n throw new Error(\n `SolanaSigner requires a 32-byte Ed25519 seed, got ${privateKey.length} bytes`\n );\n }\n this.privateKey = privateKey;\n this.pubkeyBase58Cache = publicKeyBase58;\n }\n\n private ensurePublicKey(): string {\n if (this.pubkeyBase58Cache) return this.pubkeyBase58Cache;\n const pk = ed25519.getPublicKey(this.privateKey);\n this.pubkeyBase58Cache = base58Encode(new Uint8Array(pk));\n return this.pubkeyBase58Cache;\n }\n\n get signerIdentifier(): string {\n return this.pubkeyBase58Cache ?? 'uninitialized';\n }\n\n async signBalanceProof(params: {\n channelId: string;\n nonce: number;\n transferredAmount: bigint;\n lockedAmount: bigint;\n locksRoot: string;\n recipient: string;\n metadata: ChainMetadata;\n }): Promise<SignedBalanceProof> {\n if (params.metadata.chainType !== 'solana') {\n throw new Error(\n `SolanaSigner cannot sign for chain type: ${params.metadata.chainType}`\n );\n }\n\n const base58 = this.ensurePublicKey();\n\n // Connector on-chain payment-channel balance-proof message:\n // channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)\n // `channelId` is the base58 channel PDA (from OnChainChannelClient.openChannel).\n // cumulativeAmount == transferredAmount. No recipient term — the connector's\n // verifyBalanceProof reconstructs exactly these three fields.\n const message = buildBalanceProofMessage(\n params.channelId,\n BigInt(params.nonce),\n params.transferredAmount\n );\n\n const signature = ed25519.sign(message, this.privateKey);\n const signatureHex = '0x' + bytesToHex(new Uint8Array(signature));\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature: signatureHex,\n signerAddress: base58,\n chainId: 0,\n tokenNetworkAddress: params.metadata.programId,\n recipient: params.recipient,\n };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\n // The connector verifies a base64 Ed25519 signature; the signed proof carries\n // a 0x-prefixed 64-byte hex signature, so convert hex -> bytes -> base64.\n const sigHex = proof.signature.startsWith('0x')\n ? proof.signature.slice(2)\n : proof.signature;\n const sigBytes = Uint8Array.from(\n sigHex.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? []\n );\n const signatureBase64 = Buffer.from(sigBytes).toString('base64');\n\n const claim: SolanaClaimMessage = {\n version: '1.0',\n blockchain: 'solana',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n // channelId IS the base58 channel PDA -> connector's channelAccount.\n channelAccount: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n signature: signatureBase64,\n signerPublicKey: this.pubkeyBase58Cache ?? proof.signerAddress,\n programId: proof.tokenNetworkAddress,\n };\n return claim;\n }\n}\n","import { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type {\n ChainSigner,\n ChainMetadata,\n ClaimMessage,\n MinaClaimMessage,\n} from './types.js';\nimport {\n buildMinaPaymentChannelProof,\n loadMinaPaymentChannelBindings,\n} from '../channel/mina-payment-channel.js';\nimport { readMinaDepositTotal } from '../channel/mina-deposit.js';\n\n/** Reads a channel zkApp's on-chain `depositTotal` (base units). */\nexport type MinaDepositReader = (zkAppAddress: string) => Promise<bigint>;\n\n/** Optional `MinaSigner` wiring for on-chain `depositTotal` resolution. */\nexport interface MinaSignerOptions {\n /**\n * Mina GraphQL URL used to read the channel's on-chain `depositTotal` when a\n * caller doesn't supply it to `signBalanceProof`. Enables conserved\n * `balanceB = depositTotal − balanceA` claims (settleable on funded zkApps).\n */\n graphqlUrl?: string;\n /** Inject a deposit reader (tests / custom transport). Overrides `graphqlUrl`. */\n depositReader?: MinaDepositReader;\n}\n\n/** Default Mina token id when the metadata omits one. */\nconst DEFAULT_MINA_TOKEN_ID = 'MINA';\n\n/** Mina network id carried in the claim (matches the connector devnet prefix). */\nconst MINA_CLAIM_NETWORK = 'devnet' as const;\n\n/**\n * Pallas base-field-safe salt derived from `(zkAppAddress, nonce)`.\n *\n * The commitment binds an arbitrary salt; we derive it deterministically so the\n * same (channel, nonce) reproduces the same proof, and take the first 240 bits\n * of `sha256` to stay safely inside the Pallas field (< 2^254). Non-zero by\n * construction (connector `validateMinaClaim` requires a non-empty `salt`).\n */\nexport function deriveMinaSalt(zkAppAddress: string, nonce: number): bigint {\n const digestHex = bytesToHex(\n sha256(new TextEncoder().encode(`mina-pc-salt:${zkAppAddress}:${nonce}`))\n );\n const salt = BigInt('0x' + digestHex.slice(0, 60));\n return salt === 0n ? 1n : salt;\n}\n\n/**\n * Mina (Pallas) signer for the connector payment-channel claim path.\n *\n * Produces the connector 3.9.0 `MinaClaimMessage` contract — `{ zkAppAddress,\n * tokenId, balanceCommitment, proof (base64), salt, nonce }` — by reproducing\n * `MinaPaymentChannelSDK.signBalanceProof` exactly (via\n * {@link buildMinaPaymentChannelProof}):\n *\n * commitment = Poseidon([Field(balanceA), Field(0), Field(salt)])\n * channelHashField = Poseidon([participantA.x, participantB.x, 0]) (see below)\n * proof = base64(JSON{ commitment, signature: { r, s }, nonce, signerPublicKey })\n *\n * with the Schnorr signature computed over `[commitment, Field(nonce),\n * channelHashField]` using the Mina `'devnet'` network id (matching o1js's\n * hardcoded `Signature.create` prefix). Verified field-by-field against the\n * connector's o1js `Signature.fromJSON({r,s}).verify` (see the package tests).\n *\n * `channelHashField` is the ON-CHAIN participant form\n * (`Poseidon([client.x, apex.x, 0])`, participantA=client, participantB=apex)\n * whenever the apex's Mina pubkey is known (the negotiated `recipient`), so the\n * claim can SETTLE on-chain via the zkApp's `claimFromChannel` (which only\n * verifies the participant form). When the apex pubkey is unavailable the signer\n * falls back to the legacy zkApp-x form (`Poseidon([zkApp.x])`); the connector's\n * off-chain `verifyBalanceProof` accepts EITHER, so off-chain store/FULFILL works\n * in both cases — only on-chain settle requires the participant form.\n *\n * NOTE: this is a DIFFERENT message + format from the swap peer ↔ sender swap-claim\n * wire contract (`balanceProofFieldsMina` in `@toon-protocol/core`, verified by\n * the SDK's `verifyMinaSignature`). The client here pays a payment-channel claim\n * to the apex, so it signs the connector's on-chain payment-channel scheme; the\n * swap-format hash is left untouched (mirrors the Solana #105 separation).\n *\n * `channelId` MUST be the deployed payment-channel zkApp B62 address (the same\n * address the apex's Mina provider resolves on-chain via `getChannelState`),\n * which is what `OnChainChannelClient.openMinaChannel` returns.\n *\n * `mina-signer` is an OPTIONAL dependency: its crypto (Poseidon, Pallas Schnorr,\n * the base58 signature codec) is loaded dynamically so the client builds and runs\n * for non-Mina users without it installed, and WITHOUT pulling the o1js WASM\n * circuit runtime.\n */\nexport class MinaSigner implements ChainSigner {\n readonly chainType = 'mina' as const;\n /** Big-endian hex scalar (or already-`EK…` base58) Mina private key. */\n private readonly privateKey: string;\n private publicKeyBase58?: string;\n private readonly depositReader?: MinaDepositReader;\n /** Per-zkApp `depositTotal` cache (deposits are rare; the connector re-reads). */\n private readonly depositCache = new Map<string, bigint>();\n\n /**\n * @param privateKey - Mina private key as big-endian hex scalar (the form\n * `deriveFullIdentity()` emits, `identity.mina.privateKey`) or an `EK…`\n * base58 key. Converted to the base58check form mina-signer requires.\n * @param publicKeyBase58 - Optional base58 public key (e.g.\n * `identity.mina.publicKey`). When omitted it is derived during signing.\n * @param options - Optional on-chain `depositTotal` resolution (graphqlUrl or\n * an injected reader) so claims conserve balances on funded zkApps.\n */\n constructor(\n privateKey: string,\n publicKeyBase58?: string,\n options?: MinaSignerOptions\n ) {\n this.privateKey = privateKey;\n this.publicKeyBase58 = publicKeyBase58;\n if (options?.depositReader) {\n this.depositReader = options.depositReader;\n } else if (options?.graphqlUrl) {\n const url = options.graphqlUrl;\n this.depositReader = (zkAppAddress) =>\n readMinaDepositTotal(url, zkAppAddress);\n }\n }\n\n /**\n * Resolve the channel's on-chain `depositTotal`, caching per zkApp. Returns\n * `undefined` when no reader is configured or the read fails — callers then\n * fall back to the legacy `balanceB = 0` commitment.\n */\n private async resolveDepositTotal(\n zkAppAddress: string\n ): Promise<bigint | undefined> {\n if (this.depositCache.has(zkAppAddress)) {\n return this.depositCache.get(zkAppAddress);\n }\n if (!this.depositReader) return undefined;\n try {\n const depositTotal = await this.depositReader(zkAppAddress);\n this.depositCache.set(zkAppAddress, depositTotal);\n return depositTotal;\n } catch {\n return undefined;\n }\n }\n\n get signerIdentifier(): string {\n return this.publicKeyBase58 ?? 'uninitialized';\n }\n\n /** Derive this signer's B62 public key from its (base58) private key. */\n private async deriveOwnPublicKey(\n minaPrivateKeyBase58: string\n ): Promise<string> {\n const { Client } = await loadMinaPaymentChannelBindings();\n return new Client({ network: MINA_CLAIM_NETWORK }).derivePublicKey(\n minaPrivateKeyBase58\n );\n }\n\n async signBalanceProof(params: {\n channelId: string;\n nonce: number;\n transferredAmount: bigint;\n lockedAmount: bigint;\n locksRoot: string;\n recipient: string;\n metadata: ChainMetadata;\n /**\n * On-chain channel `depositTotal`. When provided (>0), the signed commitment\n * binds `balanceB = depositTotal − balanceA` (the funder's remaining\n * balance), matching the connector's claimFromChannel reconstruction\n * (toon-protocol/connector#133) and the on-chain circuit's\n * `balanceA + balanceB == depositTotal` invariant. Omitted/0 keeps the\n * legacy `balanceB = 0` form (off-chain-store-only, non-settleable).\n */\n depositTotal?: bigint;\n }): Promise<SignedBalanceProof> {\n if (params.metadata.chainType !== 'mina') {\n throw new Error(\n `MinaSigner cannot sign for chain type: ${params.metadata.chainType}`\n );\n }\n\n // The zkApp address IS the channel id: it is the channel-hash preimage the\n // connector binds the proof to and the on-chain account it reads. Prefer the\n // negotiated channelId, fall back to the metadata zkAppAddress.\n const zkAppAddress = params.channelId || params.metadata.zkAppAddress;\n if (!zkAppAddress) {\n throw new Error(\n 'MinaSigner requires a zkAppAddress (channel id) to sign a balance proof'\n );\n }\n\n const minaPrivateKey = hexToMinaBase58PrivateKey(this.privateKey);\n const tokenId = params.metadata.tokenId ?? DEFAULT_MINA_TOKEN_ID;\n const salt = deriveMinaSalt(zkAppAddress, params.nonce);\n\n // Derive the client's own Mina pubkey now (needed as participantA for the\n // on-chain channelHash). `buildMinaPaymentChannelProof` derives it too, but\n // we need it here to pass the participant pair.\n const clientPubKey =\n this.publicKeyBase58 ?? (await this.deriveOwnPublicKey(minaPrivateKey));\n this.publicKeyBase58 = clientPubKey;\n\n // The apex's Mina settlement pubkey (B62) is the channel counterparty\n // (participantB). It flows through as the negotiated recipient. When present,\n // sign over the on-chain participant-form channelHash so the claim can SETTLE\n // on-chain (the zkApp's claimFromChannel verifies sigA over\n // Poseidon([client.x, apex.x, 0])). Order matches the open:\n // participantA = client (payer), participantB = apex (peer).\n const apexPubKey =\n params.recipient && /^B62[a-zA-Z0-9]{40,60}$/.test(params.recipient)\n ? params.recipient\n : undefined;\n\n // Conserved counterparty balance for the signed commitment. The on-chain\n // PaymentChannel.claimFromChannel circuit verifies signatureA over\n // Poseidon([balanceA, balanceB, salt]) AND asserts balanceA + balanceB ==\n // depositTotal. The connector reconstructs balanceB = depositTotal − balanceA\n // from the public on-chain depositTotal (toon-protocol/connector#133), so the\n // CLIENT must sign over that same balanceB or signatureA fails verification\n // (\"participant A signature verification failed\") at proof generation. When\n // depositTotal is unknown (legacy/off-chain-only), fall back to balanceB = 0.\n // Prefer a caller-supplied depositTotal; otherwise self-resolve it from\n // chain (when this signer was configured with a graphqlUrl / reader) so the\n // claim conserves balances on a FUNDED zkApp. Falls back to balanceB = 0\n // (legacy, off-chain-store-only) when neither is available.\n const depositTotal =\n params.depositTotal ?? (await this.resolveDepositTotal(zkAppAddress));\n let balanceB = 0n;\n if (depositTotal != null && depositTotal > 0n) {\n if (params.transferredAmount > depositTotal) {\n throw new Error(\n `Mina claim balanceA (${params.transferredAmount}) exceeds on-chain ` +\n `depositTotal (${depositTotal}) — cannot conserve balances`\n );\n }\n balanceB = depositTotal - params.transferredAmount;\n }\n\n const built = await buildMinaPaymentChannelProof({\n zkAppAddress,\n minaPrivateKeyBase58: minaPrivateKey,\n signerPublicKey: clientPubKey,\n // Recipient-credit (unidirectional): party A carries the cumulative amount;\n // party B carries the funder's remaining balance (depositTotal − balanceA)\n // so the signed commitment conserves and the on-chain claimFromChannel\n // signatureA check passes. `signatureB` remains apex-co-signed downstream.\n balanceA: params.transferredAmount,\n balanceB,\n salt,\n nonce: BigInt(params.nonce),\n // Participant-form channelHash (on-chain-settleable) when the apex pubkey\n // is known; otherwise the legacy zkApp-x form (off-chain-store only).\n ...(apexPubKey\n ? { participantA: clientPubKey, participantB: apexPubKey }\n : {}),\n });\n this.publicKeyBase58 = built.signerPublicKey;\n\n return {\n channelId: zkAppAddress,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n // `signature` is unused on the Mina wire (the proof carries the Schnorr\n // signature); keep the base64 proof here too for symmetry / debugging.\n signature: built.proof,\n signerAddress: built.signerPublicKey,\n chainId: 0,\n tokenNetworkAddress: zkAppAddress,\n recipient: params.recipient,\n mina: {\n balanceCommitment: built.balanceCommitment,\n proof: built.proof,\n salt: built.salt,\n tokenId,\n },\n };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\n if (!proof.mina) {\n throw new Error(\n 'MinaSigner.buildClaimMessage requires a Mina-signed proof (missing `mina` fields)'\n );\n }\n const claim: MinaClaimMessage = {\n version: '1.0',\n blockchain: 'mina',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n zkAppAddress: proof.channelId,\n tokenId: proof.mina.tokenId,\n balanceCommitment: proof.mina.balanceCommitment,\n nonce: proof.nonce,\n proof: proof.mina.proof,\n salt: proof.mina.salt,\n transferredAmount: proof.transferredAmount.toString(),\n // Surface the signer's Mina pubkey top-level (it is also embedded in the\n // base64 `proof`). The connector's SettlementExecutor reads\n // `latestClaim.signerPublicKey` to resolve participant keys for the\n // on-chain claimFromChannel on an inbound/externally-opened channel;\n // without it the Mina SDK throws ACCOUNT_NOT_FOUND. `signerAddress`\n // carries the B62 base58 pubkey for Mina proofs (see MinaSigner.sign*).\n signerPublicKey: proof.signerAddress,\n network: MINA_CLAIM_NETWORK,\n };\n return claim;\n }\n}\n","/**\n * Mina payment-channel primitives — connector-parity.\n *\n * Pure, dependency-light helpers that reproduce the EXACT off-chain\n * balance-proof contract the connector's `MinaPaymentChannelSDK`\n * (`@toon-protocol/connector` `settlement/mina-payment-channel-sdk.ts`)\n * implements, so a client-issued Mina payment-channel claim is structurally\n * accepted by connector 3.9.0's `validateMinaClaim` PREPARE gate and (once the\n * connector's proof-encoding bug is fixed, see below) verified by its\n * `verifyMinaClaim` / `verifyBalanceProof` settlement path.\n *\n * ## The connector's payment-channel scheme (authoritative, from 3.9.0 dist)\n *\n * `MinaPaymentChannelSDK.signBalanceProof(channel, balA, balB, salt, nonce)`:\n *\n * commitment = Poseidon.hash([Field(balA), Field(balB), Field(salt)])\n * channelHashField = Poseidon.hash([PublicKey.fromBase58(zkAppAddress).x])\n * message = [commitment, Field(nonce), channelHashField]\n * signature = Signature.create(privKey, message) // Schnorr, Pallas\n * proof (string) = JSON.stringify({\n * commitment: commitment.toString(),\n * signature: { r, s }, // o1js JSON form\n * nonce: nonce.toString(),\n * })\n *\n * `MinaPaymentChannelSDK.verifyBalanceProof(channel, balanceCommitment, proof, nonce)`\n * re-derives `channelHashField` from `channel`, parses `proof` as the JSON above,\n * checks `proof.commitment === balanceCommitment` and `BigInt(proof.nonce) === nonce`,\n * then `Signature.fromJSON({r,s}).verify(signerPubKey, [commitment, Field(nonce),\n * channelHashField])`. The signer prefix is HARDCODED to `'devnet'` inside o1js\n * `Signature.create` / `.verify`, so the off-chain signature MUST be produced\n * with the Mina `'devnet'` network id (NOT `'mainnet'`).\n *\n * This is DISTINCT from the swap peer ↔ sender swap-claim wire contract\n * (`balanceProofFieldsMina` in `@toon-protocol/core` — a Schnorr signature over\n * `[minaHashToField(channelId), amount, nonce, minaHashToField(recipient)]`).\n * That format is unchanged; this module is the separate payment-channel path\n * (mirrors the Solana #105 precedent: a payment-channel-specific message\n * distinct from the swap-format hash).\n *\n * ## Why mina-signer (not o1js)\n *\n * Poseidon, the Pallas Schnorr signature, the base58 signature codec and the\n * Pallas `PublicKey` field decode are all shipped INSIDE `mina-signer` (the same\n * implementations o1js re-exports). We reach them via a file-URL deep import off\n * the resolved `mina-signer` package root, so the client reproduces the\n * connector's scheme byte-for-byte WITHOUT pulling the multi-hundred-MB o1js\n * WASM circuit runtime. `mina-signer` is an OPTIONAL dependency.\n *\n * ## ⚠️ Connector 3.9.0 proof-encoding bug (documented, NOT worked around here)\n *\n * `validateMinaClaim` requires `claim.proof` to match `/^[A-Za-z0-9+/]+=*$/`\n * (base64), but `verifyBalanceProof` (and the connector's OWN claim producer\n * `per-packet-claim-service`) treat `proof` as RAW JSON (`JSON.parse(proof)` with\n * no base64 decode — and the connector emits the raw `JSON.stringify(...)` above).\n * Raw JSON fails the base64 regex, so a raw-JSON proof is REJECTED at the PREPARE\n * gate (no FULFILL). To pass the PREPARE gate (the FULFILL-deciding path) the\n * client MUST base64-encode the proof JSON. The connector's settlement-side\n * `JSON.parse` then fails on the base64 string — but that is post-FULFILL and is\n * the connector's own inconsistency (its producer and consumer disagree with its\n * validator). For non-EVM dynamic hidden-service peers settlement is gated by\n * connector#88 (`No chain configured for peer`) regardless, so this is a\n * documented connector follow-up, not a client defect. See\n * {@link buildMinaPaymentChannelProof} `proofEncoding`.\n *\n * @module\n */\n\n/** Result of building a connector-parity Mina payment-channel proof. */\nexport interface MinaPaymentChannelProof {\n /** `Poseidon([balanceA, balanceB, salt]).toString()` — the claim's `balanceCommitment`. */\n balanceCommitment: string;\n /**\n * The claim's `proof` field: base64-encoded JSON\n * `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. Base64 is\n * REQUIRED to pass connector 3.9.0 `validateMinaClaim` (see module note).\n */\n proof: string;\n /** Decimal salt string — the claim's `salt` field. */\n salt: string;\n /** Base58 signer public key — echoed for convenience. */\n signerPublicKey: string;\n}\n\ninterface MinaSignerClientLike {\n signFields(fields: bigint[], privateKey: string): { signature: string };\n derivePublicKey(privateKey: string): string;\n}\ntype MinaSignerClientCtor = new (opts: {\n network: string;\n}) => MinaSignerClientLike;\n\ninterface PoseidonLike {\n hash(input: bigint[]): bigint;\n}\ninterface SignatureCodecLike {\n fromBase58(base58: string): { r: bigint; s: bigint };\n}\ninterface PublicKeyCodecLike {\n fromBase58(base58: string): { x: bigint; y: bigint };\n}\n\ninterface MinaCryptoBindings {\n Client: MinaSignerClientCtor;\n Poseidon: PoseidonLike;\n Signature: SignatureCodecLike;\n PublicKey: PublicKeyCodecLike;\n}\n\nlet cachedBindings: MinaCryptoBindings | null = null;\n\n/**\n * Resolve the `mina-signer` package and load both its public `Client` and the\n * internal Poseidon / Signature / PublicKey codecs via file-URL deep import.\n *\n * `mina-signer`'s `package.json` `exports` map does NOT expose its\n * `bindings/crypto/poseidon.js` or `mina-signer/src/{signature,curve-bigint}.js`\n * subpaths, so a bare-specifier deep import (`import('mina-signer/.../poseidon.js')`)\n * is blocked (`ERR_PACKAGE_PATH_NOT_EXPORTED`). Node only applies the exports\n * gate to bare specifiers, NOT to `file://` URLs — so we resolve the main entry\n * to a URL and navigate to the sibling internal files. These are the same\n * implementations o1js ships; reaching them here gives byte-for-byte parity with\n * the connector's o1js verify without the o1js WASM runtime.\n */\nexport async function loadMinaPaymentChannelBindings(): Promise<MinaCryptoBindings> {\n if (cachedBindings) return cachedBindings;\n\n const specifier = 'mina-signer';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const lib: any = await import(/* @vite-ignore */ specifier);\n const Client: MinaSignerClientCtor = 'default' in lib ? lib.default : lib;\n\n // Resolve the main entry to a file URL, then navigate to the internal modules.\n // mina-signer main: <root>/dist/node/mina-signer/mina-signer.js\n //\n // import.meta.resolve is unavailable under some module transforms (notably\n // vitest's SSR, which replaces import.meta and drops `.resolve` → throws\n // \"__vite_ssr_import_meta__.resolve is not a function\"). Fall back to Node's\n // createRequire().resolve and convert to a file URL. Both the ESM and CJS\n // entries live in dist/node/mina-signer/, so the relative navigation below is\n // identical either way.\n const resolveFn = (import.meta as { resolve?: (specifier: string) => string })\n .resolve;\n let mainUrl: string;\n if (typeof resolveFn === 'function') {\n mainUrl = resolveFn(specifier);\n } else {\n const { createRequire } = await import('node:module');\n const { pathToFileURL } = await import('node:url');\n mainUrl = pathToFileURL(\n createRequire(import.meta.url).resolve(specifier)\n ).href;\n }\n const minaSignerDir = new URL('./', mainUrl); // .../dist/node/mina-signer/\n const poseidonUrl = new URL('../bindings/crypto/poseidon.js', minaSignerDir)\n .href;\n const signatureUrl = new URL('./src/signature.js', minaSignerDir).href;\n const curveUrl = new URL('./src/curve-bigint.js', minaSignerDir).href;\n\n const [poseidonMod, signatureMod, curveMod] = await Promise.all([\n import(/* @vite-ignore */ poseidonUrl),\n import(/* @vite-ignore */ signatureUrl),\n import(/* @vite-ignore */ curveUrl),\n ]);\n\n cachedBindings = {\n Client,\n Poseidon: poseidonMod.Poseidon as PoseidonLike,\n Signature: signatureMod.Signature as SignatureCodecLike,\n PublicKey: curveMod.PublicKey as PublicKeyCodecLike,\n };\n return cachedBindings;\n}\n\n/** Reset the cached bindings (test hook). */\nexport function _resetMinaBindingsCache(): void {\n cachedBindings = null;\n}\n\n/**\n * Compute the connector's Mina balance commitment:\n * `Poseidon.hash([Field(balanceA), Field(balanceB), Field(salt)])`.\n */\nexport function minaBalanceCommitment(\n poseidon: PoseidonLike,\n balanceA: bigint,\n balanceB: bigint,\n salt: bigint\n): bigint {\n return poseidon.hash([balanceA, balanceB, salt]);\n}\n\n/**\n * Compute the connector's LEGACY channel-hash field, bound into the signed\n * message: `Poseidon.hash([PublicKey.fromBase58(zkAppAddress).x])`.\n *\n * This is the off-chain-only form. The connector's `verifyBalanceProof` accepts\n * a signature over EITHER this OR the participant form (see\n * {@link minaParticipantChannelHashField}), but the on-chain\n * `PaymentChannel.claimFromChannel` only accepts the PARTICIPANT form — so for a\n * claim that must SETTLE on-chain, sign the participant form instead.\n */\nexport function minaChannelHashField(\n poseidon: PoseidonLike,\n publicKeyCodec: PublicKeyCodecLike,\n zkAppAddress: string\n): bigint {\n const zkAppPubKey = publicKeyCodec.fromBase58(zkAppAddress);\n return poseidon.hash([zkAppPubKey.x]);\n}\n\n/**\n * Compute the ON-CHAIN channel-hash field the zkApp stores and verifies:\n * `Poseidon.hash([participantA.x, participantB.x, channelNonce])`.\n *\n * This MUST byte-for-byte reproduce what `PaymentChannel.initializeChannel`\n * wrote (`Poseidon([participantA.x, participantB.x, nonce])`, see\n * `packages/mina-zkapp/src/PaymentChannel.ts`) so that:\n * 1. the on-chain `claimFromChannel` signature check\n * (`signatureA.verify(participantA, [commitment, nonce, storedChannelHash])`)\n * passes, and\n * 2. the connector's off-chain `verifyBalanceProof` accepts it via its\n * on-chain-channelHash message branch.\n *\n * Participant ORDER must match how the channel was opened. The client opens with\n * `participantA = client (payer)`, `participantB = apex (peer)`\n * (`OnChainChannelClient.openMinaChannel` → `openMinaChannelOnChain`), so the\n * client signs `Poseidon([client.x, apex.x, 0])`. The connector tries both\n * orderings when reconstructing, so it resolves the matching one regardless.\n */\nexport function minaParticipantChannelHashField(\n poseidon: PoseidonLike,\n publicKeyCodec: PublicKeyCodecLike,\n participantA_B62: string,\n participantB_B62: string,\n channelNonce: bigint\n): bigint {\n const a = publicKeyCodec.fromBase58(participantA_B62);\n const b = publicKeyCodec.fromBase58(participantB_B62);\n return poseidon.hash([a.x, b.x, channelNonce]);\n}\n\n/**\n * Build a connector-parity Mina payment-channel balance proof.\n *\n * Reproduces `MinaPaymentChannelSDK.signBalanceProof` exactly: computes the\n * Poseidon balance commitment, the Poseidon channel-hash field, signs the\n * `[commitment, Field(nonce), channelHashField]` Pallas Schnorr message with the\n * `'devnet'` network id (matching o1js's hardcoded prefix), decodes the base58\n * signature to `{ r, s }` (o1js JSON form), and serializes\n * `{ commitment, signature: { r, s }, nonce, signerPublicKey }`.\n *\n * @param params.zkAppAddress Deployed payment-channel zkApp B62 address — the\n * claim's `zkAppAddress` AND the channel-hash preimage. MUST be the SAME\n * address the apex's Mina provider resolves on-chain.\n * @param params.minaPrivateKeyBase58 Signer's Mina `EK…` base58 private key.\n * @param params.balanceA Cumulative amount credited to the recipient (apex).\n * @param params.balanceB Counterparty balance (0 for the unidirectional\n * recipient-credit direction; `balanceB`/`signatureB` are OPTIONAL at\n * connector validation, so single-party suffices).\n * @param params.salt Commitment salt (bigint).\n * @param params.nonce Monotonic claim nonce.\n * @param params.proofEncoding `'base64'` (default) base64-encodes the proof JSON\n * so it passes connector 3.9.0 `validateMinaClaim`'s base64 regex (REQUIRED for\n * FULFILL). `'json'` emits the raw JSON the connector's `verifyBalanceProof`\n * actually parses (rejected at the PREPARE gate today; provided for forward\n * compatibility / tests once the connector's regex is fixed). See module note.\n */\nexport async function buildMinaPaymentChannelProof(params: {\n zkAppAddress: string;\n minaPrivateKeyBase58: string;\n signerPublicKey?: string;\n balanceA: bigint;\n balanceB: bigint;\n salt: bigint;\n nonce: bigint;\n proofEncoding?: 'base64' | 'json';\n /**\n * Participant pubkeys (B62) of the on-chain channel. When BOTH are supplied,\n * the proof is signed over the ON-CHAIN participant-form channelHash\n * (`Poseidon([participantA.x, participantB.x, channelNonce])`) instead of the\n * legacy zkApp-x form, so the resulting claim can settle on-chain via the\n * zkApp's `claimFromChannel` (which only verifies the participant form). The\n * order MUST match how the channel was opened (participantA = client/payer,\n * participantB = apex/peer). The connector accepts EITHER form off-chain, so\n * this is strictly an enabler for the on-chain settle path.\n */\n participantA?: string;\n participantB?: string;\n /** Channel nonce baked into the on-chain channelHash (default 0). */\n channelNonce?: bigint;\n}): Promise<MinaPaymentChannelProof> {\n const { Client, Poseidon, Signature, PublicKey } =\n await loadMinaPaymentChannelBindings();\n\n // o1js `Signature.create`/`.verify` hardcode the 'devnet' prefix; the off-chain\n // signature must be produced with the matching mina-signer network id.\n const client = new Client({ network: 'devnet' });\n const signerPublicKey =\n params.signerPublicKey ??\n client.derivePublicKey(params.minaPrivateKeyBase58);\n\n const commitment = minaBalanceCommitment(\n Poseidon,\n params.balanceA,\n params.balanceB,\n params.salt\n );\n // Sign over the on-chain participant-form channelHash when both participants\n // are known (enables on-chain settle); otherwise fall back to the legacy\n // zkApp-x form (off-chain-store-only). Both are accepted by the connector's\n // verifyBalanceProof.\n const channelHashField =\n params.participantA && params.participantB\n ? minaParticipantChannelHashField(\n Poseidon,\n PublicKey,\n params.participantA,\n params.participantB,\n params.channelNonce ?? 0n\n )\n : minaChannelHashField(Poseidon, PublicKey, params.zkAppAddress);\n\n const message = [commitment, params.nonce, channelHashField];\n const signed = client.signFields(message, params.minaPrivateKeyBase58);\n // mina-signer `signFields` emits a base58 signature string; the connector\n // expects the o1js JSON `{ r, s }` decimal form, reachable by decoding the\n // base58 sig with the shared Pallas codec.\n const { r, s } = Signature.fromBase58(signed.signature);\n\n const proofObject = {\n commitment: commitment.toString(),\n signature: { r: r.toString(), s: s.toString() },\n nonce: params.nonce.toString(),\n signerPublicKey,\n };\n const proofJson = JSON.stringify(proofObject);\n const encoding = params.proofEncoding ?? 'base64';\n const proof =\n encoding === 'base64'\n ? Buffer.from(proofJson, 'utf8').toString('base64')\n : proofJson;\n\n return {\n balanceCommitment: commitment.toString(),\n proof,\n salt: params.salt.toString(),\n signerPublicKey,\n };\n}\n","/**\n * Read a Mina payment-channel zkApp's on-chain `depositTotal` via a plain\n * GraphQL query (no o1js / WASM). Used by {@link MinaSigner} to bind the\n * conserved `balanceB = depositTotal − balanceA` commitment that a FUNDED zkApp\n * requires (connector#133); without it the connector's `claimFromChannel`\n * verification rejects the claim with `F06 - Invalid zk-SNARK proof on claim`.\n *\n * The `PaymentChannel` zkApp app-state field order is\n * `[channelHash, balanceCommitment, nonceField, channelState, depositTotal, …]`\n * (see `mina-channel-open.ts`), so `depositTotal` is `zkappState[4]`.\n */\n\n/** GraphQL response shape (only the fields we read). */\ninterface AccountStateResponse {\n data?: { account?: { zkappState?: string[] | null } | null } | null;\n errors?: { message: string }[];\n}\n\n/**\n * `PaymentChannel` zkApp app-state field order (see `mina-channel-open.ts` and\n * the zkApp's `@state` declarations in `@toon-protocol/mina-zkapp`):\n * [0] channelHash, [1] balanceCommitment, [2] nonceField, [3] channelState,\n * [4] depositTotal, [5] closedAtSlot, [6] settlementTimeout, [7] tokenId_.\n */\nconst CHANNEL_STATE_INDEX = {\n channelHash: 0,\n balanceCommitment: 1,\n nonceField: 2,\n channelState: 3,\n depositTotal: 4,\n} as const;\n\nconst DEPOSIT_TOTAL_STATE_INDEX = CHANNEL_STATE_INDEX.depositTotal;\n\n/**\n * Query `account(publicKey).zkappState` and return the channel's `depositTotal`\n * (base units). Throws when the account/state is unavailable so callers can fall\n * back to the legacy `balanceB = 0` behavior.\n *\n * @param fetchImpl - injectable for tests; defaults to global `fetch`.\n */\nexport async function readMinaDepositTotal(\n graphqlUrl: string,\n zkAppAddress: string,\n fetchImpl: typeof fetch = fetch\n): Promise<bigint> {\n const query = 'query($pk:String!){account(publicKey:$pk){zkappState}}';\n const res = await fetchImpl(graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables: { pk: zkAppAddress } }),\n });\n if (!res.ok) {\n throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);\n }\n const json = (await res.json()) as AccountStateResponse;\n if (json.errors && json.errors.length > 0) {\n throw new Error(\n `Mina GraphQL error: ${json.errors[0]?.message ?? 'unknown'}`\n );\n }\n const state = json.data?.account?.zkappState;\n if (!state || state.length <= DEPOSIT_TOTAL_STATE_INDEX) {\n throw new Error(\n `Mina zkApp ${zkAppAddress} has no readable zkappState (account not found or not a zkApp)`\n );\n }\n return BigInt(state[DEPOSIT_TOTAL_STATE_INDEX] as string);\n}\n\n/** Channel-lifecycle enum written to `channelState` (matches the zkApp). */\nexport const MINA_CHANNEL_STATE = {\n UNINITIALIZED: 0,\n OPEN: 1,\n CLOSING: 2,\n SETTLED: 3,\n} as const;\n\n/**\n * On-chain `PaymentChannel` state the receive-side settler needs to assemble a\n * co-signed `claimFromChannel` — all read via plain GraphQL (NO o1js / WASM), so\n * the read path stays lightweight and unit-testable. Field semantics match\n * {@link MinaChannelState} in the connector's Mina SDK.\n */\nexport interface MinaOnChainChannelState {\n /** `Poseidon([participantA.x, participantB.x, channelNonce])`, decimal Field. */\n channelHash: string;\n /** Current `Poseidon([balanceA, balanceB, salt])`, decimal Field. */\n balanceCommitment: string;\n /** Highest claimed nonce recorded on-chain. */\n nonceField: bigint;\n /** {@link MINA_CHANNEL_STATE} enum value. */\n channelState: number;\n /** Total escrowed deposit (base units). */\n depositTotal: bigint;\n}\n\n/**\n * Read the channel's on-chain `PaymentChannel` state via GraphQL.\n *\n * Reuses the same `account(publicKey).zkappState` query as\n * {@link readMinaDepositTotal} and decodes the fields the co-signed\n * `claimFromChannel` assembly binds: `channelHash` (participant identity),\n * `depositTotal` (balance conservation), `nonceField` (monotonicity gate), and\n * `channelState` (must be OPEN). No o1js is loaded — the values are raw Field\n * decimal strings straight off the node.\n *\n * @param fetchImpl - injectable for tests; defaults to global `fetch`.\n */\nexport async function readMinaChannelState(\n graphqlUrl: string,\n zkAppAddress: string,\n fetchImpl: typeof fetch = fetch\n): Promise<MinaOnChainChannelState> {\n const query = 'query($pk:String!){account(publicKey:$pk){zkappState}}';\n const res = await fetchImpl(graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables: { pk: zkAppAddress } }),\n });\n if (!res.ok) {\n throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);\n }\n const json = (await res.json()) as AccountStateResponse;\n if (json.errors && json.errors.length > 0) {\n throw new Error(\n `Mina GraphQL error: ${json.errors[0]?.message ?? 'unknown'}`\n );\n }\n const state = json.data?.account?.zkappState;\n if (!state || state.length <= DEPOSIT_TOTAL_STATE_INDEX) {\n throw new Error(\n `Mina zkApp ${zkAppAddress} has no readable zkappState (account not found or not a zkApp)`\n );\n }\n return {\n channelHash: String(state[CHANNEL_STATE_INDEX.channelHash]),\n balanceCommitment: String(state[CHANNEL_STATE_INDEX.balanceCommitment]),\n nonceField: BigInt(state[CHANNEL_STATE_INDEX.nonceField] as string),\n channelState: Number(state[CHANNEL_STATE_INDEX.channelState]),\n depositTotal: BigInt(state[CHANNEL_STATE_INDEX.depositTotal] as string),\n };\n}\n","import { EvmSigner } from '../signing/evm-signer.js';\nimport type { ChainSigner, ChainMetadata } from '../signing/types.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type { ChannelStore } from './ChannelStore.js';\nimport type { ConnectorChannelClient } from '@toon-protocol/core';\n\ninterface ChannelTracking {\n nonce: number;\n cumulativeAmount: bigint;\n chainType: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n /**\n * Counterparty settlement address on this channel's chain. Required to sign\n * Solana/Mina balance proofs (folded into the canonical message); unused for\n * the EVM EIP-712 path.\n */\n recipient?: string;\n /**\n * On-chain channel `depositTotal` (base units), captured at channel-open time\n * (#220). Threaded into the Mina signer so it binds `balanceB = depositTotal −\n * balanceA` (toon-protocol/connector#133); the Solana signer ignores it and\n * the EVM path never reads it. When unset (e.g. a channel RESUMED via\n * `trackChannel` rather than opened, or an idempotent re-open that didn't\n * surface it), the Mina signer self-resolves it from chain via its GraphQL URL\n * (#223).\n */\n depositTotal?: bigint;\n /** Withdraw flow (unix SECONDS): set when close is initiated / becomes settleable / settled. */\n closedAt?: bigint;\n settleableAt?: bigint;\n settledAt?: bigint;\n}\n\nexport interface ChannelManagerConfig {\n initialDeposit?: string;\n settlementTimeout?: number;\n}\n\nexport interface PeerNegotiation {\n chain: string;\n chainType: string;\n chainId: number | string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n initialDeposit?: string;\n settlementTimeout?: number;\n}\n\n/**\n * Local nonce tracking, multi-chain signing, and lazy channel opening.\n *\n * Supports multiple ChainSigner implementations (EVM, Solana, Mina).\n * The ensureChannel() method provides idempotent lazy channel opening.\n */\nexport class ChannelManager {\n private readonly channels = new Map<string, ChannelTracking>();\n private readonly chainSigners = new Map<string, ChainSigner>();\n private readonly peerChannels = new Map<string, string>();\n private readonly pendingOpens = new Map<string, Promise<string>>();\n private readonly store?: ChannelStore;\n private readonly defaultInitialDeposit: string;\n private readonly defaultSettlementTimeout: number;\n private channelClient?: ConnectorChannelClient;\n\n // Legacy: keep EvmSigner reference for backwards compatibility\n private readonly evmSigner?: EvmSigner;\n\n constructor(\n evmSigner?: EvmSigner,\n store?: ChannelStore,\n config?: ChannelManagerConfig\n ) {\n this.evmSigner = evmSigner;\n this.store = store;\n this.defaultInitialDeposit = config?.initialDeposit ?? '100000';\n this.defaultSettlementTimeout = config?.settlementTimeout ?? 86400;\n }\n\n /**\n * Register a chain-specific signer.\n */\n registerChainSigner(chainType: string, signer: ChainSigner): void {\n this.chainSigners.set(chainType, signer);\n }\n\n /**\n * Set the on-chain channel client for lazy channel opening.\n */\n setChannelClient(client: ConnectorChannelClient): void {\n this.channelClient = client;\n }\n\n /**\n * Get the signer for a tracked channel's chain type.\n * For EVM, returns an adapter wrapping the EvmSigner.\n */\n getSignerForChannel(channelId: string): ChainSigner {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n\n // Check non-EVM signers first\n const signer = this.chainSigners.get(tracking.chainType);\n if (signer) return signer;\n\n // EVM: wrap EvmSigner as ChainSigner adapter\n if (tracking.chainType === 'evm' && this.evmSigner) {\n const evmSigner = this.evmSigner;\n return {\n chainType: 'evm' as const,\n signerIdentifier: evmSigner.address,\n async signBalanceProof(params) {\n if (params.metadata.chainType !== 'evm')\n throw new Error('Expected EVM metadata');\n return evmSigner.signBalanceProof({\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n chainId: params.metadata.chainId,\n tokenNetworkAddress: params.metadata.tokenNetworkAddress,\n tokenAddress: params.metadata.tokenAddress,\n });\n },\n buildClaimMessage(proof, senderId) {\n return EvmSigner.buildClaimMessage(proof, senderId);\n },\n };\n }\n\n throw new Error(\n `No signer registered for chain type: ${tracking.chainType}`\n );\n }\n\n /**\n * Lazily open a channel for a peer. Idempotent — returns existing channel\n * if already open. Deduplicates concurrent opens for the same peer.\n */\n async ensureChannel(\n peerId: string,\n negotiation: PeerNegotiation\n ): Promise<string> {\n // Return existing channel\n const existing = this.peerChannels.get(peerId);\n if (existing) return existing;\n\n // Deduplicate concurrent opens\n const pending = this.pendingOpens.get(peerId);\n if (pending) return pending;\n\n if (!this.channelClient) {\n throw new Error(\n 'No channel client configured — cannot open payment channel'\n );\n }\n\n const openPromise = (async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- channelClient checked in constructor\n const result = await this.channelClient!.openChannel({\n peerId,\n chain: negotiation.chain,\n token: negotiation.tokenAddress,\n tokenNetwork: negotiation.tokenNetwork,\n peerAddress: negotiation.settlementAddress,\n initialDeposit:\n negotiation.initialDeposit ?? this.defaultInitialDeposit,\n settlementTimeout:\n negotiation.settlementTimeout ?? this.defaultSettlementTimeout,\n });\n\n this.trackChannel(result.channelId, {\n chainType: negotiation.chainType,\n chainId:\n typeof negotiation.chainId === 'number' ? negotiation.chainId : 0,\n tokenNetworkAddress: negotiation.tokenNetwork ?? '',\n tokenAddress: negotiation.tokenAddress,\n recipient: negotiation.settlementAddress,\n // On-chain depositTotal (Mina only) — needed so the Mina signer binds\n // balanceB = depositTotal − balanceA (connector#133).\n depositTotal: result.depositTotal,\n });\n this.peerChannels.set(peerId, result.channelId);\n return result.channelId;\n } finally {\n this.pendingOpens.delete(peerId);\n }\n })();\n\n this.pendingOpens.set(peerId, openPromise);\n return openPromise;\n }\n\n /**\n * Get channel ID for a peer (if any).\n */\n getChannelForPeer(peerId: string): string | undefined {\n return this.peerChannels.get(peerId);\n }\n\n /**\n * Start tracking a channel.\n * Called after bootstrap returns a channelId.\n *\n * @param channelId - Payment channel identifier\n * @param chainContext - Chain context for signing (chainType + chainId + tokenNetworkAddress)\n * @param initialNonce - Starting nonce (default: 0)\n * @param initialAmount - Starting cumulative amount (default: 0n)\n */\n trackChannel(\n channelId: string,\n chainContext?: {\n chainType?: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n recipient?: string;\n depositTotal?: bigint;\n },\n initialNonce = 0,\n initialAmount = 0n\n ): void {\n const cId = chainContext?.chainId ?? 31337;\n const tnAddr =\n chainContext?.tokenNetworkAddress ??\n '0x0000000000000000000000000000000000000000';\n\n // If store has persisted state for this channel, resume from it\n if (this.store) {\n const persisted = this.store.load(channelId);\n if (persisted) {\n this.channels.set(channelId, {\n nonce: persisted.nonce,\n cumulativeAmount: persisted.cumulativeAmount,\n chainType: chainContext?.chainType ?? 'evm',\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n recipient: chainContext?.recipient,\n depositTotal: chainContext?.depositTotal,\n // Resume the withdraw-flow timers so a daemon restart mid-grace\n // doesn't strand funds (the gate can't be evaluated without them).\n ...(persisted.closedAt !== undefined ? { closedAt: persisted.closedAt } : {}),\n ...(persisted.settleableAt !== undefined ? { settleableAt: persisted.settleableAt } : {}),\n ...(persisted.settledAt !== undefined ? { settledAt: persisted.settledAt } : {}),\n });\n return;\n }\n }\n\n this.channels.set(channelId, {\n nonce: initialNonce,\n cumulativeAmount: initialAmount,\n chainType: chainContext?.chainType ?? 'evm',\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n recipient: chainContext?.recipient,\n depositTotal: chainContext?.depositTotal,\n });\n }\n\n /**\n * Signs a balance proof for the given channel.\n * Auto-increments nonce and adds to cumulative amount.\n * Routes to the correct ChainSigner based on the channel's chain type.\n *\n * @param channelId - Payment channel identifier\n * @param additionalAmount - Amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws Error if channel is not being tracked\n */\n async signBalanceProof(\n channelId: string,\n additionalAmount: bigint\n ): Promise<SignedBalanceProof> {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(\n `Channel \"${channelId}\" is not being tracked. Call trackChannel() first.`\n );\n }\n\n tracking.nonce += 1;\n tracking.cumulativeAmount += additionalAmount;\n\n // Persist updated state (preserving any withdraw-flow timers).\n this.persist(channelId);\n\n // Route to appropriate signer for non-EVM chains\n const signer = this.chainSigners.get(tracking.chainType);\n if (signer && tracking.chainType !== 'evm') {\n if (!tracking.recipient) {\n throw new Error(\n `Channel \"${channelId}\" (${tracking.chainType}) has no recipient settlement address; ` +\n 'cannot sign a Solana/Mina balance proof. Ensure the peer negotiation supplied a settlementAddress.'\n );\n }\n const metadata = this.buildMetadata(tracking);\n return signer.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n recipient: tracking.recipient,\n metadata,\n // On-chain depositTotal captured at open time (#220) — the Mina signer\n // binds balanceB = depositTotal − balanceA (connector#133); the Solana\n // signer ignores it. When undefined (resume / idempotent re-open) the\n // Mina signer self-resolves it from chain (#223).\n depositTotal: tracking.depositTotal,\n });\n }\n\n // EVM path (backwards compatible — uses EvmSigner directly)\n if (!this.evmSigner) {\n throw new Error('No EVM signer configured for EVM channel signing.');\n }\n return this.evmSigner.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n chainId: tracking.chainId,\n tokenNetworkAddress: tracking.tokenNetworkAddress,\n tokenAddress: tracking.tokenAddress,\n });\n }\n\n private buildMetadata(tracking: ChannelTracking): ChainMetadata {\n switch (tracking.chainType) {\n case 'solana':\n return { chainType: 'solana', programId: tracking.tokenNetworkAddress };\n case 'mina':\n return {\n chainType: 'mina',\n zkAppAddress: tracking.tokenNetworkAddress,\n };\n default:\n return {\n chainType: 'evm',\n chainId: tracking.chainId,\n tokenNetworkAddress: tracking.tokenNetworkAddress,\n tokenAddress: tracking.tokenAddress,\n };\n }\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getNonce(channelId: string): number {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.nonce;\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getCumulativeAmount(channelId: string): bigint {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.cumulativeAmount;\n }\n\n /**\n * Gets the on-chain deposit total (collateral locked at open / via deposits)\n * for a tracked channel, or `0n` when none was captured. The available\n * (spendable) balance is `depositTotal - cumulativeAmount`.\n */\n getDepositTotal(channelId: string): bigint {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.depositTotal ?? 0n;\n }\n\n /**\n * Update the tracked on-chain deposit total after a successful deposit, so the\n * available balance (`depositTotal - cumulativeAmount`) reflects the new\n * collateral on the next read.\n */\n setDepositTotal(channelId: string, total: bigint): void {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n tracking.depositTotal = total;\n }\n\n /** Persist a channel's full nonce/amount + withdraw-timer state to the store. */\n private persist(channelId: string): void {\n if (!this.store) return;\n const t = this.channels.get(channelId);\n if (!t) return;\n this.store.save(channelId, {\n nonce: t.nonce,\n cumulativeAmount: t.cumulativeAmount,\n ...(t.closedAt !== undefined ? { closedAt: t.closedAt } : {}),\n ...(t.settleableAt !== undefined ? { settleableAt: t.settleableAt } : {}),\n ...(t.settledAt !== undefined ? { settledAt: t.settledAt } : {}),\n });\n }\n\n /**\n * Record that a channel was closed (withdraw flow): stores `closedAt` +\n * `settleableAt` (unix SECONDS) so the grace timer survives a daemon restart.\n */\n setChannelClosed(channelId: string, closedAt: bigint, settleableAt: bigint): void {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n tracking.closedAt = closedAt;\n tracking.settleableAt = settleableAt;\n this.persist(channelId);\n }\n\n /** Record that a channel was settled (collateral released). */\n setChannelSettled(channelId: string, settledAt: bigint): void {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n tracking.settledAt = settledAt;\n this.persist(channelId);\n }\n\n /** The `settleableAt` timestamp (unix seconds) for a closed channel, if set. */\n getSettleableAt(channelId: string): bigint | undefined {\n return this.channels.get(channelId)?.settleableAt;\n }\n\n /**\n * Where a channel sits in the withdraw journey, from the tracked timers:\n * `open` (never closed) → `closing` (closed, grace not elapsed) →\n * `settleable` (grace elapsed) → `settled`. `nowSec` is injectable for tests.\n */\n getChannelCloseState(\n channelId: string,\n nowSec = BigInt(Math.floor(Date.now() / 1000))\n ): 'open' | 'closing' | 'settleable' | 'settled' {\n const t = this.channels.get(channelId);\n if (!t || t.closedAt === undefined) return 'open';\n if (t.settledAt !== undefined) return 'settled';\n if (t.settleableAt !== undefined && nowSec >= t.settleableAt) return 'settleable';\n return 'closing';\n }\n\n /**\n * Gets all tracked channel IDs.\n */\n getTrackedChannels(): string[] {\n return Array.from(this.channels.keys());\n }\n\n /**\n * Returns true if the channel is being tracked.\n */\n isTracking(channelId: string): boolean {\n return this.channels.has(channelId);\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\n\nexport interface ChannelStoreEntry {\n nonce: number;\n cumulativeAmount: bigint;\n /** Unix SECONDS when close was initiated (withdraw flow). */\n closedAt?: bigint;\n /** Unix SECONDS the channel becomes settleable (= closedAt + settlementTimeout). */\n settleableAt?: bigint;\n /** Unix SECONDS the channel was settled (collateral released). */\n settledAt?: bigint;\n}\n\n/**\n * Persistence interface for payment channel nonce/amount state.\n */\nexport interface ChannelStore {\n save(channelId: string, tracking: ChannelStoreEntry): void;\n load(channelId: string): ChannelStoreEntry | undefined;\n list(): string[];\n delete(channelId: string): void;\n}\n\ninterface JsonEntry {\n nonce: number;\n /** Stored as string to preserve bigint precision */\n cumulativeAmount: string;\n /** Withdraw-flow timers, string-encoded SECONDS (bigint precision). */\n closedAt?: string;\n settleableAt?: string;\n settledAt?: string;\n}\n\n/**\n * JSON file-backed ChannelStore.\n * Uses synchronous I/O to match ChannelManager's sync API surface.\n */\nexport class JsonFileChannelStore implements ChannelStore {\n private readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n save(channelId: string, tracking: ChannelStoreEntry): void {\n const data = this.readFile();\n data[channelId] = {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount.toString(),\n ...(tracking.closedAt !== undefined ? { closedAt: tracking.closedAt.toString() } : {}),\n ...(tracking.settleableAt !== undefined ? { settleableAt: tracking.settleableAt.toString() } : {}),\n ...(tracking.settledAt !== undefined ? { settledAt: tracking.settledAt.toString() } : {}),\n };\n this.writeFile(data);\n }\n\n load(channelId: string): ChannelStoreEntry | undefined {\n const data = this.readFile();\n const entry = data[channelId];\n if (!entry) return undefined;\n return {\n nonce: entry.nonce,\n cumulativeAmount: BigInt(entry.cumulativeAmount),\n ...(entry.closedAt !== undefined ? { closedAt: BigInt(entry.closedAt) } : {}),\n ...(entry.settleableAt !== undefined ? { settleableAt: BigInt(entry.settleableAt) } : {}),\n ...(entry.settledAt !== undefined ? { settledAt: BigInt(entry.settledAt) } : {}),\n };\n }\n\n list(): string[] {\n return Object.keys(this.readFile());\n }\n\n delete(channelId: string): void {\n const data = this.readFile();\n const { [channelId]: _, ...rest } = data;\n this.writeFile(rest);\n }\n\n private readFile(): Record<string, JsonEntry> {\n if (!existsSync(this.filePath)) {\n return {};\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as Record<string, JsonEntry>;\n }\n\n private writeFile(data: Record<string, JsonEntry>): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n}\n","/**\n * Read-only on-chain wallet balances.\n *\n * Reports the settlement-token balance of the client's OWN wallet on each\n * configured chain — a free read, no signing, no fund movement. Each per-chain\n * reader is independent and injectable (viem for EVM; raw JSON-RPC / GraphQL\n * `fetch` for Solana / Mina, mirroring the existing channel modules) so they\n * unit-test without a live chain. The caller ({@link ../ToonClient}) treats each\n * chain best-effort: a missing config or a failed read drops that chain rather\n * than failing the whole wallet view.\n */\nimport { createPublicClient, http, defineChain } from 'viem';\nimport { sha256 } from '@noble/hashes/sha2.js';\n\n/** One on-chain wallet token balance. `amount` is base-unit integer, decimal. */\nexport interface WalletBalance {\n chain: 'evm' | 'solana' | 'mina';\n address: string;\n amount: string;\n /** Token symbol, when resolved (e.g. `'USDC'`, `'MINA'`). */\n asset?: string;\n /** Token decimals, when resolved. */\n assetScale?: number;\n}\n\n/**\n * One asset amount within a chain's wallet view — the native coin or one token.\n * `amount` is a base-unit integer, decimal string.\n */\nexport interface WalletTokenAmount {\n /** Asset symbol (e.g. `'ETH'`, `'SOL'`, `'MINA'`, `'USDC'`), when known. */\n symbol?: string;\n /** Base-unit integer, decimal string. */\n amount: string;\n /** Decimals for formatting (ETH 18, SOL 9, MINA 9, USDC 6). */\n decimals?: number;\n /** Token contract / SPL mint address. Absent for the native coin. */\n address?: string;\n}\n\n/**\n * The full wallet view for ONE chain the identity is configured for: the native\n * coin plus every configured token, keyed by the chain's full key (e.g.\n * `'evm:31337'`). `unreadable` marks a chain whose RPC could not be reached at\n * all — the caller renders a per-chain notice rather than crashing.\n */\nexport interface WalletChainBalances {\n chain: 'evm' | 'solana' | 'mina';\n /** Full chain key, e.g. `'evm:31337'`, `'solana'`, `'mina'`. */\n chainKey: string;\n address: string;\n /** Native-coin balance, when readable. */\n native?: WalletTokenAmount;\n /** Configured token balances (e.g. USDC), each best-effort. */\n tokens: WalletTokenAmount[];\n /** True when nothing on this chain could be read (RPC unreachable). */\n unreadable?: boolean;\n /** First read error, when any read failed (for diagnostics). */\n error?: string;\n}\n\n/** Minimal ERC-20 read ABI (balance + metadata). */\nconst ERC20_READ_ABI = [\n { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },\n { name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] },\n { name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },\n] as const;\n\n/** Extract the numeric chainId from an `evm:{network}:{chainId}` / `evm:{chainId}` key. */\nexport function parseEvmChainId(chainKey: string): number {\n const parts = chainKey.split(':');\n const idStr = parts.length >= 3 ? parts[2] : parts[1];\n const id = Number.parseInt(idStr ?? '', 10);\n if (!Number.isFinite(id)) throw new Error(`Invalid EVM chain key \"${chainKey}\".`);\n return id;\n}\n\n/** Read an ERC-20 token balance (balance + decimals + symbol) for `owner`. */\nexport async function readEvmTokenBalance(opts: {\n rpcUrl: string;\n chainKey: string;\n tokenAddress: string;\n owner: string;\n}): Promise<WalletBalance> {\n const chainId = parseEvmChainId(opts.chainKey);\n const client = createPublicClient({\n transport: http(opts.rpcUrl),\n chain: defineChain({\n id: chainId,\n name: opts.chainKey,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [opts.rpcUrl] } },\n }),\n });\n const token = opts.tokenAddress as `0x${string}`;\n const owner = opts.owner as `0x${string}`;\n const [amount, decimals, symbol] = await Promise.all([\n client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: 'balanceOf', args: [owner] }),\n client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: 'decimals' }).catch(() => undefined),\n client.readContract({ address: token, abi: ERC20_READ_ABI, functionName: 'symbol' }).catch(() => undefined),\n ]);\n const out: WalletBalance = { chain: 'evm', address: opts.owner, amount: (amount as bigint).toString() };\n if (typeof symbol === 'string' && symbol) out.asset = symbol;\n if (decimals !== undefined) out.assetScale = Number(decimals);\n return out;\n}\n\n/** Read the native ETH balance (wei) for `owner` via `eth_getBalance`. */\nexport async function readEvmNativeBalance(opts: {\n rpcUrl: string;\n chainKey: string;\n owner: string;\n}): Promise<WalletTokenAmount> {\n const chainId = parseEvmChainId(opts.chainKey);\n const client = createPublicClient({\n transport: http(opts.rpcUrl),\n chain: defineChain({\n id: chainId,\n name: opts.chainKey,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [opts.rpcUrl] } },\n }),\n });\n const wei = await client.getBalance({ address: opts.owner as `0x${string}` });\n return { symbol: 'ETH', amount: wei.toString(), decimals: 18 };\n}\n\n/** Read the native SOL balance (lamports) for `owner` via the `getBalance` RPC. */\nexport async function readSolanaNativeBalance(opts: {\n rpcUrl: string;\n owner: string;\n fetchImpl?: typeof fetch;\n}): Promise<WalletTokenAmount> {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const res = await fetchImpl(opts.rpcUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getBalance',\n params: [opts.owner, { commitment: 'confirmed' }],\n }),\n });\n if (!res.ok) throw new Error(`Solana RPC request failed: HTTP ${res.status}`);\n const json = (await res.json()) as {\n result?: { value?: number | string };\n error?: { message?: string };\n };\n if (json.error) throw new Error(`Solana RPC error: ${json.error.message ?? 'unknown'}`);\n const lamports = BigInt(json.result?.value ?? 0);\n return { symbol: 'SOL', amount: lamports.toString(), decimals: 9 };\n}\n\n/** Read the SPL-token balance for `owner`'s token account(s) of `mint`. */\nexport async function readSolanaTokenBalance(opts: {\n rpcUrl: string;\n mint: string;\n owner: string;\n fetchImpl?: typeof fetch;\n}): Promise<WalletBalance> {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const res = await fetchImpl(opts.rpcUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [opts.owner, { mint: opts.mint }, { encoding: 'jsonParsed', commitment: 'confirmed' }],\n }),\n });\n if (!res.ok) throw new Error(`Solana RPC request failed: HTTP ${res.status}`);\n const json = (await res.json()) as {\n result?: { value?: { account?: { data?: { parsed?: { info?: { tokenAmount?: { amount?: string; decimals?: number } } } } } }[] };\n error?: { message?: string };\n };\n if (json.error) throw new Error(`Solana RPC error: ${json.error.message ?? 'unknown'}`);\n let amount = 0n;\n let decimals: number | undefined;\n for (const acc of json.result?.value ?? []) {\n const ta = acc.account?.data?.parsed?.info?.tokenAmount;\n if (ta?.amount) amount += BigInt(ta.amount);\n if (ta?.decimals !== undefined) decimals = ta.decimals;\n }\n const out: WalletBalance = { chain: 'solana', address: opts.owner, amount: amount.toString() };\n if (decimals !== undefined) out.assetScale = decimals;\n return out;\n}\n\n/** Read the native MINA balance (nanomina) for `owner` via the Mina GraphQL API. */\nexport async function readMinaBalance(opts: {\n graphqlUrl: string;\n owner: string;\n fetchImpl?: typeof fetch;\n}): Promise<WalletBalance> {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const query = 'query($pk:String!){account(publicKey:$pk){balance{total}}}';\n const res = await fetchImpl(opts.graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables: { pk: opts.owner } }),\n });\n if (!res.ok) throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: { account?: { balance?: { total?: string } | null } | null };\n errors?: { message?: string }[];\n };\n if (json.errors && json.errors.length > 0) {\n throw new Error(`Mina GraphQL error: ${json.errors[0]?.message ?? 'unknown'}`);\n }\n return {\n chain: 'mina',\n address: opts.owner,\n amount: String(json.data?.account?.balance?.total ?? '0'),\n asset: 'MINA',\n assetScale: 9,\n };\n}\n\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\n/** base58-encode raw bytes (Bitcoin/Mina alphabet, leading-zero preserving). */\nfunction base58encode(bytes: Uint8Array): string {\n let num = 0n;\n for (const b of bytes) num = num * 256n + BigInt(b);\n let out = '';\n while (num > 0n) {\n out = BASE58_ALPHABET[Number(num % 58n)] + out;\n num /= 58n;\n }\n for (const b of bytes) {\n if (b === 0) out = '1' + out;\n else break;\n }\n return out;\n}\n\n/**\n * Encode a Mina token id (a Field, given as a decimal string) into the base58\n * `TokenId` scalar the Mina GraphQL `account(publicKey, token)` query expects.\n *\n * Mina's base58check layout is `0x1c` version byte ++ the 32-byte\n * little-endian field ++ a 4-byte double-SHA256 checksum. (The client avoids the\n * heavy o1js dependency — `TokenId.toBase58` — so this reproduces its encoding;\n * it round-trips the native token id `1` to\n * `wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf`.) The decimal form the\n * GraphQL layer rejects outright, hence this conversion.\n */\nexport function minaTokenIdToBase58(tokenId: string): string {\n if (!/^\\d+$/.test(tokenId)) {\n throw new Error(`Mina tokenId must be a decimal Field string: \"${tokenId}\"`);\n }\n let field = BigInt(tokenId);\n const le = new Uint8Array(32);\n for (let i = 0; i < 32; i++) {\n le[i] = Number(field & 0xffn);\n field >>= 8n;\n }\n const body = new Uint8Array(1 + 32);\n body[0] = 0x1c;\n body.set(le, 1);\n const checksum = sha256(sha256(body)).slice(0, 4);\n const raw = new Uint8Array(body.length + 4);\n raw.set(body, 0);\n raw.set(checksum, body.length);\n return base58encode(raw);\n}\n\n/**\n * Read a CUSTOM Mina token balance (e.g. the settlement USDC) for `owner`.\n *\n * Unlike {@link readMinaBalance} (native MINA), this passes the settlement\n * `tokenId` so the GraphQL `account(publicKey, token)` query returns the\n * token-specific balance. An account holding none of the token resolves to\n * `null` → reported as `0`. Same 9-decimal scale as native Mina amounts.\n */\nexport async function readMinaTokenBalance(opts: {\n graphqlUrl: string;\n owner: string;\n tokenId: string;\n fetchImpl?: typeof fetch;\n}): Promise<WalletBalance> {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const token = minaTokenIdToBase58(opts.tokenId);\n const query =\n 'query($pk:PublicKey!,$t:TokenId){account(publicKey:$pk,token:$t){balance{total}}}';\n const res = await fetchImpl(opts.graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables: { pk: opts.owner, t: token } }),\n });\n if (!res.ok) throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: { account?: { balance?: { total?: string } | null } | null };\n errors?: { message?: string }[];\n };\n if (json.errors && json.errors.length > 0) {\n throw new Error(`Mina GraphQL error: ${json.errors[0]?.message ?? 'unknown'}`);\n }\n return {\n chain: 'mina',\n address: opts.owner,\n amount: String(json.data?.account?.balance?.total ?? '0'),\n asset: 'USDC',\n assetScale: 9,\n };\n}\n\n/**\n * Per-chain inputs for {@link readWalletBalances}: the resolved RPC URL, the\n * identity's address on that chain, and the configured token (USDC) — sourced by\n * the caller from the network topology / presets, never hardcoded here. A chain\n * key absent from the object is simply not read.\n */\nexport interface WalletBalanceSources {\n evm?: { chainKey: string; rpcUrl: string; owner: string; tokenAddress?: string };\n solana?: { chainKey?: string; rpcUrl: string; owner: string; tokenMint?: string };\n mina?: {\n chainKey?: string;\n graphqlUrl: string;\n owner: string;\n /** Settlement-token Field id (decimal). Reads the token balance when set. */\n tokenId?: string;\n };\n /** Injectable fetch (Solana/Mina JSON-RPC & GraphQL) for tests. */\n fetchImpl?: typeof fetch;\n}\n\nconst errText = (e: unknown): string => (e instanceof Error ? e.message : String(e));\n\n/** Fold a settled native read into the chain view, recording any failure. */\nfunction foldNative(\n out: WalletChainBalances,\n settled: PromiseSettledResult<WalletTokenAmount>,\n errors: string[]\n): void {\n if (settled.status === 'fulfilled') out.native = settled.value;\n else errors.push(errText(settled.reason));\n}\n\n/** Fold a settled token read (a `WalletBalance`) into the chain view. */\nfunction foldToken(\n out: WalletChainBalances,\n settled: PromiseSettledResult<WalletBalance | undefined>,\n tokenAddress: string | undefined,\n errors: string[]\n): void {\n if (settled.status === 'rejected') {\n errors.push(errText(settled.reason));\n return;\n }\n const bal = settled.value;\n if (!bal) return;\n out.tokens.push({\n symbol: bal.asset,\n amount: bal.amount,\n decimals: bal.assetScale,\n ...(tokenAddress ? { address: tokenAddress } : {}),\n });\n}\n\n/** Mark a chain unreadable when nothing could be read; attach the first error. */\nfunction finalizeChain(out: WalletChainBalances, errors: string[]): void {\n if (errors.length > 0) out.error = errors[0];\n if (out.native === undefined && out.tokens.length === 0) out.unreadable = true;\n}\n\n/**\n * Read the full wallet view — native coin + configured tokens — for every chain\n * in `sources`, keyed per chain. FREE: read-only RPC, no signing. Each chain is\n * read independently and in parallel; a chain whose RPC is unreachable degrades\n * to `{ unreadable: true, error }` instead of failing the others. Within a chain\n * the native and token reads are independent, so a native read can succeed even\n * if the token read fails (and vice versa).\n */\nexport async function readWalletBalances(\n sources: WalletBalanceSources\n): Promise<WalletChainBalances[]> {\n const { fetchImpl } = sources;\n const tasks: Promise<WalletChainBalances>[] = [];\n\n if (sources.evm) {\n const { chainKey, rpcUrl, owner, tokenAddress } = sources.evm;\n tasks.push(\n (async () => {\n const out: WalletChainBalances = { chain: 'evm', chainKey, address: owner, tokens: [] };\n const errors: string[] = [];\n const [nativeR, tokenR] = await Promise.allSettled([\n readEvmNativeBalance({ rpcUrl, chainKey, owner }),\n tokenAddress\n ? readEvmTokenBalance({ rpcUrl, chainKey, tokenAddress, owner })\n : Promise.resolve<WalletBalance | undefined>(undefined),\n ]);\n foldNative(out, nativeR, errors);\n foldToken(out, tokenR, tokenAddress, errors);\n finalizeChain(out, errors);\n return out;\n })()\n );\n }\n\n if (sources.solana) {\n const { chainKey = 'solana', rpcUrl, owner, tokenMint } = sources.solana;\n tasks.push(\n (async () => {\n const out: WalletChainBalances = { chain: 'solana', chainKey, address: owner, tokens: [] };\n const errors: string[] = [];\n const [nativeR, tokenR] = await Promise.allSettled([\n readSolanaNativeBalance({ rpcUrl, owner, fetchImpl }),\n tokenMint\n ? readSolanaTokenBalance({ rpcUrl, mint: tokenMint, owner, fetchImpl })\n : Promise.resolve<WalletBalance | undefined>(undefined),\n ]);\n foldNative(out, nativeR, errors);\n // Solana SPL reads carry no symbol from `getTokenAccountsByOwner`; the\n // negotiated settlement token is USDC on every configured chain.\n if (tokenR.status === 'fulfilled' && tokenR.value && tokenR.value.asset === undefined) {\n tokenR.value.asset = 'USDC';\n }\n foldToken(out, tokenR, tokenMint, errors);\n finalizeChain(out, errors);\n return out;\n })()\n );\n }\n\n if (sources.mina) {\n const { chainKey = 'mina', graphqlUrl, owner, tokenId } = sources.mina;\n tasks.push(\n (async () => {\n const out: WalletChainBalances = { chain: 'mina', chainKey, address: owner, tokens: [] };\n const errors: string[] = [];\n // Native MINA (gas) plus, when the deployment settles a custom token,\n // that token's balance (USDC) — read via the tokenId. Independent reads.\n const [nativeR, tokenR] = await Promise.allSettled([\n readMinaBalance({ graphqlUrl, owner, fetchImpl }),\n tokenId\n ? readMinaTokenBalance({ graphqlUrl, owner, tokenId, fetchImpl })\n : Promise.resolve<WalletBalance | undefined>(undefined),\n ]);\n foldNative(\n out,\n nativeR.status === 'fulfilled'\n ? {\n status: 'fulfilled',\n value: {\n symbol: nativeR.value.asset,\n amount: nativeR.value.amount,\n decimals: nativeR.value.assetScale,\n },\n }\n : nativeR,\n errors\n );\n foldToken(out, tokenR, undefined, errors);\n finalizeChain(out, errors);\n return out;\n })()\n );\n }\n\n return Promise.all(tasks);\n}\n","/**\n * Client-side helper for kind:5094 Arweave blob storage DVM requests.\n *\n * This composes the three steps a caller previously had to wire by hand:\n * 1. Build the kind:5094 event via `buildBlobStorageRequest()` (@toon-protocol/core).\n * 2. Publish it to the DVM destination over ILP via `ToonClient.publishEvent()`\n * (reusing the existing claim / channel plumbing).\n * 3. Decode the FULFILL `data` field into the Arweave transaction ID.\n *\n * ## FULFILL data contract\n *\n * The deployed connector is a payment-proxy (HTTP-in-ILP): a successful blob\n * upload returns the DVM's verbatim **HTTP/1.1 response message** in the ILP\n * FULFILL `data` field. For a single-packet (non-chunked) upload the body is a\n * JSON object:\n *\n * HTTP/1.1 200 OK\\r\\n\n * content-length: 189\\r\\n\n * \\r\\n\n * {\"accept\":true,\"txId\":\"<43-char base64url>\",\"data\":\"<base64 of txId>\",...}\n *\n * We parse the HTTP envelope, fail on a non-2xx status (or `accept:false`), and\n * read the Arweave tx ID from `txId` (falling back to base64-decoding `data`).\n * An Arweave tx ID is a 43-character base64url string (32 raw bytes). A legacy\n * fallback still accepts a bare `base64(utf8(txId))` FULFILL (no HTTP envelope)\n * so non-proxy providers do not regress. See {@link extractArweaveTxId}.\n *\n * See `packages/sdk/src/arweave/arweave-dvm-handler.ts` for the server side and\n * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts` for the reference\n * end-to-end flow this helper mirrors.\n *\n * Chunked uploads (multi-packet, via `uploadId` / `chunkIndex` / `totalChunks`\n * params) are intentionally out of scope for this single-packet helper — the\n * provider returns `ack:<n>` for intermediate chunks and the tx ID only on the\n * final chunk. Callers needing chunking should drive `publishEvent()` directly\n * (see the chunked case in the reference E2E test). Tracked as a follow-up.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { buildBlobStorageRequest } from '@toon-protocol/core';\nimport { fromBase64, decodeUtf8 } from './utils/binary.js';\nimport { parseFulfillHttp } from './utils/fulfill-http.js';\nimport type { ToonClient } from './ToonClient.js';\nimport type { SignedBalanceProof } from './types.js';\n\n/** Arweave tx IDs are base64url-encoded 32-byte values (43 chars, no padding). */\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\n/**\n * Parameters for {@link requestBlobStorage}.\n */\nexport interface RequestBlobStorageParams {\n /** The raw blob data to store on Arweave. */\n blobData: Uint8Array;\n\n /**\n * MIME type of the blob. Defaults to `'application/octet-stream'`\n * (matching `buildBlobStorageRequest`).\n */\n contentType?: string;\n\n /**\n * Bid amount in USDC micro-units, as a string (declared in the event's\n * `bid` tag). Defaults to the stringified `ilpAmount` when omitted.\n *\n * At least one of `bid` / `ilpAmount` should be provided so the declared\n * bid and the paid ILP amount agree.\n */\n bid?: string;\n\n /**\n * ILP destination address of the DVM that performs the upload\n * (e.g. `'g.toon.peer1'`). Falls back to the client's configured\n * `destinationAddress` when omitted.\n */\n destination?: string;\n\n /**\n * Pre-signed balance proof claim for this packet. When omitted, the\n * client's channel manager auto-opens a channel and auto-signs a claim\n * (same lazy-channel behavior as `publishEvent`).\n */\n claim?: SignedBalanceProof;\n\n /**\n * Explicit ILP payment amount (bigint micro-units). When omitted,\n * `publishEvent` computes it from the encoded event size. When `bid`\n * is omitted, this value is stringified to populate the event's bid tag.\n */\n ilpAmount?: bigint;\n}\n\n/**\n * Typed result of {@link requestBlobStorage}.\n */\nexport interface RequestBlobStorageResult {\n /** Whether the upload succeeded and a tx ID was decoded. */\n success: boolean;\n\n /** The Arweave transaction ID (43-char base64url) when `success` is true. */\n txId?: string;\n\n /** The id of the kind:5094 event that was published. */\n eventId?: string;\n\n /** Error message when `success` is false. */\n error?: string;\n}\n\n/**\n * Requests permanent Arweave blob storage from a DVM in a single ILP packet.\n *\n * Mirrors the single-packet flow in\n * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts`: builds a signed\n * kind:5094 event, publishes it through the supplied {@link ToonClient}\n * (reusing its claim/channel plumbing), and decodes the FULFILL `data` field\n * into an Arweave transaction ID.\n *\n * @param client - A started `ToonClient` (call `client.start()` first).\n * @param secretKey - The Nostr secret key used to sign the kind:5094 event.\n * @param params - Blob, content type, bid, destination, and payment options.\n * @returns `{ success, txId?, eventId?, error? }`.\n */\nexport async function requestBlobStorage(\n client: ToonClient,\n secretKey: Uint8Array,\n params: RequestBlobStorageParams\n): Promise<RequestBlobStorageResult> {\n const bid =\n params.bid ??\n (params.ilpAmount !== undefined ? String(params.ilpAmount) : undefined);\n\n if (bid === undefined || bid === '') {\n return {\n success: false,\n error: 'requestBlobStorage requires a bid (or ilpAmount to derive it)',\n };\n }\n\n // buildBlobStorageRequest expects a Node Buffer (it calls .toString('base64')).\n // ToonClient surfaces Uint8Array for browser-compat, so normalize here.\n const blobBuffer = Buffer.from(\n params.blobData.buffer,\n params.blobData.byteOffset,\n params.blobData.byteLength\n );\n\n let event: NostrEvent;\n try {\n event = buildBlobStorageRequest(\n {\n blobData: blobBuffer,\n contentType: params.contentType,\n bid,\n },\n secretKey\n );\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n const result = await client.publishEvent(event, {\n destination: params.destination,\n claim: params.claim,\n ilpAmount: params.ilpAmount,\n // The store/DVM backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n\n if (!result.success) {\n return {\n success: false,\n eventId: result.eventId ?? event.id,\n error: result.error ?? 'Blob storage request rejected',\n };\n }\n\n if (!result.data) {\n return {\n success: false,\n eventId: event.id,\n error: 'FULFILL contained no data; expected an HTTP response with the Arweave tx ID',\n };\n }\n\n let txId: string;\n try {\n txId = extractArweaveTxId(result.data);\n } catch (error) {\n return {\n success: false,\n eventId: event.id,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n return {\n success: true,\n txId,\n eventId: event.id,\n };\n}\n\n/**\n * Extract the Arweave tx ID from the FULFILL `data` of a successful blob upload.\n *\n * The deployed payment-proxy returns the DVM's verbatim HTTP/1.1 response inside\n * the FULFILL `data`. For a successful single-packet upload that response is:\n *\n * HTTP/1.1 200 OK\\r\\n\n * content-length: 189\\r\\n\n * \\r\\n\n * {\"accept\":true,\"txId\":\"<43-char base64url>\",\"data\":\"<base64 of txId>\",...}\n *\n * We parse the HTTP envelope, fail on a non-2xx status or `accept:false`, then\n * read `txId` (preferred) from the JSON body — falling back to base64-decoding\n * the `data` field when `txId` is absent.\n *\n * LEGACY FALLBACK: older / non-proxy providers returned the bare tx ID as\n * `base64(utf8(txId))` directly in the FULFILL data (no HTTP envelope). When the\n * payload is not HTTP-enveloped we preserve that original decode so non-HTTP\n * FULFILLs do not regress.\n *\n * Exported for callers that drive `publishEvent` directly with a hand-built\n * kind:5094 event (e.g. git-object uploads carrying Git-SHA/Git-Type/Repo\n * tags, toon-client#227) and need the same FULFILL→txId decode this helper\n * applies.\n *\n * @throws {Error} If the response is non-2xx, `accept:false`, the body is not\n * parseable JSON, or no valid Arweave tx ID can be extracted.\n */\nexport function extractArweaveTxId(base64Data: string): string {\n const http = parseFulfillHttp(base64Data);\n\n // Legacy / non-HTTP FULFILL: bare base64(utf8(txId)).\n if (!http.isHttp) {\n const legacy = decodeUtf8(fromBase64(base64Data));\n if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {\n throw new Error(\n `Decoded FULFILL data is not a valid Arweave tx ID: \"${legacy}\"`\n );\n }\n return legacy;\n }\n\n if (http.status < 200 || http.status >= 300) {\n const detail = http.body ? ` - ${http.body}` : '';\n throw new Error(\n `Blob upload failed: DVM returned HTTP ${http.status} ${http.statusText}`.trimEnd() +\n detail\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(http.body);\n } catch {\n throw new Error(\n `Blob upload response body was not valid JSON: \"${http.body}\"`\n );\n }\n\n const body = parsed as {\n accept?: boolean;\n txId?: unknown;\n data?: unknown;\n error?: unknown;\n };\n\n if (body.accept === false) {\n const reason =\n typeof body.error === 'string' ? `: ${body.error}` : '';\n throw new Error(`Blob upload rejected by DVM (accept:false)${reason}`);\n }\n\n // Preferred: explicit txId field.\n if (typeof body.txId === 'string' && ARWEAVE_TX_ID_REGEX.test(body.txId)) {\n return body.txId;\n }\n\n // Fallback: base64-encoded tx id in the `data` field.\n if (typeof body.data === 'string' && body.data.length > 0) {\n const decoded = decodeUtf8(fromBase64(body.data));\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) {\n return decoded;\n }\n }\n\n throw new Error(\n `Blob upload response did not contain a valid Arweave tx ID: \"${http.body}\"`\n );\n}\n","/**\n * Payment-aware HTTP fetch over TOON (the \"h402\" flow).\n *\n * This adapter makes paying for an HTTP resource transparent: it issues an\n * ordinary HTTP request, and when the origin answers `402 Payment Required`\n * with an x402-style challenge that offers a `toon-channel` payment option, it\n * opens/reuses a payment channel, signs a balance-proof claim, and re-sends the\n * SAME HTTP request as a \"transparent HTTP-in-ILP\" packet to the connector's\n * `POST /ilp` endpoint (via {@link HttpIlpClient}). The connector terminates the\n * payment, forwards the request to the origin, and returns the origin's HTTP\n * response inside the ILP FULFILL `data`. We reconstruct a normal Web `Response`\n * from those bytes — the caller never sees ILP.\n *\n * ─── x402 wire contract (the 402 challenge body) ────────────────────────────\n * The connector side (the 402 greeting + the `accepts` entries) is a separate,\n * NOT-YET-BUILT dependency, so we parse DEFENSIVELY (mirroring\n * `readDiscoveredIlpPeer` in selectIlpTransport.ts): a slightly different\n * connector shape should degrade gracefully (fall back to the vanilla 402)\n * rather than throw.\n *\n * Expected 402 JSON body (x402 v1-ish):\n * ```jsonc\n * {\n * \"x402Version\": 1,\n * \"accepts\": [\n * {\n * \"scheme\": \"toon-channel\", // REQUIRED — selects the TOON option.\n * \"network\": \"evm:base:8453\", // optional — chain key (informational).\n * \"destination\": \"g.toon.apex\", // ILP destination address to pay (the\n * // connector route that fronts the URL).\n * \"amount\": \"1000\", // price in ILP base units (string|number).\n * \"httpEndpoint\": \"https://apex/ilp\", // the connector's POST /ilp URL.\n * \"supportsUpgrade\": true // optional — host accepts the BTP upgrade.\n * }\n * ]\n * }\n * ```\n *\n * Field aliases read defensively (first present wins):\n * - destination: `destination` | `ilpAddress` | `payTo` | `maxAmountRequired`'s\n * sibling `payTo`. (We do NOT invent a value — a missing destination makes\n * the entry unusable and we fall back to the vanilla 402.)\n * - amount: `amount` | `price` | `maxAmountRequired`.\n * - httpEndpoint:`httpEndpoint` | `ilpEndpoint` | `endpoint`.\n * - upgrade: `supportsUpgrade` | `upgradable`.\n *\n * ─── HTTP-in-ILP framing ────────────────────────────────────────────────────\n * The raw HTTP request/response is serialized as minimal HTTP/1.1 wire bytes\n * (request-line / status-line + headers + CRLFCRLF + body) and carried as the\n * ILP packet `data` (base64). See {@link serializeHttpRequest} /\n * {@link parseHttpResponse}. This keeps the connector free to forward the bytes\n * verbatim and lets us rebuild a standard `Response`.\n *\n * Claim signing/construction is owned by the CALLER (ToonClient wires the live\n * ChannelManager + signer). This adapter never builds or validates claims —\n * payment-claim validation lives ONLY in the connector.\n */\n\nimport type { IlpSendResult } from '@toon-protocol/core';\nimport { HttpIlpClient } from './HttpIlpClient.js';\nimport {\n selectIlpTransport,\n type DiscoveredIlpPeer,\n type IlpTransportChoice,\n} from './selectIlpTransport.js';\nimport { ConnectorError, ToonClientError } from '../errors.js';\nimport {\n toBase64,\n fromBase64,\n encodeUtf8,\n decodeUtf8,\n} from '../utils/binary.js';\n\n// ─── x402 challenge types (documented wire contract above) ──────────────────\n\n/** A single parsed `accepts` entry that offers the `toon-channel` scheme. */\nexport interface ToonChannelAccept {\n /** Always `'toon-channel'` for a matched entry. */\n scheme: 'toon-channel';\n /** Optional chain key, e.g. `evm:base:8453` — informational. */\n network?: string;\n /** ILP destination address to pay (the connector route fronting the URL). */\n destination: string;\n /** Price in ILP base units. */\n amount: bigint;\n /** The connector's `POST /ilp` URL. */\n httpEndpoint: string;\n /** Whether the host accepts the BTP upgrade over the HTTP endpoint. */\n supportsUpgrade: boolean;\n}\n\n/** The parsed x402 402 body, with the selected `toon-channel` entry (if any). */\nexport interface ParsedX402Challenge {\n x402Version?: number;\n /** The first usable `toon-channel` accepts entry, or `undefined`. */\n toonChannel?: ToonChannelAccept;\n}\n\n// ─── h402Fetch options (PINNED PUBLIC CONTRACT) ─────────────────────────────\n\n/** Options for {@link Http402Client.fetch} / `ToonClient.h402Fetch`. */\nexport interface H402FetchOptions {\n /** HTTP method. Default `'GET'`. */\n method?: string;\n /** Request headers. */\n headers?: Record<string, string>;\n /** Request body. */\n body?: string | Uint8Array;\n /** Request timeout in milliseconds. */\n timeout?: number;\n /** Optional explicit ILP destination override (else the x402 entry's value). */\n destination?: string;\n}\n\n/**\n * Caller-supplied hook that signs a balance-proof claim for `(destination,\n * amount)` and returns the chain-appropriate claim message to attach to the ILP\n * PREPARE. ToonClient wires this to its ChannelManager + per-chain signer (the\n * exact same plumbing as `publishEvent`). The returned value is forwarded\n * opaquely as the `ILP-Payment-Channel-Claim` header by {@link HttpIlpClient}.\n */\nexport type ClaimResolver = (\n destination: string,\n amount: bigint\n) => Promise<unknown>;\n\n/** Factory for an {@link HttpIlpClient} given a resolved `POST /ilp` endpoint. */\nexport type HttpIlpClientFactory = (httpEndpoint: string) => HttpIlpClient;\n\nexport interface Http402ClientConfig {\n /**\n * Underlying HTTP fetch for the INITIAL (un-paid) request that probes for a\n * 402. Default: global `fetch`.\n */\n fetch?: typeof fetch;\n /**\n * Resolves + signs the payment-channel claim. REQUIRED to pay; if omitted,\n * a 402 with a `toon-channel` offer is surfaced unchanged (vanilla challenge).\n */\n resolveClaim?: ClaimResolver;\n /**\n * Builds the {@link HttpIlpClient} for a resolved endpoint. Default: construct\n * a new `HttpIlpClient({ httpEndpoint })`. Injectable for tests.\n */\n createIlpClient?: HttpIlpClientFactory;\n /**\n * AC4: request a duplex transport for the paid send. When `true` and the\n * toon-channel entry advertises `supportsUpgrade`, {@link selectIlpTransport}\n * returns `http-upgradable` and the send path calls\n * {@link HttpIlpClient.upgradeToBtp} before writing — the wiring for\n * large/streaming responses. Default `false` (stateless one-shot HTTP).\n *\n * NOTE (v1 limitation): even on the upgrade path the actual write is still a\n * one-shot `sendIlpPacketWithClaim`; full duplex body streaming over the BTP\n * session is a documented follow-up. The selection + upgrade CALL PATH is\n * wired and exercised here so the streaming consumer can take over the\n * returned session in a later iteration.\n */\n needsDuplex?: boolean;\n}\n\n/**\n * Reusable h402 fetch engine. `ToonClient.h402Fetch` is a thin wrapper that\n * constructs this with the live claim/channel plumbing.\n */\nexport class Http402Client {\n private readonly fetchImpl: typeof fetch;\n private readonly resolveClaim?: ClaimResolver;\n private readonly createIlpClient: HttpIlpClientFactory;\n private readonly needsDuplex: boolean;\n\n constructor(config: Http402ClientConfig = {}) {\n this.fetchImpl = config.fetch ?? fetch;\n this.resolveClaim = config.resolveClaim;\n this.createIlpClient =\n config.createIlpClient ??\n ((httpEndpoint) => new HttpIlpClient({ httpEndpoint }));\n this.needsDuplex = config.needsDuplex ?? false;\n }\n\n /**\n * `fetch()`-like entry point. Issues the request; on `402` parses the x402\n * challenge and — when a usable `toon-channel` offer is present and a claim\n * resolver is configured — pays over TOON and returns the reconstructed\n * `Response`. Otherwise returns the original 402 unchanged (AC5).\n */\n async fetch(url: string, opts: H402FetchOptions = {}): Promise<Response> {\n const method = (opts.method ?? 'GET').toUpperCase();\n\n // 1. Probe: issue the ordinary HTTP request.\n const probe = await this.fetchImpl(url, {\n method,\n ...(opts.headers ? { headers: opts.headers } : {}),\n ...(opts.body !== undefined ? { body: opts.body as BodyInit } : {}),\n ...(opts.timeout !== undefined\n ? { signal: AbortSignal.timeout(opts.timeout) }\n : {}),\n });\n\n // 2. Pass-through anything that isn't a 402.\n if (probe.status !== 402) return probe;\n\n // 3. Parse the x402 challenge defensively. We must read the body to inspect\n // `accepts`; clone first so we can still return the ORIGINAL 402 on\n // fallback (a Response body is single-use).\n const challenge = await parseX402Challenge(probe.clone());\n const accept = challenge.toonChannel;\n\n // AC5: no toon-channel offer (or no signer) → surface the vanilla challenge.\n if (!accept || !this.resolveClaim) return probe;\n\n // 4. Pay over TOON and return the reconstructed Response.\n return this.payOverToon(url, method, opts, accept, this.resolveClaim);\n }\n\n /**\n * Open/reuse a channel (via the injected claim resolver), serialize the HTTP\n * request into the ILP packet `data`, send it to `POST /ilp` with the claim,\n * and reconstruct the origin `Response` from the FULFILL `data`.\n */\n private async payOverToon(\n url: string,\n method: string,\n opts: H402FetchOptions,\n accept: ToonChannelAccept,\n resolveClaim: ClaimResolver\n ): Promise<Response> {\n const destination = opts.destination ?? accept.destination;\n\n // Sign the balance-proof claim for the demanded price (caller-owned).\n const claim = await resolveClaim(destination, accept.amount);\n\n // Serialize the raw HTTP request into HTTP/1.1 wire bytes for `data`.\n const requestBytes = serializeHttpRequest({\n method,\n url,\n headers: opts.headers,\n body: opts.body,\n });\n\n // AC4: drive transport SELECTION through selectIlpTransport. A streaming\n // response (`needsDuplex`) selects the BTP upgrade path; the one-shot case\n // stays on stateless HTTP. Full duplex byte-streaming is a documented v1\n // limitation (see selectTransport below) — the selection + upgrade call path\n // is wired and unit-tested.\n const peer: DiscoveredIlpPeer = {\n httpEndpoint: accept.httpEndpoint,\n supportsUpgrade: accept.supportsUpgrade,\n };\n const choice = selectIlpTransport(peer, {\n needsDuplex: this.needsDuplex,\n });\n\n const ilpClient = this.createIlpClient(accept.httpEndpoint);\n\n const result = await this.sendOverChoice(\n ilpClient,\n choice,\n {\n destination,\n amount: String(accept.amount),\n data: toBase64(requestBytes),\n ...(opts.timeout !== undefined ? { timeout: opts.timeout } : {}),\n },\n claim\n );\n\n if (!result.accepted) {\n throw new ConnectorError(\n `h402 payment rejected by connector: ${result.code ?? 'F00'} ${\n result.message ?? ''\n }`.trim()\n );\n }\n if (!result.data) {\n throw new ConnectorError(\n 'h402 FULFILL carried no data (expected an HTTP response payload)'\n );\n }\n\n // Reconstruct the standard Response from the FULFILL `data` bytes.\n return parseHttpResponse(fromBase64(result.data));\n }\n\n /**\n * Send the serialized HTTP-in-ILP PREPARE over the selected transport.\n *\n * - `http` / `http-upgradable`: stateless one-shot `POST /ilp` with the claim.\n * - `http-upgradable` additionally exercises {@link HttpIlpClient.upgradeToBtp}\n * for the duplex/streaming path (AC4). v1 still drives the actual write over\n * the one-shot HTTP method even after upgrading — full duplex body streaming\n * is a documented follow-up — but the upgrade call path is wired here.\n * - `btp`: not reachable from h402 (the x402 offer only carries an\n * `httpEndpoint`); guarded for completeness.\n */\n private async sendOverChoice(\n ilpClient: HttpIlpClient,\n choice: IlpTransportChoice,\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: unknown\n ): Promise<IlpSendResult> {\n if (choice.kind === 'http-upgradable') {\n // Wire the upgrade path: obtain (and immediately release) a duplex BTP\n // session so a streaming consumer can take it over in a follow-up. The\n // one-shot write below still terminates the payment for v1.\n const btp = await ilpClient.upgradeToBtp();\n try {\n // BtpRuntimeClient types `claim` as Record<string, unknown>; the claim\n // message is an opaque forwarded envelope (same cast ToonClient uses).\n return await btp.sendIlpPacketWithClaim(\n params,\n claim as Record<string, unknown>\n );\n } finally {\n await btp.disconnect().catch(() => undefined);\n }\n }\n if (choice.kind === 'btp') {\n throw new ToonClientError(\n 'h402 offer resolved to a BTP-only transport; the x402 toon-channel entry must advertise an httpEndpoint',\n 'INVALID_STATE'\n );\n }\n // 'http'\n return ilpClient.sendIlpPacketWithClaim(params, claim);\n }\n}\n\n// ─── x402 challenge parsing (defensive) ─────────────────────────────────────\n\n/** First defined string among the given keys on `obj`. */\nfunction readString(\n obj: Record<string, unknown>,\n keys: string[]\n): string | undefined {\n for (const k of keys) {\n const v = obj[k];\n if (typeof v === 'string' && v.trim().length > 0) return v.trim();\n }\n return undefined;\n}\n\n/** First parseable bigint among the given keys (string|number) on `obj`. */\nfunction readAmount(\n obj: Record<string, unknown>,\n keys: string[]\n): bigint | undefined {\n for (const k of keys) {\n const v = obj[k];\n if (typeof v === 'bigint') return v;\n if (typeof v === 'number' && Number.isFinite(v)) return BigInt(Math.trunc(v));\n if (typeof v === 'string' && /^\\d+$/.test(v.trim())) return BigInt(v.trim());\n }\n return undefined;\n}\n\n/**\n * Parse a 402 `Response` body into a {@link ParsedX402Challenge}, selecting the\n * first usable `toon-channel` entry. Reads every field defensively; a malformed\n * body, a non-JSON body, or an entry missing its `destination`/`httpEndpoint`\n * yields `{ toonChannel: undefined }` so the caller falls back to the vanilla\n * 402 rather than throwing.\n */\nexport async function parseX402Challenge(\n response: Response\n): Promise<ParsedX402Challenge> {\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return {};\n }\n return parseX402Body(body);\n}\n\n/** Pure parser over an already-decoded x402 body (testable without a Response). */\nexport function parseX402Body(body: unknown): ParsedX402Challenge {\n if (typeof body !== 'object' || body === null) return {};\n const b = body as Record<string, unknown>;\n\n const version =\n typeof b['x402Version'] === 'number'\n ? (b['x402Version'] as number)\n : undefined;\n\n const accepts = Array.isArray(b['accepts'])\n ? (b['accepts'] as unknown[])\n : [];\n\n for (const raw of accepts) {\n if (typeof raw !== 'object' || raw === null) continue;\n const entry = raw as Record<string, unknown>;\n const scheme = readString(entry, ['scheme']);\n if (scheme !== 'toon-channel') continue;\n\n const destination = readString(entry, [\n 'destination',\n 'ilpAddress',\n 'payTo',\n ]);\n const httpEndpoint = readString(entry, [\n 'httpEndpoint',\n 'ilpEndpoint',\n 'endpoint',\n ]);\n const amount = readAmount(entry, ['amount', 'price', 'maxAmountRequired']);\n\n // A usable entry MUST carry where to pay, how much, and how to reach /ilp.\n if (!destination || !httpEndpoint || amount === undefined) continue;\n\n const network = readString(entry, ['network', 'chain']);\n const supportsUpgrade =\n entry['supportsUpgrade'] === true || entry['upgradable'] === true;\n\n return {\n ...(version !== undefined ? { x402Version: version } : {}),\n toonChannel: {\n scheme: 'toon-channel',\n ...(network !== undefined ? { network } : {}),\n destination,\n amount,\n httpEndpoint,\n supportsUpgrade,\n },\n };\n }\n\n return version !== undefined ? { x402Version: version } : {};\n}\n\n// ─── HTTP-in-ILP framing (minimal HTTP/1.1 serialize/parse) ─────────────────\n\nconst CRLF = '\\r\\n';\n\n/** Bytes of a string concatenated with a Uint8Array body (no extra copies of body). */\nfunction concatHeadAndBody(head: string, body: Uint8Array): Uint8Array {\n const headBytes = encodeUtf8(head);\n const out = new Uint8Array(headBytes.length + body.length);\n out.set(headBytes, 0);\n out.set(body, headBytes.length);\n return out;\n}\n\n/** Normalize an optional string|Uint8Array body to bytes. */\nfunction bodyToBytes(body: string | Uint8Array | undefined): Uint8Array {\n if (body === undefined) return new Uint8Array(0);\n return typeof body === 'string' ? encodeUtf8(body) : body;\n}\n\n/**\n * Serialize a raw HTTP request to HTTP/1.1 wire bytes:\n * `METHOD path HTTP/1.1\\r\\n` + `Host:` + headers + `\\r\\n\\r\\n` + body.\n *\n * The request-line target is the URL's path+query (origin-form); we add a\n * `Host` header from the URL authority and a `Content-Length` when there's a\n * body, unless the caller already supplied them. Header names are matched\n * case-insensitively so we never duplicate `Host`/`Content-Length`.\n */\nexport function serializeHttpRequest(req: {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: string | Uint8Array;\n}): Uint8Array {\n const u = new URL(req.url);\n const target = `${u.pathname}${u.search}` || '/';\n const bodyBytes = bodyToBytes(req.body);\n\n const headers = new Map<string, string>(); // lower-name → \"Name: value\"\n const put = (name: string, value: string) =>\n headers.set(name.toLowerCase(), `${name}: ${value}`);\n const has = (name: string) => headers.has(name.toLowerCase());\n\n for (const [name, value] of Object.entries(req.headers ?? {})) {\n put(name, value);\n }\n if (!has('host')) put('Host', u.host);\n if (bodyBytes.length > 0 && !has('content-length')) {\n put('Content-Length', String(bodyBytes.length));\n }\n\n const lines = [\n `${req.method.toUpperCase()} ${target} HTTP/1.1`,\n ...headers.values(),\n ];\n const head = lines.join(CRLF) + CRLF + CRLF;\n return concatHeadAndBody(head, bodyBytes);\n}\n\n/** Find the index just past the first `\\r\\n\\r\\n` (header/body boundary). */\nfunction findHeaderEnd(bytes: Uint8Array): number {\n for (let i = 0; i + 3 < bytes.length; i++) {\n if (\n bytes[i] === 0x0d &&\n bytes[i + 1] === 0x0a &&\n bytes[i + 2] === 0x0d &&\n bytes[i + 3] === 0x0a\n ) {\n return i + 4;\n }\n }\n return -1;\n}\n\n/**\n * Parse HTTP/1.1 wire bytes (status-line + headers + CRLFCRLF + body) into a\n * standard Web `Response`. Used to reconstruct the origin response from the ILP\n * FULFILL `data`.\n *\n * @throws {ConnectorError} If the bytes are not a parseable HTTP/1.1 response.\n */\nexport function parseHttpResponse(bytes: Uint8Array): Response {\n const headerEnd = findHeaderEnd(bytes);\n // No header/body separator: treat the whole payload as a header block (some\n // bodiless responses may omit the trailing CRLFCRLF); fall back to end.\n const headBytes =\n headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);\n const body = headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);\n\n const headText = decodeUtf8(headBytes);\n const lines = headText.split(CRLF).filter((l) => l.length > 0);\n const statusLine = lines.shift();\n if (!statusLine) {\n throw new ConnectorError(\n 'h402 response payload had no HTTP status line'\n );\n }\n\n // `HTTP/1.1 200 OK` — tolerate a missing reason phrase.\n const match = /^HTTP\\/\\d\\.\\d\\s+(\\d{3})(?:\\s+(.*))?$/.exec(statusLine.trim());\n if (!match) {\n throw new ConnectorError(\n `h402 response payload had a malformed status line: \"${statusLine}\"`\n );\n }\n const status = parseInt(match[1] as string, 10);\n const statusText = match[2] ?? '';\n\n const headers = new Headers();\n for (const line of lines) {\n const idx = line.indexOf(':');\n if (idx === -1) continue;\n const name = line.slice(0, idx).trim();\n const value = line.slice(idx + 1).trim();\n if (name.length === 0) continue;\n // 204/304 etc. must not carry these on a Web Response — Headers tolerates\n // them, but Response construction below sets the real body length anyway.\n headers.append(name, value);\n }\n\n // `Response` forbids a body for null-body statuses (101/204/205/304).\n const nullBodyStatus =\n status === 101 || status === 204 || status === 205 || status === 304;\n const init: ResponseInit = { status, headers };\n if (statusText) init.statusText = statusText;\n\n return new Response(\n nullBodyStatus || body.length === 0 ? null : body.slice(),\n init\n );\n}\n","/**\n * Receive-side swap settlement (toon-client#352): turn persisted\n * {@link ReceivedClaimEntry} watermarks into on-chain settlement submissions.\n *\n * Building is pure and chain-agnostic — `buildSettlementTx` (sdk) re-verifies\n * the stored claim's signature and produces one {@link SettlementBundle} per\n * `(chain, channelId)` with the FINAL watermark, so N received advances net to\n * one on-chain close per channel (spec §9). Submission is chain-gated:\n *\n * - **EVM** — implemented here ({@link submitEvmSettlement}): the bundle's\n * unsigned RLP is decoded for `to`/calldata, gas is estimated against the\n * configured RPC, and the tx is signed by the client's EVM account (the\n * claim recipient) and broadcast. Real submission is therefore env-gated on\n * `chainRpcUrls[chain]` — absent RPC config yields a built-not-submitted\n * result, never a throw.\n * - **Solana** — the bundle carries a serialized Message; a submission path is\n * not wired yet (follow-up under toon-meta#145).\n * - **Mina** — receive-side redemption needs a co-sign (`claimFromChannel`\n * takes `signatureA` AND `signatureB`) plus o1js proof generation; the\n * client has no receive-side co-sign path. Explicitly out of scope for #352\n * (documented gap; follow-up: toon-client#357).\n */\n\nimport type { AccumulatedClaim } from '@toon-protocol/sdk/swap';\nimport {\n buildSettlementTx,\n SettlementTxError,\n type MinaSignerClientLike,\n type SettlementBundle,\n type SwapSignerConfig,\n} from '@toon-protocol/sdk';\nimport {\n createPublicClient,\n defineChain,\n fromRlp,\n http,\n type Hex,\n type PrivateKeyAccount,\n} from 'viem';\nimport type { ReceivedClaimEntry } from '../channel/ReceivedClaimStore.js';\n\n/** One per-channel settlement build outcome (result-shaped, never thrown). */\nexport interface SwapSettlementBuild {\n chain: string;\n channelId: string;\n /** The bundle, when the claim verified and the chain config sufficed. */\n bundle?: SettlementBundle;\n /** Why no bundle was produced (missing config, failed re-verification, …). */\n error?: { code: string; message: string };\n}\n\nexport interface BuildSwapSettlementsParams {\n /** Persisted watermarks to settle (typically `store.list()`, filtered). */\n entries: readonly ReceivedClaimEntry[];\n /**\n * Per-chain settlement contract: EVM TokenNetwork address / Solana\n * programId, keyed by the FULL chain key (e.g. `evm:base:8453`). Matches the\n * daemon config's `tokenNetworks` map.\n */\n tokenNetworks?: Record<string, string>;\n /** Pre-loaded `mina-signer` client for `mina:*` re-verification. */\n minaSignerClient?: MinaSignerClientLike;\n /**\n * Re-verify the stored claim's signature at settle time (defense in depth\n * over the store file). Default `true` — the published **v2** sdk\n * (`@toon-protocol/sdk@^3`, connector#324 finding #1) verifies EVM claims\n * against the **v2** EIP-712 domain-separated balance-proof digest, the SAME\n * digest the receive-side used (`ingestReceivedClaims`). Reconstructing that\n * EIP-712 domain needs `chainId` + `verifyingContract`, which this builder\n * threads into the sdk signer config from `tokenNetworks` (for EVM claims) —\n * so a claim verified at receipt re-verifies correctly here.\n *\n * Set `false` only to skip the settle-time re-verify entirely (e.g. when the\n * receive-side verify is treated as the sole authoritative gate); the sdk is\n * then used only to BUILD the settlement calldata.\n */\n verifySignatures?: boolean;\n}\n\n/** Rebuild the sdk `AccumulatedClaim` shape from a persisted entry. */\nexport function entryToAccumulatedClaim(\n entry: ReceivedClaimEntry\n): AccumulatedClaim {\n return {\n packetIndex: 0,\n sourceAmount: 0n,\n targetAmount: entry.cumulativeAmount,\n claimBytes: entry.claimBytes,\n swapEphemeralPubkey: '',\n ...(entry.claimId !== undefined ? { claimId: entry.claimId } : {}),\n pair: entry.pair,\n receivedAt: entry.receivedAt,\n channelId: entry.channelId,\n nonce: entry.nonce.toString(),\n cumulativeAmount: entry.cumulativeAmount.toString(),\n recipient: entry.recipient,\n swapSignerAddress: entry.swapSignerAddress,\n };\n}\n\n/**\n * Parse the numeric chain id off an `evm:{network}:{chainId}` / `evm:{chainId}`\n * chain key. Returns undefined for malformed keys (reported result-shaped).\n */\nexport function parseEvmChainId(chain: string): number | undefined {\n const parts = chain.split(':');\n const raw = parts.length >= 3 ? parts[2] : parts[1];\n const chainId = Number.parseInt(raw ?? '', 10);\n return Number.isInteger(chainId) && chainId > 0 ? chainId : undefined;\n}\n\n/**\n * Build one settlement bundle per persisted entry via the sdk's\n * `buildSettlementTx` (signature re-verified at settle time — a tampered\n * store never reaches a submission). Per-entry isolation: one bad channel\n * cannot block the others.\n */\nexport function buildSwapSettlements(\n params: BuildSwapSettlementsParams\n): SwapSettlementBuild[] {\n return params.entries.map((entry) => {\n const base = { chain: entry.chain, channelId: entry.channelId };\n const signer: SwapSignerConfig = { address: entry.swapSignerAddress };\n const contract = params.tokenNetworks?.[entry.chain];\n if (entry.chain.startsWith('evm')) {\n const chainId = parseEvmChainId(entry.chain);\n if (!contract || chainId === undefined) {\n return {\n ...base,\n error: {\n code: 'MISSING_CHAIN_CONFIG',\n message:\n `EVM settlement for ${entry.chain} needs tokenNetworks[\"${entry.chain}\"] ` +\n `(TokenNetwork contract) and a numeric chain id in the chain key.`,\n },\n };\n }\n signer.contractAddress = contract;\n signer.chainId = chainId;\n } else if (entry.chain.startsWith('solana')) {\n if (!contract) {\n return {\n ...base,\n error: {\n code: 'MISSING_CHAIN_CONFIG',\n message: `Solana settlement for ${entry.chain} needs tokenNetworks[\"${entry.chain}\"] (programId).`,\n },\n };\n }\n signer.programId = contract;\n }\n try {\n const result = buildSettlementTx({\n claims: [entryToAccumulatedClaim(entry)],\n signers: { [entry.chain]: signer },\n recipients: { [entry.chain]: entry.recipient },\n ...(params.verifySignatures !== undefined\n ? { verifySignatures: params.verifySignatures }\n : {}),\n ...(params.minaSignerClient\n ? { minaSignerClient: params.minaSignerClient }\n : {}),\n });\n const firstRejected = result.rejected[0];\n if (firstRejected) {\n return {\n ...base,\n error: {\n code: firstRejected.reason,\n message:\n firstRejected.details ??\n `stored claim for ${entry.chain}/${entry.channelId} failed settlement re-verification`,\n },\n };\n }\n const bundle = result.bundles[0];\n if (!bundle) {\n return {\n ...base,\n error: {\n code: 'NO_BUNDLE',\n message: `buildSettlementTx produced no bundle for ${entry.chain}/${entry.channelId}`,\n },\n };\n }\n return { ...base, bundle };\n } catch (err) {\n const code = err instanceof SettlementTxError ? err.code : 'BUILD_FAILED';\n return {\n ...base,\n error: {\n code,\n message: err instanceof Error ? err.message : String(err),\n },\n };\n }\n });\n}\n\nexport interface SubmitEvmSettlementParams {\n /** JSON-RPC endpoint of the bundle's chain (`chainRpcUrls[bundle.chain]`). */\n rpcUrl: string;\n /** The claim recipient's EVM account (signs + pays gas for the redeem). */\n account: PrivateKeyAccount;\n /** Receipt wait bound, ms (default 60_000). */\n timeoutMs?: number;\n}\n\nexport interface SubmitEvmSettlementResult {\n txHash: string;\n /** Receipt status when the wait succeeded within the bound. */\n status?: 'success' | 'reverted';\n}\n\n/**\n * Decode the unsigned legacy-RLP tx a settlement bundle carries.\n * Layout (EIP-155 unsigned): [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0].\n */\nexport function decodeEvmSettlementTx(bundle: SettlementBundle): {\n to: Hex;\n data: Hex;\n chainId: number;\n} {\n const fields = fromRlp(bundle.unsignedTxBytes, 'hex');\n if (!Array.isArray(fields) || fields.length !== 9) {\n throw new Error(\n `settlement bundle RLP is not a 9-field unsigned EVM tx (got ${Array.isArray(fields) ? fields.length : typeof fields})`\n );\n }\n const to = fields[3] as Hex;\n const data = fields[5] as Hex;\n const chainIdHex = fields[6] as Hex;\n const chainId = Number.parseInt(chainIdHex === '0x' ? '0x0' : chainIdHex, 16);\n if (typeof to !== 'string' || to.length !== 42) {\n throw new Error(\n `settlement tx \"to\" is not a 20-byte address: ${String(to)}`\n );\n }\n return { to, data: (data === '0x' ? '0x' : data) as Hex, chainId };\n}\n\n/**\n * Sign + broadcast an EVM settlement bundle from the recipient's account.\n * Gas/nonce are read from the RPC; the tx is signed locally (non-custodial)\n * and sent as a raw transaction, then awaited to a receipt (bounded).\n */\nexport async function submitEvmSettlement(\n bundle: SettlementBundle,\n params: SubmitEvmSettlementParams\n): Promise<SubmitEvmSettlementResult> {\n if (bundle.chainKind !== 'evm') {\n throw new Error(\n `submitEvmSettlement only submits evm bundles (got ${bundle.chainKind} for ${bundle.chain})`\n );\n }\n const { to, data, chainId } = decodeEvmSettlementTx(bundle);\n const chain = defineChain({\n id: chainId,\n name: bundle.chain,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [params.rpcUrl] } },\n });\n const publicClient = createPublicClient({\n chain,\n transport: http(params.rpcUrl),\n });\n\n const [nonce, gasPrice, gas] = await Promise.all([\n publicClient.getTransactionCount({\n address: params.account.address,\n blockTag: 'pending',\n }),\n publicClient.getGasPrice(),\n publicClient.estimateGas({ account: params.account.address, to, data }),\n ]);\n\n const signed = await params.account.signTransaction({\n type: 'legacy',\n chainId,\n nonce,\n gasPrice,\n gas,\n to,\n value: 0n,\n data,\n });\n const txHash = await publicClient.sendRawTransaction({\n serializedTransaction: signed,\n });\n try {\n const receipt = await publicClient.waitForTransactionReceipt({\n hash: txHash,\n timeout: params.timeoutMs ?? 60_000,\n });\n return { txHash, status: receipt.status };\n } catch {\n // The tx is broadcast; a slow chain just means the caller re-checks later.\n return { txHash };\n }\n}\n","/**\n * Mina receive-side swap settlement (toon-client#357, part of the rolling-swap\n * epic toon-meta#145; follow-up to the #352 receive-side ingestion PR).\n *\n * #352 shipped VERIFICATION of swapped-in Mina claims but explicitly left\n * REDEMPTION out: `POST /swap/settle` returned `SUBMISSION_UNSUPPORTED` for\n * `mina:*` bundles. This module is the missing redemption seam — it turns a\n * verified accumulated Mina claim into an on-chain co-signed `claimFromChannel`\n * transaction against the payment-channel zkApp.\n *\n * ## Why Mina needs a co-sign (and EVM/Solana do not)\n *\n * The zkApp's `claimFromChannel(newBalanceA, newBalanceB, newSalt, signatureA,\n * signatureB, participantA, participantB, channelNonce, newBalanceCommitment,\n * newNonce)` is DUAL-PARTY (connector#84): it verifies BOTH participants signed\n * the SAME message `[newBalanceCommitment, newNonce, storedChannelHash]`\n * (`packages/mina-zkapp/src/PaymentChannel.ts`). The client's existing\n * {@link MinaSigner} is payer-side only. On the swap RECEIVE side the client is\n * the RECIPIENT of the channel, so it must contribute the recipient's\n * co-signature (`signatureB` for its participant slot).\n *\n * ## Signature-message mismatch — the maker-side dependency\n *\n * The swap-wire claim (`AccumulatedClaim.claimBytes`) carries the maker's\n * Schnorr signature over `balanceProofFieldsMina(channelId, cumulativeAmount,\n * nonce, recipient)` (verified by the sdk's `verifyMinaSignature`). That is a\n * DIFFERENT message than the on-chain `[commitment, nonce, channelHash]`, so it\n * CANNOT be reused as an on-chain participant signature. On-chain redemption\n * therefore needs the maker to ALSO contribute a payment-channel-commitment-form\n * signature over `[commitment, nonce, channelHash]` (the same form the connector\n * payment-channel provider produces from a client claim). Until a maker delivers\n * that, {@link submitMinaSettlement} fails closed with\n * `MINA_MAKER_COSIGN_REQUIRED` (never a silent pass). See the PR body for the\n * cross-repo maker/wire follow-up.\n *\n * ## globalSlot preconditions\n *\n * Unlike `initiateClose`/`settle` (which pin `network.globalSlotSinceGenesis`\n * and were the subject of the #202 exact-slot-precondition bug), the zkApp's\n * `claimFromChannel` binds NO slot precondition — only `channelState == OPEN`,\n * `channelHash`, `depositTotal`, and a strictly-increasing `nonceField`. The\n * redeem path is therefore immune to the #202 slot-drift failure. The eventual\n * close+settle that pays escrow out to the recipient DOES touch the slot window\n * and remains the connector's `settleChannel` responsibility (a separate step).\n *\n * ## What runs where\n *\n * - On-chain STATE READ + co-sign assembly ({@link buildMinaCoSignedClaim}):\n * plain GraphQL + `mina-signer` (Pallas Schnorr / Poseidon) — NO o1js, fully\n * unit-testable in-process.\n * - Proof generation + broadcast ({@link submitMinaSettlement}): drives an\n * injectable {@link MinaClaimSubmitter}; the default is an o1js-backed settler\n * ({@link createO1jsMinaClaimSubmitter}) that dynamically imports `o1js` +\n * `@toon-protocol/mina-zkapp` only when invoked (never loaded by the non-Mina\n * suite). o1js circuit compilation + proving is slow (30-120s) and is exercised\n * only against live devnet Mina behind an env gate.\n */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport type { SettlementBundle } from '@toon-protocol/sdk';\nimport {\n buildMinaPaymentChannelProof,\n loadMinaPaymentChannelBindings,\n minaBalanceCommitment,\n minaParticipantChannelHashField,\n} from '../channel/mina-payment-channel.js';\nimport {\n readMinaChannelState,\n MINA_CHANNEL_STATE,\n type MinaOnChainChannelState,\n} from '../channel/mina-deposit.js';\nimport { deriveMinaSalt } from '../signing/mina-signer.js';\n\n/** Bare Pallas Schnorr signature in the o1js JSON (decimal `r`/`s`) form. */\nexport interface MinaSignaturePair {\n r: string;\n s: string;\n}\n\n/** Stable failure codes for Mina receive-side settlement. */\nexport type MinaSettlementErrorCode =\n | 'NOT_MINA_BUNDLE'\n | 'NO_GRAPHQL_CONFIGURED'\n | 'CHANNEL_NOT_OPEN'\n | 'NONCE_NOT_ADVANCING'\n | 'CHANNEL_HASH_MISMATCH'\n | 'CUMULATIVE_EXCEEDS_DEPOSIT'\n | 'MINA_MAKER_COSIGN_REQUIRED'\n | 'PROVING_FAILED';\n\n/** Result-shaped-by-throw settlement error carrying a stable {@link code}. */\nexport class MinaSettlementError extends Error {\n readonly code: MinaSettlementErrorCode;\n constructor(code: MinaSettlementErrorCode, message: string) {\n super(`${code}: ${message}`);\n this.name = 'MinaSettlementError';\n this.code = code;\n }\n}\n\n/** Inputs to {@link buildMinaCoSignedClaim} (pure assembly, no o1js). */\nexport interface MinaCoSignInputs {\n /** Deployed payment-channel zkApp address (B62) — the on-chain channel id. */\n channelId: string;\n /** Claim nonce being redeemed (must exceed the on-chain `nonceField`). */\n nonce: bigint;\n /** Cumulative amount credited to the recipient (base units). */\n cumulativeAmount: bigint;\n /** Recipient's Mina B62 pubkey — one channel participant (the co-signer). */\n recipient: string;\n /** Swap maker's Mina B62 pubkey — the other channel participant. */\n swapSignerAddress: string;\n /** On-chain `depositTotal` (base units) for balance conservation. */\n depositTotal: bigint;\n /** On-chain `channelHash` (decimal Field) used to resolve A/B ordering. */\n onChainChannelHash: string;\n /** Recipient's Mina private key (big-endian hex scalar OR `EK…` base58). */\n recipientPrivateKey: string;\n /** Channel nonce baked into the on-chain `channelHash` (default 0). */\n channelNonce?: bigint;\n /** Override the deterministic salt (else {@link deriveMinaSalt}). */\n saltOverride?: bigint;\n /**\n * Maker's payment-channel-commitment-form signature over\n * `[commitment, nonce, channelHash]`. REQUIRED for a fully-redeemable claim;\n * when absent the returned claim reports {@link makerSignatureMissing}.\n */\n makerSignature?: MinaSignaturePair;\n}\n\n/** A dual-party claim assembled for the zkApp's `claimFromChannel`. */\nexport interface MinaCoSignedClaim {\n channelId: string;\n /** Balance credited to `participantA` (base units). */\n balanceA: bigint;\n /** Balance credited to `participantB` (`depositTotal - balanceA`). */\n balanceB: bigint;\n salt: bigint;\n nonce: bigint;\n channelNonce: bigint;\n /** `Poseidon([balanceA, balanceB, salt])`, decimal Field. */\n balanceCommitment: string;\n /** Participant A B62 (ordered to reproduce the on-chain `channelHash`). */\n participantA: string;\n /** Participant B B62. */\n participantB: string;\n /** Which participant slot the recipient/co-signer occupies. */\n recipientRole: 'A' | 'B';\n /** The recipient's co-signature over `[commitment, nonce, channelHash]`. */\n recipientSignature: MinaSignaturePair;\n /** Signature for participant A (recipient's or maker's per {@link recipientRole}). */\n signatureA?: MinaSignaturePair;\n /** Signature for participant B. */\n signatureB?: MinaSignaturePair;\n /** True when no maker co-signature was supplied (claim not yet redeemable). */\n makerSignatureMissing: boolean;\n}\n\n/** Extract the bare `{ r, s }` from a `buildMinaPaymentChannelProof` proof JSON. */\nfunction parseProofSignature(proofJson: string): MinaSignaturePair {\n const parsed = JSON.parse(proofJson) as {\n signature?: { r?: unknown; s?: unknown };\n };\n const r = parsed.signature?.r;\n const s = parsed.signature?.s;\n if (typeof r !== 'string' || typeof s !== 'string') {\n throw new Error('recipient co-signature proof did not carry string r/s');\n }\n return { r, s };\n}\n\n/**\n * Assemble a dual-party `claimFromChannel` claim from a verified swap claim.\n *\n * Pure crypto (GraphQL-read state is passed in): resolves the on-chain A/B\n * participant ordering by reproducing `channelHash`, conserves balances against\n * `depositTotal`, computes the Poseidon commitment, and produces the recipient's\n * co-signature over `[commitment, nonce, channelHash]` with `mina-signer`. No\n * o1js is loaded.\n *\n * @throws {MinaSettlementError} `CHANNEL_HASH_MISMATCH` when neither ordering of\n * `{recipient, swapSignerAddress}` reproduces `onChainChannelHash`;\n * `CUMULATIVE_EXCEEDS_DEPOSIT` when the credit exceeds the escrow.\n */\nexport async function buildMinaCoSignedClaim(\n inputs: MinaCoSignInputs\n): Promise<MinaCoSignedClaim> {\n const channelNonce = inputs.channelNonce ?? 0n;\n const salt =\n inputs.saltOverride ?? deriveMinaSalt(inputs.channelId, Number(inputs.nonce));\n\n if (inputs.cumulativeAmount > inputs.depositTotal) {\n throw new MinaSettlementError(\n 'CUMULATIVE_EXCEEDS_DEPOSIT',\n `claim cumulativeAmount (${inputs.cumulativeAmount}) exceeds the channel ` +\n `depositTotal (${inputs.depositTotal}) — cannot conserve balances`\n );\n }\n\n const { Poseidon, PublicKey } = await loadMinaPaymentChannelBindings();\n\n // Resolve which participant slot each party occupies by reproducing the\n // on-chain channelHash = Poseidon([A.x, B.x, channelNonce]). The zkApp verifies\n // signatureA against participantA and signatureB against participantB, so the\n // balances + signatures must be assigned to the ordering the channel was\n // opened with.\n const hashRecipientFirst = minaParticipantChannelHashField(\n Poseidon,\n PublicKey,\n inputs.recipient,\n inputs.swapSignerAddress,\n channelNonce\n ).toString();\n const hashMakerFirst = minaParticipantChannelHashField(\n Poseidon,\n PublicKey,\n inputs.swapSignerAddress,\n inputs.recipient,\n channelNonce\n ).toString();\n\n let participantA: string;\n let participantB: string;\n let recipientRole: 'A' | 'B';\n if (hashRecipientFirst === inputs.onChainChannelHash) {\n participantA = inputs.recipient;\n participantB = inputs.swapSignerAddress;\n recipientRole = 'A';\n } else if (hashMakerFirst === inputs.onChainChannelHash) {\n participantA = inputs.swapSignerAddress;\n participantB = inputs.recipient;\n recipientRole = 'B';\n } else {\n throw new MinaSettlementError(\n 'CHANNEL_HASH_MISMATCH',\n `neither ordering of recipient ${inputs.recipient} / maker ` +\n `${inputs.swapSignerAddress} reproduces the on-chain channelHash ` +\n `${inputs.onChainChannelHash} (channelNonce ${channelNonce})`\n );\n }\n\n // Recipient-credit: the recipient's balance is the cumulative amount; the\n // maker (funder) keeps the remainder. Assign to A/B by the resolved ordering.\n const recipientBalance = inputs.cumulativeAmount;\n const makerBalance = inputs.depositTotal - inputs.cumulativeAmount;\n const balanceA = recipientRole === 'A' ? recipientBalance : makerBalance;\n const balanceB = inputs.depositTotal - balanceA;\n\n const balanceCommitment = minaBalanceCommitment(\n Poseidon,\n balanceA,\n balanceB,\n salt\n ).toString();\n\n // Recipient co-signs [commitment, nonce, channelHash] with its own key over\n // the SAME participant-form channelHash the zkApp stored. Reuse the connector-\n // parity proof builder so signer and on-chain verifier cannot drift.\n const recipientPrivateKeyBase58 = hexToMinaBase58PrivateKey(\n inputs.recipientPrivateKey\n );\n const built = await buildMinaPaymentChannelProof({\n zkAppAddress: inputs.channelId,\n minaPrivateKeyBase58: recipientPrivateKeyBase58,\n signerPublicKey: inputs.recipient,\n balanceA,\n balanceB,\n salt,\n nonce: inputs.nonce,\n participantA,\n participantB,\n channelNonce,\n proofEncoding: 'json',\n });\n if (built.balanceCommitment !== balanceCommitment) {\n // Defense-in-depth: the proof builder recomputes the commitment; a mismatch\n // means the two Poseidon paths diverged (should never happen).\n throw new Error(\n `co-sign commitment drift: builder ${built.balanceCommitment} != ${balanceCommitment}`\n );\n }\n const recipientSignature = parseProofSignature(built.proof);\n\n const signatureA =\n recipientRole === 'A' ? recipientSignature : inputs.makerSignature;\n const signatureB =\n recipientRole === 'B' ? recipientSignature : inputs.makerSignature;\n\n return {\n channelId: inputs.channelId,\n balanceA,\n balanceB,\n salt,\n nonce: inputs.nonce,\n channelNonce,\n balanceCommitment,\n participantA,\n participantB,\n recipientRole,\n recipientSignature,\n ...(signatureA ? { signatureA } : {}),\n ...(signatureB ? { signatureB } : {}),\n makerSignatureMissing: inputs.makerSignature === undefined,\n };\n}\n\n/** Arguments handed to a {@link MinaClaimSubmitter} (o1js proving boundary). */\nexport interface MinaClaimSubmitArgs {\n /** Mina GraphQL endpoint bound as the active o1js network. */\n graphqlUrl: string;\n /** Channel zkApp address (B62). */\n channelId: string;\n balanceA: bigint;\n balanceB: bigint;\n salt: bigint;\n nonce: bigint;\n /** Participant pubkeys (B62), ordered to match the on-chain `channelHash`. */\n participantA: string;\n participantB: string;\n channelNonce: bigint;\n signatureA: MinaSignaturePair;\n signatureB: MinaSignaturePair;\n /** Fee-payer / submitter Mina private key (hex scalar or `EK…` base58). */\n feePayerPrivateKey: string;\n /** zkApp tx fee (nanomina); the default settler applies 0.1 MINA when unset. */\n txFeeNanomina?: bigint;\n}\n\n/**\n * Generates the o1js proof and broadcasts the `claimFromChannel` tx. Injected so\n * the assembly logic can be unit-tested without loading the WASM circuit runtime.\n */\nexport interface MinaClaimSubmitter {\n claimFromChannel(args: MinaClaimSubmitArgs): Promise<{ txHash: string }>;\n}\n\n/** Read seam for the on-chain channel state (default: plain GraphQL). */\nexport type MinaChannelStateReader = (\n graphqlUrl: string,\n zkAppAddress: string\n) => Promise<MinaOnChainChannelState>;\n\n/** Context for {@link submitMinaSettlement}. */\nexport interface MinaSettlementContext {\n /** Mina GraphQL URL (reads state + binds the o1js network). */\n graphqlUrl?: string;\n /** Recipient's Mina private key (co-signs; also the default fee payer). */\n recipientPrivateKey: string;\n /** Maker's on-chain-form co-signature over `[commitment, nonce, channelHash]`. */\n makerSignature?: MinaSignaturePair;\n /** Fee-payer key override (defaults to `recipientPrivateKey`). */\n feePayerPrivateKey?: string;\n /** zkApp tx fee (nanomina). */\n txFeeNanomina?: bigint;\n /** Channel nonce for the on-chain `channelHash` (default 0). */\n channelNonce?: bigint;\n /** Inject a state reader (tests). Defaults to {@link readMinaChannelState}. */\n reader?: MinaChannelStateReader;\n /** Inject a proof submitter (tests). Defaults to an o1js-backed settler. */\n submitter?: MinaClaimSubmitter;\n}\n\nexport interface MinaSettlementResult {\n txHash: string;\n}\n\n/**\n * Redeem a verified Mina swap claim via a co-signed on-chain `claimFromChannel`.\n *\n * Env/config-gated like the EVM path: with no `graphqlUrl` the caller gets\n * `NO_GRAPHQL_CONFIGURED` (built-not-submitted). Reads the live channel state,\n * asserts it is OPEN and the claim nonce advances the on-chain watermark,\n * assembles the dual-party claim ({@link buildMinaCoSignedClaim}), and — only\n * with a maker co-signature present — drives the o1js proof + broadcast.\n *\n * @throws {MinaSettlementError} with a stable {@link MinaSettlementErrorCode}.\n */\nexport async function submitMinaSettlement(\n bundle: SettlementBundle,\n context: MinaSettlementContext\n): Promise<MinaSettlementResult> {\n if (bundle.chainKind !== 'mina') {\n throw new MinaSettlementError(\n 'NOT_MINA_BUNDLE',\n `submitMinaSettlement only settles mina bundles (got ${bundle.chainKind} for ${bundle.chain})`\n );\n }\n if (!context.graphqlUrl) {\n throw new MinaSettlementError(\n 'NO_GRAPHQL_CONFIGURED',\n `no Mina graphqlUrl configured for \"${bundle.chain}\" — set minaChannel.graphqlUrl to enable receive-side settlement.`\n );\n }\n const graphqlUrl = context.graphqlUrl;\n const channelNonce = context.channelNonce ?? 0n;\n const nonce = BigInt(bundle.nonce);\n const cumulativeAmount = BigInt(bundle.cumulativeAmount);\n\n const read = context.reader ?? readMinaChannelState;\n const state = await read(graphqlUrl, bundle.channelId);\n\n if (state.channelState !== MINA_CHANNEL_STATE.OPEN) {\n throw new MinaSettlementError(\n 'CHANNEL_NOT_OPEN',\n `channel ${bundle.channelId} is not OPEN (channelState=${state.channelState}); claimFromChannel only applies to an OPEN channel.`\n );\n }\n if (nonce <= state.nonceField) {\n throw new MinaSettlementError(\n 'NONCE_NOT_ADVANCING',\n `claim nonce ${nonce} does not advance the on-chain nonceField ${state.nonceField} for ${bundle.channelId} (already claimed).`\n );\n }\n\n const claim = await buildMinaCoSignedClaim({\n channelId: bundle.channelId,\n nonce,\n cumulativeAmount,\n recipient: bundle.recipient,\n swapSignerAddress: bundle.swapSignerAddress,\n depositTotal: state.depositTotal,\n onChainChannelHash: state.channelHash,\n recipientPrivateKey: context.recipientPrivateKey,\n channelNonce,\n ...(context.makerSignature ? { makerSignature: context.makerSignature } : {}),\n });\n\n if (claim.makerSignatureMissing || !claim.signatureA || !claim.signatureB) {\n throw new MinaSettlementError(\n 'MINA_MAKER_COSIGN_REQUIRED',\n `on-chain claimFromChannel is dual-party: it needs the maker's ` +\n `payment-channel-commitment signature over [commitment, nonce, channelHash] ` +\n `in addition to the recipient's co-signature. The swap-wire claim only carries ` +\n `the maker's balanceProofFieldsMina signature (a different message), so the maker ` +\n `must additionally deliver an on-chain-form co-signature. Recipient co-signature ` +\n `assembled and ready (${bundle.channelId} nonce ${nonce}).`\n );\n }\n\n const submitter = context.submitter ?? createO1jsMinaClaimSubmitter();\n const { txHash } = await submitter.claimFromChannel({\n graphqlUrl,\n channelId: bundle.channelId,\n balanceA: claim.balanceA,\n balanceB: claim.balanceB,\n salt: claim.salt,\n nonce: claim.nonce,\n participantA: claim.participantA,\n participantB: claim.participantB,\n channelNonce: claim.channelNonce,\n signatureA: claim.signatureA,\n signatureB: claim.signatureB,\n feePayerPrivateKey:\n context.feePayerPrivateKey ?? context.recipientPrivateKey,\n ...(context.txFeeNanomina !== undefined\n ? { txFeeNanomina: context.txFeeNanomina }\n : {}),\n });\n return { txHash };\n}\n\n/** Default zkApp tx fee (0.1 MINA in nanomina) — real networks reject 0-fee txs. */\nconst DEFAULT_MINA_TX_FEE_NANOMINA = 100_000_000n;\n\n/**\n * Build the default o1js-backed {@link MinaClaimSubmitter}.\n *\n * Mirrors the connector's `MinaPaymentChannelSDK.claimFromChannel`: binds the\n * active Mina network, fetches the channel + fee-payer accounts, deserializes the\n * `{r,s}` signatures, and builds + proves + broadcasts the zkApp\n * `claimFromChannel` method. o1js and `@toon-protocol/mina-zkapp` are imported\n * dynamically INSIDE `claimFromChannel` so the non-Mina test suite never loads\n * the multi-hundred-MB WASM circuit runtime. Compilation + proving are slow\n * (30-120s) and only run against live devnet Mina.\n */\nexport function createO1jsMinaClaimSubmitter(): MinaClaimSubmitter {\n return {\n async claimFromChannel(args: MinaClaimSubmitArgs): Promise<{ txHash: string }> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const o1js: any = await import('o1js');\n const {\n Mina,\n PrivateKey,\n PublicKey,\n Field,\n Poseidon,\n Signature,\n fetchAccount,\n } = o1js;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const zkAppMod: any = await import('@toon-protocol/mina-zkapp');\n const PaymentChannel = zkAppMod.PaymentChannel;\n\n Mina.setActiveInstance(Mina.Network(args.graphqlUrl));\n\n const feePayerKey = PrivateKey.fromBase58(\n hexToMinaBase58PrivateKey(args.feePayerPrivateKey)\n );\n const feePayerPub = feePayerKey.toPublicKey();\n\n await PaymentChannel.compile();\n\n const zkAppPub = PublicKey.fromBase58(args.channelId);\n await fetchAccount({ publicKey: zkAppPub });\n await fetchAccount({ publicKey: feePayerPub });\n const zkApp = new PaymentChannel(zkAppPub);\n\n const balA = Field(args.balanceA);\n const balB = Field(args.balanceB);\n const saltField = Field(args.salt);\n const newNonce = Field(args.nonce);\n const channelNonce = Field(args.channelNonce);\n const newBalanceCommitment = Poseidon.hash([balA, balB, saltField]);\n const sigA = Signature.fromJSON({\n r: args.signatureA.r,\n s: args.signatureA.s,\n });\n const sigB = Signature.fromJSON({\n r: args.signatureB.r,\n s: args.signatureB.s,\n });\n const partA = PublicKey.fromBase58(args.participantA);\n const partB = PublicKey.fromBase58(args.participantB);\n\n const fee = (\n args.txFeeNanomina ?? DEFAULT_MINA_TX_FEE_NANOMINA\n ).toString();\n const txn = await Mina.transaction(\n { sender: feePayerPub, fee },\n async () => {\n await zkApp.claimFromChannel(\n balA,\n balB,\n saltField,\n sigA,\n sigB,\n partA,\n partB,\n channelNonce,\n newBalanceCommitment,\n newNonce\n );\n }\n );\n await txn.prove();\n const sent = await txn.sign([feePayerKey]).send();\n return { txHash: sent.hash ?? '' };\n } catch (err) {\n throw new MinaSettlementError(\n 'PROVING_FAILED',\n err instanceof Error ? err.message : String(err)\n );\n }\n },\n };\n}\n","import type { ConnectorAdminClient } from '@toon-protocol/core';\nimport {\n ValidationError,\n NetworkError,\n ConnectorError,\n UnauthorizedError,\n PeerNotFoundError,\n PeerAlreadyExistsError,\n} from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration for HttpConnectorAdmin.\n */\nexport interface HttpConnectorAdminConfig {\n /** Admin API base URL (e.g., 'http://localhost:8081') */\n adminUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client for testing (default: global fetch) */\n httpClient?: typeof fetch;\n}\n\n/**\n * Result of a bulk peer operation.\n */\nexport interface PeerOperationResult {\n /** Peer ID that was operated on */\n peerId: string;\n /** Whether the operation succeeded */\n success: boolean;\n /** Error that occurred (if failed) */\n error?: Error;\n}\n\n/**\n * HTTP-based connector admin client for managing ILP peers via REST API.\n *\n * Implements the ConnectorAdminClient interface using HTTP requests to the\n * connector's admin API (typically port 8081).\n *\n * @example\n * ```typescript\n * // Embedded mode (DirectConnectorAdmin)\n * const adminClient = new DirectConnectorAdmin(connectorNode);\n *\n * // HTTP mode (HttpConnectorAdmin)\n * const adminClient = new HttpConnectorAdmin({\n * adminUrl: 'http://localhost:8081'\n * });\n *\n * // Add peer\n * await adminClient.addPeer({\n * id: 'nostr-abc123',\n * url: 'btp+ws://alice.example.com:3000',\n * authToken: 'secret-token',\n * routes: [{ prefix: 'g.toon.alice' }]\n * });\n *\n * // Remove peer\n * await adminClient.removePeer('nostr-abc123');\n * ```\n *\n * @throws {ValidationError} Input validation failed (before HTTP request)\n * @throws {NetworkError} Connection failed (ECONNREFUSED, ETIMEDOUT)\n * @throws {UnauthorizedError} Admin API returned 401 (missing/invalid auth)\n * @throws {PeerAlreadyExistsError} Admin API returned 409 (duplicate peer)\n * @throws {PeerNotFoundError} Admin API returned 404 (peer not found)\n * @throws {ConnectorError} Admin API returned 5xx (server error)\n */\nexport class HttpConnectorAdmin implements ConnectorAdminClient {\n private readonly adminUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpConnectorAdminConfig) {\n this.adminUrl = config.adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Add a peer to the connector via the admin API.\n *\n * Validates peer config parameters and sends HTTP POST to /admin/peers.\n *\n * @param config - Peer configuration\n * @param config.id - Unique peer identifier (non-empty string)\n * @param config.url - BTP WebSocket URL (must start with 'btp+ws://' or 'btp+wss://')\n * @param config.authToken - Authentication token (non-empty string)\n * @param config.routes - Optional routing table entries\n * @param config.settlement - Optional settlement configuration\n *\n * @throws {ValidationError} Invalid peer config (missing id, invalid url, etc.)\n * @throws {PeerAlreadyExistsError} Peer with same ID already exists (409 Conflict)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async addPeer(config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }): Promise<void> {\n // Validate required fields\n if (\n !config.id ||\n typeof config.id !== 'string' ||\n config.id.trim() === ''\n ) {\n throw new ValidationError('Peer id must be a non-empty string');\n }\n\n if (\n !config.url ||\n typeof config.url !== 'string' ||\n config.url.trim() === ''\n ) {\n throw new ValidationError('Peer url must be a non-empty string');\n }\n\n // Validate BTP URL format (accept both ws:// and btp+ws:// formats)\n const hasWsPrefix =\n config.url.startsWith('ws://') || config.url.startsWith('wss://');\n const hasBtpPrefix =\n config.url.startsWith('btp+ws://') || config.url.startsWith('btp+wss://');\n\n if (!hasWsPrefix && !hasBtpPrefix) {\n throw new ValidationError(\n `Invalid BTP URL format: \"${config.url}\". Must start with 'ws://', 'wss://', 'btp+ws://', or 'btp+wss://'`\n );\n }\n\n // authToken can be empty string for BTP (doesn't require authentication)\n if (\n config.authToken === undefined ||\n config.authToken === null ||\n typeof config.authToken !== 'string'\n ) {\n throw new ValidationError(\n 'Peer authToken must be a string (can be empty for no auth)'\n );\n }\n\n // Validate routes (if provided)\n if (config.routes !== undefined) {\n if (!Array.isArray(config.routes)) {\n throw new ValidationError('Peer routes must be an array');\n }\n\n for (const route of config.routes) {\n if (\n !route.prefix ||\n typeof route.prefix !== 'string' ||\n route.prefix.trim() === ''\n ) {\n throw new ValidationError('Route prefix must be a non-empty string');\n }\n if (\n route.priority !== undefined &&\n typeof route.priority !== 'number'\n ) {\n throw new ValidationError('Route priority must be a number');\n }\n }\n }\n\n // Validate settlement (if provided)\n if (config.settlement !== undefined) {\n if (typeof config.settlement !== 'object' || config.settlement === null) {\n throw new ValidationError('Peer settlement must be an object');\n }\n\n if (\n !config.settlement.preference ||\n typeof config.settlement.preference !== 'string'\n ) {\n throw new ValidationError(\n 'Settlement preference must be a non-empty string'\n );\n }\n }\n\n // Send HTTP POST request with retry logic\n const url = `${this.adminUrl}/admin/peers`;\n\n await withRetry(async () => this.sendAddPeerRequest(url, config), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Remove a peer from the connector via the admin API.\n *\n * Sends HTTP DELETE to /admin/peers/:id.\n *\n * @param peerId - Unique peer identifier to remove (non-empty string)\n *\n * @throws {ValidationError} Invalid peerId (empty string)\n * @throws {PeerNotFoundError} Peer does not exist (404 Not Found)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async removePeer(peerId: string): Promise<void> {\n // Validate peerId\n if (!peerId || typeof peerId !== 'string' || peerId.trim() === '') {\n throw new ValidationError('peerId must be a non-empty string');\n }\n\n // Send HTTP DELETE request with retry logic\n const url = `${this.adminUrl}/admin/peers/${encodeURIComponent(peerId)}`;\n\n await withRetry(async () => this.sendRemovePeerRequest(url, peerId), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Add multiple peers in parallel for efficient bootstrapping.\n *\n * Uses Promise.allSettled() to execute peer additions concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param configs - Array of peer configurations to add\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.addPeers([\n * { id: 'peer1', url: 'btp+ws://...', authToken: 'token1' },\n * { id: 'peer2', url: 'btp+ws://...', authToken: 'token2' },\n * ]);\n *\n * results.forEach(result => {\n * if (result.success) {\n * console.log(`Added peer: ${result.peerId}`);\n * } else {\n * console.error(`Failed to add ${result.peerId}:`, result.error);\n * }\n * });\n * ```\n */\n async addPeers(\n configs: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }[]\n ): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n configs.map((config) => this.addPeer(config))\n );\n\n return results.map((result, index) => {\n const config = configs[index];\n return {\n peerId: config ? config.id : 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Remove multiple peers in parallel.\n *\n * Uses Promise.allSettled() to execute peer removals concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param peerIds - Array of peer IDs to remove\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.removePeers(['peer1', 'peer2', 'peer3']);\n *\n * const succeeded = results.filter(r => r.success).length;\n * console.log(`Removed ${succeeded}/${results.length} peers`);\n * ```\n */\n async removePeers(peerIds: string[]): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n peerIds.map((peerId) => this.removePeer(peerId))\n );\n\n return results.map((result, index) => {\n const peerId = peerIds[index];\n return {\n peerId: peerId ?? 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Send HTTP POST request to add a peer.\n * Separated for retry logic wrapping.\n */\n private async sendAddPeerRequest(\n url: string,\n config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }\n ): Promise<void> {\n // Normalize URL for connector API (expects ws:// or wss://)\n // Strip btp+ prefix if present, or use as-is if already plain ws://\n let connectorUrl = config.url;\n if (connectorUrl.startsWith('btp+')) {\n connectorUrl = connectorUrl.replace(/^btp\\+/, '');\n }\n\n try {\n const response = await this.httpClient(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...config,\n url: connectorUrl,\n }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (201 Created)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `POST ${url}`, config.id);\n } catch (error) {\n this.handleNetworkError(error, url, 'addPeer');\n }\n }\n\n /**\n * Send HTTP DELETE request to remove a peer.\n * Separated for retry logic wrapping.\n */\n private async sendRemovePeerRequest(\n url: string,\n peerId: string\n ): Promise<void> {\n try {\n const response = await this.httpClient(url, {\n method: 'DELETE',\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (204 No Content)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `DELETE ${url}`, peerId);\n } catch (error) {\n this.handleNetworkError(error, url, 'removePeer');\n }\n }\n\n /**\n * Handle network errors from HTTP requests.\n *\n * Converts connection failures, timeouts, and unknown errors to NetworkError.\n * Re-throws existing ToonClientError instances.\n *\n * @param error - Error thrown by HTTP client\n * @param url - Request URL (for error messages)\n * @param operation - Operation name (for error messages)\n * @throws {NetworkError} Network connection or timeout error\n */\n private handleNetworkError(\n error: unknown,\n url: string,\n operation: string\n ): never {\n // Timeout errors (AbortSignal)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request to ${url} timed out after ${this.timeout}ms`,\n error\n );\n }\n\n // Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failures)\n if (\n error instanceof Error &&\n (error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('ENOTFOUND'))\n ) {\n throw new NetworkError(\n `Failed to connect to connector admin API at ${url}: ${error.message}`,\n error\n );\n }\n\n // Re-throw if already a ToonClientError\n if (\n error instanceof ValidationError ||\n error instanceof PeerAlreadyExistsError ||\n error instanceof PeerNotFoundError ||\n error instanceof UnauthorizedError ||\n error instanceof ConnectorError\n ) {\n throw error;\n }\n\n // Unknown error - wrap in NetworkError\n throw new NetworkError(\n `Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Handle HTTP error responses from the admin API.\n *\n * Converts HTTP status codes to appropriate error types.\n *\n * @param response - HTTP response from admin API\n * @param endpoint - Endpoint being called (for error messages)\n * @param peerId - Peer ID (for error messages)\n * @throws {UnauthorizedError} 401 Unauthorized\n * @throws {PeerNotFoundError} 404 Not Found\n * @throws {PeerAlreadyExistsError} 409 Conflict\n * @throws {ConnectorError} 5xx Server Error\n */\n private async handleErrorResponse(\n response: Response,\n endpoint: string,\n peerId: string\n ): Promise<never> {\n const status = response.status;\n const statusText = response.statusText;\n\n // Try to extract error message from response body\n let errorMessage = '';\n try {\n const body = await response.text();\n if (body) {\n errorMessage = ` - ${body}`;\n }\n } catch {\n // Ignore body parsing errors\n }\n\n switch (status) {\n case 401:\n throw new UnauthorizedError(\n `Admin API authentication failed for ${endpoint}: ${statusText}${errorMessage}`\n );\n\n case 404:\n throw new PeerNotFoundError(\n `Peer not found: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n case 409:\n throw new PeerAlreadyExistsError(\n `Peer already exists: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n default:\n if (status >= 500) {\n throw new ConnectorError(\n `Connector admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n\n // Other 4xx errors (400, 403, etc.)\n throw new ConnectorError(\n `Admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n }\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { SwapPair } from '@toon-protocol/core';\n\n/**\n * The persisted receive-side watermark for one swap target channel: the\n * HIGHEST-NONCE verified chain-B claim per `(chain, channelId)` (toon-client\n * issue #352, rolling-swap spec toon-meta docs/rolling-swap.md §3.2/§9).\n *\n * Claims are cumulative balance proofs — a higher-nonce claim supersedes every\n * earlier one — so persisting only the winner per channel is lossless for\n * settlement: `buildSettlementTx` redeems exactly this claim. Superseded claims\n * are informational and dropped.\n */\nexport interface ReceivedClaimEntry {\n /** Target chain the claim settles on (`pair.to.chain`, e.g. `evm:base:8453`). */\n chain: string;\n /** Payment-channel id on the target chain (0x-hex for EVM, base58 otherwise). */\n channelId: string;\n /** Balance-proof nonce (strictly increasing per channel). */\n nonce: bigint;\n /** Cumulative transferred amount, target micro-units. */\n cumulativeAmount: bigint;\n /** Recipient address the claim pays (the sender's `chainRecipient`). */\n recipient: string;\n /** Swap peer's on-chain signer address the signature verified against. */\n swapSignerAddress: string;\n /** The verified signed claim bytes (chain-specific encoding). */\n claimBytes: Uint8Array;\n /** Optional swap-side claim id. */\n claimId?: string;\n /** The SwapPair the claim was priced against (settlement-time routing). */\n pair: SwapPair;\n /** Unix ms the winning claim was accepted off the wire. */\n receivedAt: number;\n /** Unix ms this entry was last advanced. */\n updatedAt: number;\n /** Unix ms of the last successful on-chain settlement submission. */\n settledAt?: number;\n /** Watermark nonce redeemed by that settlement (claims ≤ this are settled). */\n settledNonce?: bigint;\n /** Tx hash of the last settlement submission. */\n settleTxHash?: string;\n}\n\n/**\n * Persistence interface for received (chain-B) swap claims. Mirrors\n * {@link ChannelStore}'s sync surface; keyed by `(chain, channelId)`.\n */\nexport interface ReceivedClaimStore {\n save(entry: ReceivedClaimEntry): void;\n load(chain: string, channelId: string): ReceivedClaimEntry | undefined;\n list(): ReceivedClaimEntry[];\n delete(chain: string, channelId: string): void;\n}\n\ninterface JsonEntry {\n chain: string;\n channelId: string;\n /** Stored as strings to preserve bigint precision. */\n nonce: string;\n cumulativeAmount: string;\n recipient: string;\n swapSignerAddress: string;\n /** base64 */\n claimBytes: string;\n claimId?: string;\n pair: SwapPair;\n receivedAt: number;\n updatedAt: number;\n settledAt?: number;\n settledNonce?: string;\n settleTxHash?: string;\n}\n\nfunction key(chain: string, channelId: string): string {\n return `${chain}|${channelId}`;\n}\n\n/**\n * JSON file-backed {@link ReceivedClaimStore}. Synchronous I/O to match the\n * `JsonFileChannelStore` pattern (`ChannelStore.ts`); the parent directory is\n * created on first save so a fresh daemon home works out of the box.\n */\nexport class JsonFileReceivedClaimStore implements ReceivedClaimStore {\n private readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n save(entry: ReceivedClaimEntry): void {\n const data = this.readFile();\n data[key(entry.chain, entry.channelId)] = {\n chain: entry.chain,\n channelId: entry.channelId,\n nonce: entry.nonce.toString(),\n cumulativeAmount: entry.cumulativeAmount.toString(),\n recipient: entry.recipient,\n swapSignerAddress: entry.swapSignerAddress,\n claimBytes: Buffer.from(entry.claimBytes).toString('base64'),\n ...(entry.claimId !== undefined ? { claimId: entry.claimId } : {}),\n pair: entry.pair,\n receivedAt: entry.receivedAt,\n updatedAt: entry.updatedAt,\n ...(entry.settledAt !== undefined ? { settledAt: entry.settledAt } : {}),\n ...(entry.settledNonce !== undefined\n ? { settledNonce: entry.settledNonce.toString() }\n : {}),\n ...(entry.settleTxHash !== undefined\n ? { settleTxHash: entry.settleTxHash }\n : {}),\n };\n this.writeFile(data);\n }\n\n load(chain: string, channelId: string): ReceivedClaimEntry | undefined {\n const entry = this.readFile()[key(chain, channelId)];\n return entry ? fromJson(entry) : undefined;\n }\n\n list(): ReceivedClaimEntry[] {\n return Object.values(this.readFile()).map(fromJson);\n }\n\n delete(chain: string, channelId: string): void {\n const data = this.readFile();\n const { [key(chain, channelId)]: _, ...rest } = data;\n this.writeFile(rest);\n }\n\n private readFile(): Record<string, JsonEntry> {\n if (!existsSync(this.filePath)) {\n return {};\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as Record<string, JsonEntry>;\n }\n\n private writeFile(data: Record<string, JsonEntry>): void {\n mkdirSync(dirname(this.filePath), { recursive: true });\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n}\n\n/**\n * In-memory {@link ReceivedClaimStore} for tests and path-less configs. NOT\n * restart-safe — a daemon should always be given a `JsonFileReceivedClaimStore`.\n */\nexport class InMemoryReceivedClaimStore implements ReceivedClaimStore {\n private readonly entries = new Map<string, ReceivedClaimEntry>();\n\n save(entry: ReceivedClaimEntry): void {\n this.entries.set(key(entry.chain, entry.channelId), { ...entry });\n }\n\n load(chain: string, channelId: string): ReceivedClaimEntry | undefined {\n const entry = this.entries.get(key(chain, channelId));\n return entry ? { ...entry } : undefined;\n }\n\n list(): ReceivedClaimEntry[] {\n return [...this.entries.values()].map((e) => ({ ...e }));\n }\n\n delete(chain: string, channelId: string): void {\n this.entries.delete(key(chain, channelId));\n }\n}\n\nfunction fromJson(entry: JsonEntry): ReceivedClaimEntry {\n return {\n chain: entry.chain,\n channelId: entry.channelId,\n nonce: BigInt(entry.nonce),\n cumulativeAmount: BigInt(entry.cumulativeAmount),\n recipient: entry.recipient,\n swapSignerAddress: entry.swapSignerAddress,\n claimBytes: new Uint8Array(Buffer.from(entry.claimBytes, 'base64')),\n ...(entry.claimId !== undefined ? { claimId: entry.claimId } : {}),\n pair: entry.pair,\n receivedAt: entry.receivedAt,\n updatedAt: entry.updatedAt,\n ...(entry.settledAt !== undefined ? { settledAt: entry.settledAt } : {}),\n ...(entry.settledNonce !== undefined\n ? { settledNonce: BigInt(entry.settledNonce) }\n : {}),\n ...(entry.settleTxHash !== undefined\n ? { settleTxHash: entry.settleTxHash }\n : {}),\n };\n}\n","/**\n * Receive-side swap-claim ingestion + verification (toon-client#352, part of\n * the rolling-swap epic toon-meta#145; spec: toon-meta docs/rolling-swap.md\n * §3.2/§9 dependency 1).\n *\n * The deployed client used to accept swapped-in chain-B claims blind: no\n * signature check, no watermark, no persistence — `buildSettlementTx` /\n * `verifyAccumulatedClaim` had zero call sites in this repo. This module is\n * the missing pipeline: every accumulated claim harvested from a swap stream\n * is VERIFIED at receipt time and, only if it passes, persisted as the\n * highest-nonce watermark for its `(chain, channelId)` in a\n * {@link ReceivedClaimStore}. A claim that fails verification is NEVER counted\n * as value received — failures are loud and result-shaped, not thrown.\n *\n * Verification order per claim (cheapest first, every check fail-closed):\n * 1. settlement metadata completeness — a claim missing any of channelId /\n * nonce / cumulativeAmount / recipient / swapSignerAddress cannot be\n * settled (`MISSING_SETTLEMENT_METADATA` later) and is bucketed `legacy`\n * (the pre-rename-peer path, #349) rather than rejected: the existing\n * loud swap-time warning stays the surface for those.\n * 2. chain consistency — the claim must settle on the session's target chain.\n * 3. recipient — must equal the session `chainRecipient` (EVM\n * case-insensitive), the anti-substitution check.\n * 4. advertised signer — when the caller supplies the maker's advertised\n * `swapSignerAddress` (kind:10032 discovery / operator config), the\n * claim's self-reported signer must match (`SWAP_SIGNER_MISMATCH`,\n * sdk 2.x vocabulary).\n * 5. signature — against the expected signer (advertised address when given,\n * else the claim's self-reported one). EVM claims verify against the\n * **v2 EIP-712 domain-separated** balance-proof digest\n * (`verifyEvmClaimSignature`, connector#324 finding #1) — the digest binds\n * `chainId` + `verifyingContract`, so `tokenNetworks[chain]` (the\n * RollingSwapChannel address) and a numeric chain id are REQUIRED (an EVM\n * claim without them is rejected `MISSING_CHAIN_CONFIG`, fail-closed).\n * Solana/Mina keep the sdk `verifyAccumulatedClaim` path (their domain is\n * folded into the message).\n * 6. signer pinning — a claim may not silently rotate the signer of an\n * already-persisted watermark for the same channel.\n * 7. nonce/cumulative monotonicity vs the locally persisted watermark —\n * strictly increasing nonce AND cumulative; the cumulative advance must\n * cover this packet's `targetAmount` (an under-delivering advance means\n * the maker short-paid the packet).\n */\n\nimport type { AccumulatedClaim } from '@toon-protocol/sdk/swap';\nimport {\n verifyAccumulatedClaim,\n type MinaSignerClientLike,\n} from '@toon-protocol/sdk';\nimport type {\n ReceivedClaimEntry,\n ReceivedClaimStore,\n} from '../channel/ReceivedClaimStore.js';\nimport { verifyEvmClaimSignature } from './evm-claim-digest.js';\nimport { parseEvmChainId } from './settle-received-claims.js';\n\n/** Why a claim was rejected at receipt time. Codes are stable API. */\nexport type ReceivedClaimRejectionCode =\n | 'CHAIN_MISMATCH'\n | 'RECIPIENT_MISMATCH'\n | 'SWAP_SIGNER_MISMATCH'\n | 'SIGNER_MISMATCH'\n | 'SIGNATURE_INVALID'\n | 'MINA_VERIFICATION_UNSUPPORTED'\n | 'UNSUPPORTED_CHAIN'\n | 'NON_MONOTONIC_NONCE'\n | 'NON_MONOTONIC_CUMULATIVE'\n | 'CUMULATIVE_SHORTFALL'\n | 'MISSING_CHAIN_CONFIG'\n | 'MALFORMED_METADATA';\n\nexport interface ReceivedClaimRejection {\n claim: AccumulatedClaim;\n code: ReceivedClaimRejectionCode;\n message: string;\n}\n\nexport interface VerifiedReceivedClaim {\n claim: AccumulatedClaim;\n /** How far this claim advanced the persisted cumulative watermark. */\n watermarkAdvance: bigint;\n}\n\nexport interface IngestReceivedClaimsParams {\n /** Accumulated claims off a swap stream (`streamSwap().claims`), in order. */\n claims: readonly AccumulatedClaim[];\n /** The session's target chain (`pair.to.chain` of the requested swap). */\n expectedChain: string;\n /** The session's payout address on `expectedChain`. */\n chainRecipient: string;\n /**\n * The maker's ADVERTISED on-chain signer address for `expectedChain`\n * (kind:10032 discovery or operator-supplied). When set, a claim whose\n * self-reported `swapSignerAddress` differs is rejected with\n * `SWAP_SIGNER_MISMATCH` and the signature is verified against THIS address,\n * never the claim's own.\n */\n expectedSignerAddress?: string;\n /**\n * Per-chain settlement contract addresses (the deployed `RollingSwapChannel`\n * / `verifyingContract`), keyed by the FULL chain key (e.g. `evm:base:8453`).\n * Matches the daemon config's `tokenNetworks` map and what\n * `buildSwapSettlements` already threads on the submit side.\n *\n * REQUIRED for EVM claims under the v2 EIP-712 digest (connector#324 finding\n * #1): the claim signature is domain-separated over `(chainId,\n * verifyingContract)`, so an EVM claim whose chain key lacks a `tokenNetworks`\n * entry (or a numeric chain id) is rejected `MISSING_CHAIN_CONFIG` — it cannot\n * be verified fail-closed without the domain. Supplied by the connector/swap\n * session context (the RollingSwapChannel the client settles against). Unused\n * for Solana/Mina claims, which fold their domain into the message itself.\n */\n tokenNetworks?: Record<string, string>;\n /** Durable watermark store; verified claims are persisted here. */\n store: ReceivedClaimStore;\n /** Pre-loaded `mina-signer` client — required to verify `mina:*` claims. */\n minaSignerClient?: MinaSignerClientLike;\n /** Clock seam for tests. */\n now?: () => number;\n}\n\nexport interface IngestReceivedClaimsResult {\n /** Claims that passed every check and advanced a persisted watermark. */\n verified: VerifiedReceivedClaim[];\n /** Claims that failed verification. NEVER counted as value received. */\n rejected: ReceivedClaimRejection[];\n /**\n * Claims missing settlement metadata (pre-rename / legacy swap peer, #349).\n * Not verified, not persisted, surfaced through the existing swap-time\n * warning — behavior for legacy no-metadata swaps is unchanged.\n */\n legacy: AccumulatedClaim[];\n /** Total watermark advance across verified claims (target micro-units). */\n valueReceived: bigint;\n}\n\n/** True when the claim carries every settlement-context field. */\nexport function hasSettlementMetadata(\n claim: AccumulatedClaim\n): claim is AccumulatedClaim &\n Required<\n Pick<\n AccumulatedClaim,\n | 'channelId'\n | 'nonce'\n | 'cumulativeAmount'\n | 'recipient'\n | 'swapSignerAddress'\n >\n > {\n return (\n claim.channelId !== undefined &&\n claim.nonce !== undefined &&\n claim.cumulativeAmount !== undefined &&\n claim.recipient !== undefined &&\n claim.swapSignerAddress !== undefined\n );\n}\n\n/** EVM addresses compare case-insensitively; other chains byte-exact. */\nfunction sameAddress(chain: string, a: string, b: string): boolean {\n return chain.startsWith('evm')\n ? a.toLowerCase() === b.toLowerCase()\n : a === b;\n}\n\n/** Map a `verifyAccumulatedClaim` reason string onto a stable rejection code. */\nfunction signatureRejectionCode(reason: string): ReceivedClaimRejectionCode {\n if (reason.startsWith('SIGNER_MISMATCH')) return 'SIGNER_MISMATCH';\n if (reason.startsWith('MINA_VERIFICATION_UNSUPPORTED'))\n return 'MINA_VERIFICATION_UNSUPPORTED';\n if (reason.startsWith('UNSUPPORTED_CHAIN')) return 'UNSUPPORTED_CHAIN';\n return 'SIGNATURE_INVALID';\n}\n\n/**\n * Verify a batch of received chain-B claims and persist the winning watermark\n * per `(chain, channelId)`. See the module doc for the check ladder.\n *\n * Result-shaped: never throws on a bad claim — it lands in `rejected` with a\n * stable code, and later claims are still processed (a channel's watermark\n * only advances through claims that verified).\n */\nexport function ingestReceivedClaims(\n params: IngestReceivedClaimsParams\n): IngestReceivedClaimsResult {\n const now = params.now ?? Date.now;\n const verified: VerifiedReceivedClaim[] = [];\n const rejected: ReceivedClaimRejection[] = [];\n const legacy: AccumulatedClaim[] = [];\n let valueReceived = 0n;\n\n // Session-local watermarks layered over the store so a multi-packet stream\n // is checked claim-against-claim without re-reading the file per packet.\n const watermarks = new Map<string, ReceivedClaimEntry>();\n const reject = (\n claim: AccumulatedClaim,\n code: ReceivedClaimRejectionCode,\n message: string\n ): void => {\n rejected.push({ claim, code, message });\n };\n\n for (const claim of params.claims) {\n if (!hasSettlementMetadata(claim)) {\n legacy.push(claim);\n continue;\n }\n const chain = claim.pair.to.chain;\n if (chain !== params.expectedChain) {\n reject(\n claim,\n 'CHAIN_MISMATCH',\n `claim settles on \"${chain}\", session expected \"${params.expectedChain}\"`\n );\n continue;\n }\n if (!sameAddress(chain, claim.recipient, params.chainRecipient)) {\n reject(\n claim,\n 'RECIPIENT_MISMATCH',\n `claim recipient \"${claim.recipient}\" is not the session chainRecipient \"${params.chainRecipient}\"`\n );\n continue;\n }\n if (\n params.expectedSignerAddress !== undefined &&\n !sameAddress(chain, claim.swapSignerAddress, params.expectedSignerAddress)\n ) {\n reject(\n claim,\n 'SWAP_SIGNER_MISMATCH',\n `claim swapSignerAddress \"${claim.swapSignerAddress}\" does not match the maker's advertised signer \"${params.expectedSignerAddress}\"`\n );\n continue;\n }\n const expectedSigner =\n params.expectedSignerAddress ?? claim.swapSignerAddress;\n // Signature check. EVM uses the v2 EIP-712 domain-separated digest\n // (connector#324 finding #1): the digest binds `chainId` +\n // `verifyingContract`, so the recovered signer is only valid for the exact\n // (chain, deployment) the claim settles against — closing cross-chain /\n // cross-deployment replay. Solana/Mina fold their domain into the message\n // itself and stay on the sdk `verifyAccumulatedClaim` path.\n let sig: { valid: true } | { valid: false; reason: string };\n if (chain.startsWith('evm')) {\n const chainId = parseEvmChainId(chain);\n const verifyingContract = params.tokenNetworks?.[chain];\n if (chainId === undefined || !verifyingContract) {\n reject(\n claim,\n 'MISSING_CHAIN_CONFIG',\n `EVM v2 balance-proof verification for ${chain} needs a numeric chain id in the ` +\n `chain key AND tokenNetworks[\"${chain}\"] (the RollingSwapChannel / verifyingContract) ` +\n `to reconstruct the EIP-712 domain; supply it from the connector/swap session context.`\n );\n continue;\n }\n let msgNonce: bigint;\n let msgCumulative: bigint;\n try {\n msgNonce = BigInt(claim.nonce);\n msgCumulative = BigInt(claim.cumulativeAmount);\n } catch {\n reject(\n claim,\n 'MALFORMED_METADATA',\n `nonce \"${claim.nonce}\" / cumulativeAmount \"${claim.cumulativeAmount}\" are not decimal integers`\n );\n continue;\n }\n sig = verifyEvmClaimSignature({\n ctx: { chainId, verifyingContract },\n message: {\n channelId: claim.channelId,\n cumulativeAmount: msgCumulative,\n nonce: msgNonce,\n recipient: claim.recipient,\n },\n signature: claim.claimBytes,\n expectedSigner,\n });\n } else {\n sig = verifyAccumulatedClaim(\n claim,\n { address: expectedSigner },\n params.minaSignerClient\n );\n }\n if (!sig.valid) {\n reject(claim, signatureRejectionCode(sig.reason), sig.reason);\n continue;\n }\n\n let nonce: bigint;\n let cumulativeAmount: bigint;\n try {\n nonce = BigInt(claim.nonce);\n cumulativeAmount = BigInt(claim.cumulativeAmount);\n } catch {\n reject(\n claim,\n 'MALFORMED_METADATA',\n `nonce \"${claim.nonce}\" / cumulativeAmount \"${claim.cumulativeAmount}\" are not decimal integers`\n );\n continue;\n }\n\n const wmKey = `${chain}|${claim.channelId}`;\n const watermark =\n watermarks.get(wmKey) ?? params.store.load(chain, claim.channelId);\n\n if (watermark) {\n if (!sameAddress(chain, watermark.swapSignerAddress, expectedSigner)) {\n reject(\n claim,\n 'SWAP_SIGNER_MISMATCH',\n `channel ${claim.channelId} watermark was signed by \"${watermark.swapSignerAddress}\"; a claim signed by \"${expectedSigner}\" may not rotate it`\n );\n continue;\n }\n if (nonce <= watermark.nonce) {\n reject(\n claim,\n 'NON_MONOTONIC_NONCE',\n `nonce ${nonce} does not advance the persisted watermark nonce ${watermark.nonce} for channel ${claim.channelId}`\n );\n continue;\n }\n if (cumulativeAmount <= watermark.cumulativeAmount) {\n reject(\n claim,\n 'NON_MONOTONIC_CUMULATIVE',\n `cumulativeAmount ${cumulativeAmount} does not advance the persisted watermark ${watermark.cumulativeAmount} for channel ${claim.channelId}`\n );\n continue;\n }\n }\n\n // The watermark advance is the value this claim actually adds. It must\n // cover the packet's expected targetAmount — a smaller advance means the\n // maker short-paid the packet (e.g. re-signed value we already held).\n const advance = cumulativeAmount - (watermark?.cumulativeAmount ?? 0n);\n if (advance < claim.targetAmount) {\n reject(\n claim,\n 'CUMULATIVE_SHORTFALL',\n `cumulative advance ${advance} is less than the packet's targetAmount ${claim.targetAmount} for channel ${claim.channelId}`\n );\n continue;\n }\n\n const entry: ReceivedClaimEntry = {\n chain,\n channelId: claim.channelId,\n nonce,\n cumulativeAmount,\n recipient: claim.recipient,\n swapSignerAddress: expectedSigner,\n claimBytes: claim.claimBytes,\n ...(claim.claimId !== undefined ? { claimId: claim.claimId } : {}),\n pair: claim.pair,\n receivedAt: claim.receivedAt,\n updatedAt: now(),\n // Settlement bookkeeping survives watermark advances.\n ...(watermark?.settledAt !== undefined\n ? { settledAt: watermark.settledAt }\n : {}),\n ...(watermark?.settledNonce !== undefined\n ? { settledNonce: watermark.settledNonce }\n : {}),\n ...(watermark?.settleTxHash !== undefined\n ? { settleTxHash: watermark.settleTxHash }\n : {}),\n };\n params.store.save(entry);\n watermarks.set(wmKey, entry);\n verified.push({ claim, watermarkAdvance: advance });\n valueReceived += advance;\n }\n\n return { verified, rejected, legacy, valueReceived };\n}\n","/**\n * EVM rolling-swap **v2** balance-proof claim digest (EIP-712 domain-separated).\n *\n * Refs connector#324 **finding #1** / connector#325 (canonical spec:\n * `docs/rolling-swap-v2-digest-spec.md`). This is the client's byte-for-byte\n * conformance anchor for the v2 migration.\n *\n * ## Why this exists in the client\n *\n * The v1 digest that shipped in `@toon-protocol/core` `balanceProofHashEvm`\n * (imported transitively by the sdk's `verifyAccumulatedClaim` /\n * `buildSettlementTx`, which is what this repo used to lean on) was\n *\n * keccak256( channelId(32) || cumulativeAmount(32BE)\n * || nonce(32BE) || recipient(20) )\n *\n * — it bound **neither** `chainId` **nor** the settling contract address, so a\n * signer-signed claim redeemed on one (chain, deployment) could be replayed\n * verbatim on another for the same tuple (cross-chain / cross-deployment\n * replay). v2 folds `chainId` **and** `verifyingContract` into the signed\n * preimage via a standard **EIP-712** typed-data domain, so a signature is\n * valid on **exactly one `(chainId, contract)` pair**, and the `version=\"2\"`\n * string makes the cutover fail-closed (a v1 raw-keccak sig can never validate\n * as v2 and vice-versa).\n *\n * ## Lockstep dependency\n *\n * The canonical v2 digest util lives in `@toon-protocol/core`\n * (`balanceProofHashEvm` → EIP-712) + `@toon-protocol/sdk`\n * (`verifyAccumulatedClaim` / `buildSettlementTx`). Those packages migrate in\n * lockstep (connector#324 release order: core/sdk → swap → client). Until a v2\n * core/sdk is published, this module is the client's own v2 digest so the\n * receive-side verify (see {@link file://./received-claims.ts}) is already\n * domain-separated and the golden-vector conformance test\n * (`evm-claim-digest.test.ts`) pins the exact literals from the spec. When the\n * v2 core/sdk ships, the recompute here should delegate to it (or be asserted\n * byte-identical to it) — the golden-vector test stays as the shared fixture.\n *\n * The signer leg (`swap` `EvmPaymentChannelSigner.signBalanceProof`) MUST also\n * move to this same EIP-712 preimage and now REQUIRES `chainId` +\n * `verifyingContract` inputs it did not take before.\n */\n\nimport {\n hashTypedData,\n type Hex,\n type Address,\n type TypedDataDomain,\n} from 'viem';\nimport { secp256k1 } from '@noble/curves/secp256k1.js';\nimport { keccak_256 } from '@noble/hashes/sha3.js';\nimport { hexToBytes, bytesToHex } from '@noble/hashes/utils.js';\n\n/** EIP-712 domain `name` for the RollingSwapChannel (spec §2.1). */\nexport const ROLLING_SWAP_DOMAIN_NAME = 'RollingSwapChannel' as const;\n/** EIP-712 domain `version` — `\"2\"` is the domain-separated migration. */\nexport const ROLLING_SWAP_DOMAIN_VERSION = '2' as const;\n\n/**\n * `keccak256(\"ClaimBalanceProof(bytes32 channelId,uint256 cumulativeAmount,uint256 nonce,address recipient)\")`.\n * Pinned from the spec; asserted in `evm-claim-digest.test.ts`.\n */\nexport const CLAIM_TYPEHASH =\n '0xa0c8262c1a8615f7674d3af796b14d19672d3634f89c6093502ab35c0afe2d91' as const;\n/**\n * `keccak256(\"CooperativeClose(bytes32 channelId,uint256 cumulativeAmount,uint256 nonce)\")`.\n * Pinned from the spec; asserted in `evm-claim-digest.test.ts`.\n */\nexport const COOP_CLOSE_TYPEHASH =\n '0xa5753389755fea51cd5016d7b02b508ac03f2e822d9a7ee345ec45b36574ff9f' as const;\n\nconst CLAIM_TYPES = {\n ClaimBalanceProof: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'cumulativeAmount', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n { name: 'recipient', type: 'address' },\n ],\n} as const;\n\nconst COOP_CLOSE_TYPES = {\n CooperativeClose: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'cumulativeAmount', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n ],\n} as const;\n\n/** The two chain-context inputs v2 adds over v1 (the domain binding). */\nexport interface EvmClaimDomainContext {\n /** Settlement chain id (`block.chainid` on-chain). e.g. `8453` for Base. */\n chainId: number;\n /** Deployed `RollingSwapChannel` address (`address(this)` / EIP-712 `verifyingContract`). */\n verifyingContract: string;\n}\n\n/** Fields of the `ClaimBalanceProof` message (the balance-proof claim leg). */\nexport interface EvmClaimMessage {\n /** Channel id, 0x-prefixed 32-byte hex. */\n channelId: string;\n /** Cumulative transferred amount (target micro-units). */\n cumulativeAmount: bigint;\n /** Monotonic balance-proof nonce. */\n nonce: bigint;\n /** Recipient (the claim payout address), 0x-prefixed 20-byte hex. */\n recipient: string;\n}\n\n/** Fields of the `CooperativeClose` message (recipient close-ack leg). */\nexport interface EvmCooperativeCloseMessage {\n channelId: string;\n cumulativeAmount: bigint;\n nonce: bigint;\n}\n\n/**\n * viem validates EIP-712 `address` fields against their checksum; the wire\n * carries non-checksummed hex (e.g. an all-lowercase or all-uppercase\n * recipient). The address BYTES are what EIP-712 encodes, so lowercasing is a\n * no-op on the digest while side-stepping the checksum guard.\n */\nfunction normalizeAddress(addr: string): Address {\n return addr.toLowerCase() as Address;\n}\n\nfunction domainOf(ctx: EvmClaimDomainContext): TypedDataDomain {\n return {\n name: ROLLING_SWAP_DOMAIN_NAME,\n version: ROLLING_SWAP_DOMAIN_VERSION,\n chainId: ctx.chainId,\n verifyingContract: normalizeAddress(ctx.verifyingContract),\n };\n}\n\n/**\n * The v2 EIP-712 claim digest — `keccak256(0x1901 || domainSeparator ||\n * hashStruct(ClaimBalanceProof))` — that the on-chain `updateBalance` verifier\n * (and the swap signer) produce. This is what a received claim's signature must\n * recover against.\n */\nexport function evmClaimDigest(\n ctx: EvmClaimDomainContext,\n message: EvmClaimMessage\n): Hex {\n return hashTypedData({\n domain: domainOf(ctx),\n types: CLAIM_TYPES,\n primaryType: 'ClaimBalanceProof',\n message: {\n channelId: message.channelId as Hex,\n cumulativeAmount: message.cumulativeAmount,\n nonce: message.nonce,\n recipient: normalizeAddress(message.recipient),\n },\n });\n}\n\n/**\n * The v2 EIP-712 cooperative-close digest — same domain as the claim, distinct\n * type hash — that a recipient's close-ack signature must recover against.\n */\nexport function evmCooperativeCloseDigest(\n ctx: EvmClaimDomainContext,\n message: EvmCooperativeCloseMessage\n): Hex {\n return hashTypedData({\n domain: domainOf(ctx),\n types: COOP_CLOSE_TYPES,\n primaryType: 'CooperativeClose',\n message: {\n channelId: message.channelId as Hex,\n cumulativeAmount: message.cumulativeAmount,\n nonce: message.nonce,\n },\n });\n}\n\n/** A 65-byte `r || s || v` signature, as 0x-hex or raw bytes. */\nexport type ClaimSignature = Hex | Uint8Array;\n\n/** Byte length of a raw `r||s||v` signature. */\nconst SIG_BYTES = 65;\n\nfunction signatureToBytes(sig: ClaimSignature): Uint8Array {\n if (typeof sig !== 'string') return sig;\n const hex = sig.startsWith('0x') ? sig.slice(2) : sig;\n return hexToBytes(hex);\n}\n\n/**\n * Recover the EVM address that signed a v2 claim digest. Uses the same\n * `@noble/curves` secp256k1 recovery + keccak address derivation as the sdk's\n * on-chain-matching `recoverEvmSignerAddress`, over the **v2** EIP-712 digest.\n * Enforces the same 65-byte `r||s||v`, `v ∈ {27,28}` envelope the on-chain OZ\n * `ECDSA.recover` accepts (fail-closed on a malformed signature).\n *\n * @returns the recovered address (lowercase 0x-hex).\n * @throws if the signature is not 65 bytes or `v` is not 27/28.\n */\nexport function recoverEvmClaimSigner(\n ctx: EvmClaimDomainContext,\n message: EvmClaimMessage,\n signature: ClaimSignature\n): Address {\n const bytes = signatureToBytes(signature);\n if (bytes.length !== SIG_BYTES) {\n throw new Error(\n `EVM signature must be 65 bytes (r||s||v), got ${bytes.length}`\n );\n }\n const v = bytes[64];\n if (v !== 27 && v !== 28) {\n throw new Error(`EVM signature v must be 27 or 28, got ${v}`);\n }\n const digest = evmClaimDigest(ctx, message).slice(2);\n const recovered = secp256k1.Signature.fromBytes(bytes.slice(0, 64), 'compact')\n .addRecoveryBit(v - 27)\n .recoverPublicKey(hexToBytes(digest));\n const uncompressed = recovered.toBytes(false);\n const addrHash = keccak_256(uncompressed.slice(1));\n return `0x${bytesToHex(addrHash.slice(-20))}` as Address;\n}\n\n/** Result of {@link verifyEvmClaimSignature}. Mirrors the sdk verify shape. */\nexport type EvmClaimVerifyResult =\n | { valid: true; recovered: Address }\n | { valid: false; reason: string };\n\n/**\n * Verify a received EVM balance-proof claim: recover the v2-digest signer and\n * check it equals `expectedSigner` (case-insensitive, per EVM address rules).\n *\n * Result-shaped — never throws; a malformed signature or a mismatch is a\n * `{ valid: false, reason }` so the receive ladder can bucket it fail-closed.\n */\nexport function verifyEvmClaimSignature(params: {\n ctx: EvmClaimDomainContext;\n message: EvmClaimMessage;\n signature: ClaimSignature;\n expectedSigner: string;\n}): EvmClaimVerifyResult {\n let recovered: Address;\n try {\n recovered = recoverEvmClaimSigner(\n params.ctx,\n params.message,\n params.signature\n );\n } catch (err) {\n return {\n valid: false,\n reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (recovered.toLowerCase() !== params.expectedSigner.toLowerCase()) {\n return {\n valid: false,\n reason: `SIGNER_MISMATCH: recovered ${recovered}, expected ${params.expectedSigner}`,\n };\n }\n return { valid: true, recovered };\n}\n","/**\n * Per-packet preimage retention (toon-client#360, rolling-swap epic\n * toon-meta#145 — spec `docs/rolling-swap.md` §3 R1 / §3.2 leg-B reveal).\n *\n * `withSenderConditions` (toon-client#354) mints a fresh 32-byte preimage\n * `P_i` per fill packet, sets `C_i = sha256(P_i)` on the leg-A PREPARE, and —\n * before this module — DISCARDED `P_i`. Leg-B reveal (spec §3.2) is the commit\n * act: the sender reveals `P_i` only AFTER verifying the leg-B claim, so the\n * daemon must retain each `P_i` from mint time until the reveal step consumes\n * it. This is that retention seam.\n *\n * Keyed by `packetIndex` — the 0-indexed position in the swap's packet stream.\n * `packetIndex` is the one identifier shared between the send side (the wrapper\n * mints one condition per `sendSwapPacket` call, which `streamSwap` issues in\n * strictly increasing `packetIndex` order) and the receive side\n * (`AccumulatedClaim.packetIndex`), so it correlates a retained preimage to the\n * claim whose reveal will consume it.\n *\n * Reveal is single-use: {@link PreimageRetentionStore.take} removes the entry\n * so a preimage is never revealed twice (spec R1 — a reused preimage lets an\n * observer of packet *i* fulfill packet *i+1* without the sender's consent).\n *\n * Session-scoped and in-memory by design: a preimage is only meaningful within\n * the swap stream that minted it (a fresh stream mints fresh preimages), and a\n * preimage revealed after a crash would commit leg A for a leg-B claim the\n * restarted daemon can no longer verify. Durability lives on the *watermark*\n * (`ReceivedClaimStore`), not here.\n */\n\n/** A retained per-packet hashlock secret awaiting (or past) its leg-B reveal. */\nexport interface RetainedPreimage {\n /** 0-indexed position in the swap's packet stream; the correlation key. */\n packetIndex: number;\n /** The 32-byte preimage `P_i` — revealed to commit leg A (spec R6). */\n preimage: Uint8Array;\n /** `C_i = sha256(P_i)` — what went on the leg-A PREPARE (kept for auditing). */\n condition: Uint8Array;\n /** Unix ms the preimage was minted/retained. */\n retainedAt: number;\n}\n\n/**\n * Retention interface for per-packet preimages, keyed by `packetIndex`. Mirrors\n * the sync surface of the daemon's other stores ({@link ReceivedClaimStore}).\n */\nexport interface PreimageRetentionStore {\n /** Retain `entry`, replacing any prior entry for the same `packetIndex`. */\n retain(entry: RetainedPreimage): void;\n /** Peek the retained preimage for `packetIndex` without consuming it. */\n get(packetIndex: number): RetainedPreimage | undefined;\n /** Consume (return AND remove) the preimage — single-use reveal (spec R1). */\n take(packetIndex: number): RetainedPreimage | undefined;\n /** Number of preimages still retained (awaiting reveal). */\n size(): number;\n /** Drop every retained preimage — call when the session ends. */\n clear(): void;\n}\n\n/** Defensive copy so a retained preimage cannot be mutated after the fact. */\nfunction copy(entry: RetainedPreimage): RetainedPreimage {\n return {\n packetIndex: entry.packetIndex,\n preimage: Uint8Array.from(entry.preimage),\n condition: Uint8Array.from(entry.condition),\n retainedAt: entry.retainedAt,\n };\n}\n\n/**\n * In-memory {@link PreimageRetentionStore}. The only implementation: preimages\n * are session-scoped secrets that MUST NOT outlive their stream (see the module\n * doc), so there is deliberately no file-backed variant.\n */\nexport class InMemoryPreimageRetentionStore implements PreimageRetentionStore {\n private readonly entries = new Map<number, RetainedPreimage>();\n\n retain(entry: RetainedPreimage): void {\n this.entries.set(entry.packetIndex, copy(entry));\n }\n\n get(packetIndex: number): RetainedPreimage | undefined {\n const entry = this.entries.get(packetIndex);\n return entry ? copy(entry) : undefined;\n }\n\n take(packetIndex: number): RetainedPreimage | undefined {\n const entry = this.entries.get(packetIndex);\n if (!entry) return undefined;\n this.entries.delete(packetIndex);\n return copy(entry);\n }\n\n size(): number {\n return this.entries.size;\n }\n\n clear(): void {\n this.entries.clear();\n }\n}\n","/**\n * Atomic verify -> persist -> reveal composition (toon-client#360, rolling-swap\n * epic toon-meta#145 — spec `docs/rolling-swap.md` §3 R5/R8, §3.2 leg-B reveal).\n *\n * {@link ingestReceivedClaims} (toon-client#358) verifies a received chain-B\n * claim and persists its watermark AT VERIFY TIME. But verification is not the\n * commit: leg-B reveal is (spec R5 — the sender reveals `P_i` only after the\n * claim verifies). A daemon that verifies a claim, advances the watermark, and\n * then WITHHOLDS or FAILS the reveal has advanced a watermark for value it never\n * committed to. Left there, engine rule R8 bites: after the maker rolls its own\n * side back it REUSES the rolled-back nonce for the next fill, and the client's\n * monotonicity check (`nonce <= watermark.nonce -> NON_MONOTONIC_NONCE`) would\n * falsely reject that legitimate re-fill.\n *\n * So verify/persist/reveal must compose as ONE unit whose durable effect is\n * \"watermark advanced iff the reveal committed\":\n *\n * 1. snapshot the prior watermark for the claim's `(chain, channelId)`;\n * 2. verify + persist via {@link ingestReceivedClaims} (single claim);\n * 3. reveal the retained preimage `P_i` for `claim.packetIndex`;\n * 4. on `withheld` / thrown / preimage-missing -> COMPENSATING ROLLBACK:\n * restore the snapshot (or delete when there was none), so the watermark\n * tracks only ACCEPTED/REVEALED packets and R8's reused nonce is accepted.\n *\n * Claims are processed strictly in order and each claim's fate is decided\n * before the next is verified, so cross-claim monotonicity flows through the\n * durable store: a rolled-back claim leaves no trace for the next to trip over.\n * Legacy no-metadata claims (spec §349 path) and hard verification rejects\n * never reach a reveal and never touch a watermark — behavior unchanged.\n */\n\nimport type { AccumulatedClaim } from '@toon-protocol/sdk/swap';\nimport {\n ingestReceivedClaims,\n hasSettlementMetadata,\n type IngestReceivedClaimsParams,\n type ReceivedClaimRejection,\n} from './received-claims.js';\nimport type { ReceivedClaimEntry } from '../channel/ReceivedClaimStore.js';\nimport type {\n PreimageRetentionStore,\n RetainedPreimage,\n} from './preimage-retention.js';\n\n/** The sender's leg-B decision for a verified claim (spec R5). */\nexport type RevealDecision = 'revealed' | 'withheld';\n\nexport interface RevealResult {\n decision: RevealDecision;\n /** Human-readable detail, surfaced on a rolled-back record when withheld. */\n reason?: string;\n}\n\n/**\n * Reveal a verified claim's preimage to commit leg A, OR withhold it. Receives\n * the claim and the retained preimage for its `packetIndex` (`undefined` when\n * none was retained — e.g. a legacy zero-condition packet reaching this path).\n * Returning `withheld`, resolving to no decision, or THROWING all trigger the\n * compensating rollback; only `revealed` keeps the watermark advance.\n */\nexport type RevealFn = (\n claim: AccumulatedClaim,\n preimage: RetainedPreimage | undefined\n) => RevealResult | Promise<RevealResult>;\n\nexport interface IngestAndRevealParams extends IngestReceivedClaimsParams {\n /** Leg-B reveal decision, invoked once per VERIFIED claim (spec R5/R6). */\n reveal: RevealFn;\n /**\n * Retained per-packet preimages ({@link PreimageRetentionStore}). The reveal\n * for a verified claim CONSUMES (`take`) the preimage for its `packetIndex`,\n * so a preimage is never revealed twice (spec R1). Optional: without it the\n * reveal fn receives `undefined` and decides on its own.\n */\n preimages?: PreimageRetentionStore;\n}\n\nexport interface RevealedClaim {\n claim: AccumulatedClaim;\n /** How far the (now committed) watermark advanced. */\n watermarkAdvance: bigint;\n}\n\nexport interface RolledBackClaim {\n claim: AccumulatedClaim;\n /** The advance that was persisted then rolled back (never counted). */\n watermarkAdvance: bigint;\n /** Why the reveal did not commit (withheld reason, or the thrown error). */\n reason: string;\n}\n\nexport interface IngestAndRevealResult {\n /** Claims that verified AND revealed — the only ones whose value counts. */\n revealed: RevealedClaim[];\n /**\n * Claims that verified but whose reveal was withheld/failed: the watermark\n * advance was persisted then ROLLED BACK, so a subsequent re-fill reusing\n * the same nonce (engine R8) is accepted, not falsely rejected.\n */\n rolledBack: RolledBackClaim[];\n /** Claims that failed verification. NEVER revealed, NEVER persisted. */\n rejected: ReceivedClaimRejection[];\n /** Legacy no-metadata claims (spec §349). Unchanged: not verified/revealed. */\n legacy: AccumulatedClaim[];\n /** Total watermark advance across REVEALED claims only. */\n valueRevealed: bigint;\n}\n\n/**\n * Restore the watermark to `prior` (or delete it when the channel had none),\n * compensating a verify-time persist whose reveal did not commit.\n */\nfunction rollbackWatermark(\n store: IngestReceivedClaimsParams['store'],\n chain: string,\n channelId: string,\n prior: ReceivedClaimEntry | undefined\n): void {\n if (prior === undefined) {\n store.delete(chain, channelId);\n } else {\n store.save(prior);\n }\n}\n\n/**\n * Verify, persist, and reveal a batch of received chain-B claims atomically:\n * a verified claim's watermark advance survives iff its reveal commits, and is\n * rolled back otherwise (see the module doc for why R8 needs this).\n *\n * Never throws on a bad claim or a throwing reveal — both land in the result\n * (`rejected` / `rolledBack`) with the durable watermark left consistent.\n */\nexport async function ingestAndReveal(\n params: IngestAndRevealParams\n): Promise<IngestAndRevealResult> {\n const { reveal, preimages, claims, store, ...shared } = params;\n\n const revealed: RevealedClaim[] = [];\n const rolledBack: RolledBackClaim[] = [];\n const rejected: ReceivedClaimRejection[] = [];\n const legacy: AccumulatedClaim[] = [];\n let valueRevealed = 0n;\n\n for (const claim of claims) {\n // Verify + persist this one claim against the CURRENT durable watermark.\n // Single-claim so each claim's fate is sealed (revealed or rolled back)\n // before the next is checked — rollback leaves nothing for the next claim.\n //\n // A metadata-less (legacy) claim has no `(chain, channelId)` — and its\n // `pair.to` may be absent — so only snapshot when metadata is present, and\n // never dereference `pair.to.chain` before that guard.\n const hasMeta = hasSettlementMetadata(claim);\n const chain = hasMeta ? claim.pair.to.chain : undefined;\n const channelId = hasMeta ? claim.channelId : undefined;\n // Snapshot BEFORE persist so a withheld reveal can be compensated exactly.\n const prior =\n chain !== undefined && channelId !== undefined\n ? store.load(chain, channelId)\n : undefined;\n\n const ingest = ingestReceivedClaims({ ...shared, claims: [claim], store });\n\n if (ingest.legacy.length > 0) {\n legacy.push(claim);\n continue;\n }\n const rejection = ingest.rejected[0];\n if (rejection) {\n // Hard verification failure: nothing was persisted, nothing to reveal.\n rejected.push(rejection);\n continue;\n }\n\n const verified = ingest.verified[0];\n // A metadata-carrying claim that went neither legacy nor rejected verified\n // and was persisted, so `verified`, `chain`, and `channelId` are defined.\n // The guard is belt-and-suspenders for the type-checker and future changes.\n if (!verified || chain === undefined || channelId === undefined) continue;\n const advance = verified.watermarkAdvance;\n const preimage = preimages?.take(claim.packetIndex);\n\n let outcome: RevealResult;\n try {\n outcome = await reveal(claim, preimage);\n } catch (err) {\n outcome = {\n decision: 'withheld',\n reason: err instanceof Error ? err.message : String(err),\n };\n }\n\n if (outcome.decision === 'revealed') {\n revealed.push({ claim, watermarkAdvance: advance });\n valueRevealed += advance;\n continue;\n }\n\n // Withheld / failed: compensate the verify-time persist so the watermark\n // reflects only revealed packets (engine R8 — the maker reuses the nonce).\n rollbackWatermark(store, chain, channelId, prior);\n rolledBack.push({\n claim,\n watermarkAdvance: advance,\n reason: outcome.reason ?? 'reveal withheld',\n });\n }\n\n return { revealed, rolledBack, rejected, legacy, valueRevealed };\n}\n","/**\n * Devnet faucet helper.\n *\n * The deployed TOON devnet exposes a faucet that drips the settlement token\n * (USDC) to a given chain address so a client can open payment channels and pay\n * for writes. This helper hits the USDC-ONLY faucet legs — it funds USDC and\n * assumes the wallet already holds enough native gas to transact:\n *\n * EVM `POST {faucetUrl}/api/base-sepolia/request` body `{ address }` → USDC (Base Sepolia mint)\n * Solana `POST {faucetUrl}/api/solana/usdc-request` body `{ address }` → USDC (no SOL leg)\n * Mina `POST {faucetUrl}/api/mina/usdc-request` body `{ address }` → USDC (no MINA leg)\n *\n * Devnet edge (today): `https://faucet.devnet.toonprotocol.dev`.\n *\n * All three chains are live on the deployed faucet — the request shape is\n * identical (`{ address }`); only the path differs. (The EVM leg mints the\n * ungated mock USDC on Base Sepolia and best-effort tops up gas; the Solana and\n * Mina legs are strictly USDC-only and expect the address to already hold gas.)\n */\n\nimport { NetworkError } from './errors.js';\n\n/** Supported faucet chains. */\nexport type FaucetChain = 'evm' | 'solana' | 'mina';\n\n/** Result of a successful faucet drip. */\nexport interface FundWalletResult {\n /** The chain that was funded. */\n chain: FaucetChain;\n /** The funded address (echoed back). */\n address: string;\n /** Raw parsed JSON body from the faucet (shape is faucet-defined). */\n response: unknown;\n}\n\n/** Options for {@link fundWallet}. */\nexport interface FundWalletOptions {\n /** Custom fetch implementation (for testing / custom transports). */\n fetchImpl?: typeof fetch;\n /**\n * Request timeout in milliseconds. Defaults to {@link defaultFaucetTimeout}\n * for the chain (fast 30s for evm/solana, a longer 120s for the slow-settling\n * mina faucet).\n */\n timeout?: number;\n}\n\n/**\n * Default faucet request timeout (ms) for a chain.\n *\n * The EVM (Base Sepolia mint) and Solana USDC legs respond in a few seconds, so\n * 30s is plenty. The Mina USDC leg TRANSFERS the token via an o1js-proven\n * transaction on a chain that settles much more slowly: the drip routinely\n * succeeds server-side but takes well over 30s to answer the HTTP request, so a\n * flat 30s budget makes the client give up on a request that actually worked.\n * Give mina a much longer budget.\n */\nexport function defaultFaucetTimeout(chain: FaucetChain): number {\n return chain === 'mina' ? 120000 : 30000;\n}\n\n/**\n * Map a chain to its USDC-only faucet request path. Each leg funds USDC and\n * assumes the address already holds native gas (the EVM leg still best-effort\n * tops up Base Sepolia ETH, but does not depend on it).\n */\nfunction faucetPath(chain: FaucetChain): string {\n switch (chain) {\n case 'evm':\n return '/api/base-sepolia/request';\n case 'solana':\n return '/api/solana/usdc-request';\n case 'mina':\n return '/api/mina/usdc-request';\n }\n}\n\n/**\n * Drip test funds to `address` on `chain` from the devnet `faucetUrl`.\n *\n * @param faucetUrl - Faucet base URL, e.g. `https://faucet.devnet.toonprotocol.dev`.\n * A trailing `/` is tolerated.\n * @param address - The chain address to fund (EVM 0x address, Solana base58, etc).\n * @param chain - `'evm'` | `'solana'` | `'mina'` (all live on the devnet faucet).\n * @throws {Error} If `faucetUrl` or `address` is missing.\n * @throws {NetworkError} On transport failure or a non-2xx faucet response.\n */\nexport async function fundWallet(\n faucetUrl: string,\n address: string,\n chain: FaucetChain,\n options: FundWalletOptions = {}\n): Promise<FundWalletResult> {\n if (!faucetUrl) {\n throw new Error('fundWallet: faucetUrl is required');\n }\n if (!address) {\n throw new Error('fundWallet: address is required');\n }\n\n const base = faucetUrl.replace(/\\/+$/, '');\n const url = `${base}${faucetPath(chain)}`;\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeout = options.timeout ?? defaultFaucetTimeout(chain);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n let response: Response;\n try {\n response = await fetchImpl(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ address }),\n signal: controller.signal,\n });\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Faucet request timed out after ${timeout}ms (${url})`,\n error\n );\n }\n throw new NetworkError(\n `Faucet request failed (${url}): ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new NetworkError(\n `Faucet responded ${response.status} ${response.statusText}${\n detail ? `: ${detail}` : ''\n } (${url})`\n );\n }\n\n // The faucet returns JSON; tolerate an empty/non-JSON body (some faucets\n // return `204`/plain text on success).\n const body = await response.text().catch(() => '');\n let parsed: unknown = body;\n if (body) {\n try {\n parsed = JSON.parse(body);\n } catch {\n parsed = body;\n }\n }\n\n return { chain, address, response: parsed };\n}\n","import { finalizeEvent } from 'nostr-tools/pure';\nimport { nip19 } from 'nostr-tools';\nimport { EvmSigner } from '../signing/evm-signer.js';\nimport { SolanaSigner } from '../signing/solana-signer.js';\nimport { MinaSigner } from '../signing/mina-signer.js';\nimport type {\n KeyManagerConfig,\n PasskeyInfo,\n ToonIdentity,\n VaultData,\n} from './types.js';\nimport {\n deriveFullIdentity,\n deriveFromNsec,\n generateMnemonic,\n validateMnemonic,\n} from './KeyDerivation.js';\nimport {\n registerPasskey,\n assertPasskey,\n hashCredentialId,\n isPrfSupported,\n} from './PasskeyAuth.js';\nimport {\n createVault,\n deriveKek,\n deriveKekFromPassword,\n unlockVault,\n addKekToVault,\n removeKekFromVault,\n addRecoveryCodeToVault,\n unlockVaultWithRecoveryCode,\n generateRecoveryCode as generateRecoveryCodeRaw,\n} from './KeyVault.js';\nimport {\n buildBackupEvent,\n publishBackupToRelays,\n fetchBackupFromRelays,\n} from './BackupService.js';\nimport { fromBase64, hexToBytes, bytesToHex } from './encoding.js';\n\n/**\n * KeyManager orchestrates the full key lifecycle:\n * generate, derive, store, backup, recover — gated by WebAuthn Passkeys.\n *\n * Usage:\n * const km = new KeyManager({ relayUrls: ['wss://relay.example'] });\n * const identity = await km.create();\n * // or: const identity = await km.recover();\n *\n * const client = new ToonClient({\n * secretKey: identity.nostr.secretKey,\n * // ...\n * });\n *\n * Security note: JavaScript strings (like mnemonics) are immutable and cannot be\n * zeroed from memory. Uint8Array key material is zeroed on lock(), but mnemonic\n * strings persist until garbage collected. This is a known JS platform limitation.\n */\nexport class KeyManager {\n private readonly config: Required<KeyManagerConfig>;\n private identity: ToonIdentity | null = null;\n private vault: VaultData | null = null;\n private activeCredentialIdHash: string | null = null;\n\n constructor(config: KeyManagerConfig) {\n if (!config.relayUrls || config.relayUrls.length === 0) {\n throw new Error('KeyManager requires at least one relay URL');\n }\n\n this.config = {\n relayUrls: config.relayUrls,\n rpId:\n config.rpId ??\n (typeof window !== 'undefined'\n ? window.location.hostname\n : 'localhost'),\n rpName: config.rpName ?? 'TOON Protocol',\n storageKey: config.storageKey ?? 'toon:keys',\n };\n }\n\n // --- Account Lifecycle ---\n\n /**\n * Create a new account: generate mnemonic, create Passkey, encrypt, backup.\n */\n async create(): Promise<ToonIdentity> {\n const mnemonic = generateMnemonic();\n const identity = await deriveFullIdentity(mnemonic);\n\n // Generate a unique PRF salt for this credential\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n\n // Convert pubkey hex to bytes for userHandle\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n // Register Passkey with PRF\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n // Derive KEK from PRF output\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n // Create encrypted vault\n this.vault = await createVault(mnemonic, kek, credIdHash, prfSalt);\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n // Persist to IndexedDB\n await this.saveToLocalStorage();\n\n // Backup to relay (best-effort)\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal — local vault is still available\n });\n\n return identity;\n }\n\n /**\n * Recover an account using a synced Passkey.\n * The Nostr pubkey is extracted from the Passkey's userHandle.\n *\n * Flow: single assertion → userHandle → fetch backup → derive KEK → unlock.\n * If the local vault is available (has the PRF salt), we use a single assertion\n * with the correct salt. Otherwise, we need the backup from relays first, which\n * requires a discovery assertion to get the pubkey.\n */\n async recover(): Promise<ToonIdentity> {\n // Check local vault first — if available, we can do a single-assertion unlock\n const localVault = await this.loadFromLocalStorage();\n if (localVault) {\n return this.unlockWithVault(localVault);\n }\n\n // No local vault — need to discover pubkey from Passkey userHandle,\n // fetch backup from relays, then do a second assertion with the correct salt.\n // This is the cross-device recovery path (two assertions unavoidable).\n const discovery = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: crypto.getRandomValues(new Uint8Array(32)), // Dummy salt for discovery\n });\n\n if (!discovery.userHandle || discovery.userHandle.length === 0) {\n throw new Error(\n 'Passkey did not return a userHandle. Cannot determine Nostr pubkey for recovery.'\n );\n }\n\n // Extract Nostr pubkey from userHandle\n const pubkey = bytesToHex(discovery.userHandle);\n\n // Fetch backup from relays\n const vault = await fetchBackupFromRelays(pubkey, this.config.relayUrls);\n if (!vault) {\n throw new Error(\n 'No backup found on configured relays for this identity. ' +\n 'Try importing with a mnemonic or nsec instead.'\n );\n }\n\n // Find the matching wrapped key entry for this credential\n const credIdHash = await hashCredentialId(discovery.credentialId);\n const entry = vault.wrappedKeys.find((e) => e.id === credIdHash);\n if (!entry) {\n throw new Error(\n 'This Passkey is not registered with the backup. ' +\n 'Try a different Passkey or use a recovery code.'\n );\n }\n\n // Re-assert with the correct PRF salt from the backup\n const saltBytes = fromBase64(entry.salt);\n const reassertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: saltBytes,\n allowCredentials: [discovery.credentialId],\n });\n\n // Derive KEK and unlock vault\n const kek = await deriveKek(reassertion.prfOutput);\n const mnemonic = await unlockVault(vault, kek, credIdHash);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n // Cache locally for future single-assertion unlocks\n await this.saveToLocalStorage();\n\n return identity;\n }\n\n /**\n * Import an existing BIP-39 mnemonic. Creates a Passkey and backup.\n */\n async importMnemonic(mnemonic: string): Promise<ToonIdentity> {\n if (!validateMnemonic(mnemonic)) {\n throw new Error('Invalid BIP-39 mnemonic phrase');\n }\n\n const identity = await deriveFullIdentity(mnemonic);\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n this.vault = await createVault(mnemonic, kek, credIdHash, prfSalt);\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n\n return identity;\n }\n\n /**\n * Import from an nsec (Nostr-only key).\n * Nostr + EVM are derived; Solana + Mina get fresh keys (not deterministically linked).\n */\n async importNsec(nsec: string): Promise<ToonIdentity> {\n const decoded = nip19.decode(nsec);\n if (decoded.type !== 'nsec') {\n throw new Error('Invalid nsec string');\n }\n const secretKey = decoded.data;\n const identity = deriveFromNsec(secretKey);\n\n if (isPrfSupported()) {\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n try {\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n // For nsec import without mnemonic, we store the hex-encoded secret key\n const hexKey = bytesToHex(secretKey);\n this.vault = await createVault(hexKey, kek, credIdHash, prfSalt);\n this.activeCredentialIdHash = credIdHash;\n\n await this.saveToLocalStorage();\n } catch {\n // PRF may be reported as supported but fail at registration time.\n // Proceed without vault — identity is still usable.\n }\n }\n\n this.identity = identity;\n return identity;\n }\n\n // --- Passkey Management ---\n\n /**\n * Register an additional Passkey for this identity.\n */\n async addPasskey(): Promise<void> {\n if (!this.identity || !this.vault || !this.activeCredentialIdHash) {\n throw new Error('No active identity — call create() or recover() first');\n }\n\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(this.identity.nostr.pubkey);\n\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${this.identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const newKek = await deriveKek(registration.prfOutput);\n const newCredIdHash = await hashCredentialId(registration.credentialId);\n\n // Re-assert the current Passkey to get KEK for unwrapping\n const currentEntry = this.vault.wrappedKeys.find(\n (e) => e.id === this.activeCredentialIdHash\n );\n if (!currentEntry) {\n throw new Error('Active credential not found in vault');\n }\n\n const currentSaltBytes = fromBase64(currentEntry.salt);\n const currentAssertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: currentSaltBytes,\n });\n const currentKek = await deriveKek(currentAssertion.prfOutput);\n\n this.vault = await addKekToVault(\n this.vault,\n currentKek,\n this.activeCredentialIdHash,\n newKek,\n newCredIdHash,\n prfSalt\n );\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n }\n\n /**\n * List registered Passkey credentials.\n */\n listPasskeys(): PasskeyInfo[] {\n if (!this.vault) return [];\n return this.vault.wrappedKeys.map((entry) => ({\n credentialIdHash: entry.id,\n createdAt: entry.created_at,\n }));\n }\n\n /**\n * Remove a Passkey from the vault. Cannot remove the last one.\n */\n async removePasskey(credentialIdHash: string): Promise<void> {\n if (!this.vault) {\n throw new Error('No active vault');\n }\n\n this.vault = removeKekFromVault(this.vault, credentialIdHash);\n\n // If we removed the active credential, switch to another\n if (this.activeCredentialIdHash === credentialIdHash) {\n const remaining = this.vault.wrappedKeys[0];\n this.activeCredentialIdHash = remaining ? remaining.id : null;\n }\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n }\n\n // --- Recovery ---\n\n /**\n * Generate a printable recovery code and add it to the vault.\n * The PBKDF2 salt is persisted alongside the wrapped DEK so the code\n * can be verified later without the original salt.\n *\n * @returns The recovery code — user must store it securely.\n */\n async generateRecoveryCode(): Promise<string> {\n if (!this.vault || !this.activeCredentialIdHash) {\n throw new Error('No active vault');\n }\n\n const code = generateRecoveryCodeRaw();\n\n // Generate and persist PBKDF2 salt for recovery code\n const salt = crypto.getRandomValues(new Uint8Array(16));\n const recoveryKek = await deriveKekFromPassword(code, salt);\n\n // Get current KEK to unwrap DEK\n const currentEntry = this.vault.wrappedKeys.find(\n (e) => e.id === this.activeCredentialIdHash\n );\n if (!currentEntry) {\n throw new Error('Active credential not found in vault');\n }\n\n const currentSaltBytes = fromBase64(currentEntry.salt);\n const currentAssertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: currentSaltBytes,\n });\n const currentKek = await deriveKek(currentAssertion.prfOutput);\n\n this.vault = await addRecoveryCodeToVault(\n this.vault,\n currentKek,\n this.activeCredentialIdHash,\n recoveryKek,\n salt\n );\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n\n return code;\n }\n\n /**\n * Recover identity using a recovery code.\n * The PBKDF2 salt is read from the persisted vault data.\n */\n async recoverWithCode(code: string): Promise<ToonIdentity> {\n const vault = await this.loadFromLocalStorage();\n\n if (!vault) {\n throw new Error(\n 'No local vault found. Recovery code requires the encrypted vault. ' +\n 'If you have a Passkey, use recover() to fetch from relays first.'\n );\n }\n\n if (!vault.recoveryCodeWrappedDek || !vault.recoveryCodeSalt) {\n throw new Error('No recovery code configured for this vault');\n }\n\n // Use the persisted PBKDF2 salt to reproduce the exact KEK\n const salt = fromBase64(vault.recoveryCodeSalt);\n const recoveryKek = await deriveKekFromPassword(code, salt);\n\n const mnemonic = await unlockVaultWithRecoveryCode(vault, recoveryKek);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n\n return identity;\n }\n\n // --- Key Access ---\n\n /**\n * Get the current identity, or null if not unlocked.\n */\n getIdentity(): ToonIdentity | null {\n return this.identity;\n }\n\n /**\n * Get the Nostr secret key. Throws if not unlocked.\n */\n getNostrSecretKey(): Uint8Array {\n if (!this.identity) throw new Error('Identity not unlocked');\n return this.identity.nostr.secretKey;\n }\n\n /**\n * Get an EvmSigner instance. Throws if not unlocked.\n */\n getEvmSigner(): EvmSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n return new EvmSigner(this.identity.evm.privateKey);\n }\n\n /**\n * Get a SolanaSigner instance. Throws if not unlocked or Solana not derived.\n */\n getSolanaSigner(): SolanaSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n if (!this.identity.solana.publicKey) {\n throw new Error(\n 'Solana keys not available — was this imported from nsec?'\n );\n }\n return new SolanaSigner(this.identity.solana.secretKey);\n }\n\n /**\n * Get a MinaSigner instance. Throws if not unlocked or Mina not derived.\n */\n getMinaSigner(): MinaSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n if (!this.identity.mina.publicKey) {\n throw new Error('Mina keys not available — was this imported from nsec?');\n }\n return new MinaSigner(this.identity.mina.privateKey);\n }\n\n // --- Backup ---\n\n /**\n * Publish the current vault to configured relays as a kind:30078 event.\n */\n async backupToRelay(): Promise<void> {\n if (!this.identity || !this.vault) {\n throw new Error('No active identity or vault to backup');\n }\n\n const eventTemplate = buildBackupEvent(\n this.vault,\n this.identity.nostr.secretKey\n );\n\n // Sign with nostr-tools\n const signedEvent = finalizeEvent(\n eventTemplate,\n this.identity.nostr.secretKey\n );\n\n await publishBackupToRelays(signedEvent, this.config.relayUrls);\n }\n\n // --- Lock/Unlock ---\n\n /**\n * Clear keys from memory. The vault remains in IndexedDB.\n * Note: JavaScript strings (mnemonics) cannot be zeroed — only Uint8Array keys are cleared.\n */\n lock(): void {\n if (this.identity) {\n // Zero out sensitive Uint8Array key material\n this.identity.nostr.secretKey.fill(0);\n // evm.privateKey may be the same reference as nostr.secretKey — fill is idempotent\n this.identity.evm.privateKey.fill(0);\n this.identity.solana.secretKey.fill(0);\n }\n this.identity = null;\n }\n\n /**\n * Re-assert Passkey to decrypt local vault and restore identity.\n * Uses the local vault's stored PRF salt for a single biometric prompt.\n */\n async unlock(): Promise<ToonIdentity> {\n const vault = await this.loadFromLocalStorage();\n if (!vault) {\n throw new Error('No local vault found — use create() or recover()');\n }\n\n return this.unlockWithVault(vault);\n }\n\n // --- Private helpers ---\n\n /**\n * Unlock a vault with a single Passkey assertion using stored PRF salts.\n * If the vault has only one credential, uses allowCredentials to constrain.\n */\n private async unlockWithVault(vault: VaultData): Promise<ToonIdentity> {\n // We don't have the raw credential IDs (only hashes), so we can't constrain\n // allowCredentials. The user picks a credential, then we verify it matches.\n const firstEntry = vault.wrappedKeys[0];\n if (!firstEntry) {\n throw new Error('Vault has no registered credentials');\n }\n\n const assertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: fromBase64(firstEntry.salt),\n });\n\n const credIdHash = await hashCredentialId(assertion.credentialId);\n const matchingEntry = vault.wrappedKeys.find((e) => e.id === credIdHash);\n\n if (!matchingEntry) {\n throw new Error('This Passkey is not registered with the local vault');\n }\n\n // If the user picked the first credential, we already have the correct PRF output.\n // If they picked a different one, we need to re-assert with the correct salt.\n let prfOutput = assertion.prfOutput;\n if (matchingEntry.id !== firstEntry.id) {\n const correctSalt = fromBase64(matchingEntry.salt);\n const reassertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: correctSalt,\n allowCredentials: [assertion.credentialId],\n });\n prfOutput = reassertion.prfOutput;\n }\n\n const kek = await deriveKek(prfOutput);\n const mnemonic = await unlockVault(vault, kek, credIdHash);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n return identity;\n }\n\n // --- IndexedDB Persistence ---\n\n private async saveToLocalStorage(): Promise<void> {\n if (!this.vault) return;\n if (typeof indexedDB === 'undefined') return;\n\n const dbName = this.config.storageKey;\n const db = await openDb(dbName);\n const tx = db.transaction('vault', 'readwrite');\n const store = tx.objectStore('vault');\n store.put(JSON.stringify(this.vault), 'current');\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n db.close();\n }\n\n private async loadFromLocalStorage(): Promise<VaultData | null> {\n if (typeof indexedDB === 'undefined') return null;\n\n const dbName = this.config.storageKey;\n try {\n const db = await openDb(dbName);\n const tx = db.transaction('vault', 'readonly');\n const store = tx.objectStore('vault');\n const request = store.get('current');\n const result = await new Promise<string | undefined>(\n (resolve, reject) => {\n request.onsuccess = () =>\n resolve(request.result as string | undefined);\n request.onerror = () => reject(request.error);\n }\n );\n db.close();\n if (!result) return null;\n return JSON.parse(result) as VaultData;\n } catch {\n return null;\n }\n }\n}\n\n// --- Helpers ---\n\nfunction openDb(name: string): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(name, 1);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains('vault')) {\n db.createObjectStore('vault');\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n","import type {\n PasskeyAssertionResult,\n PasskeyRegistrationResult,\n} from './types.js';\n\n/**\n * WebAuthn Passkey authentication with PRF extension for key encryption.\n *\n * Handles credential registration (create) and assertion (get) with the\n * prf extension to derive a deterministic secret from the authenticator.\n * When PRF is unavailable, callers should fall back to password-based KEK.\n */\n\n/**\n * Register a new Passkey credential with PRF extension.\n *\n * @param rpId - Relying party identifier (e.g., \"example.com\")\n * @param rpName - Human-readable relying party name\n * @param userId - Raw user ID bytes (Nostr pubkey, 32 bytes)\n * @param userName - Display name for the credential\n * @param prfSalt - Random salt for the PRF evaluation\n * @returns Registration result with PRF output and credential ID\n */\nexport async function registerPasskey(params: {\n rpId: string;\n rpName: string;\n userId: Uint8Array;\n userName: string;\n prfSalt: Uint8Array;\n}): Promise<PasskeyRegistrationResult> {\n const { rpId, rpName, userId, userName, prfSalt } = params;\n\n const publicKeyOptions: PublicKeyCredentialCreationOptions = {\n rp: { id: rpId, name: rpName },\n user: {\n id: userId as unknown as BufferSource,\n name: userName,\n displayName: userName,\n },\n challenge: crypto.getRandomValues(\n new Uint8Array(32)\n ) as unknown as BufferSource,\n pubKeyCredParams: [\n { alg: -7, type: 'public-key' }, // ES256\n { alg: -257, type: 'public-key' }, // RS256\n ],\n authenticatorSelection: {\n residentKey: 'required',\n userVerification: 'required',\n },\n extensions: {\n prf: {\n eval: {\n first: prfSalt as unknown as BufferSource,\n },\n },\n } as AuthenticationExtensionsClientInputs,\n };\n\n const credential = (await navigator.credentials.create({\n publicKey: publicKeyOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey registration was cancelled or failed');\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const extensionResults = credential.getClientExtensionResults();\n\n const prfResults = (extensionResults as Record<string, unknown>)['prf'] as\n | { enabled?: boolean; results?: { first: ArrayBuffer } }\n | undefined;\n\n if (!prfResults?.results?.first) {\n throw new Error(\n 'PRF extension not supported by this authenticator. ' +\n 'Passkey was created but cannot be used for key encryption. ' +\n 'Use password-based encryption as fallback.'\n );\n }\n\n const credentialId = new Uint8Array(credential.rawId);\n\n // Verify attestation response is valid\n if (!response.attestationObject) {\n throw new Error('Invalid attestation response');\n }\n\n return {\n prfOutput: prfResults.results.first,\n credentialId,\n };\n}\n\n/**\n * Assert an existing Passkey credential with PRF extension.\n *\n * @param rpId - Relying party identifier\n * @param prfSalt - The same salt used during registration for this credential\n * @param allowCredentials - Optional list of credential IDs to filter\n * @returns Assertion result with PRF output, credential ID, and userHandle\n */\nexport async function assertPasskey(params: {\n rpId: string;\n prfSalt: Uint8Array;\n allowCredentials?: Uint8Array[];\n}): Promise<PasskeyAssertionResult> {\n const { rpId, prfSalt, allowCredentials } = params;\n\n const publicKeyOptions = {\n rpId,\n challenge: crypto.getRandomValues(\n new Uint8Array(32)\n ) as unknown as BufferSource,\n userVerification: 'required' as const,\n ...(allowCredentials && {\n allowCredentials: allowCredentials.map((id) => ({\n id: id as unknown as BufferSource,\n type: 'public-key' as const,\n })),\n }),\n extensions: {\n prf: {\n eval: {\n first: prfSalt as unknown as BufferSource,\n },\n },\n } as AuthenticationExtensionsClientInputs,\n } as PublicKeyCredentialRequestOptions;\n\n const credential = (await navigator.credentials.get({\n publicKey: publicKeyOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey assertion was cancelled or failed');\n }\n\n const response = credential.response as AuthenticatorAssertionResponse;\n const extensionResults = credential.getClientExtensionResults();\n\n const prfResults = (extensionResults as Record<string, unknown>)['prf'] as\n | { results?: { first: ArrayBuffer } }\n | undefined;\n\n if (!prfResults?.results?.first) {\n throw new Error(\n 'PRF extension did not return a result. ' +\n 'The authenticator may not support PRF.'\n );\n }\n\n return {\n prfOutput: prfResults.results.first,\n credentialId: new Uint8Array(credential.rawId),\n userHandle: response.userHandle\n ? new Uint8Array(response.userHandle)\n : null,\n };\n}\n\n/**\n * Check whether the current browser supports WebAuthn with PRF extension.\n * Returns false in Node.js or browsers without PublicKeyCredential.\n */\nexport function isPrfSupported(): boolean {\n if (typeof window === 'undefined') return false;\n if (typeof navigator === 'undefined') return false;\n if (!navigator.credentials) return false;\n if (typeof PublicKeyCredential === 'undefined') return false;\n // PRF support cannot be feature-detected without actually creating a credential,\n // so we return true if WebAuthn is available and let registration handle the error.\n return true;\n}\n\n/**\n * Compute SHA-256 hash of a credential ID for use as a lookup key.\n */\nexport async function hashCredentialId(\n credentialId: Uint8Array\n): Promise<string> {\n const arrayBuffer = credentialId.buffer.slice(\n credentialId.byteOffset,\n credentialId.byteOffset + credentialId.byteLength\n ) as ArrayBuffer;\n const hash = await crypto.subtle.digest('SHA-256', arrayBuffer);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Browser-safe Base64 encoding/decoding utilities.\n * Accepts Uint8Array directly to avoid TypedArray .buffer offset issues.\n */\n\nexport function toBase64(data: Uint8Array | ArrayBuffer): string {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary);\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return bytes;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","import type { VaultData, WrappedKeyEntry } from './types.js';\nimport { toBase64, fromBase64 } from './encoding.js';\n\n/**\n * Envelope encryption for mnemonic storage.\n *\n * Pattern: DEK encrypts mnemonic, KEK wraps DEK.\n * Multiple KEKs (Passkeys, recovery codes) can each wrap the same DEK independently.\n *\n * - DEK: random 256-bit AES key (Data Encryption Key)\n * - KEK: derived from Passkey PRF or recovery code (Key Encryption Key)\n * - Mnemonic encrypted with AES-256-GCM using DEK\n * - DEK wrapped with AES-KW using KEK\n */\n\n// --- DEK operations ---\n\n/**\n * Generate a random 256-bit Data Encryption Key.\n */\nexport async function generateDek(): Promise<CryptoKey> {\n return crypto.subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Encrypt a mnemonic string with a DEK using AES-256-GCM.\n * @returns Base64-encoded ciphertext and IV\n */\nexport async function encryptMnemonic(\n dek: CryptoKey,\n mnemonic: string\n): Promise<{ encryptedMnemonic: string; iv: string }> {\n const encoder = new TextEncoder();\n const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit GCM nonce\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n dek,\n encoder.encode(mnemonic)\n );\n return {\n encryptedMnemonic: toBase64(ciphertext),\n iv: toBase64(iv),\n };\n}\n\n/**\n * Decrypt a mnemonic from its encrypted form using a DEK.\n */\nexport async function decryptMnemonic(\n dek: CryptoKey,\n encryptedMnemonic: string,\n iv: string\n): Promise<string> {\n const decoder = new TextDecoder();\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: fromBase64(iv) },\n dek,\n fromBase64(encryptedMnemonic)\n );\n return decoder.decode(plaintext);\n}\n\n// --- KEK operations ---\n\n/**\n * Derive a KEK from a Passkey PRF output using HKDF.\n *\n * Flow: PRF(CredRandom, salt) → HKDF-SHA-256 → 256-bit AES-KW key\n */\nexport async function deriveKek(prfOutput: ArrayBuffer): Promise<CryptoKey> {\n // Import PRF output as HKDF key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n prfOutput,\n 'HKDF',\n false,\n ['deriveKey']\n );\n\n const encoder = new TextEncoder();\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: new Uint8Array(0), // PRF salt was already applied at the WebAuthn level\n info: encoder.encode('toon:kek'),\n },\n keyMaterial,\n { name: 'AES-KW', length: 256 },\n false, // not extractable\n ['wrapKey', 'unwrapKey']\n );\n}\n\n/**\n * Derive a KEK from a password string using PBKDF2.\n * Used as fallback when PRF is not available, or for recovery codes.\n */\nexport async function deriveKekFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n hash: 'SHA-256',\n salt,\n iterations: 600_000, // OWASP 2023 recommendation for SHA-256\n },\n keyMaterial,\n { name: 'AES-KW', length: 256 },\n false,\n ['wrapKey', 'unwrapKey']\n );\n}\n\n// --- Wrap/Unwrap DEK ---\n\n/**\n * Wrap (encrypt) a DEK with a KEK using AES-KW.\n * @returns Base64-encoded wrapped DEK\n */\nexport async function wrapDek(kek: CryptoKey, dek: CryptoKey): Promise<string> {\n const wrapped = await crypto.subtle.wrapKey('raw', dek, kek, 'AES-KW');\n return toBase64(new Uint8Array(wrapped));\n}\n\n/**\n * Unwrap (decrypt) a DEK from its wrapped form using a KEK.\n * @returns The unwrapped DEK as a CryptoKey\n */\nexport async function unwrapDek(\n kek: CryptoKey,\n wrappedDek: string\n): Promise<CryptoKey> {\n return crypto.subtle.unwrapKey(\n 'raw',\n fromBase64(wrappedDek),\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: 256 },\n true, // extractable — needed for re-wrapping when adding new KEKs\n ['encrypt', 'decrypt']\n );\n}\n\n// --- High-level vault operations ---\n\n/**\n * Create a new vault: generate DEK, encrypt mnemonic, wrap DEK with initial KEK.\n */\nexport async function createVault(\n mnemonic: string,\n kek: CryptoKey,\n credentialIdHash: string,\n prfSalt: Uint8Array\n): Promise<VaultData> {\n const dek = await generateDek();\n const { encryptedMnemonic, iv } = await encryptMnemonic(dek, mnemonic);\n const wrappedDek = await wrapDek(kek, dek);\n\n const entry: WrappedKeyEntry = {\n id: credentialIdHash,\n wrapped_dek: wrappedDek,\n salt: toBase64(prfSalt),\n created_at: Math.floor(Date.now() / 1000),\n };\n\n return {\n encryptedMnemonic,\n iv,\n wrappedKeys: [entry],\n };\n}\n\n/**\n * Unlock a vault: unwrap DEK with KEK, decrypt mnemonic.\n */\nexport async function unlockVault(\n vault: VaultData,\n kek: CryptoKey,\n credentialIdHash: string\n): Promise<string> {\n const entry = vault.wrappedKeys.find((e) => e.id === credentialIdHash);\n if (!entry) {\n throw new Error(`No wrapped key found for credential ${credentialIdHash}`);\n }\n\n const dek = await unwrapDek(kek, entry.wrapped_dek);\n return decryptMnemonic(dek, vault.encryptedMnemonic, vault.iv);\n}\n\n/**\n * Add a new KEK (from a new Passkey or recovery code) to an existing vault.\n * Requires an existing KEK to unwrap the DEK first.\n */\nexport async function addKekToVault(\n vault: VaultData,\n existingKek: CryptoKey,\n existingCredentialIdHash: string,\n newKek: CryptoKey,\n newCredentialIdHash: string,\n newPrfSalt: Uint8Array\n): Promise<VaultData> {\n // Unwrap DEK with existing KEK\n const existingEntry = vault.wrappedKeys.find(\n (e) => e.id === existingCredentialIdHash\n );\n if (!existingEntry) {\n throw new Error(\n `No wrapped key found for credential ${existingCredentialIdHash}`\n );\n }\n const dek = await unwrapDek(existingKek, existingEntry.wrapped_dek);\n\n // Wrap DEK with new KEK\n const newWrappedDek = await wrapDek(newKek, dek);\n\n const newEntry: WrappedKeyEntry = {\n id: newCredentialIdHash,\n wrapped_dek: newWrappedDek,\n salt: toBase64(newPrfSalt),\n created_at: Math.floor(Date.now() / 1000),\n };\n\n return {\n ...vault,\n wrappedKeys: [...vault.wrappedKeys, newEntry],\n };\n}\n\n/**\n * Remove a KEK from a vault. Always requires at least one passkey KEK to remain.\n */\nexport function removeKekFromVault(\n vault: VaultData,\n credentialIdHash: string\n): VaultData {\n const remaining = vault.wrappedKeys.filter((e) => e.id !== credentialIdHash);\n\n if (remaining.length === 0) {\n throw new Error(\n 'Cannot remove the last passkey — at least one passkey must remain for vault access'\n );\n }\n\n return {\n ...vault,\n wrappedKeys: remaining,\n };\n}\n\n/**\n * Add a recovery code KEK to the vault.\n * The PBKDF2 salt is persisted in the vault so recoverWithCode can reproduce the KEK.\n */\nexport async function addRecoveryCodeToVault(\n vault: VaultData,\n existingKek: CryptoKey,\n existingCredentialIdHash: string,\n recoveryKek: CryptoKey,\n recoverySalt: Uint8Array\n): Promise<VaultData> {\n const existingEntry = vault.wrappedKeys.find(\n (e) => e.id === existingCredentialIdHash\n );\n if (!existingEntry) {\n throw new Error(\n `No wrapped key found for credential ${existingCredentialIdHash}`\n );\n }\n const dek = await unwrapDek(existingKek, existingEntry.wrapped_dek);\n const recoveryWrappedDek = await wrapDek(recoveryKek, dek);\n\n return {\n ...vault,\n recoveryCodeWrappedDek: recoveryWrappedDek,\n recoveryCodeSalt: toBase64(recoverySalt),\n };\n}\n\n/**\n * Unlock a vault using a recovery code.\n */\nexport async function unlockVaultWithRecoveryCode(\n vault: VaultData,\n recoveryKek: CryptoKey\n): Promise<string> {\n if (!vault.recoveryCodeWrappedDek) {\n throw new Error('No recovery code is configured for this vault');\n }\n\n const dek = await unwrapDek(recoveryKek, vault.recoveryCodeWrappedDek);\n return decryptMnemonic(dek, vault.encryptedMnemonic, vault.iv);\n}\n\n/**\n * Generate a human-readable recovery code (24 random hex chars with dashes).\n * Format: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (easy to write down).\n */\nexport function generateRecoveryCode(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(12));\n const hex = Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n const groups = hex.match(/.{4}/g) ?? [];\n return groups.join('-');\n}\n","import { getPublicKey } from 'nostr-tools/pure';\nimport type { BackupPayload, VaultData } from './types.js';\n\n/**\n * Relay-based encrypted backup service using kind:30078 (NIP-78 application-specific data).\n *\n * The backup event is a replaceable event keyed by `d: \"toon:identity-backup\"`.\n * Content is an encrypted payload — the relay cannot read the mnemonic.\n */\n\nconst BACKUP_KIND = 30078;\nconst BACKUP_D_TAG = 'toon:identity-backup';\nconst BACKUP_VERSION = '1';\n\n/**\n * Build a kind:30078 Nostr event for identity backup.\n * The event must be signed by the caller before publishing.\n *\n * @param vault - The encrypted vault data\n * @param secretKey - Nostr secret key for signing\n * @param chains - Comma-separated list of supported chains\n * @returns Unsigned Nostr event template\n */\nexport function buildBackupEvent(\n vault: VaultData,\n secretKey: Uint8Array,\n chains = 'nostr,evm,solana,mina'\n): {\n kind: number;\n pubkey: string;\n created_at: number;\n tags: string[][];\n content: string;\n} {\n const pubkey = getPublicKey(secretKey);\n\n const payload: BackupPayload = {\n encrypted_mnemonic: vault.encryptedMnemonic,\n wrapped_keys: vault.wrappedKeys,\n iv: vault.iv,\n ...(vault.recoveryCodeWrappedDek && {\n recovery_code_wrapped_dek: vault.recoveryCodeWrappedDek,\n }),\n ...(vault.recoveryCodeSalt && {\n recovery_code_salt: vault.recoveryCodeSalt,\n }),\n };\n\n return {\n kind: BACKUP_KIND,\n pubkey,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', BACKUP_D_TAG],\n ['v', BACKUP_VERSION],\n ['chains', chains],\n ],\n content: JSON.stringify(payload),\n };\n}\n\n/**\n * Build a relay filter to fetch the identity backup for a given pubkey.\n */\nexport function buildBackupFilter(pubkey: string): {\n kinds: number[];\n authors: string[];\n '#d': string[];\n} {\n return {\n kinds: [BACKUP_KIND],\n authors: [pubkey],\n '#d': [BACKUP_D_TAG],\n };\n}\n\n/**\n * Parse a backup event's content into VaultData.\n * Validates the structure before returning.\n */\nexport function parseBackupPayload(content: string): VaultData {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n throw new Error('Invalid backup event content: not valid JSON');\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('Invalid backup event content: not an object');\n }\n\n const payload = parsed as Record<string, unknown>;\n\n if (typeof payload['encrypted_mnemonic'] !== 'string') {\n throw new Error('Invalid backup: missing encrypted_mnemonic');\n }\n if (typeof payload['iv'] !== 'string') {\n throw new Error('Invalid backup: missing iv');\n }\n if (!Array.isArray(payload['wrapped_keys'])) {\n throw new Error('Invalid backup: missing wrapped_keys array');\n }\n\n // Validate each wrapped key entry\n for (const entry of payload['wrapped_keys']) {\n if (typeof entry !== 'object' || entry === null) {\n throw new Error('Invalid backup: wrapped_keys entry is not an object');\n }\n const e = entry as Record<string, unknown>;\n if (typeof e['id'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing id');\n }\n if (typeof e['wrapped_dek'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing wrapped_dek');\n }\n if (typeof e['salt'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing salt');\n }\n if (typeof e['created_at'] !== 'number') {\n throw new Error(\n 'Invalid backup: wrapped key missing or invalid created_at'\n );\n }\n }\n\n return {\n encryptedMnemonic: payload['encrypted_mnemonic'] as string,\n iv: payload['iv'] as string,\n wrappedKeys: payload['wrapped_keys'] as VaultData['wrappedKeys'],\n ...(typeof payload['recovery_code_wrapped_dek'] === 'string' && {\n recoveryCodeWrappedDek: payload['recovery_code_wrapped_dek'],\n }),\n ...(typeof payload['recovery_code_salt'] === 'string' && {\n recoveryCodeSalt: payload['recovery_code_salt'],\n }),\n };\n}\n\n/**\n * Publish a backup event to one or more relays.\n *\n * Uses the SimplePool from nostr-tools for relay communication.\n * The event must be signed before calling this function.\n */\nexport async function publishBackupToRelays(\n signedEvent: {\n id: string;\n kind: number;\n pubkey: string;\n created_at: number;\n tags: string[][];\n content: string;\n sig: string;\n },\n relayUrls: string[]\n): Promise<void> {\n // Dynamic import to avoid pulling nostr-tools/pool into non-browser bundles\n const { SimplePool } = await import('nostr-tools/pool');\n const pool = new SimplePool();\n\n try {\n await Promise.allSettled(\n relayUrls.map((url) => pool.publish([url], signedEvent))\n );\n } finally {\n pool.close(relayUrls);\n }\n}\n\n/**\n * Fetch a backup event from relays for a given pubkey.\n * Returns the most recent backup event, or null if none found.\n */\nexport async function fetchBackupFromRelays(\n pubkey: string,\n relayUrls: string[]\n): Promise<VaultData | null> {\n const { SimplePool } = await import('nostr-tools/pool');\n const pool = new SimplePool();\n\n try {\n const filter = buildBackupFilter(pubkey);\n const events = await pool.querySync(relayUrls, filter);\n\n if (!events || events.length === 0) {\n return null;\n }\n\n // Sort by created_at descending, take most recent\n events.sort(\n (a: { created_at: number }, b: { created_at: number }) =>\n b.created_at - a.created_at\n );\n\n const latest = events[0];\n if (!latest) return null;\n return parseBackupPayload(latest.content);\n } finally {\n pool.close(relayUrls);\n }\n}\n","/**\n * Node-only encrypted mnemonic keystore for @toon-protocol/client.\n *\n * Mirrors the relay node wallet crypto (`packages/relay/src/wallet/\n * crypto.ts`): a BIP-39 mnemonic is encrypted at rest with scrypt (KDF) +\n * AES-256-GCM (authenticated encryption), serialized as JSON, and written to\n * disk with mode 0o600. Decryption requires the operator password; a wrong\n * password fails the GCM auth-tag verification and throws.\n *\n * This is the Node-side counterpart to the browser Passkey/IndexedDB\n * `KeyManager`/`KeyVault` flow — it does NOT touch those. It is guarded against\n * browser bundling: every entry point throws if `node:crypto`/`node:fs` are not\n * available (e.g. when accidentally imported in a browser bundle).\n *\n * @module\n */\n\nimport {\n scryptSync,\n createCipheriv,\n createDecipheriv,\n randomBytes,\n} from 'node:crypto';\nimport { writeFileSync, readFileSync } from 'node:fs';\nimport {\n generateMnemonic as genMnemonic,\n validateMnemonic as isValidMnemonic,\n} from './KeyDerivation.js';\n\n/** scrypt parameters — N=2^17 (~0.5-1s on modern hardware), r=8, p=1. */\nconst SCRYPT_N = 2 ** 17;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SCRYPT_KEY_LEN = 32;\n/** maxmem for scrypt: N * r * 128 * 2 (with headroom for Node.js overhead). */\nconst SCRYPT_MAXMEM = SCRYPT_N * SCRYPT_R * 256 + 32 * 1024 * 1024;\n\n/** Salt length in bytes. */\nconst SALT_LEN = 32;\n/** AES-GCM IV length in bytes. */\nconst IV_LEN = 12;\n/** AES-GCM authentication tag length in bytes (128-bit). */\nconst AUTH_TAG_LEN = 16;\n\n/**\n * Encrypted keystore file format (JSON, all binary fields base64-encoded).\n * Wire-compatible with the relay node's `EncryptedWallet`.\n */\nexport interface EncryptedKeystore {\n /** scrypt salt (base64). */\n salt: string;\n /** AES-GCM initialization vector (base64). */\n iv: string;\n /** AES-256-GCM ciphertext (base64). */\n ciphertext: string;\n /** AES-GCM authentication tag (base64). */\n tag: string;\n /** Envelope version for forward-compat (currently 1). */\n version?: number;\n}\n\n/**\n * Throws if this module is running outside Node.js. The scrypt/AES-256-GCM\n * primitives and the 0o600 file write are Node-only — there is no browser\n * equivalent of `node:crypto`'s `scryptSync` or POSIX file modes, so we fail\n * loudly rather than silently bundling broken code into a browser build.\n */\nfunction assertNode(): void {\n // `process.versions.node` is present in Node and absent in browsers.\n const versions = (\n globalThis as { process?: { versions?: { node?: string } } }\n ).process?.versions;\n if (!versions?.node) {\n throw new Error(\n 'keystore-node is Node.js-only and cannot run in a browser. ' +\n 'Use the Passkey/IndexedDB KeyManager for browser key storage.'\n );\n }\n}\n\n/**\n * Encrypt a mnemonic with a password using scrypt + AES-256-GCM.\n * Returns the JSON-serializable encrypted envelope (does NOT write to disk).\n */\nexport function encryptMnemonic(\n mnemonic: string,\n password: string\n): EncryptedKeystore {\n assertNode();\n if (typeof mnemonic !== 'string' || mnemonic.length === 0) {\n throw new Error('encryptMnemonic: mnemonic must be a non-empty string');\n }\n if (typeof password !== 'string' || password.length === 0) {\n throw new Error('encryptMnemonic: password must be a non-empty string');\n }\n\n const salt = randomBytes(SALT_LEN);\n const iv = randomBytes(IV_LEN);\n const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {\n N: SCRYPT_N,\n r: SCRYPT_R,\n p: SCRYPT_P,\n maxmem: SCRYPT_MAXMEM,\n });\n\n try {\n const cipher = createCipheriv('aes-256-gcm', key, iv, {\n authTagLength: AUTH_TAG_LEN,\n });\n const ciphertext = Buffer.concat([\n cipher.update(mnemonic, 'utf8'),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n\n return {\n salt: salt.toString('base64'),\n iv: iv.toString('base64'),\n ciphertext: ciphertext.toString('base64'),\n tag: tag.toString('base64'),\n version: 1,\n };\n } finally {\n key.fill(0);\n }\n}\n\n/**\n * Decrypt an encrypted keystore envelope with a password.\n * Throws on a wrong password (GCM auth-tag verification failure) or corruption.\n */\nexport function decryptMnemonic(\n encrypted: EncryptedKeystore,\n password: string\n): string {\n assertNode();\n if (typeof password !== 'string' || password.length === 0) {\n throw new Error('decryptMnemonic: password must be a non-empty string');\n }\n if (\n !encrypted ||\n typeof encrypted.salt !== 'string' ||\n typeof encrypted.iv !== 'string' ||\n typeof encrypted.ciphertext !== 'string' ||\n typeof encrypted.tag !== 'string'\n ) {\n throw new Error('decryptMnemonic: malformed keystore envelope');\n }\n\n const salt = Buffer.from(encrypted.salt, 'base64');\n const iv = Buffer.from(encrypted.iv, 'base64');\n const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');\n const tag = Buffer.from(encrypted.tag, 'base64');\n\n const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {\n N: SCRYPT_N,\n r: SCRYPT_R,\n p: SCRYPT_P,\n maxmem: SCRYPT_MAXMEM,\n });\n\n try {\n const decipher = createDecipheriv('aes-256-gcm', key, iv, {\n authTagLength: AUTH_TAG_LEN,\n });\n decipher.setAuthTag(tag);\n try {\n const plaintext = Buffer.concat([\n decipher.update(ciphertext),\n decipher.final(),\n ]);\n return plaintext.toString('utf8');\n } catch {\n throw new Error(\n 'Decryption failed: wrong password or corrupted keystore file'\n );\n }\n } finally {\n key.fill(0);\n }\n}\n\n/**\n * Generate a fresh 12-word BIP-39 mnemonic, encrypt it under `password`, and\n * write the encrypted keystore to `path` with mode 0o600.\n *\n * Returns the mnemonic (for one-time display/backup) alongside the encrypted\n * envelope. The caller is responsible for displaying the mnemonic securely and\n * NOT persisting it in plaintext.\n */\nexport function generateKeystore(\n path: string,\n password: string\n): { mnemonic: string; keystore: EncryptedKeystore } {\n assertNode();\n const mnemonic = genMnemonic();\n const keystore = encryptMnemonic(mnemonic, password);\n writeKeystoreFile(path, keystore);\n return { mnemonic, keystore };\n}\n\n/**\n * Import an existing BIP-39 mnemonic (12 or 24 words), encrypt it under\n * `password`, and write the encrypted keystore to `path` with mode 0o600.\n *\n * Throws if the mnemonic is not a valid BIP-39 phrase (wrong checksum/word\n * count) before any file is written.\n */\nexport function importKeystore(\n path: string,\n mnemonic: string,\n password: string\n): EncryptedKeystore {\n assertNode();\n if (!isValidMnemonic(mnemonic)) {\n throw new Error(\n 'Invalid BIP-39 mnemonic: checksum or word-list validation failed'\n );\n }\n const keystore = encryptMnemonic(mnemonic, password);\n writeKeystoreFile(path, keystore);\n return keystore;\n}\n\n/**\n * Load and decrypt a keystore file at `path` with `password`, returning the\n * plaintext mnemonic. Throws on a wrong password or corruption.\n */\nexport function loadKeystore(path: string, password: string): string {\n assertNode();\n const raw = readFileSync(path, 'utf8');\n let parsed: EncryptedKeystore;\n try {\n parsed = JSON.parse(raw) as EncryptedKeystore;\n } catch {\n throw new Error(`Keystore file at ${path} is not valid JSON`);\n }\n return decryptMnemonic(parsed, password);\n}\n\n/**\n * Serialize and write an encrypted keystore to disk with mode 0o600\n * (owner read/write only), mirroring the relay node wallet file permissions.\n */\nexport function writeKeystoreFile(\n path: string,\n keystore: EncryptedKeystore\n): void {\n assertNode();\n writeFileSync(path, JSON.stringify(keystore, null, 2), {\n encoding: 'utf8',\n mode: 0o600,\n });\n}\n","/**\n * PBKDF (RFC 2898). Can be used to create a key from password and salt.\n * @module\n */\nimport { hmac } from './hmac.ts';\n// prettier-ignore\nimport {\n ahash, anumber,\n asyncLoop, checkOpts, clean, createView, Hash, kdfInputToBytes,\n type CHash,\n type KDFInput\n} from './utils.ts';\n\nexport type Pbkdf2Opt = {\n c: number; // Iterations\n dkLen?: number; // Desired key length in bytes (Intended output length in octets of the derived key\n asyncTick?: number; // Maximum time in ms for which async function can block execution\n};\n// Common prologue and epilogue for sync/async functions\nfunction pbkdf2Init(hash: CHash, _password: KDFInput, _salt: KDFInput, _opts: Pbkdf2Opt) {\n ahash(hash);\n const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts);\n const { c, dkLen, asyncTick } = opts;\n anumber(c);\n anumber(dkLen);\n anumber(asyncTick);\n if (c < 1) throw new Error('iterations (c) should be >= 1');\n const password = kdfInputToBytes(_password);\n const salt = kdfInputToBytes(_salt);\n // DK = PBKDF2(PRF, Password, Salt, c, dkLen);\n const DK = new Uint8Array(dkLen);\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n const PRF = hmac.create(hash, password);\n const PRFSalt = PRF._cloneInto().update(salt);\n return { c, dkLen, asyncTick, DK, PRF, PRFSalt };\n}\n\nfunction pbkdf2Output<T extends Hash<T>>(\n PRF: Hash<T>,\n PRFSalt: Hash<T>,\n DK: Uint8Array,\n prfW: Hash<T>,\n u: Uint8Array\n) {\n PRF.destroy();\n PRFSalt.destroy();\n if (prfW) prfW.destroy();\n clean(u);\n return DK;\n}\n\n/**\n * PBKDF2-HMAC: RFC 2898 key derivation function\n * @param hash - hash function that would be used e.g. sha256\n * @param password - password from which a derived key is generated\n * @param salt - cryptographic salt\n * @param opts - {c, dkLen} where c is work factor and dkLen is output message size\n * @example\n * const key = pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) });\n */\nexport function pbkdf2(\n hash: CHash,\n password: KDFInput,\n salt: KDFInput,\n opts: Pbkdf2Opt\n): Uint8Array {\n const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);\n let prfW: any; // Working copy\n const arr = new Uint8Array(4);\n const view = createView(arr);\n const u = new Uint8Array(PRF.outputLen);\n // DK = T1 + T2 + ⋯ + Tdklen/hlen\n for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {\n // Ti = F(Password, Salt, c, i)\n const Ti = DK.subarray(pos, pos + PRF.outputLen);\n view.setInt32(0, ti, false);\n // F(Password, Salt, c, i) = U1 ^ U2 ^ ⋯ ^ Uc\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);\n Ti.set(u.subarray(0, Ti.length));\n for (let ui = 1; ui < c; ui++) {\n // Uc = PRF(Password, Uc−1)\n PRF._cloneInto(prfW).update(u).digestInto(u);\n for (let i = 0; i < Ti.length; i++) Ti[i] ^= u[i];\n }\n }\n return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);\n}\n\n/**\n * PBKDF2-HMAC: RFC 2898 key derivation function. Async version.\n * @example\n * await pbkdf2Async(sha256, 'password', 'salt', { dkLen: 32, c: 500_000 });\n */\nexport async function pbkdf2Async(\n hash: CHash,\n password: KDFInput,\n salt: KDFInput,\n opts: Pbkdf2Opt\n): Promise<Uint8Array> {\n const { c, dkLen, asyncTick, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);\n let prfW: any; // Working copy\n const arr = new Uint8Array(4);\n const view = createView(arr);\n const u = new Uint8Array(PRF.outputLen);\n // DK = T1 + T2 + ⋯ + Tdklen/hlen\n for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {\n // Ti = F(Password, Salt, c, i)\n const Ti = DK.subarray(pos, pos + PRF.outputLen);\n view.setInt32(0, ti, false);\n // F(Password, Salt, c, i) = U1 ^ U2 ^ ⋯ ^ Uc\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);\n Ti.set(u.subarray(0, Ti.length));\n await asyncLoop(c - 1, asyncTick, () => {\n // Uc = PRF(Password, Uc−1)\n PRF._cloneInto(prfW).update(u).digestInto(u);\n for (let i = 0; i < Ti.length; i++) Ti[i] ^= u[i];\n });\n }\n return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);\n}\n","/**\n * Audited & minimal JS implementation of\n * [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).\n * @module\n * @example\n```js\nimport * as bip39 from '@scure/bip39';\nimport { wordlist } from '@scure/bip39/wordlists/english';\nconst mn = bip39.generateMnemonic(wordlist);\nconsole.log(mn);\nconst ent = bip39.mnemonicToEntropy(mn, wordlist)\nbip39.entropyToMnemonic(ent, wordlist);\nbip39.validateMnemonic(mn, wordlist);\nawait bip39.mnemonicToSeed(mn, 'password');\nbip39.mnemonicToSeedSync(mn, 'password');\n\n// Wordlists\nimport { wordlist as czech } from '@scure/bip39/wordlists/czech';\nimport { wordlist as english } from '@scure/bip39/wordlists/english';\nimport { wordlist as french } from '@scure/bip39/wordlists/french';\nimport { wordlist as italian } from '@scure/bip39/wordlists/italian';\nimport { wordlist as japanese } from '@scure/bip39/wordlists/japanese';\nimport { wordlist as korean } from '@scure/bip39/wordlists/korean';\nimport { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese';\nimport { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese';\nimport { wordlist as spanish } from '@scure/bip39/wordlists/spanish';\nimport { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese';\n```\n */\n/*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */\nimport { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport { abytes, anumber, randomBytes } from '@noble/hashes/utils';\nimport { utils as baseUtils } from '@scure/base';\n// Japanese wordlist\nconst isJapanese = (wordlist) => wordlist[0] === '\\u3042\\u3044\\u3053\\u304f\\u3057\\u3093';\n// Normalization replaces equivalent sequences of characters\n// so that any two texts that are equivalent will be reduced\n// to the same sequence of code points, called the normal form of the original text.\n// https://tonsky.me/blog/unicode/#why-is-a----\nfunction nfkd(str) {\n if (typeof str !== 'string')\n throw new TypeError('invalid mnemonic type: ' + typeof str);\n return str.normalize('NFKD');\n}\nfunction normalize(str) {\n const norm = nfkd(str);\n const words = norm.split(' ');\n if (![12, 15, 18, 21, 24].includes(words.length))\n throw new Error('Invalid mnemonic');\n return { nfkd: norm, words };\n}\nfunction aentropy(ent) {\n abytes(ent, 16, 20, 24, 28, 32);\n}\n/**\n * Generate x random words. Uses Cryptographically-Secure Random Number Generator.\n * @param wordlist imported wordlist for specific language\n * @param strength mnemonic strength 128-256 bits\n * @example\n * generateMnemonic(wordlist, 128)\n * // 'legal winner thank year wave sausage worth useful legal winner thank yellow'\n */\nexport function generateMnemonic(wordlist, strength = 128) {\n anumber(strength);\n if (strength % 32 !== 0 || strength > 256)\n throw new TypeError('Invalid entropy');\n return entropyToMnemonic(randomBytes(strength / 8), wordlist);\n}\nconst calcChecksum = (entropy) => {\n // Checksum is ent.length/4 bits long\n const bitsLeft = 8 - entropy.length / 4;\n // Zero rightmost \"bitsLeft\" bits in byte\n // For example: bitsLeft=4 val=10111101 -> 10110000\n return new Uint8Array([(sha256(entropy)[0] >> bitsLeft) << bitsLeft]);\n};\nfunction getCoder(wordlist) {\n if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string')\n throw new Error('Wordlist: expected array of 2048 strings');\n wordlist.forEach((i) => {\n if (typeof i !== 'string')\n throw new Error('wordlist: non-string element: ' + i);\n });\n return baseUtils.chain(baseUtils.checksum(1, calcChecksum), baseUtils.radix2(11, true), baseUtils.alphabet(wordlist));\n}\n/**\n * Reversible: Converts mnemonic string to raw entropy in form of byte array.\n * @param mnemonic 12-24 words\n * @param wordlist imported wordlist for specific language\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * mnemonicToEntropy(mnem, wordlist)\n * // Produces\n * new Uint8Array([\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f\n * ])\n */\nexport function mnemonicToEntropy(mnemonic, wordlist) {\n const { words } = normalize(mnemonic);\n const entropy = getCoder(wordlist).decode(words);\n aentropy(entropy);\n return entropy;\n}\n/**\n * Reversible: Converts raw entropy in form of byte array to mnemonic string.\n * @param entropy byte array\n * @param wordlist imported wordlist for specific language\n * @returns 12-24 words\n * @example\n * const ent = new Uint8Array([\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f\n * ]);\n * entropyToMnemonic(ent, wordlist);\n * // 'legal winner thank year wave sausage worth useful legal winner thank yellow'\n */\nexport function entropyToMnemonic(entropy, wordlist) {\n aentropy(entropy);\n const words = getCoder(wordlist).encode(entropy);\n return words.join(isJapanese(wordlist) ? '\\u3000' : ' ');\n}\n/**\n * Validates mnemonic for being 12-24 words contained in `wordlist`.\n */\nexport function validateMnemonic(mnemonic, wordlist) {\n try {\n mnemonicToEntropy(mnemonic, wordlist);\n }\n catch (e) {\n return false;\n }\n return true;\n}\nconst psalt = (passphrase) => nfkd('mnemonic' + passphrase);\n/**\n * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.\n * @param mnemonic 12-24 words\n * @param passphrase string that will additionally protect the key\n * @returns 64 bytes of key data\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * await mnemonicToSeed(mnem, 'password');\n * // new Uint8Array([...64 bytes])\n */\nexport function mnemonicToSeed(mnemonic, passphrase = '') {\n return pbkdf2Async(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });\n}\n/**\n * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.\n * @param mnemonic 12-24 words\n * @param passphrase string that will additionally protect the key\n * @returns 64 bytes of key data\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * mnemonicToSeedSync(mnem, 'password');\n * // new Uint8Array([...64 bytes])\n */\nexport function mnemonicToSeedSync(mnemonic, passphrase = '') {\n return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });\n}\n","export const wordlist = `abandon\nability\nable\nabout\nabove\nabsent\nabsorb\nabstract\nabsurd\nabuse\naccess\naccident\naccount\naccuse\nachieve\nacid\nacoustic\nacquire\nacross\nact\naction\nactor\nactress\nactual\nadapt\nadd\naddict\naddress\nadjust\nadmit\nadult\nadvance\nadvice\naerobic\naffair\nafford\nafraid\nagain\nage\nagent\nagree\nahead\naim\nair\nairport\naisle\nalarm\nalbum\nalcohol\nalert\nalien\nall\nalley\nallow\nalmost\nalone\nalpha\nalready\nalso\nalter\nalways\namateur\namazing\namong\namount\namused\nanalyst\nanchor\nancient\nanger\nangle\nangry\nanimal\nankle\nannounce\nannual\nanother\nanswer\nantenna\nantique\nanxiety\nany\napart\napology\nappear\napple\napprove\napril\narch\narctic\narea\narena\nargue\narm\narmed\narmor\narmy\naround\narrange\narrest\narrive\narrow\nart\nartefact\nartist\nartwork\nask\naspect\nassault\nasset\nassist\nassume\nasthma\nathlete\natom\nattack\nattend\nattitude\nattract\nauction\naudit\naugust\naunt\nauthor\nauto\nautumn\naverage\navocado\navoid\nawake\naware\naway\nawesome\nawful\nawkward\naxis\nbaby\nbachelor\nbacon\nbadge\nbag\nbalance\nbalcony\nball\nbamboo\nbanana\nbanner\nbar\nbarely\nbargain\nbarrel\nbase\nbasic\nbasket\nbattle\nbeach\nbean\nbeauty\nbecause\nbecome\nbeef\nbefore\nbegin\nbehave\nbehind\nbelieve\nbelow\nbelt\nbench\nbenefit\nbest\nbetray\nbetter\nbetween\nbeyond\nbicycle\nbid\nbike\nbind\nbiology\nbird\nbirth\nbitter\nblack\nblade\nblame\nblanket\nblast\nbleak\nbless\nblind\nblood\nblossom\nblouse\nblue\nblur\nblush\nboard\nboat\nbody\nboil\nbomb\nbone\nbonus\nbook\nboost\nborder\nboring\nborrow\nboss\nbottom\nbounce\nbox\nboy\nbracket\nbrain\nbrand\nbrass\nbrave\nbread\nbreeze\nbrick\nbridge\nbrief\nbright\nbring\nbrisk\nbroccoli\nbroken\nbronze\nbroom\nbrother\nbrown\nbrush\nbubble\nbuddy\nbudget\nbuffalo\nbuild\nbulb\nbulk\nbullet\nbundle\nbunker\nburden\nburger\nburst\nbus\nbusiness\nbusy\nbutter\nbuyer\nbuzz\ncabbage\ncabin\ncable\ncactus\ncage\ncake\ncall\ncalm\ncamera\ncamp\ncan\ncanal\ncancel\ncandy\ncannon\ncanoe\ncanvas\ncanyon\ncapable\ncapital\ncaptain\ncar\ncarbon\ncard\ncargo\ncarpet\ncarry\ncart\ncase\ncash\ncasino\ncastle\ncasual\ncat\ncatalog\ncatch\ncategory\ncattle\ncaught\ncause\ncaution\ncave\nceiling\ncelery\ncement\ncensus\ncentury\ncereal\ncertain\nchair\nchalk\nchampion\nchange\nchaos\nchapter\ncharge\nchase\nchat\ncheap\ncheck\ncheese\nchef\ncherry\nchest\nchicken\nchief\nchild\nchimney\nchoice\nchoose\nchronic\nchuckle\nchunk\nchurn\ncigar\ncinnamon\ncircle\ncitizen\ncity\ncivil\nclaim\nclap\nclarify\nclaw\nclay\nclean\nclerk\nclever\nclick\nclient\ncliff\nclimb\nclinic\nclip\nclock\nclog\nclose\ncloth\ncloud\nclown\nclub\nclump\ncluster\nclutch\ncoach\ncoast\ncoconut\ncode\ncoffee\ncoil\ncoin\ncollect\ncolor\ncolumn\ncombine\ncome\ncomfort\ncomic\ncommon\ncompany\nconcert\nconduct\nconfirm\ncongress\nconnect\nconsider\ncontrol\nconvince\ncook\ncool\ncopper\ncopy\ncoral\ncore\ncorn\ncorrect\ncost\ncotton\ncouch\ncountry\ncouple\ncourse\ncousin\ncover\ncoyote\ncrack\ncradle\ncraft\ncram\ncrane\ncrash\ncrater\ncrawl\ncrazy\ncream\ncredit\ncreek\ncrew\ncricket\ncrime\ncrisp\ncritic\ncrop\ncross\ncrouch\ncrowd\ncrucial\ncruel\ncruise\ncrumble\ncrunch\ncrush\ncry\ncrystal\ncube\nculture\ncup\ncupboard\ncurious\ncurrent\ncurtain\ncurve\ncushion\ncustom\ncute\ncycle\ndad\ndamage\ndamp\ndance\ndanger\ndaring\ndash\ndaughter\ndawn\nday\ndeal\ndebate\ndebris\ndecade\ndecember\ndecide\ndecline\ndecorate\ndecrease\ndeer\ndefense\ndefine\ndefy\ndegree\ndelay\ndeliver\ndemand\ndemise\ndenial\ndentist\ndeny\ndepart\ndepend\ndeposit\ndepth\ndeputy\nderive\ndescribe\ndesert\ndesign\ndesk\ndespair\ndestroy\ndetail\ndetect\ndevelop\ndevice\ndevote\ndiagram\ndial\ndiamond\ndiary\ndice\ndiesel\ndiet\ndiffer\ndigital\ndignity\ndilemma\ndinner\ndinosaur\ndirect\ndirt\ndisagree\ndiscover\ndisease\ndish\ndismiss\ndisorder\ndisplay\ndistance\ndivert\ndivide\ndivorce\ndizzy\ndoctor\ndocument\ndog\ndoll\ndolphin\ndomain\ndonate\ndonkey\ndonor\ndoor\ndose\ndouble\ndove\ndraft\ndragon\ndrama\ndrastic\ndraw\ndream\ndress\ndrift\ndrill\ndrink\ndrip\ndrive\ndrop\ndrum\ndry\nduck\ndumb\ndune\nduring\ndust\ndutch\nduty\ndwarf\ndynamic\neager\neagle\nearly\nearn\nearth\neasily\neast\neasy\necho\necology\neconomy\nedge\nedit\neducate\neffort\negg\neight\neither\nelbow\nelder\nelectric\nelegant\nelement\nelephant\nelevator\nelite\nelse\nembark\nembody\nembrace\nemerge\nemotion\nemploy\nempower\nempty\nenable\nenact\nend\nendless\nendorse\nenemy\nenergy\nenforce\nengage\nengine\nenhance\nenjoy\nenlist\nenough\nenrich\nenroll\nensure\nenter\nentire\nentry\nenvelope\nepisode\nequal\nequip\nera\nerase\nerode\nerosion\nerror\nerupt\nescape\nessay\nessence\nestate\neternal\nethics\nevidence\nevil\nevoke\nevolve\nexact\nexample\nexcess\nexchange\nexcite\nexclude\nexcuse\nexecute\nexercise\nexhaust\nexhibit\nexile\nexist\nexit\nexotic\nexpand\nexpect\nexpire\nexplain\nexpose\nexpress\nextend\nextra\neye\neyebrow\nfabric\nface\nfaculty\nfade\nfaint\nfaith\nfall\nfalse\nfame\nfamily\nfamous\nfan\nfancy\nfantasy\nfarm\nfashion\nfat\nfatal\nfather\nfatigue\nfault\nfavorite\nfeature\nfebruary\nfederal\nfee\nfeed\nfeel\nfemale\nfence\nfestival\nfetch\nfever\nfew\nfiber\nfiction\nfield\nfigure\nfile\nfilm\nfilter\nfinal\nfind\nfine\nfinger\nfinish\nfire\nfirm\nfirst\nfiscal\nfish\nfit\nfitness\nfix\nflag\nflame\nflash\nflat\nflavor\nflee\nflight\nflip\nfloat\nflock\nfloor\nflower\nfluid\nflush\nfly\nfoam\nfocus\nfog\nfoil\nfold\nfollow\nfood\nfoot\nforce\nforest\nforget\nfork\nfortune\nforum\nforward\nfossil\nfoster\nfound\nfox\nfragile\nframe\nfrequent\nfresh\nfriend\nfringe\nfrog\nfront\nfrost\nfrown\nfrozen\nfruit\nfuel\nfun\nfunny\nfurnace\nfury\nfuture\ngadget\ngain\ngalaxy\ngallery\ngame\ngap\ngarage\ngarbage\ngarden\ngarlic\ngarment\ngas\ngasp\ngate\ngather\ngauge\ngaze\ngeneral\ngenius\ngenre\ngentle\ngenuine\ngesture\nghost\ngiant\ngift\ngiggle\nginger\ngiraffe\ngirl\ngive\nglad\nglance\nglare\nglass\nglide\nglimpse\nglobe\ngloom\nglory\nglove\nglow\nglue\ngoat\ngoddess\ngold\ngood\ngoose\ngorilla\ngospel\ngossip\ngovern\ngown\ngrab\ngrace\ngrain\ngrant\ngrape\ngrass\ngravity\ngreat\ngreen\ngrid\ngrief\ngrit\ngrocery\ngroup\ngrow\ngrunt\nguard\nguess\nguide\nguilt\nguitar\ngun\ngym\nhabit\nhair\nhalf\nhammer\nhamster\nhand\nhappy\nharbor\nhard\nharsh\nharvest\nhat\nhave\nhawk\nhazard\nhead\nhealth\nheart\nheavy\nhedgehog\nheight\nhello\nhelmet\nhelp\nhen\nhero\nhidden\nhigh\nhill\nhint\nhip\nhire\nhistory\nhobby\nhockey\nhold\nhole\nholiday\nhollow\nhome\nhoney\nhood\nhope\nhorn\nhorror\nhorse\nhospital\nhost\nhotel\nhour\nhover\nhub\nhuge\nhuman\nhumble\nhumor\nhundred\nhungry\nhunt\nhurdle\nhurry\nhurt\nhusband\nhybrid\nice\nicon\nidea\nidentify\nidle\nignore\nill\nillegal\nillness\nimage\nimitate\nimmense\nimmune\nimpact\nimpose\nimprove\nimpulse\ninch\ninclude\nincome\nincrease\nindex\nindicate\nindoor\nindustry\ninfant\ninflict\ninform\ninhale\ninherit\ninitial\ninject\ninjury\ninmate\ninner\ninnocent\ninput\ninquiry\ninsane\ninsect\ninside\ninspire\ninstall\nintact\ninterest\ninto\ninvest\ninvite\ninvolve\niron\nisland\nisolate\nissue\nitem\nivory\njacket\njaguar\njar\njazz\njealous\njeans\njelly\njewel\njob\njoin\njoke\njourney\njoy\njudge\njuice\njump\njungle\njunior\njunk\njust\nkangaroo\nkeen\nkeep\nketchup\nkey\nkick\nkid\nkidney\nkind\nkingdom\nkiss\nkit\nkitchen\nkite\nkitten\nkiwi\nknee\nknife\nknock\nknow\nlab\nlabel\nlabor\nladder\nlady\nlake\nlamp\nlanguage\nlaptop\nlarge\nlater\nlatin\nlaugh\nlaundry\nlava\nlaw\nlawn\nlawsuit\nlayer\nlazy\nleader\nleaf\nlearn\nleave\nlecture\nleft\nleg\nlegal\nlegend\nleisure\nlemon\nlend\nlength\nlens\nleopard\nlesson\nletter\nlevel\nliar\nliberty\nlibrary\nlicense\nlife\nlift\nlight\nlike\nlimb\nlimit\nlink\nlion\nliquid\nlist\nlittle\nlive\nlizard\nload\nloan\nlobster\nlocal\nlock\nlogic\nlonely\nlong\nloop\nlottery\nloud\nlounge\nlove\nloyal\nlucky\nluggage\nlumber\nlunar\nlunch\nluxury\nlyrics\nmachine\nmad\nmagic\nmagnet\nmaid\nmail\nmain\nmajor\nmake\nmammal\nman\nmanage\nmandate\nmango\nmansion\nmanual\nmaple\nmarble\nmarch\nmargin\nmarine\nmarket\nmarriage\nmask\nmass\nmaster\nmatch\nmaterial\nmath\nmatrix\nmatter\nmaximum\nmaze\nmeadow\nmean\nmeasure\nmeat\nmechanic\nmedal\nmedia\nmelody\nmelt\nmember\nmemory\nmention\nmenu\nmercy\nmerge\nmerit\nmerry\nmesh\nmessage\nmetal\nmethod\nmiddle\nmidnight\nmilk\nmillion\nmimic\nmind\nminimum\nminor\nminute\nmiracle\nmirror\nmisery\nmiss\nmistake\nmix\nmixed\nmixture\nmobile\nmodel\nmodify\nmom\nmoment\nmonitor\nmonkey\nmonster\nmonth\nmoon\nmoral\nmore\nmorning\nmosquito\nmother\nmotion\nmotor\nmountain\nmouse\nmove\nmovie\nmuch\nmuffin\nmule\nmultiply\nmuscle\nmuseum\nmushroom\nmusic\nmust\nmutual\nmyself\nmystery\nmyth\nnaive\nname\nnapkin\nnarrow\nnasty\nnation\nnature\nnear\nneck\nneed\nnegative\nneglect\nneither\nnephew\nnerve\nnest\nnet\nnetwork\nneutral\nnever\nnews\nnext\nnice\nnight\nnoble\nnoise\nnominee\nnoodle\nnormal\nnorth\nnose\nnotable\nnote\nnothing\nnotice\nnovel\nnow\nnuclear\nnumber\nnurse\nnut\noak\nobey\nobject\noblige\nobscure\nobserve\nobtain\nobvious\noccur\nocean\noctober\nodor\noff\noffer\noffice\noften\noil\nokay\nold\nolive\nolympic\nomit\nonce\none\nonion\nonline\nonly\nopen\nopera\nopinion\noppose\noption\norange\norbit\norchard\norder\nordinary\norgan\norient\noriginal\norphan\nostrich\nother\noutdoor\nouter\noutput\noutside\noval\noven\nover\nown\nowner\noxygen\noyster\nozone\npact\npaddle\npage\npair\npalace\npalm\npanda\npanel\npanic\npanther\npaper\nparade\nparent\npark\nparrot\nparty\npass\npatch\npath\npatient\npatrol\npattern\npause\npave\npayment\npeace\npeanut\npear\npeasant\npelican\npen\npenalty\npencil\npeople\npepper\nperfect\npermit\nperson\npet\nphone\nphoto\nphrase\nphysical\npiano\npicnic\npicture\npiece\npig\npigeon\npill\npilot\npink\npioneer\npipe\npistol\npitch\npizza\nplace\nplanet\nplastic\nplate\nplay\nplease\npledge\npluck\nplug\nplunge\npoem\npoet\npoint\npolar\npole\npolice\npond\npony\npool\npopular\nportion\nposition\npossible\npost\npotato\npottery\npoverty\npowder\npower\npractice\npraise\npredict\nprefer\nprepare\npresent\npretty\nprevent\nprice\npride\nprimary\nprint\npriority\nprison\nprivate\nprize\nproblem\nprocess\nproduce\nprofit\nprogram\nproject\npromote\nproof\nproperty\nprosper\nprotect\nproud\nprovide\npublic\npudding\npull\npulp\npulse\npumpkin\npunch\npupil\npuppy\npurchase\npurity\npurpose\npurse\npush\nput\npuzzle\npyramid\nquality\nquantum\nquarter\nquestion\nquick\nquit\nquiz\nquote\nrabbit\nraccoon\nrace\nrack\nradar\nradio\nrail\nrain\nraise\nrally\nramp\nranch\nrandom\nrange\nrapid\nrare\nrate\nrather\nraven\nraw\nrazor\nready\nreal\nreason\nrebel\nrebuild\nrecall\nreceive\nrecipe\nrecord\nrecycle\nreduce\nreflect\nreform\nrefuse\nregion\nregret\nregular\nreject\nrelax\nrelease\nrelief\nrely\nremain\nremember\nremind\nremove\nrender\nrenew\nrent\nreopen\nrepair\nrepeat\nreplace\nreport\nrequire\nrescue\nresemble\nresist\nresource\nresponse\nresult\nretire\nretreat\nreturn\nreunion\nreveal\nreview\nreward\nrhythm\nrib\nribbon\nrice\nrich\nride\nridge\nrifle\nright\nrigid\nring\nriot\nripple\nrisk\nritual\nrival\nriver\nroad\nroast\nrobot\nrobust\nrocket\nromance\nroof\nrookie\nroom\nrose\nrotate\nrough\nround\nroute\nroyal\nrubber\nrude\nrug\nrule\nrun\nrunway\nrural\nsad\nsaddle\nsadness\nsafe\nsail\nsalad\nsalmon\nsalon\nsalt\nsalute\nsame\nsample\nsand\nsatisfy\nsatoshi\nsauce\nsausage\nsave\nsay\nscale\nscan\nscare\nscatter\nscene\nscheme\nschool\nscience\nscissors\nscorpion\nscout\nscrap\nscreen\nscript\nscrub\nsea\nsearch\nseason\nseat\nsecond\nsecret\nsection\nsecurity\nseed\nseek\nsegment\nselect\nsell\nseminar\nsenior\nsense\nsentence\nseries\nservice\nsession\nsettle\nsetup\nseven\nshadow\nshaft\nshallow\nshare\nshed\nshell\nsheriff\nshield\nshift\nshine\nship\nshiver\nshock\nshoe\nshoot\nshop\nshort\nshoulder\nshove\nshrimp\nshrug\nshuffle\nshy\nsibling\nsick\nside\nsiege\nsight\nsign\nsilent\nsilk\nsilly\nsilver\nsimilar\nsimple\nsince\nsing\nsiren\nsister\nsituate\nsix\nsize\nskate\nsketch\nski\nskill\nskin\nskirt\nskull\nslab\nslam\nsleep\nslender\nslice\nslide\nslight\nslim\nslogan\nslot\nslow\nslush\nsmall\nsmart\nsmile\nsmoke\nsmooth\nsnack\nsnake\nsnap\nsniff\nsnow\nsoap\nsoccer\nsocial\nsock\nsoda\nsoft\nsolar\nsoldier\nsolid\nsolution\nsolve\nsomeone\nsong\nsoon\nsorry\nsort\nsoul\nsound\nsoup\nsource\nsouth\nspace\nspare\nspatial\nspawn\nspeak\nspecial\nspeed\nspell\nspend\nsphere\nspice\nspider\nspike\nspin\nspirit\nsplit\nspoil\nsponsor\nspoon\nsport\nspot\nspray\nspread\nspring\nspy\nsquare\nsqueeze\nsquirrel\nstable\nstadium\nstaff\nstage\nstairs\nstamp\nstand\nstart\nstate\nstay\nsteak\nsteel\nstem\nstep\nstereo\nstick\nstill\nsting\nstock\nstomach\nstone\nstool\nstory\nstove\nstrategy\nstreet\nstrike\nstrong\nstruggle\nstudent\nstuff\nstumble\nstyle\nsubject\nsubmit\nsubway\nsuccess\nsuch\nsudden\nsuffer\nsugar\nsuggest\nsuit\nsummer\nsun\nsunny\nsunset\nsuper\nsupply\nsupreme\nsure\nsurface\nsurge\nsurprise\nsurround\nsurvey\nsuspect\nsustain\nswallow\nswamp\nswap\nswarm\nswear\nsweet\nswift\nswim\nswing\nswitch\nsword\nsymbol\nsymptom\nsyrup\nsystem\ntable\ntackle\ntag\ntail\ntalent\ntalk\ntank\ntape\ntarget\ntask\ntaste\ntattoo\ntaxi\nteach\nteam\ntell\nten\ntenant\ntennis\ntent\nterm\ntest\ntext\nthank\nthat\ntheme\nthen\ntheory\nthere\nthey\nthing\nthis\nthought\nthree\nthrive\nthrow\nthumb\nthunder\nticket\ntide\ntiger\ntilt\ntimber\ntime\ntiny\ntip\ntired\ntissue\ntitle\ntoast\ntobacco\ntoday\ntoddler\ntoe\ntogether\ntoilet\ntoken\ntomato\ntomorrow\ntone\ntongue\ntonight\ntool\ntooth\ntop\ntopic\ntopple\ntorch\ntornado\ntortoise\ntoss\ntotal\ntourist\ntoward\ntower\ntown\ntoy\ntrack\ntrade\ntraffic\ntragic\ntrain\ntransfer\ntrap\ntrash\ntravel\ntray\ntreat\ntree\ntrend\ntrial\ntribe\ntrick\ntrigger\ntrim\ntrip\ntrophy\ntrouble\ntruck\ntrue\ntruly\ntrumpet\ntrust\ntruth\ntry\ntube\ntuition\ntumble\ntuna\ntunnel\nturkey\nturn\nturtle\ntwelve\ntwenty\ntwice\ntwin\ntwist\ntwo\ntype\ntypical\nugly\numbrella\nunable\nunaware\nuncle\nuncover\nunder\nundo\nunfair\nunfold\nunhappy\nuniform\nunique\nunit\nuniverse\nunknown\nunlock\nuntil\nunusual\nunveil\nupdate\nupgrade\nuphold\nupon\nupper\nupset\nurban\nurge\nusage\nuse\nused\nuseful\nuseless\nusual\nutility\nvacant\nvacuum\nvague\nvalid\nvalley\nvalve\nvan\nvanish\nvapor\nvarious\nvast\nvault\nvehicle\nvelvet\nvendor\nventure\nvenue\nverb\nverify\nversion\nvery\nvessel\nveteran\nviable\nvibrant\nvicious\nvictory\nvideo\nview\nvillage\nvintage\nviolin\nvirtual\nvirus\nvisa\nvisit\nvisual\nvital\nvivid\nvocal\nvoice\nvoid\nvolcano\nvolume\nvote\nvoyage\nwage\nwagon\nwait\nwalk\nwall\nwalnut\nwant\nwarfare\nwarm\nwarrior\nwash\nwasp\nwaste\nwater\nwave\nway\nwealth\nweapon\nwear\nweasel\nweather\nweb\nwedding\nweekend\nweird\nwelcome\nwest\nwet\nwhale\nwhat\nwheat\nwheel\nwhen\nwhere\nwhip\nwhisper\nwide\nwidth\nwife\nwild\nwill\nwin\nwindow\nwine\nwing\nwink\nwinner\nwinter\nwire\nwisdom\nwise\nwish\nwitness\nwolf\nwoman\nwonder\nwood\nwool\nword\nwork\nworld\nworry\nworth\nwrap\nwreck\nwrestle\nwrist\nwrite\nwrong\nyard\nyear\nyellow\nyou\nyoung\nyouth\nzebra\nzero\nzone\nzoo`.split('\\n');\n","/**\n * Hex, bytes and number utilities.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n abytes as abytes_,\n bytesToHex as bytesToHex_,\n concatBytes as concatBytes_,\n hexToBytes as hexToBytes_,\n isBytes as isBytes_,\n} from '@noble/hashes/utils.js';\nexport {\n abytes,\n anumber,\n bytesToHex,\n bytesToUtf8,\n concatBytes,\n hexToBytes,\n isBytes,\n randomBytes,\n utf8ToBytes,\n} from '@noble/hashes/utils.js';\nconst _0n = /* @__PURE__ */ BigInt(0);\nconst _1n = /* @__PURE__ */ BigInt(1);\nexport type Hex = Uint8Array | string; // hex strings are accepted for simplicity\nexport type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve\nexport type CHash = {\n (message: Uint8Array | string): Uint8Array;\n blockLen: number;\n outputLen: number;\n create(opts?: { dkLen?: number }): any; // For shake\n};\nexport type FHash = (message: Uint8Array | string) => Uint8Array;\n\nexport function abool(title: string, value: boolean): void {\n if (typeof value !== 'boolean') throw new Error(title + ' boolean expected, got ' + value);\n}\n\n// tmp name until v2\nexport function _abool2(value: boolean, title: string = ''): boolean {\n if (typeof value !== 'boolean') {\n const prefix = title && `\"${title}\"`;\n throw new Error(prefix + 'expected boolean, got type=' + typeof value);\n }\n return value;\n}\n\n// tmp name until v2\n/** Asserts something is Uint8Array. */\nexport function _abytes2(value: Uint8Array, length?: number, title: string = ''): Uint8Array {\n const bytes = isBytes_(value);\n const len = value?.length;\n const needsLen = length !== undefined;\n if (!bytes || (needsLen && len !== length)) {\n const prefix = title && `\"${title}\" `;\n const ofLen = needsLen ? ` of length ${length}` : '';\n const got = bytes ? `length=${len}` : `type=${typeof value}`;\n throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);\n }\n return value;\n}\n\n// Used in weierstrass, der\nexport function numberToHexUnpadded(num: number | bigint): string {\n const hex = num.toString(16);\n return hex.length & 1 ? '0' + hex : hex;\n}\n\nexport function hexToNumber(hex: string): bigint {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian\n}\n\n// BE: Big Endian, LE: Little Endian\nexport function bytesToNumberBE(bytes: Uint8Array): bigint {\n return hexToNumber(bytesToHex_(bytes));\n}\nexport function bytesToNumberLE(bytes: Uint8Array): bigint {\n abytes_(bytes);\n return hexToNumber(bytesToHex_(Uint8Array.from(bytes).reverse()));\n}\n\nexport function numberToBytesBE(n: number | bigint, len: number): Uint8Array {\n return hexToBytes_(n.toString(16).padStart(len * 2, '0'));\n}\nexport function numberToBytesLE(n: number | bigint, len: number): Uint8Array {\n return numberToBytesBE(n, len).reverse();\n}\n// Unpadded, rarely used\nexport function numberToVarBytesBE(n: number | bigint): Uint8Array {\n return hexToBytes_(numberToHexUnpadded(n));\n}\n\n/**\n * Takes hex string or Uint8Array, converts to Uint8Array.\n * Validates output length.\n * Will throw error for other types.\n * @param title descriptive title for an error e.g. 'secret key'\n * @param hex hex string or Uint8Array\n * @param expectedLength optional, will compare to result array's length\n * @returns\n */\nexport function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {\n let res: Uint8Array;\n if (typeof hex === 'string') {\n try {\n res = hexToBytes_(hex);\n } catch (e) {\n throw new Error(title + ' must be hex string or Uint8Array, cause: ' + e);\n }\n } else if (isBytes_(hex)) {\n // Uint8Array.from() instead of hash.slice() because node.js Buffer\n // is instance of Uint8Array, and its slice() creates **mutable** copy\n res = Uint8Array.from(hex);\n } else {\n throw new Error(title + ' must be hex string or Uint8Array');\n }\n const len = res.length;\n if (typeof expectedLength === 'number' && len !== expectedLength)\n throw new Error(title + ' of length ' + expectedLength + ' expected, got ' + len);\n return res;\n}\n\n// Compares 2 u8a-s in kinda constant time\nexport function equalBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];\n return diff === 0;\n}\n/**\n * Copies Uint8Array. We can't use u8a.slice(), because u8a can be Buffer,\n * and Buffer#slice creates mutable copy. Never use Buffers!\n */\nexport function copyBytes(bytes: Uint8Array): Uint8Array {\n return Uint8Array.from(bytes);\n}\n\n/**\n * Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols\n * Should be safe to use for things expected to be ASCII.\n * Returns exact same result as utf8ToBytes for ASCII or throws.\n */\nexport function asciiToBytes(ascii: string): Uint8Array {\n return Uint8Array.from(ascii, (c, i) => {\n const charCode = c.charCodeAt(0);\n if (c.length !== 1 || charCode > 127) {\n throw new Error(\n `string contains non-ASCII character \"${ascii[i]}\" with code ${charCode} at position ${i}`\n );\n }\n return charCode;\n });\n}\n\n/**\n * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])\n */\n// export const utf8ToBytes: typeof utf8ToBytes_ = utf8ToBytes_;\n/**\n * Converts bytes to string using UTF8 encoding.\n * @example bytesToUtf8(Uint8Array.from([97, 98, 99])) // 'abc'\n */\n// export const bytesToUtf8: typeof bytesToUtf8_ = bytesToUtf8_;\n\n// Is positive bigint\nconst isPosBig = (n: bigint) => typeof n === 'bigint' && _0n <= n;\n\nexport function inRange(n: bigint, min: bigint, max: bigint): boolean {\n return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;\n}\n\n/**\n * Asserts min <= n < max. NOTE: It's < max and not <= max.\n * @example\n * aInRange('x', x, 1n, 256n); // would assume x is in (1n..255n)\n */\nexport function aInRange(title: string, n: bigint, min: bigint, max: bigint): void {\n // Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)?\n // consider P=256n, min=0n, max=P\n // - a for min=0 would require -1: `inRange('x', x, -1n, P)`\n // - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)`\n // - our way is the cleanest: `inRange('x', x, 0n, P)\n if (!inRange(n, min, max))\n throw new Error('expected valid ' + title + ': ' + min + ' <= n < ' + max + ', got ' + n);\n}\n\n// Bit operations\n\n/**\n * Calculates amount of bits in a bigint.\n * Same as `n.toString(2).length`\n * TODO: merge with nLength in modular\n */\nexport function bitLen(n: bigint): number {\n let len;\n for (len = 0; n > _0n; n >>= _1n, len += 1);\n return len;\n}\n\n/**\n * Gets single bit at position.\n * NOTE: first bit position is 0 (same as arrays)\n * Same as `!!+Array.from(n.toString(2)).reverse()[pos]`\n */\nexport function bitGet(n: bigint, pos: number): bigint {\n return (n >> BigInt(pos)) & _1n;\n}\n\n/**\n * Sets single bit at position.\n */\nexport function bitSet(n: bigint, pos: number, value: boolean): bigint {\n return n | ((value ? _1n : _0n) << BigInt(pos));\n}\n\n/**\n * Calculate mask for N bits. Not using ** operator with bigints because of old engines.\n * Same as BigInt(`0b${Array(i).fill('1').join('')}`)\n */\nexport const bitMask = (n: number): bigint => (_1n << BigInt(n)) - _1n;\n\n// DRBG\n\ntype Pred<T> = (v: Uint8Array) => T | undefined;\n/**\n * Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n * @returns function that will call DRBG until 2nd arg returns something meaningful\n * @example\n * const drbg = createHmacDRBG<Key>(32, 32, hmac);\n * drbg(seed, bytesToKey); // bytesToKey must return Key or undefined\n */\nexport function createHmacDrbg<T>(\n hashLen: number,\n qByteLen: number,\n hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array\n): (seed: Uint8Array, predicate: Pred<T>) => T {\n if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');\n if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');\n if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');\n // Step B, Step C: set hashLen to 8*ceil(hlen/8)\n const u8n = (len: number) => new Uint8Array(len); // creates Uint8Array\n const u8of = (byte: number) => Uint8Array.of(byte); // another shortcut\n let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same\n let i = 0; // Iterations counter, will throw when over 1000\n const reset = () => {\n v.fill(1);\n k.fill(0);\n i = 0;\n };\n const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)\n const reseed = (seed = u8n(0)) => {\n // HMAC-DRBG reseed() function. Steps D-G\n k = h(u8of(0x00), seed); // k = hmac(k || v || 0x00 || seed)\n v = h(); // v = hmac(k || v)\n if (seed.length === 0) return;\n k = h(u8of(0x01), seed); // k = hmac(k || v || 0x01 || seed)\n v = h(); // v = hmac(k || v)\n };\n const gen = () => {\n // HMAC-DRBG generate() function\n if (i++ >= 1000) throw new Error('drbg: tried 1000 values');\n let len = 0;\n const out: Uint8Array[] = [];\n while (len < qByteLen) {\n v = h();\n const sl = v.slice();\n out.push(sl);\n len += v.length;\n }\n return concatBytes_(...out);\n };\n const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {\n reset();\n reseed(seed); // Steps D-G\n let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]\n while (!(res = pred(gen()))) reseed();\n reset();\n return res;\n };\n return genUntil;\n}\n\n// Validating curves and fields\n\nconst validatorFns = {\n bigint: (val: any): boolean => typeof val === 'bigint',\n function: (val: any): boolean => typeof val === 'function',\n boolean: (val: any): boolean => typeof val === 'boolean',\n string: (val: any): boolean => typeof val === 'string',\n stringOrUint8Array: (val: any): boolean => typeof val === 'string' || isBytes_(val),\n isSafeInteger: (val: any): boolean => Number.isSafeInteger(val),\n array: (val: any): boolean => Array.isArray(val),\n field: (val: any, object: any): any => (object as any).Fp.isValid(val),\n hash: (val: any): boolean => typeof val === 'function' && Number.isSafeInteger(val.outputLen),\n} as const;\ntype Validator = keyof typeof validatorFns;\ntype ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };\n// type Record<K extends string | number | symbol, T> = { [P in K]: T; }\n\nexport function validateObject<T extends Record<string, any>>(\n object: T,\n validators: ValMap<T>,\n optValidators: ValMap<T> = {}\n): T {\n const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {\n const checkVal = validatorFns[type];\n if (typeof checkVal !== 'function') throw new Error('invalid validator function');\n\n const val = object[fieldName as keyof typeof object];\n if (isOptional && val === undefined) return;\n if (!checkVal(val, object)) {\n throw new Error(\n 'param ' + String(fieldName) + ' is invalid. Expected ' + type + ', got ' + val\n );\n }\n };\n for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);\n for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);\n return object;\n}\n// validate type tests\n// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };\n// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!\n// // Should fail type-check\n// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });\n// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });\n// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });\n// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });\n\nexport function isHash(val: CHash): boolean {\n return typeof val === 'function' && Number.isSafeInteger(val.outputLen);\n}\nexport function _validateObject(\n object: Record<string, any>,\n fields: Record<string, string>,\n optFields: Record<string, string> = {}\n): void {\n if (!object || typeof object !== 'object') throw new Error('expected valid options object');\n type Item = keyof typeof object;\n function checkField(fieldName: Item, expectedType: string, isOpt: boolean) {\n const val = object[fieldName];\n if (isOpt && val === undefined) return;\n const current = typeof val;\n if (current !== expectedType || val === null)\n throw new Error(`param \"${fieldName}\" is invalid: expected ${expectedType}, got ${current}`);\n }\n Object.entries(fields).forEach(([k, v]) => checkField(k, v, false));\n Object.entries(optFields).forEach(([k, v]) => checkField(k, v, true));\n}\n\n/**\n * throws not implemented error\n */\nexport const notImplemented = (): never => {\n throw new Error('not implemented');\n};\n\n/**\n * Memoizes (caches) computation result.\n * Uses WeakMap: the value is going auto-cleaned by GC after last reference is removed.\n */\nexport function memoized<T extends object, R, O extends any[]>(\n fn: (arg: T, ...args: O) => R\n): (arg: T, ...args: O) => R {\n const map = new WeakMap<T, R>();\n return (arg: T, ...args: O): R => {\n const val = map.get(arg);\n if (val !== undefined) return val;\n const computed = fn(arg, ...args);\n map.set(arg, computed);\n return computed;\n };\n}\n","/**\n * Utils for modular division and fields.\n * Field over 11 is a finite (Galois) field is integer number operations `mod 11`.\n * There is no division: it is replaced by modular multiplicative inverse.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n _validateObject,\n anumber,\n bitMask,\n bytesToNumberBE,\n bytesToNumberLE,\n ensureBytes,\n numberToBytesBE,\n numberToBytesLE,\n} from '../utils.ts';\n\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3);\n// prettier-ignore\nconst _4n = /* @__PURE__ */ BigInt(4), _5n = /* @__PURE__ */ BigInt(5), _7n = /* @__PURE__ */ BigInt(7);\n// prettier-ignore\nconst _8n = /* @__PURE__ */ BigInt(8), _9n = /* @__PURE__ */ BigInt(9), _16n = /* @__PURE__ */ BigInt(16);\n\n// Calculates a modulo b\nexport function mod(a: bigint, b: bigint): bigint {\n const result = a % b;\n return result >= _0n ? result : b + result;\n}\n/**\n * Efficiently raise num to power and do modular division.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n * @example\n * pow(2n, 6n, 11n) // 64n % 11n == 9n\n */\nexport function pow(num: bigint, power: bigint, modulo: bigint): bigint {\n return FpPow(Field(modulo), num, power);\n}\n\n/** Does `x^(2^power)` mod p. `pow2(30, 4)` == `30^(2^4)` */\nexport function pow2(x: bigint, power: bigint, modulo: bigint): bigint {\n let res = x;\n while (power-- > _0n) {\n res *= res;\n res %= modulo;\n }\n return res;\n}\n\n/**\n * Inverses number over modulo.\n * Implemented using [Euclidean GCD](https://brilliant.org/wiki/extended-euclidean-algorithm/).\n */\nexport function invert(number: bigint, modulo: bigint): bigint {\n if (number === _0n) throw new Error('invert: expected non-zero number');\n if (modulo <= _0n) throw new Error('invert: expected positive modulus, got ' + modulo);\n // Fermat's little theorem \"CT-like\" version inv(n) = n^(m-2) mod m is 30x slower.\n let a = mod(number, modulo);\n let b = modulo;\n // prettier-ignore\n let x = _0n, y = _1n, u = _1n, v = _0n;\n while (a !== _0n) {\n // JIT applies optimization if those two lines follow each other\n const q = b / a;\n const r = b % a;\n const m = x - u * q;\n const n = y - v * q;\n // prettier-ignore\n b = a, a = r, x = u, y = v, u = m, v = n;\n }\n const gcd = b;\n if (gcd !== _1n) throw new Error('invert: does not exist');\n return mod(x, modulo);\n}\n\nfunction assertIsSquare<T>(Fp: IField<T>, root: T, n: T): void {\n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');\n}\n\n// Not all roots are possible! Example which will throw:\n// const NUM =\n// n = 72057594037927816n;\n// Fp = Field(BigInt('0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'));\nfunction sqrt3mod4<T>(Fp: IField<T>, n: T) {\n const p1div4 = (Fp.ORDER + _1n) / _4n;\n const root = Fp.pow(n, p1div4);\n assertIsSquare(Fp, root, n);\n return root;\n}\n\nfunction sqrt5mod8<T>(Fp: IField<T>, n: T) {\n const p5div8 = (Fp.ORDER - _5n) / _8n;\n const n2 = Fp.mul(n, _2n);\n const v = Fp.pow(n2, p5div8);\n const nv = Fp.mul(n, v);\n const i = Fp.mul(Fp.mul(nv, _2n), v);\n const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));\n assertIsSquare(Fp, root, n);\n return root;\n}\n\n// Based on RFC9380, Kong algorithm\n// prettier-ignore\nfunction sqrt9mod16(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n const Fp_ = Field(P);\n const tn = tonelliShanks(P);\n const c1 = tn(Fp_, Fp_.neg(Fp_.ONE));// 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F\n const c2 = tn(Fp_, c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F\n const c3 = tn(Fp_, Fp_.neg(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F\n const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic\n return <T>(Fp: IField<T>, n: T) => {\n let tv1 = Fp.pow(n, c4); // 1. tv1 = x^c4\n let tv2 = Fp.mul(tv1, c1); // 2. tv2 = c1 * tv1\n const tv3 = Fp.mul(tv1, c2); // 3. tv3 = c2 * tv1\n const tv4 = Fp.mul(tv1, c3); // 4. tv4 = c3 * tv1\n const e1 = Fp.eql(Fp.sqr(tv2), n); // 5. e1 = (tv2^2) == x\n const e2 = Fp.eql(Fp.sqr(tv3), n); // 6. e2 = (tv3^2) == x\n tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x\n tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x\n const e3 = Fp.eql(Fp.sqr(tv2), n); // 9. e3 = (tv2^2) == x\n const root = Fp.cmov(tv1, tv2, e3);// 10. z = CMOV(tv1, tv2, e3) # Select sqrt from tv1 & tv2\n assertIsSquare(Fp, root, n);\n return root;\n };\n}\n\n/**\n * Tonelli-Shanks square root search algorithm.\n * 1. https://eprint.iacr.org/2012/685.pdf (page 12)\n * 2. Square Roots from 1; 24, 51, 10 to Dan Shanks\n * @param P field order\n * @returns function that takes field Fp (created from P) and number n\n */\nexport function tonelliShanks(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n // Initialization (precomputation).\n // Caching initialization could boost perf by 7%.\n if (P < _3n) throw new Error('sqrt is not defined for small field');\n // Factor P - 1 = Q * 2^S, where Q is odd\n let Q = P - _1n;\n let S = 0;\n while (Q % _2n === _0n) {\n Q /= _2n;\n S++;\n }\n\n // Find the first quadratic non-residue Z >= 2\n let Z = _2n;\n const _Fp = Field(P);\n while (FpLegendre(_Fp, Z) === 1) {\n // Basic primality test for P. After x iterations, chance of\n // not finding quadratic non-residue is 2^x, so 2^1000.\n if (Z++ > 1000) throw new Error('Cannot find square root: probably non-prime P');\n }\n // Fast-path; usually done before Z, but we do \"primality test\".\n if (S === 1) return sqrt3mod4;\n\n // Slow-path\n // TODO: test on Fp2 and others\n let cc = _Fp.pow(Z, Q); // c = z^Q\n const Q1div2 = (Q + _1n) / _2n;\n return function tonelliSlow<T>(Fp: IField<T>, n: T): T {\n if (Fp.is0(n)) return n;\n // Check if n is a quadratic residue using Legendre symbol\n if (FpLegendre(Fp, n) !== 1) throw new Error('Cannot find square root');\n\n // Initialize variables for the main loop\n let M = S;\n let c = Fp.mul(Fp.ONE, cc); // c = z^Q, move cc from field _Fp into field Fp\n let t = Fp.pow(n, Q); // t = n^Q, first guess at the fudge factor\n let R = Fp.pow(n, Q1div2); // R = n^((Q+1)/2), first guess at the square root\n\n // Main loop\n // while t != 1\n while (!Fp.eql(t, Fp.ONE)) {\n if (Fp.is0(t)) return Fp.ZERO; // if t=0 return R=0\n let i = 1;\n\n // Find the smallest i >= 1 such that t^(2^i) ≡ 1 (mod P)\n let t_tmp = Fp.sqr(t); // t^(2^1)\n while (!Fp.eql(t_tmp, Fp.ONE)) {\n i++;\n t_tmp = Fp.sqr(t_tmp); // t^(2^2)...\n if (i === M) throw new Error('Cannot find square root');\n }\n\n // Calculate the exponent for b: 2^(M - i - 1)\n const exponent = _1n << BigInt(M - i - 1); // bigint is important\n const b = Fp.pow(c, exponent); // b = 2^(M - i - 1)\n\n // Update variables\n M = i;\n c = Fp.sqr(b); // c = b^2\n t = Fp.mul(t, c); // t = (t * b^2)\n R = Fp.mul(R, b); // R = R*b\n }\n return R;\n };\n}\n\n/**\n * Square root for a finite field. Will try optimized versions first:\n *\n * 1. P ≡ 3 (mod 4)\n * 2. P ≡ 5 (mod 8)\n * 3. P ≡ 9 (mod 16)\n * 4. Tonelli-Shanks algorithm\n *\n * Different algorithms can give different roots, it is up to user to decide which one they want.\n * For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).\n */\nexport function FpSqrt(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n // P ≡ 3 (mod 4) => √n = n^((P+1)/4)\n if (P % _4n === _3n) return sqrt3mod4;\n // P ≡ 5 (mod 8) => Atkin algorithm, page 10 of https://eprint.iacr.org/2012/685.pdf\n if (P % _8n === _5n) return sqrt5mod8;\n // P ≡ 9 (mod 16) => Kong algorithm, page 11 of https://eprint.iacr.org/2012/685.pdf (algorithm 4)\n if (P % _16n === _9n) return sqrt9mod16(P);\n // Tonelli-Shanks algorithm\n return tonelliShanks(P);\n}\n\n// Little-endian check for first LE bit (last BE bit);\nexport const isNegativeLE = (num: bigint, modulo: bigint): boolean =>\n (mod(num, modulo) & _1n) === _1n;\n\n/** Field is not always over prime: for example, Fp2 has ORDER(q)=p^m. */\nexport interface IField<T> {\n ORDER: bigint;\n isLE: boolean;\n BYTES: number;\n BITS: number;\n MASK: bigint;\n ZERO: T;\n ONE: T;\n // 1-arg\n create: (num: T) => T;\n isValid: (num: T) => boolean;\n is0: (num: T) => boolean;\n isValidNot0: (num: T) => boolean;\n neg(num: T): T;\n inv(num: T): T;\n sqrt(num: T): T;\n sqr(num: T): T;\n // 2-args\n eql(lhs: T, rhs: T): boolean;\n add(lhs: T, rhs: T): T;\n sub(lhs: T, rhs: T): T;\n mul(lhs: T, rhs: T | bigint): T;\n pow(lhs: T, power: bigint): T;\n div(lhs: T, rhs: T | bigint): T;\n // N for NonNormalized (for now)\n addN(lhs: T, rhs: T): T;\n subN(lhs: T, rhs: T): T;\n mulN(lhs: T, rhs: T | bigint): T;\n sqrN(num: T): T;\n\n // Optional\n // Should be same as sgn0 function in\n // [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).\n // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.\n isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2\n allowedLengths?: number[];\n // legendre?(num: T): T;\n invertBatch: (lst: T[]) => T[];\n toBytes(num: T): Uint8Array;\n fromBytes(bytes: Uint8Array, skipValidation?: boolean): T;\n // If c is False, CMOV returns a, otherwise it returns b.\n cmov(a: T, b: T, c: boolean): T;\n}\n// prettier-ignore\nconst FIELD_FIELDS = [\n 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',\n 'eql', 'add', 'sub', 'mul', 'pow', 'div',\n 'addN', 'subN', 'mulN', 'sqrN'\n] as const;\nexport function validateField<T>(field: IField<T>): IField<T> {\n const initial = {\n ORDER: 'bigint',\n MASK: 'bigint',\n BYTES: 'number',\n BITS: 'number',\n } as Record<string, string>;\n const opts = FIELD_FIELDS.reduce((map, val: string) => {\n map[val] = 'function';\n return map;\n }, initial);\n _validateObject(field, opts);\n // const max = 16384;\n // if (field.BYTES < 1 || field.BYTES > max) throw new Error('invalid field');\n // if (field.BITS < 1 || field.BITS > 8 * max) throw new Error('invalid field');\n return field;\n}\n\n// Generic field functions\n\n/**\n * Same as `pow` but for Fp: non-constant-time.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n */\nexport function FpPow<T>(Fp: IField<T>, num: T, power: bigint): T {\n if (power < _0n) throw new Error('invalid exponent, negatives unsupported');\n if (power === _0n) return Fp.ONE;\n if (power === _1n) return num;\n let p = Fp.ONE;\n let d = num;\n while (power > _0n) {\n if (power & _1n) p = Fp.mul(p, d);\n d = Fp.sqr(d);\n power >>= _1n;\n }\n return p;\n}\n\n/**\n * Efficiently invert an array of Field elements.\n * Exception-free. Will return `undefined` for 0 elements.\n * @param passZero map 0 to 0 (instead of undefined)\n */\nexport function FpInvertBatch<T>(Fp: IField<T>, nums: T[], passZero = false): T[] {\n const inverted = new Array(nums.length).fill(passZero ? Fp.ZERO : undefined);\n // Walk from first to last, multiply them by each other MOD p\n const multipliedAcc = nums.reduce((acc, num, i) => {\n if (Fp.is0(num)) return acc;\n inverted[i] = acc;\n return Fp.mul(acc, num);\n }, Fp.ONE);\n // Invert last element\n const invertedAcc = Fp.inv(multipliedAcc);\n // Walk from last to first, multiply them by inverted each other MOD p\n nums.reduceRight((acc, num, i) => {\n if (Fp.is0(num)) return acc;\n inverted[i] = Fp.mul(acc, inverted[i]);\n return Fp.mul(acc, num);\n }, invertedAcc);\n return inverted;\n}\n\n// TODO: remove\nexport function FpDiv<T>(Fp: IField<T>, lhs: T, rhs: T | bigint): T {\n return Fp.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, Fp.ORDER) : Fp.inv(rhs));\n}\n\n/**\n * Legendre symbol.\n * Legendre constant is used to calculate Legendre symbol (a | p)\n * which denotes the value of a^((p-1)/2) (mod p).\n *\n * * (a | p) ≡ 1 if a is a square (mod p), quadratic residue\n * * (a | p) ≡ -1 if a is not a square (mod p), quadratic non residue\n * * (a | p) ≡ 0 if a ≡ 0 (mod p)\n */\nexport function FpLegendre<T>(Fp: IField<T>, n: T): -1 | 0 | 1 {\n // We can use 3rd argument as optional cache of this value\n // but seems unneeded for now. The operation is very fast.\n const p1mod2 = (Fp.ORDER - _1n) / _2n;\n const powered = Fp.pow(n, p1mod2);\n const yes = Fp.eql(powered, Fp.ONE);\n const zero = Fp.eql(powered, Fp.ZERO);\n const no = Fp.eql(powered, Fp.neg(Fp.ONE));\n if (!yes && !zero && !no) throw new Error('invalid Legendre symbol result');\n return yes ? 1 : zero ? 0 : -1;\n}\n\n// This function returns True whenever the value x is a square in the field F.\nexport function FpIsSquare<T>(Fp: IField<T>, n: T): boolean {\n const l = FpLegendre(Fp, n);\n return l === 1;\n}\n\nexport type NLength = { nByteLength: number; nBitLength: number };\n// CURVE.n lengths\nexport function nLength(n: bigint, nBitLength?: number): NLength {\n // Bit size, byte size of CURVE.n\n if (nBitLength !== undefined) anumber(nBitLength);\n const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;\n const nByteLength = Math.ceil(_nBitLength / 8);\n return { nBitLength: _nBitLength, nByteLength };\n}\n\ntype FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;\ntype SqrtFn = (n: bigint) => bigint;\ntype FieldOpts = Partial<{\n sqrt: SqrtFn;\n isLE: boolean;\n BITS: number;\n modFromBytes: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n\n allowedLengths?: readonly number[]; // for P521 (adds padding for smaller sizes)\n}>;\n/**\n * Creates a finite field. Major performance optimizations:\n * * 1. Denormalized operations like mulN instead of mul.\n * * 2. Identical object shape: never add or remove keys.\n * * 3. `Object.freeze`.\n * Fragile: always run a benchmark on a change.\n * Security note: operations don't check 'isValid' for all elements for performance reasons,\n * it is caller responsibility to check this.\n * This is low-level code, please make sure you know what you're doing.\n *\n * Note about field properties:\n * * CHARACTERISTIC p = prime number, number of elements in main subgroup.\n * * ORDER q = similar to cofactor in curves, may be composite `q = p^m`.\n *\n * @param ORDER field order, probably prime, or could be composite\n * @param bitLen how many bits the field consumes\n * @param isLE (default: false) if encoding / decoding should be in little-endian\n * @param redef optional faster redefinitions of sqrt and other methods\n */\nexport function Field(\n ORDER: bigint,\n bitLenOrOpts?: number | FieldOpts, // TODO: use opts only in v2?\n isLE = false,\n opts: { sqrt?: SqrtFn } = {}\n): Readonly<FpField> {\n if (ORDER <= _0n) throw new Error('invalid field: expected ORDER > 0, got ' + ORDER);\n let _nbitLength: number | undefined = undefined;\n let _sqrt: SqrtFn | undefined = undefined;\n let modFromBytes: boolean = false;\n let allowedLengths: undefined | readonly number[] = undefined;\n if (typeof bitLenOrOpts === 'object' && bitLenOrOpts != null) {\n if (opts.sqrt || isLE) throw new Error('cannot specify opts in two arguments');\n const _opts = bitLenOrOpts;\n if (_opts.BITS) _nbitLength = _opts.BITS;\n if (_opts.sqrt) _sqrt = _opts.sqrt;\n if (typeof _opts.isLE === 'boolean') isLE = _opts.isLE;\n if (typeof _opts.modFromBytes === 'boolean') modFromBytes = _opts.modFromBytes;\n allowedLengths = _opts.allowedLengths;\n } else {\n if (typeof bitLenOrOpts === 'number') _nbitLength = bitLenOrOpts;\n if (opts.sqrt) _sqrt = opts.sqrt;\n }\n const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, _nbitLength);\n if (BYTES > 2048) throw new Error('invalid field: expected ORDER of <= 2048 bytes');\n let sqrtP: ReturnType<typeof FpSqrt>; // cached sqrtP\n const f: Readonly<FpField> = Object.freeze({\n ORDER,\n isLE,\n BITS,\n BYTES,\n MASK: bitMask(BITS),\n ZERO: _0n,\n ONE: _1n,\n allowedLengths: allowedLengths,\n create: (num) => mod(num, ORDER),\n isValid: (num) => {\n if (typeof num !== 'bigint')\n throw new Error('invalid field element: expected bigint, got ' + typeof num);\n return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible\n },\n is0: (num) => num === _0n,\n // is valid and invertible\n isValidNot0: (num: bigint) => !f.is0(num) && f.isValid(num),\n isOdd: (num) => (num & _1n) === _1n,\n neg: (num) => mod(-num, ORDER),\n eql: (lhs, rhs) => lhs === rhs,\n\n sqr: (num) => mod(num * num, ORDER),\n add: (lhs, rhs) => mod(lhs + rhs, ORDER),\n sub: (lhs, rhs) => mod(lhs - rhs, ORDER),\n mul: (lhs, rhs) => mod(lhs * rhs, ORDER),\n pow: (num, power) => FpPow(f, num, power),\n div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),\n\n // Same as above, but doesn't normalize\n sqrN: (num) => num * num,\n addN: (lhs, rhs) => lhs + rhs,\n subN: (lhs, rhs) => lhs - rhs,\n mulN: (lhs, rhs) => lhs * rhs,\n\n inv: (num) => invert(num, ORDER),\n sqrt:\n _sqrt ||\n ((n) => {\n if (!sqrtP) sqrtP = FpSqrt(ORDER);\n return sqrtP(f, n);\n }),\n toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),\n fromBytes: (bytes, skipValidation = true) => {\n if (allowedLengths) {\n if (!allowedLengths.includes(bytes.length) || bytes.length > BYTES) {\n throw new Error(\n 'Field.fromBytes: expected ' + allowedLengths + ' bytes, got ' + bytes.length\n );\n }\n const padded = new Uint8Array(BYTES);\n // isLE add 0 to right, !isLE to the left.\n padded.set(bytes, isLE ? 0 : padded.length - bytes.length);\n bytes = padded;\n }\n if (bytes.length !== BYTES)\n throw new Error('Field.fromBytes: expected ' + BYTES + ' bytes, got ' + bytes.length);\n let scalar = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);\n if (modFromBytes) scalar = mod(scalar, ORDER);\n if (!skipValidation)\n if (!f.isValid(scalar)) throw new Error('invalid field element: outside of range 0..ORDER');\n // NOTE: we don't validate scalar here, please use isValid. This done such way because some\n // protocol may allow non-reduced scalar that reduced later or changed some other way.\n return scalar;\n },\n // TODO: we don't need it here, move out to separate fn\n invertBatch: (lst) => FpInvertBatch(f, lst),\n // We can't move this out because Fp6, Fp12 implement it\n // and it's unclear what to return in there.\n cmov: (a, b, c) => (c ? b : a),\n } as FpField);\n return Object.freeze(f);\n}\n\n// Generic random scalar, we can do same for other fields if via Fp2.mul(Fp2.ONE, Fp2.random)?\n// This allows unsafe methods like ignore bias or zero. These unsafe, but often used in different protocols (if deterministic RNG).\n// which mean we cannot force this via opts.\n// Not sure what to do with randomBytes, we can accept it inside opts if wanted.\n// Probably need to export getMinHashLength somewhere?\n// random(bytes?: Uint8Array, unsafeAllowZero = false, unsafeAllowBias = false) {\n// const LEN = !unsafeAllowBias ? getMinHashLength(ORDER) : BYTES;\n// if (bytes === undefined) bytes = randomBytes(LEN); // _opts.randomBytes?\n// const num = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);\n// // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0\n// const reduced = unsafeAllowZero ? mod(num, ORDER) : mod(num, ORDER - _1n) + _1n;\n// return reduced;\n// },\n\nexport function FpSqrtOdd<T>(Fp: IField<T>, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? root : Fp.neg(root);\n}\n\nexport function FpSqrtEven<T>(Fp: IField<T>, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? Fp.neg(root) : root;\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).\n * Which makes it slightly more biased, less secure.\n * @deprecated use `mapKeyToField` instead\n */\nexport function hashToPrivateScalar(\n hash: string | Uint8Array,\n groupOrder: bigint,\n isLE = false\n): bigint {\n hash = ensureBytes('privateHash', hash);\n const hashLen = hash.length;\n const minLen = nLength(groupOrder).nByteLength + 8;\n if (minLen < 24 || hashLen < minLen || hashLen > 1024)\n throw new Error(\n 'hashToPrivateScalar: expected ' + minLen + '-1024 bytes of input, got ' + hashLen\n );\n const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);\n return mod(num, groupOrder - _1n) + _1n;\n}\n\n/**\n * Returns total number of bytes consumed by the field element.\n * For example, 32 bytes for usual 256-bit weierstrass curve.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of field\n */\nexport function getFieldBytesLength(fieldOrder: bigint): number {\n if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');\n const bitLength = fieldOrder.toString(2).length;\n return Math.ceil(bitLength / 8);\n}\n\n/**\n * Returns minimal amount of bytes that can be safely reduced\n * by field order.\n * Should be 2^-128 for 128-bit curve such as P256.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of target hash\n */\nexport function getMinHashLength(fieldOrder: bigint): number {\n const length = getFieldBytesLength(fieldOrder);\n return length + Math.ceil(length / 2);\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF\n * and convert them into private scalar, with the modulo bias being negligible.\n * Needs at least 48 bytes of input for 32-byte private key.\n * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/\n * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final\n * RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5\n * @param hash hash output from SHA3 or a similar function\n * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)\n * @param isLE interpret hash bytes as LE num\n * @returns valid private scalar\n */\nexport function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {\n const len = key.length;\n const fieldLen = getFieldBytesLength(fieldOrder);\n const minLen = getMinHashLength(fieldOrder);\n // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.\n if (len < 16 || len < minLen || len > 1024)\n throw new Error('expected ' + minLen + '-1024 bytes of input, got ' + len);\n const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);\n // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0\n const reduced = mod(num, fieldOrder - _1n) + _1n;\n return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);\n}\n","/**\n * Methods for elliptic curve multiplication by scalars.\n * Contains wNAF, pippenger.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { bitLen, bitMask, validateObject } from '../utils.ts';\nimport { Field, FpInvertBatch, nLength, validateField, type IField } from './modular.ts';\n\nconst _0n = BigInt(0);\nconst _1n = BigInt(1);\n\nexport type AffinePoint<T> = {\n x: T;\n y: T;\n} & { Z?: never };\n\n// This was initialy do this way to re-use montgomery ladder in field (add->mul,double->sqr), but\n// that didn't happen and there is probably not much reason to have separate Group like this?\nexport interface Group<T extends Group<T>> {\n double(): T;\n negate(): T;\n add(other: T): T;\n subtract(other: T): T;\n equals(other: T): boolean;\n multiply(scalar: bigint): T;\n toAffine?(invertedZ?: any): AffinePoint<any>;\n}\n\n// We can't \"abstract out\" coordinates (X, Y, Z; and T in Edwards): argument names of constructor\n// are not accessible. See Typescript gh-56093, gh-41594.\n//\n// We have to use recursive types, so it will return actual point, not constained `CurvePoint`.\n// If, at any point, P is `any`, it will erase all types and replace it\n// with `any`, because of recursion, `any implements CurvePoint`,\n// but we lose all constrains on methods.\n\n/** Base interface for all elliptic curve Points. */\nexport interface CurvePoint<F, P extends CurvePoint<F, P>> extends Group<P> {\n /** Affine x coordinate. Different from projective / extended X coordinate. */\n x: F;\n /** Affine y coordinate. Different from projective / extended Y coordinate. */\n y: F;\n Z?: F;\n double(): P;\n negate(): P;\n add(other: P): P;\n subtract(other: P): P;\n equals(other: P): boolean;\n multiply(scalar: bigint): P;\n assertValidity(): void;\n clearCofactor(): P;\n is0(): boolean;\n isTorsionFree(): boolean;\n isSmallOrder(): boolean;\n multiplyUnsafe(scalar: bigint): P;\n /**\n * Massively speeds up `p.multiply(n)` by using precompute tables (caching). See {@link wNAF}.\n * @param isLazy calculate cache now. Default (true) ensures it's deferred to first `multiply()`\n */\n precompute(windowSize?: number, isLazy?: boolean): P;\n /** Converts point to 2D xy affine coordinates */\n toAffine(invertedZ?: F): AffinePoint<F>;\n toBytes(): Uint8Array;\n toHex(): string;\n}\n\n/** Base interface for all elliptic curve Point constructors. */\nexport interface CurvePointCons<P extends CurvePoint<any, P>> {\n [Symbol.hasInstance]: (item: unknown) => boolean;\n BASE: P;\n ZERO: P;\n /** Field for basic curve math */\n Fp: IField<P_F<P>>;\n /** Scalar field, for scalars in multiply and others */\n Fn: IField<bigint>;\n /** Creates point from x, y. Does NOT validate if the point is valid. Use `.assertValidity()`. */\n fromAffine(p: AffinePoint<P_F<P>>): P;\n fromBytes(bytes: Uint8Array): P;\n fromHex(hex: Uint8Array | string): P;\n}\n\n// Type inference helpers: PC - PointConstructor, P - Point, Fp - Field element\n// Short names, because we use them a lot in result types:\n// * we can't do 'P = GetCurvePoint<PC>': this is default value and doesn't constrain anything\n// * we can't do 'type X = GetCurvePoint<PC>': it won't be accesible for arguments/return types\n// * `CurvePointCons<P extends CurvePoint<any, P>>` constraints from interface definition\n// won't propagate, if `PC extends CurvePointCons<any>`: the P would be 'any', which is incorrect\n// * PC could be super specific with super specific P, which implements CurvePoint<any, P>.\n// this means we need to do stuff like\n// `function test<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(`\n// if we want type safety around P, otherwise PC_P<PC> will be any\n\n/** Returns Fp type from Point (P_F<P> == P.F) */\nexport type P_F<P extends CurvePoint<any, P>> = P extends CurvePoint<infer F, P> ? F : never;\n/** Returns Fp type from PointCons (PC_F<PC> == PC.P.F) */\nexport type PC_F<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['Fp']['ZERO'];\n/** Returns Point type from PointCons (PC_P<PC> == PC.P) */\nexport type PC_P<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['ZERO'];\n\n// Ugly hack to get proper type inference, because in typescript fails to infer resursively.\n// The hack allows to do up to 10 chained operations without applying type erasure.\n//\n// Types which won't work:\n// * `CurvePointCons<CurvePoint<any, any>>`, will return `any` after 1 operation\n// * `CurvePointCons<any>: WeierstrassPointCons<bigint> extends CurvePointCons<any> = false`\n// * `P extends CurvePoint, PC extends CurvePointCons<P>`\n// * It can't infer P from PC alone\n// * Too many relations between F, P & PC\n// * It will infer P/F if `arg: CurvePointCons<F, P>`, but will fail if PC is generic\n// * It will work correctly if there is an additional argument of type P\n// * But generally, we don't want to parametrize `CurvePointCons` over `F`: it will complicate\n// types, making them un-inferable\n// prettier-ignore\nexport type PC_ANY = CurvePointCons<\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any, any>\n >>>>>>>>>\n>;\n\nexport interface CurveLengths {\n secretKey?: number;\n publicKey?: number;\n publicKeyUncompressed?: number;\n publicKeyHasPrefix?: boolean;\n signature?: number;\n seed?: number;\n}\nexport type GroupConstructor<T> = {\n BASE: T;\n ZERO: T;\n};\n/** @deprecated */\nexport type ExtendedGroupConstructor<T> = GroupConstructor<T> & {\n Fp: IField<any>;\n Fn: IField<bigint>;\n fromAffine(ap: AffinePoint<any>): T;\n};\nexport type Mapper<T> = (i: T[]) => T[];\n\nexport function negateCt<T extends { negate: () => T }>(condition: boolean, item: T): T {\n const neg = item.negate();\n return condition ? neg : item;\n}\n\n/**\n * Takes a bunch of Projective Points but executes only one\n * inversion on all of them. Inversion is very slow operation,\n * so this improves performance massively.\n * Optimization: converts a list of projective points to a list of identical points with Z=1.\n */\nexport function normalizeZ<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n points: P[]\n): P[] {\n const invertedZs = FpInvertBatch(\n c.Fp,\n points.map((p) => p.Z!)\n );\n return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));\n}\n\nfunction validateW(W: number, bits: number) {\n if (!Number.isSafeInteger(W) || W <= 0 || W > bits)\n throw new Error('invalid window size, expected [1..' + bits + '], got W=' + W);\n}\n\n/** Internal wNAF opts for specific W and scalarBits */\nexport type WOpts = {\n windows: number;\n windowSize: number;\n mask: bigint;\n maxNumber: number;\n shiftBy: bigint;\n};\n\nfunction calcWOpts(W: number, scalarBits: number): WOpts {\n validateW(W, scalarBits);\n const windows = Math.ceil(scalarBits / W) + 1; // W=8 33. Not 32, because we skip zero\n const windowSize = 2 ** (W - 1); // W=8 128. Not 256, because we skip zero\n const maxNumber = 2 ** W; // W=8 256\n const mask = bitMask(W); // W=8 255 == mask 0b11111111\n const shiftBy = BigInt(W); // W=8 8\n return { windows, windowSize, mask, maxNumber, shiftBy };\n}\n\nfunction calcOffsets(n: bigint, window: number, wOpts: WOpts) {\n const { windowSize, mask, maxNumber, shiftBy } = wOpts;\n let wbits = Number(n & mask); // extract W bits.\n let nextN = n >> shiftBy; // shift number by W bits.\n\n // What actually happens here:\n // const highestBit = Number(mask ^ (mask >> 1n));\n // let wbits2 = wbits - 1; // skip zero\n // if (wbits2 & highestBit) { wbits2 ^= Number(mask); // (~);\n\n // split if bits > max: +224 => 256-32\n if (wbits > windowSize) {\n // we skip zero, which means instead of `>= size-1`, we do `> size`\n wbits -= maxNumber; // -32, can be maxNumber - wbits, but then we need to set isNeg here.\n nextN += _1n; // +256 (carry)\n }\n const offsetStart = window * windowSize;\n const offset = offsetStart + Math.abs(wbits) - 1; // -1 because we skip zero\n const isZero = wbits === 0; // is current window slice a 0?\n const isNeg = wbits < 0; // is current window slice negative?\n const isNegF = window % 2 !== 0; // fake random statement for noise\n const offsetF = offsetStart; // fake offset for noise\n return { nextN, offset, isZero, isNeg, isNegF, offsetF };\n}\n\nfunction validateMSMPoints(points: any[], c: any) {\n if (!Array.isArray(points)) throw new Error('array expected');\n points.forEach((p, i) => {\n if (!(p instanceof c)) throw new Error('invalid point at index ' + i);\n });\n}\nfunction validateMSMScalars(scalars: any[], field: any) {\n if (!Array.isArray(scalars)) throw new Error('array of scalars expected');\n scalars.forEach((s, i) => {\n if (!field.isValid(s)) throw new Error('invalid scalar at index ' + i);\n });\n}\n\n// Since points in different groups cannot be equal (different object constructor),\n// we can have single place to store precomputes.\n// Allows to make points frozen / immutable.\nconst pointPrecomputes = new WeakMap<any, any[]>();\nconst pointWindowSizes = new WeakMap<any, number>();\n\nfunction getW(P: any): number {\n // To disable precomputes:\n // return 1;\n return pointWindowSizes.get(P) || 1;\n}\n\nfunction assert0(n: bigint): void {\n if (n !== _0n) throw new Error('invalid wNAF');\n}\n\n/**\n * Elliptic curve multiplication of Point by scalar. Fragile.\n * Table generation takes **30MB of ram and 10ms on high-end CPU**,\n * but may take much longer on slow devices. Actual generation will happen on\n * first call of `multiply()`. By default, `BASE` point is precomputed.\n *\n * Scalars should always be less than curve order: this should be checked inside of a curve itself.\n * Creates precomputation tables for fast multiplication:\n * - private scalar is split by fixed size windows of W bits\n * - every window point is collected from window's table & added to accumulator\n * - since windows are different, same point inside tables won't be accessed more than once per calc\n * - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)\n * - +1 window is neccessary for wNAF\n * - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication\n *\n * @todo Research returning 2d JS array of windows, instead of a single window.\n * This would allow windows to be in different memory locations\n */\nexport class wNAF<PC extends PC_ANY> {\n private readonly BASE: PC_P<PC>;\n private readonly ZERO: PC_P<PC>;\n private readonly Fn: PC['Fn'];\n readonly bits: number;\n\n // Parametrized with a given Point class (not individual point)\n constructor(Point: PC, bits: number) {\n this.BASE = Point.BASE;\n this.ZERO = Point.ZERO;\n this.Fn = Point.Fn;\n this.bits = bits;\n }\n\n // non-const time multiplication ladder\n _unsafeLadder(elm: PC_P<PC>, n: bigint, p: PC_P<PC> = this.ZERO): PC_P<PC> {\n let d: PC_P<PC> = elm;\n while (n > _0n) {\n if (n & _1n) p = p.add(d);\n d = d.double();\n n >>= _1n;\n }\n return p;\n }\n\n /**\n * Creates a wNAF precomputation window. Used for caching.\n * Default window size is set by `utils.precompute()` and is equal to 8.\n * Number of precomputed points depends on the curve size:\n * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:\n * - 𝑊 is the window size\n * - 𝑛 is the bitlength of the curve order.\n * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.\n * @param point Point instance\n * @param W window size\n * @returns precomputed point tables flattened to a single array\n */\n private precomputeWindow(point: PC_P<PC>, W: number): PC_P<PC>[] {\n const { windows, windowSize } = calcWOpts(W, this.bits);\n const points: PC_P<PC>[] = [];\n let p: PC_P<PC> = point;\n let base = p;\n for (let window = 0; window < windows; window++) {\n base = p;\n points.push(base);\n // i=1, bc we skip 0\n for (let i = 1; i < windowSize; i++) {\n base = base.add(p);\n points.push(base);\n }\n p = base.double();\n }\n return points;\n }\n\n /**\n * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.\n * More compact implementation:\n * https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541\n * @returns real and fake (for const-time) points\n */\n private wNAF(W: number, precomputes: PC_P<PC>[], n: bigint): { p: PC_P<PC>; f: PC_P<PC> } {\n // Scalar should be smaller than field order\n if (!this.Fn.isValid(n)) throw new Error('invalid scalar');\n // Accumulators\n let p = this.ZERO;\n let f = this.BASE;\n // This code was first written with assumption that 'f' and 'p' will never be infinity point:\n // since each addition is multiplied by 2 ** W, it cannot cancel each other. However,\n // there is negate now: it is possible that negated element from low value\n // would be the same as high element, which will create carry into next window.\n // It's not obvious how this can fail, but still worth investigating later.\n const wo = calcWOpts(W, this.bits);\n for (let window = 0; window < wo.windows; window++) {\n // (n === _0n) is handled and not early-exited. isEven and offsetF are used for noise\n const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window, wo);\n n = nextN;\n if (isZero) {\n // bits are 0: add garbage to fake point\n // Important part for const-time getPublicKey: add random \"noise\" point to f.\n f = f.add(negateCt(isNegF, precomputes[offsetF]));\n } else {\n // bits are 1: add to result point\n p = p.add(negateCt(isNeg, precomputes[offset]));\n }\n }\n assert0(n);\n // Return both real and fake points: JIT won't eliminate f.\n // At this point there is a way to F be infinity-point even if p is not,\n // which makes it less const-time: around 1 bigint multiply.\n return { p, f };\n }\n\n /**\n * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.\n * @param acc accumulator point to add result of multiplication\n * @returns point\n */\n private wNAFUnsafe(\n W: number,\n precomputes: PC_P<PC>[],\n n: bigint,\n acc: PC_P<PC> = this.ZERO\n ): PC_P<PC> {\n const wo = calcWOpts(W, this.bits);\n for (let window = 0; window < wo.windows; window++) {\n if (n === _0n) break; // Early-exit, skip 0 value\n const { nextN, offset, isZero, isNeg } = calcOffsets(n, window, wo);\n n = nextN;\n if (isZero) {\n // Window bits are 0: skip processing.\n // Move to next window.\n continue;\n } else {\n const item = precomputes[offset];\n acc = acc.add(isNeg ? item.negate() : item); // Re-using acc allows to save adds in MSM\n }\n }\n assert0(n);\n return acc;\n }\n\n private getPrecomputes(W: number, point: PC_P<PC>, transform?: Mapper<PC_P<PC>>): PC_P<PC>[] {\n // Calculate precomputes on a first run, reuse them after\n let comp = pointPrecomputes.get(point);\n if (!comp) {\n comp = this.precomputeWindow(point, W) as PC_P<PC>[];\n if (W !== 1) {\n // Doing transform outside of if brings 15% perf hit\n if (typeof transform === 'function') comp = transform(comp);\n pointPrecomputes.set(point, comp);\n }\n }\n return comp;\n }\n\n cached(\n point: PC_P<PC>,\n scalar: bigint,\n transform?: Mapper<PC_P<PC>>\n ): { p: PC_P<PC>; f: PC_P<PC> } {\n const W = getW(point);\n return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);\n }\n\n unsafe(point: PC_P<PC>, scalar: bigint, transform?: Mapper<PC_P<PC>>, prev?: PC_P<PC>): PC_P<PC> {\n const W = getW(point);\n if (W === 1) return this._unsafeLadder(point, scalar, prev); // For W=1 ladder is ~x2 faster\n return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);\n }\n\n // We calculate precomputes for elliptic curve point multiplication\n // using windowed method. This specifies window size and\n // stores precomputed values. Usually only base point would be precomputed.\n createCache(P: PC_P<PC>, W: number): void {\n validateW(W, this.bits);\n pointWindowSizes.set(P, W);\n pointPrecomputes.delete(P);\n }\n\n hasCache(elm: PC_P<PC>): boolean {\n return getW(elm) !== 1;\n }\n}\n\n/**\n * Endomorphism-specific multiplication for Koblitz curves.\n * Cost: 128 dbl, 0-256 adds.\n */\nexport function mulEndoUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n Point: PC,\n point: P,\n k1: bigint,\n k2: bigint\n): { p1: P; p2: P } {\n let acc = point;\n let p1 = Point.ZERO;\n let p2 = Point.ZERO;\n while (k1 > _0n || k2 > _0n) {\n if (k1 & _1n) p1 = p1.add(acc);\n if (k2 & _1n) p2 = p2.add(acc);\n acc = acc.double();\n k1 >>= _1n;\n k2 >>= _1n;\n }\n return { p1, p2 };\n}\n\n/**\n * Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * 30x faster vs naive addition on L=4096, 10x faster than precomputes.\n * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.\n * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @param scalars array of L scalars (aka secret keys / bigints)\n */\nexport function pippenger<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n fieldN: IField<bigint>,\n points: P[],\n scalars: bigint[]\n): P {\n // If we split scalars by some window (let's say 8 bits), every chunk will only\n // take 256 buckets even if there are 4096 scalars, also re-uses double.\n // TODO:\n // - https://eprint.iacr.org/2024/750.pdf\n // - https://tches.iacr.org/index.php/TCHES/article/view/10287\n // 0 is accepted in scalars\n validateMSMPoints(points, c);\n validateMSMScalars(scalars, fieldN);\n const plength = points.length;\n const slength = scalars.length;\n if (plength !== slength) throw new Error('arrays of points and scalars must have equal length');\n // if (plength === 0) throw new Error('array must be of length >= 2');\n const zero = c.ZERO;\n const wbits = bitLen(BigInt(plength));\n let windowSize = 1; // bits\n if (wbits > 12) windowSize = wbits - 3;\n else if (wbits > 4) windowSize = wbits - 2;\n else if (wbits > 0) windowSize = 2;\n const MASK = bitMask(windowSize);\n const buckets = new Array(Number(MASK) + 1).fill(zero); // +1 for zero array\n const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;\n let sum = zero;\n for (let i = lastBits; i >= 0; i -= windowSize) {\n buckets.fill(zero);\n for (let j = 0; j < slength; j++) {\n const scalar = scalars[j];\n const wbits = Number((scalar >> BigInt(i)) & MASK);\n buckets[wbits] = buckets[wbits].add(points[j]);\n }\n let resI = zero; // not using this will do small speed-up, but will lose ct\n // Skip first bucket, because it is zero\n for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {\n sumI = sumI.add(buckets[j]);\n resI = resI.add(sumI);\n }\n sum = sum.add(resI);\n if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double();\n }\n return sum as P;\n}\n/**\n * Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @returns function which multiplies points with scaars\n */\nexport function precomputeMSMUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n fieldN: IField<bigint>,\n points: P[],\n windowSize: number\n): (scalars: bigint[]) => P {\n /**\n * Performance Analysis of Window-based Precomputation\n *\n * Base Case (256-bit scalar, 8-bit window):\n * - Standard precomputation requires:\n * - 31 additions per scalar × 256 scalars = 7,936 ops\n * - Plus 255 summary additions = 8,191 total ops\n * Note: Summary additions can be optimized via accumulator\n *\n * Chunked Precomputation Analysis:\n * - Using 32 chunks requires:\n * - 255 additions per chunk\n * - 256 doublings\n * - Total: (255 × 32) + 256 = 8,416 ops\n *\n * Memory Usage Comparison:\n * Window Size | Standard Points | Chunked Points\n * ------------|-----------------|---------------\n * 4-bit | 520 | 15\n * 8-bit | 4,224 | 255\n * 10-bit | 13,824 | 1,023\n * 16-bit | 557,056 | 65,535\n *\n * Key Advantages:\n * 1. Enables larger window sizes due to reduced memory overhead\n * 2. More efficient for smaller scalar counts:\n * - 16 chunks: (16 × 255) + 256 = 4,336 ops\n * - ~2x faster than standard 8,191 ops\n *\n * Limitations:\n * - Not suitable for plain precomputes (requires 256 constant doublings)\n * - Performance degrades with larger scalar counts:\n * - Optimal for ~256 scalars\n * - Less efficient for 4096+ scalars (Pippenger preferred)\n */\n validateW(windowSize, fieldN.BITS);\n validateMSMPoints(points, c);\n const zero = c.ZERO;\n const tableSize = 2 ** windowSize - 1; // table size (without zero)\n const chunks = Math.ceil(fieldN.BITS / windowSize); // chunks of item\n const MASK = bitMask(windowSize);\n const tables = points.map((p: P) => {\n const res = [];\n for (let i = 0, acc = p; i < tableSize; i++) {\n res.push(acc);\n acc = acc.add(p);\n }\n return res;\n });\n return (scalars: bigint[]): P => {\n validateMSMScalars(scalars, fieldN);\n if (scalars.length > points.length)\n throw new Error('array of scalars must be smaller than array of points');\n let res = zero;\n for (let i = 0; i < chunks; i++) {\n // No need to double if accumulator is still zero.\n if (res !== zero) for (let j = 0; j < windowSize; j++) res = res.double();\n const shiftBy = BigInt(chunks * windowSize - (i + 1) * windowSize);\n for (let j = 0; j < scalars.length; j++) {\n const n = scalars[j];\n const curr = Number((n >> shiftBy) & MASK);\n if (!curr) continue; // skip zero scalars chunks\n res = res.add(tables[j][curr - 1]);\n }\n }\n return res;\n };\n}\n\n// TODO: remove\n/**\n * Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.\n * Though generator can be different (Fp2 / Fp6 for BLS).\n */\nexport type BasicCurve<T> = {\n Fp: IField<T>; // Field over which we'll do calculations (Fp)\n n: bigint; // Curve order, total count of valid points in the field\n nBitLength?: number; // bit length of curve order\n nByteLength?: number; // byte length of curve order\n h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation\n hEff?: bigint; // Number to multiply to clear cofactor\n Gx: T; // base point X coordinate\n Gy: T; // base point Y coordinate\n allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey\n};\n\n// TODO: remove\n/** @deprecated */\nexport function validateBasic<FP, T>(\n curve: BasicCurve<FP> & T\n): Readonly<\n {\n readonly nBitLength: number;\n readonly nByteLength: number;\n } & BasicCurve<FP> &\n T & {\n p: bigint;\n }\n> {\n validateField(curve.Fp);\n validateObject(\n curve,\n {\n n: 'bigint',\n h: 'bigint',\n Gx: 'field',\n Gy: 'field',\n },\n {\n nBitLength: 'isSafeInteger',\n nByteLength: 'isSafeInteger',\n }\n );\n // Set defaults\n return Object.freeze({\n ...nLength(curve.n, curve.nBitLength),\n ...curve,\n ...{ p: curve.Fp.ORDER },\n } as const);\n}\n\nexport type ValidCurveParams<T> = {\n p: bigint;\n n: bigint;\n h: bigint;\n a: T;\n b?: T;\n d?: T;\n Gx: T;\n Gy: T;\n};\n\nfunction createField<T>(order: bigint, field?: IField<T>, isLE?: boolean): IField<T> {\n if (field) {\n if (field.ORDER !== order) throw new Error('Field.ORDER must match order: Fp == p, Fn == n');\n validateField(field);\n return field;\n } else {\n return Field(order, { isLE }) as unknown as IField<T>;\n }\n}\nexport type FpFn<T> = { Fp: IField<T>; Fn: IField<bigint> };\n\n/** Validates CURVE opts and creates fields */\nexport function _createCurveFields<T>(\n type: 'weierstrass' | 'edwards',\n CURVE: ValidCurveParams<T>,\n curveOpts: Partial<FpFn<T>> = {},\n FpFnLE?: boolean\n): FpFn<T> & { CURVE: ValidCurveParams<T> } {\n if (FpFnLE === undefined) FpFnLE = type === 'edwards';\n if (!CURVE || typeof CURVE !== 'object') throw new Error(`expected valid ${type} CURVE object`);\n for (const p of ['p', 'n', 'h'] as const) {\n const val = CURVE[p];\n if (!(typeof val === 'bigint' && val > _0n))\n throw new Error(`CURVE.${p} must be positive bigint`);\n }\n const Fp = createField(CURVE.p, curveOpts.Fp, FpFnLE);\n const Fn = createField(CURVE.n, curveOpts.Fn, FpFnLE);\n const _b: 'b' | 'd' = type === 'weierstrass' ? 'b' : 'd';\n const params = ['Gx', 'Gy', 'a', _b] as const;\n for (const p of params) {\n // @ts-ignore\n if (!Fp.isValid(CURVE[p]))\n throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);\n }\n CURVE = Object.freeze(Object.assign({}, CURVE));\n return { CURVE, Fp, Fn };\n}\n","/**\n * Short Weierstrass curve methods. The formula is: y² = x³ + ax + b.\n *\n * ### Design rationale for types\n *\n * * Interaction between classes from different curves should fail:\n * `k256.Point.BASE.add(p256.Point.BASE)`\n * * For this purpose we want to use `instanceof` operator, which is fast and works during runtime\n * * Different calls of `curve()` would return different classes -\n * `curve(params) !== curve(params)`: if somebody decided to monkey-patch their curve,\n * it won't affect others\n *\n * TypeScript can't infer types for classes created inside a function. Classes is one instance\n * of nominative types in TypeScript and interfaces only check for shape, so it's hard to create\n * unique type for every function call.\n *\n * We can use generic types via some param, like curve opts, but that would:\n * 1. Enable interaction between `curve(params)` and `curve(params)` (curves of same params)\n * which is hard to debug.\n * 2. Params can be generic and we can't enforce them to be constant value:\n * if somebody creates curve from non-constant params,\n * it would be allowed to interact with other curves with non-constant params\n *\n * @todo https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { hmac as nobleHmac } from '@noble/hashes/hmac.js';\nimport { ahash } from '@noble/hashes/utils';\nimport {\n _validateObject,\n _abool2 as abool,\n _abytes2 as abytes,\n aInRange,\n bitLen,\n bitMask,\n bytesToHex,\n bytesToNumberBE,\n concatBytes,\n createHmacDrbg,\n ensureBytes,\n hexToBytes,\n inRange,\n isBytes,\n memoized,\n numberToHexUnpadded,\n randomBytes as randomBytesWeb,\n type CHash,\n type Hex,\n type PrivKey,\n} from '../utils.ts';\nimport {\n _createCurveFields,\n mulEndoUnsafe,\n negateCt,\n normalizeZ,\n pippenger,\n wNAF,\n type AffinePoint,\n type BasicCurve,\n type CurveLengths,\n type CurvePoint,\n type CurvePointCons,\n} from './curve.ts';\nimport {\n Field,\n FpInvertBatch,\n getMinHashLength,\n mapHashToField,\n nLength,\n validateField,\n type IField,\n type NLength,\n} from './modular.ts';\n\nexport type { AffinePoint };\nexport type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;\n\ntype EndoBasis = [[bigint, bigint], [bigint, bigint]];\n/**\n * When Weierstrass curve has `a=0`, it becomes Koblitz curve.\n * Koblitz curves allow using **efficiently-computable GLV endomorphism ψ**.\n * Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.\n * For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.\n *\n * Endomorphism consists of beta, lambda and splitScalar:\n *\n * 1. GLV endomorphism ψ transforms a point: `P = (x, y) ↦ ψ(P) = (β·x mod p, y)`\n * 2. GLV scalar decomposition transforms a scalar: `k ≡ k₁ + k₂·λ (mod n)`\n * 3. Then these are combined: `k·P = k₁·P + k₂·ψ(P)`\n * 4. Two 128-bit point-by-scalar multiplications + one point addition is faster than\n * one 256-bit multiplication.\n *\n * where\n * * beta: β ∈ Fₚ with β³ = 1, β ≠ 1\n * * lambda: λ ∈ Fₙ with λ³ = 1, λ ≠ 1\n * * splitScalar decomposes k ↦ k₁, k₂, by using reduced basis vectors.\n * Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`\n *\n * Check out `test/misc/endomorphism.js` and\n * [gist](https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066).\n */\nexport type EndomorphismOpts = {\n beta: bigint;\n basises?: EndoBasis;\n splitScalar?: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };\n};\n\n// We construct basis in such way that den is always positive and equals n, but num sign depends on basis (not on secret value)\nconst divNearest = (num: bigint, den: bigint) => (num + (num >= 0 ? den : -den) / _2n) / den;\n\nexport type ScalarEndoParts = { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };\n\n/**\n * Splits scalar for GLV endomorphism.\n */\nexport function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): ScalarEndoParts {\n // Split scalar into two such that part is ~half bits: `abs(part) < sqrt(N)`\n // Since part can be negative, we need to do this on point.\n // TODO: verifyScalar function which consumes lambda\n const [[a1, b1], [a2, b2]] = basis;\n const c1 = divNearest(b2 * k, n);\n const c2 = divNearest(-b1 * k, n);\n // |k1|/|k2| is < sqrt(N), but can be negative.\n // If we do `k1 mod N`, we'll get big scalar (`> sqrt(N)`): so, we do cheaper negation instead.\n let k1 = k - c1 * a1 - c2 * a2;\n let k2 = -c1 * b1 - c2 * b2;\n const k1neg = k1 < _0n;\n const k2neg = k2 < _0n;\n if (k1neg) k1 = -k1;\n if (k2neg) k2 = -k2;\n // Double check that resulting scalar less than half bits of N: otherwise wNAF will fail.\n // This should only happen on wrong basises. Also, math inside is too complex and I don't trust it.\n const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n; // Half bits of N\n if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) {\n throw new Error('splitScalar (endomorphism): failed, k=' + k);\n }\n return { k1neg, k1, k2neg, k2 };\n}\n\nexport type ECDSASigFormat = 'compact' | 'recovered' | 'der';\nexport type ECDSARecoverOpts = {\n prehash?: boolean;\n};\nexport type ECDSAVerifyOpts = {\n prehash?: boolean;\n lowS?: boolean;\n format?: ECDSASigFormat;\n};\nexport type ECDSASignOpts = {\n prehash?: boolean;\n lowS?: boolean;\n format?: ECDSASigFormat;\n extraEntropy?: Uint8Array | boolean;\n};\n\nfunction validateSigFormat(format: string): ECDSASigFormat {\n if (!['compact', 'recovered', 'der'].includes(format))\n throw new Error('Signature format must be \"compact\", \"recovered\", or \"der\"');\n return format as ECDSASigFormat;\n}\n\nfunction validateSigOpts<T extends ECDSASignOpts, D extends Required<ECDSASignOpts>>(\n opts: T,\n def: D\n): Required<ECDSASignOpts> {\n const optsn: ECDSASignOpts = {};\n for (let optName of Object.keys(def)) {\n // @ts-ignore\n optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];\n }\n abool(optsn.lowS!, 'lowS');\n abool(optsn.prehash!, 'prehash');\n if (optsn.format !== undefined) validateSigFormat(optsn.format);\n return optsn as Required<ECDSASignOpts>;\n}\n\n/** Instance methods for 3D XYZ projective points. */\nexport interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>> {\n /** projective X coordinate. Different from affine x. */\n readonly X: T;\n /** projective Y coordinate. Different from affine y. */\n readonly Y: T;\n /** projective z coordinate */\n readonly Z: T;\n /** affine x coordinate. Different from projective X. */\n get x(): T;\n /** affine y coordinate. Different from projective Y. */\n get y(): T;\n /** Encodes point using IEEE P1363 (DER) encoding. First byte is 2/3/4. Default = isCompressed. */\n toBytes(isCompressed?: boolean): Uint8Array;\n toHex(isCompressed?: boolean): string;\n\n /** @deprecated use `.X` */\n readonly px: T;\n /** @deprecated use `.Y` */\n readonly py: T;\n /** @deprecated use `.Z` */\n readonly pz: T;\n /** @deprecated use `toBytes` */\n toRawBytes(isCompressed?: boolean): Uint8Array;\n /** @deprecated use `multiplyUnsafe` */\n multiplyAndAddUnsafe(\n Q: WeierstrassPoint<T>,\n a: bigint,\n b: bigint\n ): WeierstrassPoint<T> | undefined;\n /** @deprecated use `p.y % 2n === 0n` */\n hasEvenY(): boolean;\n /** @deprecated use `p.precompute(windowSize)` */\n _setWindowSize(windowSize: number): void;\n}\n\n/** Static methods for 3D XYZ projective points. */\nexport interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint<T>> {\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n new (X: T, Y: T, Z: T): WeierstrassPoint<T>;\n CURVE(): WeierstrassOpts<T>;\n /** @deprecated use `Point.BASE.multiply(Point.Fn.fromBytes(privateKey))` */\n fromPrivateKey(privateKey: PrivKey): WeierstrassPoint<T>;\n /** @deprecated use `import { normalizeZ } from '@noble/curves/abstract/curve.js';` */\n normalizeZ(points: WeierstrassPoint<T>[]): WeierstrassPoint<T>[];\n /** @deprecated use `import { pippenger } from '@noble/curves/abstract/curve.js';` */\n msm(points: WeierstrassPoint<T>[], scalars: bigint[]): WeierstrassPoint<T>;\n}\n\n/**\n * Weierstrass curve options.\n *\n * * p: prime characteristic (order) of finite field, in which arithmetics is done\n * * n: order of prime subgroup a.k.a total amount of valid curve points\n * * h: cofactor, usually 1. h*n is group order; n is subgroup order\n * * a: formula param, must be in field of p\n * * b: formula param, must be in field of p\n * * Gx: x coordinate of generator point a.k.a. base point\n * * Gy: y coordinate of generator point\n */\nexport type WeierstrassOpts<T> = Readonly<{\n p: bigint;\n n: bigint;\n h: bigint;\n a: T;\n b: T;\n Gx: T;\n Gy: T;\n}>;\n\n// When a cofactor != 1, there can be an effective methods to:\n// 1. Determine whether a point is torsion-free\n// 2. Clear torsion component\n// wrapPrivateKey: bls12-381 requires mod(n) instead of rejecting keys >= n\nexport type WeierstrassExtraOpts<T> = Partial<{\n Fp: IField<T>;\n Fn: IField<bigint>;\n allowInfinityPoint: boolean;\n endo: EndomorphismOpts;\n isTorsionFree: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;\n clearCofactor: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;\n fromBytes: (bytes: Uint8Array) => AffinePoint<T>;\n toBytes: (\n c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ) => Uint8Array;\n}>;\n\n/**\n * Options for ECDSA signatures over a Weierstrass curve.\n *\n * * lowS: (default: true) whether produced / verified signatures occupy low half of ecdsaOpts.p. Prevents malleability.\n * * hmac: (default: noble-hashes hmac) function, would be used to init hmac-drbg for k generation.\n * * randomBytes: (default: webcrypto os-level CSPRNG) custom method for fetching secure randomness.\n * * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves\n */\nexport type ECDSAOpts = Partial<{\n lowS: boolean;\n hmac: HmacFnSync;\n randomBytes: (bytesLength?: number) => Uint8Array;\n bits2int: (bytes: Uint8Array) => bigint;\n bits2int_modN: (bytes: Uint8Array) => bigint;\n}>;\n\n/**\n * Elliptic Curve Diffie-Hellman interface.\n * Provides keygen, secret-to-public conversion, calculating shared secrets.\n */\nexport interface ECDH {\n keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };\n getPublicKey: (secretKey: PrivKey, isCompressed?: boolean) => Uint8Array;\n getSharedSecret: (secretKeyA: PrivKey, publicKeyB: Hex, isCompressed?: boolean) => Uint8Array;\n Point: WeierstrassPointCons<bigint>;\n utils: {\n isValidSecretKey: (secretKey: PrivKey) => boolean;\n isValidPublicKey: (publicKey: Uint8Array, isCompressed?: boolean) => boolean;\n randomSecretKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `randomSecretKey` */\n randomPrivateKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `isValidSecretKey` */\n isValidPrivateKey: (secretKey: PrivKey) => boolean;\n /** @deprecated use `Point.Fn.fromBytes()` */\n normPrivateKeyToScalar: (key: PrivKey) => bigint;\n /** @deprecated use `point.precompute()` */\n precompute: (windowSize?: number, point?: WeierstrassPoint<bigint>) => WeierstrassPoint<bigint>;\n };\n lengths: CurveLengths;\n}\n\n/**\n * ECDSA interface.\n * Only supported for prime fields, not Fp2 (extension fields).\n */\nexport interface ECDSA extends ECDH {\n sign: (message: Hex, secretKey: PrivKey, opts?: ECDSASignOpts) => ECDSASigRecovered;\n verify: (\n signature: Uint8Array,\n message: Uint8Array,\n publicKey: Uint8Array,\n opts?: ECDSAVerifyOpts\n ) => boolean;\n recoverPublicKey(signature: Uint8Array, message: Uint8Array, opts?: ECDSARecoverOpts): Uint8Array;\n Signature: ECDSASignatureCons;\n}\nexport class DERErr extends Error {\n constructor(m = '') {\n super(m);\n }\n}\nexport type IDER = {\n // asn.1 DER encoding utils\n Err: typeof DERErr;\n // Basic building block is TLV (Tag-Length-Value)\n _tlv: {\n encode: (tag: number, data: string) => string;\n // v - value, l - left bytes (unparsed)\n decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array };\n };\n // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,\n // since we always use positive integers here. It must always be empty:\n // - add zero byte if exists\n // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)\n _int: {\n encode(num: bigint): string;\n decode(data: Uint8Array): bigint;\n };\n toSig(hex: string | Uint8Array): { r: bigint; s: bigint };\n hexFromSig(sig: { r: bigint; s: bigint }): string;\n};\n/**\n * ASN.1 DER encoding utilities. ASN is very complex & fragile. Format:\n *\n * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]\n *\n * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html\n */\nexport const DER: IDER = {\n // asn.1 DER encoding utils\n Err: DERErr,\n // Basic building block is TLV (Tag-Length-Value)\n _tlv: {\n encode: (tag: number, data: string): string => {\n const { Err: E } = DER;\n if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');\n if (data.length & 1) throw new E('tlv.encode: unpadded data');\n const dataLen = data.length / 2;\n const len = numberToHexUnpadded(dataLen);\n if ((len.length / 2) & 0b1000_0000) throw new E('tlv.encode: long form length too big');\n // length of length with long form flag\n const lenLen = dataLen > 127 ? numberToHexUnpadded((len.length / 2) | 0b1000_0000) : '';\n const t = numberToHexUnpadded(tag);\n return t + lenLen + len + data;\n },\n // v - value, l - left bytes (unparsed)\n decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array } {\n const { Err: E } = DER;\n let pos = 0;\n if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');\n if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv');\n const first = data[pos++];\n const isLong = !!(first & 0b1000_0000); // First bit of first length byte is flag for short/long form\n let length = 0;\n if (!isLong) length = first;\n else {\n // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)]\n const lenLen = first & 0b0111_1111;\n if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported');\n if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js\n const lengthBytes = data.subarray(pos, pos + lenLen);\n if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete');\n if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte');\n for (const b of lengthBytes) length = (length << 8) | b;\n pos += lenLen;\n if (length < 128) throw new E('tlv.decode(long): not minimal encoding');\n }\n const v = data.subarray(pos, pos + length);\n if (v.length !== length) throw new E('tlv.decode: wrong value length');\n return { v, l: data.subarray(pos + length) };\n },\n },\n // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,\n // since we always use positive integers here. It must always be empty:\n // - add zero byte if exists\n // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)\n _int: {\n encode(num: bigint): string {\n const { Err: E } = DER;\n if (num < _0n) throw new E('integer: negative integers are not allowed');\n let hex = numberToHexUnpadded(num);\n // Pad with zero byte if negative flag is present\n if (Number.parseInt(hex[0], 16) & 0b1000) hex = '00' + hex;\n if (hex.length & 1) throw new E('unexpected DER parsing assertion: unpadded hex');\n return hex;\n },\n decode(data: Uint8Array): bigint {\n const { Err: E } = DER;\n if (data[0] & 0b1000_0000) throw new E('invalid signature integer: negative');\n if (data[0] === 0x00 && !(data[1] & 0b1000_0000))\n throw new E('invalid signature integer: unnecessary leading zero');\n return bytesToNumberBE(data);\n },\n },\n toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {\n // parse DER signature\n const { Err: E, _int: int, _tlv: tlv } = DER;\n const data = ensureBytes('signature', hex);\n const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data);\n if (seqLeftBytes.length) throw new E('invalid signature: left bytes after parsing');\n const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes);\n const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes);\n if (sLeftBytes.length) throw new E('invalid signature: left bytes after parsing');\n return { r: int.decode(rBytes), s: int.decode(sBytes) };\n },\n hexFromSig(sig: { r: bigint; s: bigint }): string {\n const { _tlv: tlv, _int: int } = DER;\n const rs = tlv.encode(0x02, int.encode(sig.r));\n const ss = tlv.encode(0x02, int.encode(sig.s));\n const seq = rs + ss;\n return tlv.encode(0x30, seq);\n },\n};\n\n// Be friendly to bad ECMAScript parsers by not using bigint literals\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);\n\nexport function _normFnElement(Fn: IField<bigint>, key: PrivKey): bigint {\n const { BYTES: expected } = Fn;\n let num: bigint;\n if (typeof key === 'bigint') {\n num = key;\n } else {\n let bytes = ensureBytes('private key', key);\n try {\n num = Fn.fromBytes(bytes);\n } catch (error) {\n throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`);\n }\n }\n if (!Fn.isValidNot0(num)) throw new Error('invalid private key: out of range [1..N-1]');\n return num;\n}\n\n/**\n * Creates weierstrass Point constructor, based on specified curve options.\n *\n * @example\n```js\nconst opts = {\n p: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),\n n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),\n h: BigInt(1),\n a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),\n b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),\n Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),\n Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),\n};\nconst p256_Point = weierstrass(opts);\n```\n */\nexport function weierstrassN<T>(\n params: WeierstrassOpts<T>,\n extraOpts: WeierstrassExtraOpts<T> = {}\n): WeierstrassPointCons<T> {\n const validated = _createCurveFields('weierstrass', params, extraOpts);\n const { Fp, Fn } = validated;\n let CURVE = validated.CURVE as WeierstrassOpts<T>;\n const { h: cofactor, n: CURVE_ORDER } = CURVE;\n _validateObject(\n extraOpts,\n {},\n {\n allowInfinityPoint: 'boolean',\n clearCofactor: 'function',\n isTorsionFree: 'function',\n fromBytes: 'function',\n toBytes: 'function',\n endo: 'object',\n wrapPrivateKey: 'boolean',\n }\n );\n\n const { endo } = extraOpts;\n if (endo) {\n // validateObject(endo, { beta: 'bigint', splitScalar: 'function' });\n if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) {\n throw new Error('invalid endo: expected \"beta\": bigint and \"basises\": array');\n }\n }\n\n const lengths = getWLengths(Fp, Fn);\n\n function assertCompressionIsSupported() {\n if (!Fp.isOdd) throw new Error('compression is not supported: Field does not have .isOdd()');\n }\n\n // Implements IEEE P1363 point encoding\n function pointToBytes(\n _c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ): Uint8Array {\n const { x, y } = point.toAffine();\n const bx = Fp.toBytes(x);\n abool(isCompressed, 'isCompressed');\n if (isCompressed) {\n assertCompressionIsSupported();\n const hasEvenY = !Fp.isOdd!(y);\n return concatBytes(pprefix(hasEvenY), bx);\n } else {\n return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y));\n }\n }\n function pointFromBytes(bytes: Uint8Array) {\n abytes(bytes, undefined, 'Point');\n const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65\n const length = bytes.length;\n const head = bytes[0];\n const tail = bytes.subarray(1);\n // No actual validation is done here: use .assertValidity()\n if (length === comp && (head === 0x02 || head === 0x03)) {\n const x = Fp.fromBytes(tail);\n if (!Fp.isValid(x)) throw new Error('bad point: is not on curve, wrong x');\n const y2 = weierstrassEquation(x); // y² = x³ + ax + b\n let y: T;\n try {\n y = Fp.sqrt(y2); // y = y² ^ (p+1)/4\n } catch (sqrtError) {\n const err = sqrtError instanceof Error ? ': ' + sqrtError.message : '';\n throw new Error('bad point: is not on curve, sqrt error' + err);\n }\n assertCompressionIsSupported();\n const isYOdd = Fp.isOdd!(y); // (y & _1n) === _1n;\n const isHeadOdd = (head & 1) === 1; // ECDSA-specific\n if (isHeadOdd !== isYOdd) y = Fp.neg(y);\n return { x, y };\n } else if (length === uncomp && head === 0x04) {\n // TODO: more checks\n const L = Fp.BYTES;\n const x = Fp.fromBytes(tail.subarray(0, L));\n const y = Fp.fromBytes(tail.subarray(L, L * 2));\n if (!isValidXY(x, y)) throw new Error('bad point: is not on curve');\n return { x, y };\n } else {\n throw new Error(\n `bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`\n );\n }\n }\n\n const encodePoint = extraOpts.toBytes || pointToBytes;\n const decodePoint = extraOpts.fromBytes || pointFromBytes;\n function weierstrassEquation(x: T): T {\n const x2 = Fp.sqr(x); // x * x\n const x3 = Fp.mul(x2, x); // x² * x\n return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b); // x³ + a * x + b\n }\n\n // TODO: move top-level\n /** Checks whether equation holds for given x, y: y² == x³ + ax + b */\n function isValidXY(x: T, y: T): boolean {\n const left = Fp.sqr(y); // y²\n const right = weierstrassEquation(x); // x³ + ax + b\n return Fp.eql(left, right);\n }\n\n // Validate whether the passed curve params are valid.\n // Test 1: equation y² = x³ + ax + b should work for generator point.\n if (!isValidXY(CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point');\n\n // Test 2: discriminant Δ part should be non-zero: 4a³ + 27b² != 0.\n // Guarantees curve is genus-1, smooth (non-singular).\n const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n);\n const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));\n if (Fp.is0(Fp.add(_4a3, _27b2))) throw new Error('bad curve params: a or b');\n\n /** Asserts coordinate is valid: 0 <= n < Fp.ORDER. */\n function acoord(title: string, n: T, banZero = false) {\n if (!Fp.isValid(n) || (banZero && Fp.is0(n))) throw new Error(`bad point coordinate ${title}`);\n return n;\n }\n\n function aprjpoint(other: unknown) {\n if (!(other instanceof Point)) throw new Error('ProjectivePoint expected');\n }\n\n function splitEndoScalarN(k: bigint) {\n if (!endo || !endo.basises) throw new Error('no endo');\n return _splitEndoScalar(k, endo.basises, Fn.ORDER);\n }\n\n // Memoized toAffine / validity check. They are heavy. Points are immutable.\n\n // Converts Projective point to affine (x, y) coordinates.\n // Can accept precomputed Z^-1 - for example, from invertBatch.\n // (X, Y, Z) ∋ (x=X/Z, y=Y/Z)\n const toAffineMemo = memoized((p: Point, iz?: T): AffinePoint<T> => {\n const { X, Y, Z } = p;\n // Fast-path for normalized points\n if (Fp.eql(Z, Fp.ONE)) return { x: X, y: Y };\n const is0 = p.is0();\n // If invZ was 0, we return zero point. However we still want to execute\n // all operations, so we replace invZ with a random number, 1.\n if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(Z);\n const x = Fp.mul(X, iz);\n const y = Fp.mul(Y, iz);\n const zz = Fp.mul(Z, iz);\n if (is0) return { x: Fp.ZERO, y: Fp.ZERO };\n if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid');\n return { x, y };\n });\n // NOTE: on exception this will crash 'cached' and no value will be set.\n // Otherwise true will be return\n const assertValidMemo = memoized((p: Point) => {\n if (p.is0()) {\n // (0, 1, 0) aka ZERO is invalid in most contexts.\n // In BLS, ZERO can be serialized, so we allow it.\n // (0, 0, 0) is invalid representation of ZERO.\n if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y)) return;\n throw new Error('bad point: ZERO');\n }\n // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`\n const { x, y } = p.toAffine();\n if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements');\n if (!isValidXY(x, y)) throw new Error('bad point: equation left != right');\n if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');\n return true;\n });\n\n function finishEndo(\n endoBeta: EndomorphismOpts['beta'],\n k1p: Point,\n k2p: Point,\n k1neg: boolean,\n k2neg: boolean\n ) {\n k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);\n k1p = negateCt(k1neg, k1p);\n k2p = negateCt(k2neg, k2p);\n return k1p.add(k2p);\n }\n\n /**\n * Projective Point works in 3d / projective (homogeneous) coordinates:(X, Y, Z) ∋ (x=X/Z, y=Y/Z).\n * Default Point works in 2d / affine coordinates: (x, y).\n * We're doing calculations in projective, because its operations don't require costly inversion.\n */\n class Point implements WeierstrassPoint<T> {\n // base / generator point\n static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);\n // zero / infinity / identity point\n static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0\n // math field\n static readonly Fp = Fp;\n // scalar field\n static readonly Fn = Fn;\n\n readonly X: T;\n readonly Y: T;\n readonly Z: T;\n\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n constructor(X: T, Y: T, Z: T) {\n this.X = acoord('x', X);\n this.Y = acoord('y', Y, true);\n this.Z = acoord('z', Z);\n Object.freeze(this);\n }\n\n static CURVE(): WeierstrassOpts<T> {\n return CURVE;\n }\n\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n static fromAffine(p: AffinePoint<T>): Point {\n const { x, y } = p || {};\n if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');\n if (p instanceof Point) throw new Error('projective point not allowed');\n // (0, 0) would've produced (0, 0, 1) - instead, we need (0, 1, 0)\n if (Fp.is0(x) && Fp.is0(y)) return Point.ZERO;\n return new Point(x, y, Fp.ONE);\n }\n\n static fromBytes(bytes: Uint8Array): Point {\n const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));\n P.assertValidity();\n return P;\n }\n static fromHex(hex: Hex): Point {\n return Point.fromBytes(ensureBytes('pointHex', hex));\n }\n\n get x(): T {\n return this.toAffine().x;\n }\n get y(): T {\n return this.toAffine().y;\n }\n\n /**\n *\n * @param windowSize\n * @param isLazy true will defer table computation until the first multiplication\n * @returns\n */\n precompute(windowSize: number = 8, isLazy = true): Point {\n wnaf.createCache(this, windowSize);\n if (!isLazy) this.multiply(_3n); // random number\n return this;\n }\n\n // TODO: return `this`\n /** A point on curve is valid if it conforms to equation. */\n assertValidity(): void {\n assertValidMemo(this);\n }\n\n hasEvenY(): boolean {\n const { y } = this.toAffine();\n if (!Fp.isOdd) throw new Error(\"Field doesn't support isOdd\");\n return !Fp.isOdd(y);\n }\n\n /** Compare one point to another. */\n equals(other: Point): boolean {\n aprjpoint(other);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n const { X: X2, Y: Y2, Z: Z2 } = other;\n const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));\n const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));\n return U1 && U2;\n }\n\n /** Flips point to one corresponding to (x, -y) in Affine coordinates. */\n negate(): Point {\n return new Point(this.X, Fp.neg(this.Y), this.Z);\n }\n\n // Renes-Costello-Batina exception-free doubling formula.\n // There is 30% faster Jacobian formula, but it is not complete.\n // https://eprint.iacr.org/2015/1060, algorithm 3\n // Cost: 8M + 3S + 3*a + 2*b3 + 15add.\n double() {\n const { a, b } = CURVE;\n const b3 = Fp.mul(b, _3n);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore\n let t0 = Fp.mul(X1, X1); // step 1\n let t1 = Fp.mul(Y1, Y1);\n let t2 = Fp.mul(Z1, Z1);\n let t3 = Fp.mul(X1, Y1);\n t3 = Fp.add(t3, t3); // step 5\n Z3 = Fp.mul(X1, Z1);\n Z3 = Fp.add(Z3, Z3);\n X3 = Fp.mul(a, Z3);\n Y3 = Fp.mul(b3, t2);\n Y3 = Fp.add(X3, Y3); // step 10\n X3 = Fp.sub(t1, Y3);\n Y3 = Fp.add(t1, Y3);\n Y3 = Fp.mul(X3, Y3);\n X3 = Fp.mul(t3, X3);\n Z3 = Fp.mul(b3, Z3); // step 15\n t2 = Fp.mul(a, t2);\n t3 = Fp.sub(t0, t2);\n t3 = Fp.mul(a, t3);\n t3 = Fp.add(t3, Z3);\n Z3 = Fp.add(t0, t0); // step 20\n t0 = Fp.add(Z3, t0);\n t0 = Fp.add(t0, t2);\n t0 = Fp.mul(t0, t3);\n Y3 = Fp.add(Y3, t0);\n t2 = Fp.mul(Y1, Z1); // step 25\n t2 = Fp.add(t2, t2);\n t0 = Fp.mul(t2, t3);\n X3 = Fp.sub(X3, t0);\n Z3 = Fp.mul(t2, t1);\n Z3 = Fp.add(Z3, Z3); // step 30\n Z3 = Fp.add(Z3, Z3);\n return new Point(X3, Y3, Z3);\n }\n\n // Renes-Costello-Batina exception-free addition formula.\n // There is 30% faster Jacobian formula, but it is not complete.\n // https://eprint.iacr.org/2015/1060, algorithm 1\n // Cost: 12M + 0S + 3*a + 3*b3 + 23add.\n add(other: Point): Point {\n aprjpoint(other);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n const { X: X2, Y: Y2, Z: Z2 } = other;\n let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore\n const a = CURVE.a;\n const b3 = Fp.mul(CURVE.b, _3n);\n let t0 = Fp.mul(X1, X2); // step 1\n let t1 = Fp.mul(Y1, Y2);\n let t2 = Fp.mul(Z1, Z2);\n let t3 = Fp.add(X1, Y1);\n let t4 = Fp.add(X2, Y2); // step 5\n t3 = Fp.mul(t3, t4);\n t4 = Fp.add(t0, t1);\n t3 = Fp.sub(t3, t4);\n t4 = Fp.add(X1, Z1);\n let t5 = Fp.add(X2, Z2); // step 10\n t4 = Fp.mul(t4, t5);\n t5 = Fp.add(t0, t2);\n t4 = Fp.sub(t4, t5);\n t5 = Fp.add(Y1, Z1);\n X3 = Fp.add(Y2, Z2); // step 15\n t5 = Fp.mul(t5, X3);\n X3 = Fp.add(t1, t2);\n t5 = Fp.sub(t5, X3);\n Z3 = Fp.mul(a, t4);\n X3 = Fp.mul(b3, t2); // step 20\n Z3 = Fp.add(X3, Z3);\n X3 = Fp.sub(t1, Z3);\n Z3 = Fp.add(t1, Z3);\n Y3 = Fp.mul(X3, Z3);\n t1 = Fp.add(t0, t0); // step 25\n t1 = Fp.add(t1, t0);\n t2 = Fp.mul(a, t2);\n t4 = Fp.mul(b3, t4);\n t1 = Fp.add(t1, t2);\n t2 = Fp.sub(t0, t2); // step 30\n t2 = Fp.mul(a, t2);\n t4 = Fp.add(t4, t2);\n t0 = Fp.mul(t1, t4);\n Y3 = Fp.add(Y3, t0);\n t0 = Fp.mul(t5, t4); // step 35\n X3 = Fp.mul(t3, X3);\n X3 = Fp.sub(X3, t0);\n t0 = Fp.mul(t3, t1);\n Z3 = Fp.mul(t5, Z3);\n Z3 = Fp.add(Z3, t0); // step 40\n return new Point(X3, Y3, Z3);\n }\n\n subtract(other: Point) {\n return this.add(other.negate());\n }\n\n is0(): boolean {\n return this.equals(Point.ZERO);\n }\n\n /**\n * Constant time multiplication.\n * Uses wNAF method. Windowed method may be 10% faster,\n * but takes 2x longer to generate and consumes 2x memory.\n * Uses precomputes when available.\n * Uses endomorphism for Koblitz curves.\n * @param scalar by which the point would be multiplied\n * @returns New point\n */\n multiply(scalar: bigint): Point {\n const { endo } = extraOpts;\n if (!Fn.isValidNot0(scalar)) throw new Error('invalid scalar: out of range'); // 0 is invalid\n let point: Point, fake: Point; // Fake point is used to const-time mult\n const mul = (n: bigint) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));\n /** See docs for {@link EndomorphismOpts} */\n if (endo) {\n const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);\n const { p: k1p, f: k1f } = mul(k1);\n const { p: k2p, f: k2f } = mul(k2);\n fake = k1f.add(k2f);\n point = finishEndo(endo.beta, k1p, k2p, k1neg, k2neg);\n } else {\n const { p, f } = mul(scalar);\n point = p;\n fake = f;\n }\n // Normalize `z` for both points, but return only real one\n return normalizeZ(Point, [point, fake])[0];\n }\n\n /**\n * Non-constant-time multiplication. Uses double-and-add algorithm.\n * It's faster, but should only be used when you don't care about\n * an exposed secret key e.g. sig verification, which works over *public* keys.\n */\n multiplyUnsafe(sc: bigint): Point {\n const { endo } = extraOpts;\n const p = this as Point;\n if (!Fn.isValid(sc)) throw new Error('invalid scalar: out of range'); // 0 is valid\n if (sc === _0n || p.is0()) return Point.ZERO;\n if (sc === _1n) return p; // fast-path\n if (wnaf.hasCache(this)) return this.multiply(sc);\n if (endo) {\n const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);\n const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2); // 30% faster vs wnaf.unsafe\n return finishEndo(endo.beta, p1, p2, k1neg, k2neg);\n } else {\n return wnaf.unsafe(p, sc);\n }\n }\n\n multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {\n const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));\n return sum.is0() ? undefined : sum;\n }\n\n /**\n * Converts Projective point to affine (x, y) coordinates.\n * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch\n */\n toAffine(invertedZ?: T): AffinePoint<T> {\n return toAffineMemo(this, invertedZ);\n }\n\n /**\n * Checks whether Point is free of torsion elements (is in prime subgroup).\n * Always torsion-free for cofactor=1 curves.\n */\n isTorsionFree(): boolean {\n const { isTorsionFree } = extraOpts;\n if (cofactor === _1n) return true;\n if (isTorsionFree) return isTorsionFree(Point, this);\n return wnaf.unsafe(this, CURVE_ORDER).is0();\n }\n\n clearCofactor(): Point {\n const { clearCofactor } = extraOpts;\n if (cofactor === _1n) return this; // Fast-path\n if (clearCofactor) return clearCofactor(Point, this) as Point;\n return this.multiplyUnsafe(cofactor);\n }\n\n isSmallOrder(): boolean {\n // can we use this.clearCofactor()?\n return this.multiplyUnsafe(cofactor).is0();\n }\n\n toBytes(isCompressed = true): Uint8Array {\n abool(isCompressed, 'isCompressed');\n this.assertValidity();\n return encodePoint(Point, this, isCompressed);\n }\n\n toHex(isCompressed = true): string {\n return bytesToHex(this.toBytes(isCompressed));\n }\n\n toString() {\n return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;\n }\n\n // TODO: remove\n get px(): T {\n return this.X;\n }\n get py(): T {\n return this.X;\n }\n get pz(): T {\n return this.Z;\n }\n toRawBytes(isCompressed = true): Uint8Array {\n return this.toBytes(isCompressed);\n }\n _setWindowSize(windowSize: number) {\n this.precompute(windowSize);\n }\n static normalizeZ(points: Point[]): Point[] {\n return normalizeZ(Point, points);\n }\n static msm(points: Point[], scalars: bigint[]): Point {\n return pippenger(Point, Fn, points, scalars);\n }\n static fromPrivateKey(privateKey: PrivKey) {\n return Point.BASE.multiply(_normFnElement(Fn, privateKey));\n }\n }\n const bits = Fn.BITS;\n const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);\n Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.\n return Point;\n}\n\n/** Methods of ECDSA signature instance. */\nexport interface ECDSASignature {\n readonly r: bigint;\n readonly s: bigint;\n readonly recovery?: number;\n addRecoveryBit(recovery: number): ECDSASigRecovered;\n hasHighS(): boolean;\n toBytes(format?: string): Uint8Array;\n toHex(format?: string): string;\n\n /** @deprecated */\n assertValidity(): void;\n /** @deprecated */\n normalizeS(): ECDSASignature;\n /** @deprecated use standalone method `curve.recoverPublicKey(sig.toBytes('recovered'), msg)` */\n recoverPublicKey(msgHash: Hex): WeierstrassPoint<bigint>;\n /** @deprecated use `.toBytes('compact')` */\n toCompactRawBytes(): Uint8Array;\n /** @deprecated use `.toBytes('compact')` */\n toCompactHex(): string;\n /** @deprecated use `.toBytes('der')` */\n toDERRawBytes(): Uint8Array;\n /** @deprecated use `.toBytes('der')` */\n toDERHex(): string;\n}\nexport type ECDSASigRecovered = ECDSASignature & {\n readonly recovery: number;\n};\n/** Methods of ECDSA signature constructor. */\nexport type ECDSASignatureCons = {\n new (r: bigint, s: bigint, recovery?: number): ECDSASignature;\n fromBytes(bytes: Uint8Array, format?: ECDSASigFormat): ECDSASignature;\n fromHex(hex: string, format?: ECDSASigFormat): ECDSASignature;\n\n /** @deprecated use `.fromBytes(bytes, 'compact')` */\n fromCompact(hex: Hex): ECDSASignature;\n /** @deprecated use `.fromBytes(bytes, 'der')` */\n fromDER(hex: Hex): ECDSASignature;\n};\n\n// Points start with byte 0x02 when y is even; otherwise 0x03\nfunction pprefix(hasEvenY: boolean): Uint8Array {\n return Uint8Array.of(hasEvenY ? 0x02 : 0x03);\n}\n\n/**\n * Implementation of the Shallue and van de Woestijne method for any weierstrass curve.\n * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.\n * b = True and y = sqrt(u / v) if (u / v) is square in F, and\n * b = False and y = sqrt(Z * (u / v)) otherwise.\n * @param Fp\n * @param Z\n * @returns\n */\nexport function SWUFpSqrtRatio<T>(\n Fp: IField<T>,\n Z: T\n): (u: T, v: T) => { isValid: boolean; value: T } {\n // Generic implementation\n const q = Fp.ORDER;\n let l = _0n;\n for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;\n const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.\n // We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.\n // 2n ** c1 == 2n << (c1-1)\n const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);\n const _2n_pow_c1 = _2n_pow_c1_1 * _2n;\n const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic\n const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic\n const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic\n const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic\n const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2\n const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)\n let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {\n let tv1 = c6; // 1. tv1 = c6\n let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4\n let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2\n tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v\n let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3\n tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3\n tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2\n tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v\n tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u\n let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2\n tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5\n let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1\n tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7\n tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1\n tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)\n tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)\n // 17. for i in (c1, c1 - 1, ..., 2):\n for (let i = c1; i > _1n; i--) {\n let tv5 = i - _2n; // 18. tv5 = i - 2\n tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5\n let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5\n const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1\n tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1\n tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1\n tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1\n tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)\n tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)\n }\n return { isValid: isQR, value: tv3 };\n };\n if (Fp.ORDER % _4n === _3n) {\n // sqrt_ratio_3mod4(u, v)\n const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic\n const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)\n sqrtRatio = (u: T, v: T) => {\n let tv1 = Fp.sqr(v); // 1. tv1 = v^2\n const tv2 = Fp.mul(u, v); // 2. tv2 = u * v\n tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2\n let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1\n y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2\n const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2\n const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v\n const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u\n let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)\n return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2\n };\n }\n // No curves uses that\n // if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8\n return sqrtRatio;\n}\n/**\n * Simplified Shallue-van de Woestijne-Ulas Method\n * https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2\n */\nexport function mapToCurveSimpleSWU<T>(\n Fp: IField<T>,\n opts: {\n A: T;\n B: T;\n Z: T;\n }\n): (u: T) => { x: T; y: T } {\n validateField(Fp);\n const { A, B, Z } = opts;\n if (!Fp.isValid(A) || !Fp.isValid(B) || !Fp.isValid(Z))\n throw new Error('mapToCurveSimpleSWU: invalid opts');\n const sqrtRatio = SWUFpSqrtRatio(Fp, Z);\n if (!Fp.isOdd) throw new Error('Field does not have .isOdd()');\n // Input: u, an element of F.\n // Output: (x, y), a point on E.\n return (u: T): { x: T; y: T } => {\n // prettier-ignore\n let tv1, tv2, tv3, tv4, tv5, tv6, x, y;\n tv1 = Fp.sqr(u); // 1. tv1 = u^2\n tv1 = Fp.mul(tv1, Z); // 2. tv1 = Z * tv1\n tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2\n tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1\n tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1\n tv3 = Fp.mul(tv3, B); // 6. tv3 = B * tv3\n tv4 = Fp.cmov(Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)\n tv4 = Fp.mul(tv4, A); // 8. tv4 = A * tv4\n tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2\n tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2\n tv5 = Fp.mul(tv6, A); // 11. tv5 = A * tv6\n tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5\n tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3\n tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4\n tv5 = Fp.mul(tv6, B); // 15. tv5 = B * tv6\n tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5\n x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3\n const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)\n y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1\n y = Fp.mul(y, value); // 20. y = y * y1\n x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)\n y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)\n const e1 = Fp.isOdd!(u) === Fp.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)\n y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)\n const tv4_inv = FpInvertBatch(Fp, [tv4], true)[0];\n x = Fp.mul(x, tv4_inv); // 25. x = x / tv4\n return { x, y };\n };\n}\n\nfunction getWLengths<T>(Fp: IField<T>, Fn: IField<bigint>) {\n return {\n secretKey: Fn.BYTES,\n publicKey: 1 + Fp.BYTES,\n publicKeyUncompressed: 1 + 2 * Fp.BYTES,\n publicKeyHasPrefix: true,\n signature: 2 * Fn.BYTES,\n };\n}\n\n/**\n * Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.\n * This helper ensures no signature functionality is present. Less code, smaller bundle size.\n */\nexport function ecdh(\n Point: WeierstrassPointCons<bigint>,\n ecdhOpts: { randomBytes?: (bytesLength?: number) => Uint8Array } = {}\n): ECDH {\n const { Fn } = Point;\n const randomBytes_ = ecdhOpts.randomBytes || randomBytesWeb;\n const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });\n\n function isValidSecretKey(secretKey: PrivKey) {\n try {\n return !!_normFnElement(Fn, secretKey);\n } catch (error) {\n return false;\n }\n }\n\n function isValidPublicKey(publicKey: Uint8Array, isCompressed?: boolean): boolean {\n const { publicKey: comp, publicKeyUncompressed } = lengths;\n try {\n const l = publicKey.length;\n if (isCompressed === true && l !== comp) return false;\n if (isCompressed === false && l !== publicKeyUncompressed) return false;\n return !!Point.fromBytes(publicKey);\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Produces cryptographically secure secret key from random of size\n * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.\n */\n function randomSecretKey(seed = randomBytes_(lengths.seed)): Uint8Array {\n return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER);\n }\n\n /**\n * Computes public key for a secret key. Checks for validity of the secret key.\n * @param isCompressed whether to return compact (default), or full key\n * @returns Public key, full when isCompressed=false; short when isCompressed=true\n */\n function getPublicKey(secretKey: PrivKey, isCompressed = true): Uint8Array {\n return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed);\n }\n\n function keygen(seed?: Uint8Array) {\n const secretKey = randomSecretKey(seed);\n return { secretKey, publicKey: getPublicKey(secretKey) };\n }\n\n /**\n * Quick and dirty check for item being public key. Does not validate hex, or being on-curve.\n */\n function isProbPub(item: PrivKey | PubKey): boolean | undefined {\n if (typeof item === 'bigint') return false;\n if (item instanceof Point) return true;\n const { secretKey, publicKey, publicKeyUncompressed } = lengths;\n if (Fn.allowedLengths || secretKey === publicKey) return undefined;\n const l = ensureBytes('key', item).length;\n return l === publicKey || l === publicKeyUncompressed;\n }\n\n /**\n * ECDH (Elliptic Curve Diffie Hellman).\n * Computes shared public key from secret key A and public key B.\n * Checks: 1) secret key validity 2) shared key is on-curve.\n * Does NOT hash the result.\n * @param isCompressed whether to return compact (default), or full key\n * @returns shared public key\n */\n function getSharedSecret(secretKeyA: PrivKey, publicKeyB: Hex, isCompressed = true): Uint8Array {\n if (isProbPub(secretKeyA) === true) throw new Error('first arg must be private key');\n if (isProbPub(publicKeyB) === false) throw new Error('second arg must be public key');\n const s = _normFnElement(Fn, secretKeyA);\n const b = Point.fromHex(publicKeyB); // checks for being on-curve\n return b.multiply(s).toBytes(isCompressed);\n }\n\n const utils = {\n isValidSecretKey,\n isValidPublicKey,\n randomSecretKey,\n\n // TODO: remove\n isValidPrivateKey: isValidSecretKey,\n randomPrivateKey: randomSecretKey,\n normPrivateKeyToScalar: (key: PrivKey) => _normFnElement(Fn, key),\n precompute(windowSize = 8, point = Point.BASE): WeierstrassPoint<bigint> {\n return point.precompute(windowSize, false);\n },\n };\n\n return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });\n}\n\n/**\n * Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.\n * We need `hash` for 2 features:\n * 1. Message prehash-ing. NOT used if `sign` / `verify` are called with `prehash: false`\n * 2. k generation in `sign`, using HMAC-drbg(hash)\n *\n * ECDSAOpts are only rarely needed.\n *\n * @example\n * ```js\n * const p256_Point = weierstrass(...);\n * const p256_sha256 = ecdsa(p256_Point, sha256);\n * const p256_sha224 = ecdsa(p256_Point, sha224);\n * const p256_sha224_r = ecdsa(p256_Point, sha224, { randomBytes: (length) => { ... } });\n * ```\n */\nexport function ecdsa(\n Point: WeierstrassPointCons<bigint>,\n hash: CHash,\n ecdsaOpts: ECDSAOpts = {}\n): ECDSA {\n ahash(hash);\n _validateObject(\n ecdsaOpts,\n {},\n {\n hmac: 'function',\n lowS: 'boolean',\n randomBytes: 'function',\n bits2int: 'function',\n bits2int_modN: 'function',\n }\n );\n\n const randomBytes = ecdsaOpts.randomBytes || randomBytesWeb;\n const hmac: HmacFnSync =\n ecdsaOpts.hmac ||\n (((key, ...msgs) => nobleHmac(hash, key, concatBytes(...msgs))) satisfies HmacFnSync);\n\n const { Fp, Fn } = Point;\n const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;\n const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);\n const defaultSigOpts: Required<ECDSASignOpts> = {\n prehash: false,\n lowS: typeof ecdsaOpts.lowS === 'boolean' ? ecdsaOpts.lowS : false,\n format: undefined as any, //'compact' as ECDSASigFormat,\n extraEntropy: false,\n };\n const defaultSigOpts_format = 'compact';\n\n function isBiggerThanHalfOrder(number: bigint) {\n const HALF = CURVE_ORDER >> _1n;\n return number > HALF;\n }\n function validateRS(title: string, num: bigint): bigint {\n if (!Fn.isValidNot0(num))\n throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);\n return num;\n }\n function validateSigLength(bytes: Uint8Array, format: ECDSASigFormat) {\n validateSigFormat(format);\n const size = lengths.signature!;\n const sizer = format === 'compact' ? size : format === 'recovered' ? size + 1 : undefined;\n return abytes(bytes, sizer, `${format} signature`);\n }\n\n /**\n * ECDSA signature with its (r, s) properties. Supports compact, recovered & DER representations.\n */\n class Signature implements ECDSASignature {\n readonly r: bigint;\n readonly s: bigint;\n readonly recovery?: number;\n constructor(r: bigint, s: bigint, recovery?: number) {\n this.r = validateRS('r', r); // r in [1..N-1];\n this.s = validateRS('s', s); // s in [1..N-1];\n if (recovery != null) this.recovery = recovery;\n Object.freeze(this);\n }\n\n static fromBytes(bytes: Uint8Array, format: ECDSASigFormat = defaultSigOpts_format): Signature {\n validateSigLength(bytes, format);\n let recid: number | undefined;\n if (format === 'der') {\n const { r, s } = DER.toSig(abytes(bytes));\n return new Signature(r, s);\n }\n if (format === 'recovered') {\n recid = bytes[0];\n format = 'compact';\n bytes = bytes.subarray(1);\n }\n const L = Fn.BYTES;\n const r = bytes.subarray(0, L);\n const s = bytes.subarray(L, L * 2);\n return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);\n }\n\n static fromHex(hex: string, format?: ECDSASigFormat) {\n return this.fromBytes(hexToBytes(hex), format);\n }\n\n addRecoveryBit(recovery: number): RecoveredSignature {\n return new Signature(this.r, this.s, recovery) as RecoveredSignature;\n }\n\n recoverPublicKey(messageHash: Hex): WeierstrassPoint<bigint> {\n const FIELD_ORDER = Fp.ORDER;\n const { r, s, recovery: rec } = this;\n if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');\n\n // ECDSA recovery is hard for cofactor > 1 curves.\n // In sign, `r = q.x mod n`, and here we recover q.x from r.\n // While recovering q.x >= n, we need to add r+n for cofactor=1 curves.\n // However, for cofactor>1, r+n may not get q.x:\n // r+n*i would need to be done instead where i is unknown.\n // To easily get i, we either need to:\n // a. increase amount of valid recid values (4, 5...); OR\n // b. prohibit non-prime-order signatures (recid > 1).\n const hasCofactor = CURVE_ORDER * _2n < FIELD_ORDER;\n if (hasCofactor && rec > 1) throw new Error('recovery id is ambiguous for h>1 curve');\n\n const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;\n if (!Fp.isValid(radj)) throw new Error('recovery id 2 or 3 invalid');\n const x = Fp.toBytes(radj);\n const R = Point.fromBytes(concatBytes(pprefix((rec & 1) === 0), x));\n const ir = Fn.inv(radj); // r^-1\n const h = bits2int_modN(ensureBytes('msgHash', messageHash)); // Truncate hash\n const u1 = Fn.create(-h * ir); // -hr^-1\n const u2 = Fn.create(s * ir); // sr^-1\n // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1). unsafe is fine: there is no private data.\n const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));\n if (Q.is0()) throw new Error('point at infinify');\n Q.assertValidity();\n return Q;\n }\n\n // Signatures should be low-s, to prevent malleability.\n hasHighS(): boolean {\n return isBiggerThanHalfOrder(this.s);\n }\n\n toBytes(format: ECDSASigFormat = defaultSigOpts_format) {\n validateSigFormat(format);\n if (format === 'der') return hexToBytes(DER.hexFromSig(this));\n const r = Fn.toBytes(this.r);\n const s = Fn.toBytes(this.s);\n if (format === 'recovered') {\n if (this.recovery == null) throw new Error('recovery bit must be present');\n return concatBytes(Uint8Array.of(this.recovery), r, s);\n }\n return concatBytes(r, s);\n }\n\n toHex(format?: ECDSASigFormat) {\n return bytesToHex(this.toBytes(format));\n }\n\n // TODO: remove\n assertValidity(): void {}\n static fromCompact(hex: Hex) {\n return Signature.fromBytes(ensureBytes('sig', hex), 'compact');\n }\n static fromDER(hex: Hex) {\n return Signature.fromBytes(ensureBytes('sig', hex), 'der');\n }\n normalizeS() {\n return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this;\n }\n toDERRawBytes() {\n return this.toBytes('der');\n }\n toDERHex() {\n return bytesToHex(this.toBytes('der'));\n }\n toCompactRawBytes() {\n return this.toBytes('compact');\n }\n toCompactHex() {\n return bytesToHex(this.toBytes('compact'));\n }\n }\n type RecoveredSignature = Signature & { recovery: number };\n\n // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.\n // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int.\n // bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.\n // int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors\n const bits2int =\n ecdsaOpts.bits2int ||\n function bits2int_def(bytes: Uint8Array): bigint {\n // Our custom check \"just in case\", for protection against DoS\n if (bytes.length > 8192) throw new Error('input is too large');\n // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)\n // for some cases, since bytes.length * 8 is not actual bitLength.\n const num = bytesToNumberBE(bytes); // check for == u8 done here\n const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits\n return delta > 0 ? num >> BigInt(delta) : num;\n };\n const bits2int_modN =\n ecdsaOpts.bits2int_modN ||\n function bits2int_modN_def(bytes: Uint8Array): bigint {\n return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here\n };\n // Pads output with zero as per spec\n const ORDER_MASK = bitMask(fnBits);\n /** Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. */\n function int2octets(num: bigint): Uint8Array {\n // IMPORTANT: the check ensures working for case `Fn.BYTES != Fn.BITS * 8`\n aInRange('num < 2^' + fnBits, num, _0n, ORDER_MASK);\n return Fn.toBytes(num);\n }\n\n function validateMsgAndHash(message: Uint8Array, prehash: boolean) {\n abytes(message, undefined, 'message');\n return prehash ? abytes(hash(message), undefined, 'prehashed message') : message;\n }\n\n /**\n * Steps A, D of RFC6979 3.2.\n * Creates RFC6979 seed; converts msg/privKey to numbers.\n * Used only in sign, not in verify.\n *\n * Warning: we cannot assume here that message has same amount of bytes as curve order,\n * this will be invalid at least for P521. Also it can be bigger for P224 + SHA256.\n */\n function prepSig(message: Uint8Array, privateKey: PrivKey, opts: ECDSASignOpts) {\n if (['recovered', 'canonical'].some((k) => k in opts))\n throw new Error('sign() legacy options not supported');\n const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);\n message = validateMsgAndHash(message, prehash); // RFC6979 3.2 A: h1 = H(m)\n // We can't later call bits2octets, since nested bits2int is broken for curves\n // with fnBits % 8 !== 0. Because of that, we unwrap it here as int2octets call.\n // const bits2octets = (bits) => int2octets(bits2int_modN(bits))\n const h1int = bits2int_modN(message);\n const d = _normFnElement(Fn, privateKey); // validate secret key, convert to bigint\n const seedArgs = [int2octets(d), int2octets(h1int)];\n // extraEntropy. RFC6979 3.6: additional k' (optional).\n if (extraEntropy != null && extraEntropy !== false) {\n // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')\n // gen random bytes OR pass as-is\n const e = extraEntropy === true ? randomBytes(lengths.secretKey) : extraEntropy;\n seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes\n }\n const seed = concatBytes(...seedArgs); // Step D of RFC6979 3.2\n const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!\n // Converts signature params into point w r/s, checks result for validity.\n // To transform k => Signature:\n // q = k⋅G\n // r = q.x mod n\n // s = k^-1(m + rd) mod n\n // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to\n // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:\n // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT\n function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {\n // RFC 6979 Section 3.2, step 3: k = bits2int(T)\n // Important: all mod() calls here must be done over N\n const k = bits2int(kBytes); // mod n, not mod p\n if (!Fn.isValidNot0(k)) return; // Valid scalars (including k) must be in 1..N-1\n const ik = Fn.inv(k); // k^-1 mod n\n const q = Point.BASE.multiply(k).toAffine(); // q = k⋅G\n const r = Fn.create(q.x); // r = q.x mod n\n if (r === _0n) return;\n const s = Fn.create(ik * Fn.create(m + r * d)); // Not using blinding here, see comment above\n if (s === _0n) return;\n let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)\n let normS = s;\n if (lowS && isBiggerThanHalfOrder(s)) {\n normS = Fn.neg(s); // if lowS was passed, ensure s is always\n recovery ^= 1; // // in the bottom half of N\n }\n return new Signature(r, normS, recovery) as RecoveredSignature; // use normS, not s\n }\n return { seed, k2sig };\n }\n\n /**\n * Signs message hash with a secret key.\n *\n * ```\n * sign(m, d) where\n * k = rfc6979_hmac_drbg(m, d)\n * (x, y) = G × k\n * r = x mod n\n * s = (m + dr) / k mod n\n * ```\n */\n function sign(message: Hex, secretKey: PrivKey, opts: ECDSASignOpts = {}): RecoveredSignature {\n message = ensureBytes('message', message);\n const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2.\n const drbg = createHmacDrbg<RecoveredSignature>(hash.outputLen, Fn.BYTES, hmac);\n const sig = drbg(seed, k2sig); // Steps B, C, D, E, F, G\n return sig;\n }\n\n function tryParsingSig(sg: Hex | SignatureLike) {\n // Try to deduce format\n let sig: Signature | undefined = undefined;\n const isHex = typeof sg === 'string' || isBytes(sg);\n const isObj =\n !isHex &&\n sg !== null &&\n typeof sg === 'object' &&\n typeof sg.r === 'bigint' &&\n typeof sg.s === 'bigint';\n if (!isHex && !isObj)\n throw new Error('invalid signature, expected Uint8Array, hex string or Signature instance');\n if (isObj) {\n sig = new Signature(sg.r, sg.s);\n } else if (isHex) {\n try {\n sig = Signature.fromBytes(ensureBytes('sig', sg), 'der');\n } catch (derError) {\n if (!(derError instanceof DER.Err)) throw derError;\n }\n if (!sig) {\n try {\n sig = Signature.fromBytes(ensureBytes('sig', sg), 'compact');\n } catch (error) {\n return false;\n }\n }\n }\n if (!sig) return false;\n return sig;\n }\n\n /**\n * Verifies a signature against message and public key.\n * Rejects lowS signatures by default: see {@link ECDSAVerifyOpts}.\n * Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf:\n *\n * ```\n * verify(r, s, h, P) where\n * u1 = hs^-1 mod n\n * u2 = rs^-1 mod n\n * R = u1⋅G + u2⋅P\n * mod(R.x, n) == r\n * ```\n */\n function verify(\n signature: Hex | SignatureLike,\n message: Hex,\n publicKey: Hex,\n opts: ECDSAVerifyOpts = {}\n ): boolean {\n const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);\n publicKey = ensureBytes('publicKey', publicKey);\n message = validateMsgAndHash(ensureBytes('message', message), prehash);\n if ('strict' in opts) throw new Error('options.strict was renamed to lowS');\n const sig =\n format === undefined\n ? tryParsingSig(signature)\n : Signature.fromBytes(ensureBytes('sig', signature as Hex), format);\n if (sig === false) return false;\n try {\n const P = Point.fromBytes(publicKey);\n if (lowS && sig.hasHighS()) return false;\n const { r, s } = sig;\n const h = bits2int_modN(message); // mod n, not mod p\n const is = Fn.inv(s); // s^-1 mod n\n const u1 = Fn.create(h * is); // u1 = hs^-1 mod n\n const u2 = Fn.create(r * is); // u2 = rs^-1 mod n\n const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2)); // u1⋅G + u2⋅P\n if (R.is0()) return false;\n const v = Fn.create(R.x); // v = r.x mod n\n return v === r;\n } catch (e) {\n return false;\n }\n }\n\n function recoverPublicKey(\n signature: Uint8Array,\n message: Uint8Array,\n opts: ECDSARecoverOpts = {}\n ): Uint8Array {\n const { prehash } = validateSigOpts(opts, defaultSigOpts);\n message = validateMsgAndHash(message, prehash);\n return Signature.fromBytes(signature, 'recovered').recoverPublicKey(message).toBytes();\n }\n\n return Object.freeze({\n keygen,\n getPublicKey,\n getSharedSecret,\n utils,\n lengths,\n Point,\n sign,\n verify,\n recoverPublicKey,\n Signature,\n hash,\n });\n}\n\n// TODO: remove everything below\n/** @deprecated use ECDSASignature */\nexport type SignatureType = ECDSASignature;\n/** @deprecated use ECDSASigRecovered */\nexport type RecoveredSignatureType = ECDSASigRecovered;\n/** @deprecated switch to Uint8Array signatures in format 'compact' */\nexport type SignatureLike = { r: bigint; s: bigint };\nexport type ECDSAExtraEntropy = Hex | boolean;\n/** @deprecated use `ECDSAExtraEntropy` */\nexport type Entropy = Hex | boolean;\nexport type BasicWCurve<T> = BasicCurve<T> & {\n // Params: a, b\n a: T;\n b: T;\n\n // Optional params\n allowedPrivateKeyLengths?: readonly number[]; // for P521\n wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n\n endo?: EndomorphismOpts;\n // When a cofactor != 1, there can be an effective methods to:\n // 1. Determine whether a point is torsion-free\n isTorsionFree?: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;\n // 2. Clear torsion component\n clearCofactor?: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;\n};\n/** @deprecated use ECDSASignOpts */\nexport type SignOpts = ECDSASignOpts;\n/** @deprecated use ECDSASignOpts */\nexport type VerOpts = ECDSAVerifyOpts;\n\n/** @deprecated use WeierstrassPoint */\nexport type ProjPointType<T> = WeierstrassPoint<T>;\n/** @deprecated use WeierstrassPointCons */\nexport type ProjConstructor<T> = WeierstrassPointCons<T>;\n/** @deprecated use ECDSASignatureCons */\nexport type SignatureConstructor = ECDSASignatureCons;\n\n// TODO: remove\nexport type CurvePointsType<T> = BasicWCurve<T> & {\n fromBytes?: (bytes: Uint8Array) => AffinePoint<T>;\n toBytes?: (\n c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ) => Uint8Array;\n};\n\n// LegacyWeierstrassOpts\nexport type CurvePointsTypeWithLength<T> = Readonly<CurvePointsType<T> & Partial<NLength>>;\n\n// LegacyWeierstrass\nexport type CurvePointsRes<T> = {\n Point: WeierstrassPointCons<T>;\n\n /** @deprecated use `Point.CURVE()` */\n CURVE: CurvePointsType<T>;\n /** @deprecated use `Point` */\n ProjectivePoint: WeierstrassPointCons<T>;\n /** @deprecated use `Point.Fn.fromBytes(privateKey)` */\n normPrivateKeyToScalar: (key: PrivKey) => bigint;\n /** @deprecated */\n weierstrassEquation: (x: T) => T;\n /** @deprecated use `Point.Fn.isValidNot0(num)` */\n isWithinCurveOrder: (num: bigint) => boolean;\n};\n\n// Aliases to legacy types\n// export type CurveType = LegacyECDSAOpts;\n// export type CurveFn = LegacyECDSA;\n// export type CurvePointsRes<T> = LegacyWeierstrass<T>;\n// export type CurvePointsType<T> = LegacyWeierstrassOpts<T>;\n// export type CurvePointsTypeWithLength<T> = LegacyWeierstrassOpts<T>;\n// export type BasicWCurve<T> = LegacyWeierstrassOpts<T>;\n\n/** @deprecated use `Uint8Array` */\nexport type PubKey = Hex | WeierstrassPoint<bigint>;\nexport type CurveType = BasicWCurve<bigint> & {\n hash: CHash; // CHash not FHash because we need outputLen for DRBG\n hmac?: HmacFnSync;\n randomBytes?: (bytesLength?: number) => Uint8Array;\n lowS?: boolean;\n bits2int?: (bytes: Uint8Array) => bigint;\n bits2int_modN?: (bytes: Uint8Array) => bigint;\n};\nexport type CurveFn = {\n /** @deprecated use `Point.CURVE()` */\n CURVE: CurvePointsType<bigint>;\n keygen: ECDSA['keygen'];\n getPublicKey: ECDSA['getPublicKey'];\n getSharedSecret: ECDSA['getSharedSecret'];\n sign: ECDSA['sign'];\n verify: ECDSA['verify'];\n Point: WeierstrassPointCons<bigint>;\n /** @deprecated use `Point` */\n ProjectivePoint: WeierstrassPointCons<bigint>;\n Signature: ECDSASignatureCons;\n utils: ECDSA['utils'];\n lengths: ECDSA['lengths'];\n};\n/** @deprecated use `weierstrass` in newer releases */\nexport function weierstrassPoints<T>(c: CurvePointsTypeWithLength<T>): CurvePointsRes<T> {\n const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);\n const Point = weierstrassN(CURVE, curveOpts);\n return _weierstrass_new_output_to_legacy(c, Point);\n}\nexport type WsPointComposed<T> = {\n CURVE: WeierstrassOpts<T>;\n curveOpts: WeierstrassExtraOpts<T>;\n};\nexport type WsComposed = {\n /** @deprecated use `Point.CURVE()` */\n CURVE: WeierstrassOpts<bigint>;\n hash: CHash;\n curveOpts: WeierstrassExtraOpts<bigint>;\n ecdsaOpts: ECDSAOpts;\n};\nfunction _weierstrass_legacy_opts_to_new<T>(c: CurvePointsType<T>): WsPointComposed<T> {\n const CURVE: WeierstrassOpts<T> = {\n a: c.a,\n b: c.b,\n p: c.Fp.ORDER,\n n: c.n,\n h: c.h,\n Gx: c.Gx,\n Gy: c.Gy,\n };\n const Fp = c.Fp;\n let allowedLengths = c.allowedPrivateKeyLengths\n ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2))))\n : undefined;\n const Fn = Field(CURVE.n, {\n BITS: c.nBitLength,\n allowedLengths: allowedLengths,\n modFromBytes: c.wrapPrivateKey,\n });\n const curveOpts: WeierstrassExtraOpts<T> = {\n Fp,\n Fn,\n allowInfinityPoint: c.allowInfinityPoint,\n endo: c.endo,\n isTorsionFree: c.isTorsionFree,\n clearCofactor: c.clearCofactor,\n fromBytes: c.fromBytes,\n toBytes: c.toBytes,\n };\n return { CURVE, curveOpts };\n}\nfunction _ecdsa_legacy_opts_to_new(c: CurveType): WsComposed {\n const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);\n const ecdsaOpts: ECDSAOpts = {\n hmac: c.hmac,\n randomBytes: c.randomBytes,\n lowS: c.lowS,\n bits2int: c.bits2int,\n bits2int_modN: c.bits2int_modN,\n };\n return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };\n}\nexport function _legacyHelperEquat<T>(Fp: IField<T>, a: T, b: T): (x: T) => T {\n /**\n * y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y².\n * @returns y²\n */\n function weierstrassEquation(x: T): T {\n const x2 = Fp.sqr(x); // x * x\n const x3 = Fp.mul(x2, x); // x² * x\n return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b\n }\n return weierstrassEquation;\n}\nfunction _weierstrass_new_output_to_legacy<T>(\n c: CurvePointsType<T>,\n Point: WeierstrassPointCons<T>\n): CurvePointsRes<T> {\n const { Fp, Fn } = Point;\n function isWithinCurveOrder(num: bigint): boolean {\n return inRange(num, _1n, Fn.ORDER);\n }\n const weierstrassEquation = _legacyHelperEquat(Fp, c.a, c.b);\n return Object.assign(\n {},\n {\n CURVE: c,\n Point: Point,\n ProjectivePoint: Point,\n normPrivateKeyToScalar: (key: PrivKey) => _normFnElement(Fn, key),\n weierstrassEquation,\n isWithinCurveOrder,\n }\n );\n}\nfunction _ecdsa_new_output_to_legacy(c: CurveType, _ecdsa: ECDSA): CurveFn {\n const Point = _ecdsa.Point;\n return Object.assign({}, _ecdsa, {\n ProjectivePoint: Point,\n CURVE: Object.assign({}, c, nLength(Point.Fn.ORDER, Point.Fn.BITS)),\n });\n}\n\n// _ecdsa_legacy\nexport function weierstrass(c: CurveType): CurveFn {\n const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);\n const Point = weierstrassN(CURVE, curveOpts);\n const signs = ecdsa(Point, hash, ecdsaOpts);\n return _ecdsa_new_output_to_legacy(c, signs);\n}\n","/**\n * Utilities for short weierstrass curves, combined with noble-hashes.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { type CurveFn, type CurveType, weierstrass } from './abstract/weierstrass.ts';\nimport type { CHash } from './utils.ts';\n\n/** connects noble-curves to noble-hashes */\nexport function getHash(hash: CHash): { hash: CHash } {\n return { hash };\n}\n/** Same API as @noble/hashes, with ability to create curve with custom hash */\nexport type CurveDef = Readonly<Omit<CurveType, 'hash'>>;\nexport type CurveFnWithCreate = CurveFn & { create: (hash: CHash) => CurveFn };\n\n/** @deprecated use new `weierstrass()` and `ecdsa()` methods */\nexport function createCurve(curveDef: CurveDef, defHash: CHash): CurveFnWithCreate {\n const create = (hash: CHash): CurveFn => weierstrass({ ...curveDef, hash: hash });\n return { ...create(defHash), create };\n}\n","/**\n * SECG secp256k1. See [pdf](https://www.secg.org/sec2-v2.pdf).\n *\n * Belongs to Koblitz curves: it has efficiently-computable GLV endomorphism ψ,\n * check out {@link EndomorphismOpts}. Seems to be rigid (not backdoored).\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { randomBytes } from '@noble/hashes/utils.js';\nimport { createCurve, type CurveFnWithCreate } from './_shortw_utils.ts';\nimport type { CurveLengths } from './abstract/curve.ts';\nimport {\n createHasher,\n type H2CHasher,\n type H2CMethod,\n isogenyMap,\n} from './abstract/hash-to-curve.ts';\nimport { Field, mapHashToField, mod, pow2 } from './abstract/modular.ts';\nimport {\n _normFnElement,\n type EndomorphismOpts,\n mapToCurveSimpleSWU,\n type WeierstrassPoint as PointType,\n type WeierstrassOpts,\n type WeierstrassPointCons,\n} from './abstract/weierstrass.ts';\nimport type { Hex, PrivKey } from './utils.ts';\nimport {\n bytesToNumberBE,\n concatBytes,\n ensureBytes,\n inRange,\n numberToBytesBE,\n utf8ToBytes,\n} from './utils.ts';\n\n// Seems like generator was produced from some seed:\n// `Point.BASE.multiply(Point.Fn.inv(2n, N)).toAffine().x`\n// // gives short x 0x3b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63n\nconst secp256k1_CURVE: WeierstrassOpts<bigint> = {\n p: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),\n n: BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'),\n h: BigInt(1),\n a: BigInt(0),\n b: BigInt(7),\n Gx: BigInt('0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),\n Gy: BigInt('0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'),\n};\n\nconst secp256k1_ENDO: EndomorphismOpts = {\n beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),\n basises: [\n [BigInt('0x3086d221a7d46bcde86c90e49284eb15'), -BigInt('0xe4437ed6010e88286f547fa90abfe4c3')],\n [BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8'), BigInt('0x3086d221a7d46bcde86c90e49284eb15')],\n ],\n};\n\nconst _0n = /* @__PURE__ */ BigInt(0);\nconst _1n = /* @__PURE__ */ BigInt(1);\nconst _2n = /* @__PURE__ */ BigInt(2);\n\n/**\n * √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.\n * (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]\n */\nfunction sqrtMod(y: bigint): bigint {\n const P = secp256k1_CURVE.p;\n // prettier-ignore\n const _3n = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);\n // prettier-ignore\n const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);\n const b2 = (y * y * y) % P; // x^3, 11\n const b3 = (b2 * b2 * y) % P; // x^7\n const b6 = (pow2(b3, _3n, P) * b3) % P;\n const b9 = (pow2(b6, _3n, P) * b3) % P;\n const b11 = (pow2(b9, _2n, P) * b2) % P;\n const b22 = (pow2(b11, _11n, P) * b11) % P;\n const b44 = (pow2(b22, _22n, P) * b22) % P;\n const b88 = (pow2(b44, _44n, P) * b44) % P;\n const b176 = (pow2(b88, _88n, P) * b88) % P;\n const b220 = (pow2(b176, _44n, P) * b44) % P;\n const b223 = (pow2(b220, _3n, P) * b3) % P;\n const t1 = (pow2(b223, _23n, P) * b22) % P;\n const t2 = (pow2(t1, _6n, P) * b2) % P;\n const root = pow2(t2, _2n, P);\n if (!Fpk1.eql(Fpk1.sqr(root), y)) throw new Error('Cannot find square root');\n return root;\n}\n\nconst Fpk1 = Field(secp256k1_CURVE.p, { sqrt: sqrtMod });\n\n/**\n * secp256k1 curve, ECDSA and ECDH methods.\n *\n * Field: `2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n`\n *\n * @example\n * ```js\n * import { secp256k1 } from '@noble/curves/secp256k1';\n * const { secretKey, publicKey } = secp256k1.keygen();\n * const msg = new TextEncoder().encode('hello');\n * const sig = secp256k1.sign(msg, secretKey);\n * const isValid = secp256k1.verify(sig, msg, publicKey) === true;\n * ```\n */\nexport const secp256k1: CurveFnWithCreate = createCurve(\n { ...secp256k1_CURVE, Fp: Fpk1, lowS: true, endo: secp256k1_ENDO },\n sha256\n);\n\n// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.\n// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki\n/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */\nconst TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};\nfunction taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {\n let tagP = TAGGED_HASH_PREFIXES[tag];\n if (tagP === undefined) {\n const tagH = sha256(utf8ToBytes(tag));\n tagP = concatBytes(tagH, tagH);\n TAGGED_HASH_PREFIXES[tag] = tagP;\n }\n return sha256(concatBytes(tagP, ...messages));\n}\n\n// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03\nconst pointToBytes = (point: PointType<bigint>) => point.toBytes(true).slice(1);\nconst Pointk1 = /* @__PURE__ */ (() => secp256k1.Point)();\nconst hasEven = (y: bigint) => y % _2n === _0n;\n\n// Calculate point, scalar and bytes\nfunction schnorrGetExtPubKey(priv: PrivKey) {\n const { Fn, BASE } = Pointk1;\n const d_ = _normFnElement(Fn, priv);\n const p = BASE.multiply(d_); // P = d'⋅G; 0 < d' < n check is done inside\n const scalar = hasEven(p.y) ? d_ : Fn.neg(d_);\n return { scalar, bytes: pointToBytes(p) };\n}\n/**\n * lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.\n * @returns valid point checked for being on-curve\n */\nfunction lift_x(x: bigint): PointType<bigint> {\n const Fp = Fpk1;\n if (!Fp.isValidNot0(x)) throw new Error('invalid x: Fail if x ≥ p');\n const xx = Fp.create(x * x);\n const c = Fp.create(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.\n let y = Fp.sqrt(c); // Let y = c^(p+1)/4 mod p. Same as sqrt().\n // Return the unique point P such that x(P) = x and\n // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.\n if (!hasEven(y)) y = Fp.neg(y);\n const p = Pointk1.fromAffine({ x, y });\n p.assertValidity();\n return p;\n}\nconst num = bytesToNumberBE;\n/**\n * Create tagged hash, convert it to bigint, reduce modulo-n.\n */\nfunction challenge(...args: Uint8Array[]): bigint {\n return Pointk1.Fn.create(num(taggedHash('BIP0340/challenge', ...args)));\n}\n\n/**\n * Schnorr public key is just `x` coordinate of Point as per BIP340.\n */\nfunction schnorrGetPublicKey(secretKey: Hex): Uint8Array {\n return schnorrGetExtPubKey(secretKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)\n}\n\n/**\n * Creates Schnorr signature as per BIP340. Verifies itself before returning anything.\n * auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.\n */\nfunction schnorrSign(message: Hex, secretKey: PrivKey, auxRand: Hex = randomBytes(32)): Uint8Array {\n const { Fn } = Pointk1;\n const m = ensureBytes('message', message);\n const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey); // checks for isWithinCurveOrder\n const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array\n const t = Fn.toBytes(d ^ num(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)\n const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)\n // Let k' = int(rand) mod n. Fail if k' = 0. Let R = k'⋅G\n const { bytes: rx, scalar: k } = schnorrGetExtPubKey(rand);\n const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.\n const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).\n sig.set(rx, 0);\n sig.set(Fn.toBytes(Fn.create(k + e * d)), 32);\n // If Verify(bytes(P), m, sig) (see below) returns failure, abort\n if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');\n return sig;\n}\n\n/**\n * Verifies Schnorr signature.\n * Will swallow errors & return false except for initial type validation of arguments.\n */\nfunction schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {\n const { Fn, BASE } = Pointk1;\n const sig = ensureBytes('signature', signature, 64);\n const m = ensureBytes('message', message);\n const pub = ensureBytes('publicKey', publicKey, 32);\n try {\n const P = lift_x(num(pub)); // P = lift_x(int(pk)); fail if that fails\n const r = num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.\n if (!inRange(r, _1n, secp256k1_CURVE.p)) return false;\n const s = num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.\n if (!inRange(s, _1n, secp256k1_CURVE.n)) return false;\n // int(challenge(bytes(r)||bytes(P)||m))%n\n const e = challenge(Fn.toBytes(r), pointToBytes(P), m);\n // R = s⋅G - e⋅P, where -eP == (n-e)P\n const R = BASE.multiplyUnsafe(s).add(P.multiplyUnsafe(Fn.neg(e)));\n const { x, y } = R.toAffine();\n // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.\n if (R.is0() || !hasEven(y) || x !== r) return false;\n return true;\n } catch (error) {\n return false;\n }\n}\n\nexport type SecpSchnorr = {\n keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };\n getPublicKey: typeof schnorrGetPublicKey;\n sign: typeof schnorrSign;\n verify: typeof schnorrVerify;\n Point: WeierstrassPointCons<bigint>;\n utils: {\n randomSecretKey: (seed?: Uint8Array) => Uint8Array;\n pointToBytes: (point: PointType<bigint>) => Uint8Array;\n lift_x: typeof lift_x;\n taggedHash: typeof taggedHash;\n\n /** @deprecated use `randomSecretKey` */\n randomPrivateKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `utils` */\n numberToBytesBE: typeof numberToBytesBE;\n /** @deprecated use `utils` */\n bytesToNumberBE: typeof bytesToNumberBE;\n /** @deprecated use `modular` */\n mod: typeof mod;\n };\n lengths: CurveLengths;\n};\n/**\n * Schnorr signatures over secp256k1.\n * https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki\n * @example\n * ```js\n * import { schnorr } from '@noble/curves/secp256k1';\n * const { secretKey, publicKey } = schnorr.keygen();\n * // const publicKey = schnorr.getPublicKey(secretKey);\n * const msg = new TextEncoder().encode('hello');\n * const sig = schnorr.sign(msg, secretKey);\n * const isValid = schnorr.verify(sig, msg, publicKey);\n * ```\n */\nexport const schnorr: SecpSchnorr = /* @__PURE__ */ (() => {\n const size = 32;\n const seedLength = 48;\n const randomSecretKey = (seed = randomBytes(seedLength)): Uint8Array => {\n return mapHashToField(seed, secp256k1_CURVE.n);\n };\n // TODO: remove\n secp256k1.utils.randomSecretKey;\n function keygen(seed?: Uint8Array) {\n const secretKey = randomSecretKey(seed);\n return { secretKey, publicKey: schnorrGetPublicKey(secretKey) };\n }\n return {\n keygen,\n getPublicKey: schnorrGetPublicKey,\n sign: schnorrSign,\n verify: schnorrVerify,\n Point: Pointk1,\n utils: {\n randomSecretKey: randomSecretKey,\n randomPrivateKey: randomSecretKey,\n taggedHash,\n\n // TODO: remove\n lift_x,\n pointToBytes,\n numberToBytesBE,\n bytesToNumberBE,\n mod,\n },\n lengths: {\n secretKey: size,\n publicKey: size,\n publicKeyHasPrefix: false,\n signature: size * 2,\n seed: seedLength,\n },\n };\n})();\n\nconst isoMap = /* @__PURE__ */ (() =>\n isogenyMap(\n Fpk1,\n [\n // xNum\n [\n '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',\n '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',\n '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',\n '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',\n ],\n // xDen\n [\n '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',\n '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',\n '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1\n ],\n // yNum\n [\n '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',\n '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',\n '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',\n '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',\n ],\n // yDen\n [\n '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',\n '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',\n '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',\n '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1\n ],\n ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]\n ))();\nconst mapSWU = /* @__PURE__ */ (() =>\n mapToCurveSimpleSWU(Fpk1, {\n A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),\n B: BigInt('1771'),\n Z: Fpk1.create(BigInt('-11')),\n }))();\n\n/** Hashing / encoding to secp256k1 points / field. RFC 9380 methods. */\nexport const secp256k1_hasher: H2CHasher<bigint> = /* @__PURE__ */ (() =>\n createHasher(\n secp256k1.Point,\n (scalars: bigint[]) => {\n const { x, y } = mapSWU(Fpk1.create(scalars[0]));\n return isoMap(x, y);\n },\n {\n DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',\n encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',\n p: Fpk1.ORDER,\n m: 1,\n k: 128,\n expand: 'xmd',\n hash: sha256,\n }\n ))();\n\n/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */\nexport const hashToCurve: H2CMethod<bigint> = /* @__PURE__ */ (() =>\n secp256k1_hasher.hashToCurve)();\n\n/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */\nexport const encodeToCurve: H2CMethod<bigint> = /* @__PURE__ */ (() =>\n secp256k1_hasher.encodeToCurve)();\n","/**\n\nSHA1 (RFC 3174), MD5 (RFC 1321) and RIPEMD160 (RFC 2286) legacy, weak hash functions.\nDon't use them in a new protocol. What \"weak\" means:\n\n- Collisions can be made with 2^18 effort in MD5, 2^60 in SHA1, 2^80 in RIPEMD160.\n- No practical pre-image attacks (only theoretical, 2^123.4)\n- HMAC seems kinda ok: https://datatracker.ietf.org/doc/html/rfc6151\n * @module\n */\nimport { Chi, HashMD, Maj } from './_md.ts';\nimport { type CHash, clean, createHasher, rotl } from './utils.ts';\n\n/** Initial SHA1 state */\nconst SHA1_IV = /* @__PURE__ */ Uint32Array.from([\n 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0,\n]);\n\n// Reusable temporary buffer\nconst SHA1_W = /* @__PURE__ */ new Uint32Array(80);\n\n/** SHA1 legacy hash class. */\nexport class SHA1 extends HashMD<SHA1> {\n private A = SHA1_IV[0] | 0;\n private B = SHA1_IV[1] | 0;\n private C = SHA1_IV[2] | 0;\n private D = SHA1_IV[3] | 0;\n private E = SHA1_IV[4] | 0;\n\n constructor() {\n super(64, 20, 8, false);\n }\n protected get(): [number, number, number, number, number] {\n const { A, B, C, D, E } = this;\n return [A, B, C, D, E];\n }\n protected set(A: number, B: number, C: number, D: number, E: number): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n this.E = E | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) SHA1_W[i] = view.getUint32(offset, false);\n for (let i = 16; i < 80; i++)\n SHA1_W[i] = rotl(SHA1_W[i - 3] ^ SHA1_W[i - 8] ^ SHA1_W[i - 14] ^ SHA1_W[i - 16], 1);\n // Compression function main loop, 80 rounds\n let { A, B, C, D, E } = this;\n for (let i = 0; i < 80; i++) {\n let F, K;\n if (i < 20) {\n F = Chi(B, C, D);\n K = 0x5a827999;\n } else if (i < 40) {\n F = B ^ C ^ D;\n K = 0x6ed9eba1;\n } else if (i < 60) {\n F = Maj(B, C, D);\n K = 0x8f1bbcdc;\n } else {\n F = B ^ C ^ D;\n K = 0xca62c1d6;\n }\n const T = (rotl(A, 5) + F + E + K + SHA1_W[i]) | 0;\n E = D;\n D = C;\n C = rotl(B, 30);\n B = A;\n A = T;\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n E = (E + this.E) | 0;\n this.set(A, B, C, D, E);\n }\n protected roundClean(): void {\n clean(SHA1_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/** SHA1 (RFC 3174) legacy hash function. It was cryptographically broken. */\nexport const sha1: CHash = /* @__PURE__ */ createHasher(() => new SHA1());\n\n/** Per-round constants */\nconst p32 = /* @__PURE__ */ Math.pow(2, 32);\nconst K = /* @__PURE__ */ Array.from({ length: 64 }, (_, i) =>\n Math.floor(p32 * Math.abs(Math.sin(i + 1)))\n);\n\n/** md5 initial state: same as sha1, but 4 u32 instead of 5. */\nconst MD5_IV = /* @__PURE__ */ SHA1_IV.slice(0, 4);\n\n// Reusable temporary buffer\nconst MD5_W = /* @__PURE__ */ new Uint32Array(16);\n/** MD5 legacy hash class. */\nexport class MD5 extends HashMD<MD5> {\n private A = MD5_IV[0] | 0;\n private B = MD5_IV[1] | 0;\n private C = MD5_IV[2] | 0;\n private D = MD5_IV[3] | 0;\n\n constructor() {\n super(64, 16, 8, true);\n }\n protected get(): [number, number, number, number] {\n const { A, B, C, D } = this;\n return [A, B, C, D];\n }\n protected set(A: number, B: number, C: number, D: number): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) MD5_W[i] = view.getUint32(offset, true);\n // Compression function main loop, 64 rounds\n let { A, B, C, D } = this;\n for (let i = 0; i < 64; i++) {\n let F, g, s;\n if (i < 16) {\n F = Chi(B, C, D);\n g = i;\n s = [7, 12, 17, 22];\n } else if (i < 32) {\n F = Chi(D, B, C);\n g = (5 * i + 1) % 16;\n s = [5, 9, 14, 20];\n } else if (i < 48) {\n F = B ^ C ^ D;\n g = (3 * i + 5) % 16;\n s = [4, 11, 16, 23];\n } else {\n F = C ^ (B | ~D);\n g = (7 * i) % 16;\n s = [6, 10, 15, 21];\n }\n F = F + A + K[i] + MD5_W[g];\n A = D;\n D = C;\n C = B;\n B = B + rotl(F, s[i % 4]);\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n this.set(A, B, C, D);\n }\n protected roundClean(): void {\n clean(MD5_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/**\n * MD5 (RFC 1321) legacy hash function. It was cryptographically broken.\n * MD5 architecture is similar to SHA1, with some differences:\n * - Reduced output length: 16 bytes (128 bit) instead of 20\n * - 64 rounds, instead of 80\n * - Little-endian: could be faster, but will require more code\n * - Non-linear index selection: huge speed-up for unroll\n * - Per round constants: more memory accesses, additional speed-up for unroll\n */\nexport const md5: CHash = /* @__PURE__ */ createHasher(() => new MD5());\n\n// RIPEMD-160\n\nconst Rho160 = /* @__PURE__ */ Uint8Array.from([\n 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,\n]);\nconst Id160 = /* @__PURE__ */ (() => Uint8Array.from(new Array(16).fill(0).map((_, i) => i)))();\nconst Pi160 = /* @__PURE__ */ (() => Id160.map((i) => (9 * i + 5) % 16))();\nconst idxLR = /* @__PURE__ */ (() => {\n const L = [Id160];\n const R = [Pi160];\n const res = [L, R];\n for (let i = 0; i < 4; i++) for (let j of res) j.push(j[i].map((k) => Rho160[k]));\n return res;\n})();\nconst idxL = /* @__PURE__ */ (() => idxLR[0])();\nconst idxR = /* @__PURE__ */ (() => idxLR[1])();\n// const [idxL, idxR] = idxLR;\n\nconst shifts160 = /* @__PURE__ */ [\n [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8],\n [12, 13, 11, 15, 6, 9, 9, 7, 12, 15, 11, 13, 7, 8, 7, 7],\n [13, 15, 14, 11, 7, 7, 6, 8, 13, 14, 13, 12, 5, 5, 6, 9],\n [14, 11, 12, 14, 8, 6, 5, 5, 15, 12, 15, 14, 9, 9, 8, 6],\n [15, 12, 13, 13, 9, 5, 8, 6, 14, 11, 12, 11, 8, 6, 5, 5],\n].map((i) => Uint8Array.from(i));\nconst shiftsL160 = /* @__PURE__ */ idxL.map((idx, i) => idx.map((j) => shifts160[i][j]));\nconst shiftsR160 = /* @__PURE__ */ idxR.map((idx, i) => idx.map((j) => shifts160[i][j]));\nconst Kl160 = /* @__PURE__ */ Uint32Array.from([\n 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e,\n]);\nconst Kr160 = /* @__PURE__ */ Uint32Array.from([\n 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000,\n]);\n// It's called f() in spec.\nfunction ripemd_f(group: number, x: number, y: number, z: number): number {\n if (group === 0) return x ^ y ^ z;\n if (group === 1) return (x & y) | (~x & z);\n if (group === 2) return (x | ~y) ^ z;\n if (group === 3) return (x & z) | (y & ~z);\n return x ^ (y | ~z);\n}\n// Reusable temporary buffer\nconst BUF_160 = /* @__PURE__ */ new Uint32Array(16);\nexport class RIPEMD160 extends HashMD<RIPEMD160> {\n private h0 = 0x67452301 | 0;\n private h1 = 0xefcdab89 | 0;\n private h2 = 0x98badcfe | 0;\n private h3 = 0x10325476 | 0;\n private h4 = 0xc3d2e1f0 | 0;\n\n constructor() {\n super(64, 20, 8, true);\n }\n protected get(): [number, number, number, number, number] {\n const { h0, h1, h2, h3, h4 } = this;\n return [h0, h1, h2, h3, h4];\n }\n protected set(h0: number, h1: number, h2: number, h3: number, h4: number): void {\n this.h0 = h0 | 0;\n this.h1 = h1 | 0;\n this.h2 = h2 | 0;\n this.h3 = h3 | 0;\n this.h4 = h4 | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) BUF_160[i] = view.getUint32(offset, true);\n // prettier-ignore\n let al = this.h0 | 0, ar = al,\n bl = this.h1 | 0, br = bl,\n cl = this.h2 | 0, cr = cl,\n dl = this.h3 | 0, dr = dl,\n el = this.h4 | 0, er = el;\n\n // Instead of iterating 0 to 80, we split it into 5 groups\n // And use the groups in constants, functions, etc. Much simpler\n for (let group = 0; group < 5; group++) {\n const rGroup = 4 - group;\n const hbl = Kl160[group], hbr = Kr160[group]; // prettier-ignore\n const rl = idxL[group], rr = idxR[group]; // prettier-ignore\n const sl = shiftsL160[group], sr = shiftsR160[group]; // prettier-ignore\n for (let i = 0; i < 16; i++) {\n const tl = (rotl(al + ripemd_f(group, bl, cl, dl) + BUF_160[rl[i]] + hbl, sl[i]) + el) | 0;\n al = el, el = dl, dl = rotl(cl, 10) | 0, cl = bl, bl = tl; // prettier-ignore\n }\n // 2 loops are 10% faster\n for (let i = 0; i < 16; i++) {\n const tr = (rotl(ar + ripemd_f(rGroup, br, cr, dr) + BUF_160[rr[i]] + hbr, sr[i]) + er) | 0;\n ar = er, er = dr, dr = rotl(cr, 10) | 0, cr = br, br = tr; // prettier-ignore\n }\n }\n // Add the compressed chunk to the current hash value\n this.set(\n (this.h1 + cl + dr) | 0,\n (this.h2 + dl + er) | 0,\n (this.h3 + el + ar) | 0,\n (this.h4 + al + br) | 0,\n (this.h0 + bl + cr) | 0\n );\n }\n protected roundClean(): void {\n clean(BUF_160);\n }\n destroy(): void {\n this.destroyed = true;\n clean(this.buffer);\n this.set(0, 0, 0, 0, 0);\n }\n}\n\n/**\n * RIPEMD-160 - a legacy hash function from 1990s.\n * * https://homes.esat.kuleuven.be/~bosselae/ripemd160.html\n * * https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf\n */\nexport const ripemd160: CHash = /* @__PURE__ */ createHasher(() => new RIPEMD160());\n","/**\n * @module BIP32 hierarchical deterministic (HD) wallets over secp256k1.\n * @example\n * ```js\n * import { HDKey } from \"@scure/bip32\";\n * const hdkey1 = HDKey.fromMasterSeed(seed);\n * const hdkey2 = HDKey.fromExtendedKey(base58key);\n * const hdkey3 = HDKey.fromJSON({ xpriv: string });\n *\n * // props\n * [hdkey1.depth, hdkey1.index, hdkey1.chainCode];\n * console.log(hdkey2.privateKey, hdkey2.publicKey);\n * console.log(hdkey3.derive(\"m/0/2147483647'/1\"));\n * const sig = hdkey3.sign(hash);\n * hdkey3.verify(hash, sig);\n * ```\n */\n/*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */\nimport { mod } from '@noble/curves/abstract/modular';\nimport { secp256k1 as secp } from '@noble/curves/secp256k1';\nimport { hmac } from '@noble/hashes/hmac';\nimport { ripemd160 } from '@noble/hashes/legacy';\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport {\n abytes,\n bytesToHex,\n concatBytes,\n createView,\n hexToBytes,\n utf8ToBytes,\n} from '@noble/hashes/utils';\nimport { createBase58check } from '@scure/base';\n\nconst Point = secp.ProjectivePoint;\nconst base58check = createBase58check(sha256);\n\nfunction bytesToNumber(bytes: Uint8Array): bigint {\n abytes(bytes);\n const h = bytes.length === 0 ? '0' : bytesToHex(bytes);\n return BigInt('0x' + h);\n}\n\nfunction numberToBytes(num: bigint): Uint8Array {\n if (typeof num !== 'bigint') throw new Error('bigint expected');\n return hexToBytes(num.toString(16).padStart(64, '0'));\n}\n\nconst MASTER_SECRET = utf8ToBytes('Bitcoin seed');\n// Bitcoin hardcoded by default\nconst BITCOIN_VERSIONS: Versions = { private: 0x0488ade4, public: 0x0488b21e };\nexport const HARDENED_OFFSET: number = 0x80000000;\n\nexport interface Versions {\n private: number;\n public: number;\n}\n\nconst hash160 = (data: Uint8Array) => ripemd160(sha256(data));\nconst fromU32 = (data: Uint8Array) => createView(data).getUint32(0, false);\nconst toU32 = (n: number) => {\n if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {\n throw new Error('invalid number, should be from 0 to 2**32-1, got ' + n);\n }\n const buf = new Uint8Array(4);\n createView(buf).setUint32(0, n, false);\n return buf;\n};\n\ninterface HDKeyOpt {\n versions?: Versions;\n depth?: number;\n index?: number;\n parentFingerprint?: number;\n chainCode?: Uint8Array;\n publicKey?: Uint8Array;\n privateKey?: Uint8Array | bigint;\n}\n\nexport class HDKey {\n get fingerprint(): number {\n if (!this.pubHash) {\n throw new Error('No publicKey set!');\n }\n return fromU32(this.pubHash);\n }\n get identifier(): Uint8Array | undefined {\n return this.pubHash;\n }\n get pubKeyHash(): Uint8Array | undefined {\n return this.pubHash;\n }\n get privateKey(): Uint8Array | null {\n return this.privKeyBytes || null;\n }\n get publicKey(): Uint8Array | null {\n return this.pubKey || null;\n }\n get privateExtendedKey(): string {\n const priv = this.privateKey;\n if (!priv) {\n throw new Error('No private key');\n }\n return base58check.encode(\n this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv))\n );\n }\n get publicExtendedKey(): string {\n if (!this.pubKey) {\n throw new Error('No public key');\n }\n return base58check.encode(this.serialize(this.versions.public, this.pubKey));\n }\n\n public static fromMasterSeed(seed: Uint8Array, versions: Versions = BITCOIN_VERSIONS): HDKey {\n abytes(seed);\n if (8 * seed.length < 128 || 8 * seed.length > 512) {\n throw new Error(\n 'HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +\n seed.length\n );\n }\n const I = hmac(sha512, MASTER_SECRET, seed);\n return new HDKey({\n versions,\n chainCode: I.slice(32),\n privateKey: I.slice(0, 32),\n });\n }\n\n public static fromExtendedKey(base58key: string, versions: Versions = BITCOIN_VERSIONS): HDKey {\n // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)\n const keyBuffer: Uint8Array = base58check.decode(base58key);\n const keyView = createView(keyBuffer);\n const version = keyView.getUint32(0, false);\n const opt = {\n versions,\n depth: keyBuffer[4],\n parentFingerprint: keyView.getUint32(5, false),\n index: keyView.getUint32(9, false),\n chainCode: keyBuffer.slice(13, 45),\n };\n const key = keyBuffer.slice(45);\n const isPriv = key[0] === 0;\n if (version !== versions[isPriv ? 'private' : 'public']) {\n throw new Error('Version mismatch');\n }\n if (isPriv) {\n return new HDKey({ ...opt, privateKey: key.slice(1) });\n } else {\n return new HDKey({ ...opt, publicKey: key });\n }\n }\n\n public static fromJSON(json: { xpriv: string }): HDKey {\n return HDKey.fromExtendedKey(json.xpriv);\n }\n public readonly versions: Versions;\n public readonly depth: number = 0;\n public readonly index: number = 0;\n public readonly chainCode: Uint8Array | null = null;\n public readonly parentFingerprint: number = 0;\n private privKey?: bigint;\n private privKeyBytes?: Uint8Array;\n private pubKey?: Uint8Array;\n private pubHash: Uint8Array | undefined;\n\n constructor(opt: HDKeyOpt) {\n if (!opt || typeof opt !== 'object') {\n throw new Error('HDKey.constructor must not be called directly');\n }\n this.versions = opt.versions || BITCOIN_VERSIONS;\n this.depth = opt.depth || 0;\n this.chainCode = opt.chainCode || null;\n this.index = opt.index || 0;\n this.parentFingerprint = opt.parentFingerprint || 0;\n if (!this.depth) {\n if (this.parentFingerprint || this.index) {\n throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');\n }\n }\n if (opt.publicKey && opt.privateKey) {\n throw new Error('HDKey: publicKey and privateKey at same time.');\n }\n if (opt.privateKey) {\n if (!secp.utils.isValidPrivateKey(opt.privateKey)) {\n throw new Error('Invalid private key');\n }\n this.privKey =\n typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);\n this.privKeyBytes = numberToBytes(this.privKey);\n this.pubKey = secp.getPublicKey(opt.privateKey, true);\n } else if (opt.publicKey) {\n this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true); // force compressed point\n } else {\n throw new Error('HDKey: no public or private key provided');\n }\n this.pubHash = hash160(this.pubKey);\n }\n\n public derive(path: string): HDKey {\n if (!/^[mM]'?/.test(path)) {\n throw new Error('Path must start with \"m\" or \"M\"');\n }\n if (/^[mM]'?$/.test(path)) {\n return this;\n }\n const parts = path.replace(/^[mM]'?\\//, '').split('/');\n // tslint:disable-next-line\n let child: HDKey = this;\n for (const c of parts) {\n const m = /^(\\d+)('?)$/.exec(c);\n const m1 = m && m[1];\n if (!m || m.length !== 3 || typeof m1 !== 'string')\n throw new Error('invalid child index: ' + c);\n let idx = +m1;\n if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {\n throw new Error('Invalid index');\n }\n // hardened key\n if (m[2] === \"'\") {\n idx += HARDENED_OFFSET;\n }\n child = child.deriveChild(idx);\n }\n return child;\n }\n\n public deriveChild(index: number): HDKey {\n if (!this.pubKey || !this.chainCode) {\n throw new Error('No publicKey or chainCode set');\n }\n let data = toU32(index);\n if (index >= HARDENED_OFFSET) {\n // Hardened\n const priv = this.privateKey;\n if (!priv) {\n throw new Error('Could not derive hardened child key');\n }\n // Hardened child: 0x00 || ser256(kpar) || ser32(index)\n data = concatBytes(new Uint8Array([0]), priv, data);\n } else {\n // Normal child: serP(point(kpar)) || ser32(index)\n data = concatBytes(this.pubKey, data);\n }\n const I = hmac(sha512, this.chainCode, data);\n const childTweak = bytesToNumber(I.slice(0, 32));\n const chainCode = I.slice(32);\n if (!secp.utils.isValidPrivateKey(childTweak)) {\n throw new Error('Tweak bigger than curve order');\n }\n const opt: HDKeyOpt = {\n versions: this.versions,\n chainCode,\n depth: this.depth + 1,\n parentFingerprint: this.fingerprint,\n index,\n };\n try {\n // Private parent key -> private child key\n if (this.privateKey) {\n const added = mod(this.privKey! + childTweak, secp.CURVE.n);\n if (!secp.utils.isValidPrivateKey(added)) {\n throw new Error('The tweak was out of range or the resulted private key is invalid');\n }\n opt.privateKey = added;\n } else {\n const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));\n // Cryptographically impossible: hmac-sha512 preimage would need to be found\n if (added.equals(Point.ZERO)) {\n throw new Error('The tweak was equal to negative P, which made the result key invalid');\n }\n opt.publicKey = added.toRawBytes(true);\n }\n return new HDKey(opt);\n } catch (err) {\n return this.deriveChild(index + 1);\n }\n }\n\n public sign(hash: Uint8Array): Uint8Array {\n if (!this.privateKey) {\n throw new Error('No privateKey set!');\n }\n abytes(hash, 32);\n return secp.sign(hash, this.privKey!).toCompactRawBytes();\n }\n\n public verify(hash: Uint8Array, signature: Uint8Array): boolean {\n abytes(hash, 32);\n abytes(signature, 64);\n if (!this.publicKey) {\n throw new Error('No publicKey set!');\n }\n let sig;\n try {\n sig = secp.Signature.fromCompact(signature);\n } catch (error) {\n return false;\n }\n return secp.verify(sig, hash, this.publicKey);\n }\n\n public wipePrivateData(): this {\n this.privKey = undefined;\n if (this.privKeyBytes) {\n this.privKeyBytes.fill(0);\n this.privKeyBytes = undefined;\n }\n return this;\n }\n public toJSON(): { xpriv: string; xpub: string } {\n return {\n xpriv: this.privateExtendedKey,\n xpub: this.publicExtendedKey,\n };\n }\n\n private serialize(version: number, key: Uint8Array) {\n if (!this.chainCode) {\n throw new Error('No chainCode set');\n }\n abytes(key, 33);\n // version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)\n return concatBytes(\n toU32(version),\n new Uint8Array([this.depth]),\n toU32(this.parentFingerprint),\n toU32(this.index),\n this.chainCode,\n key\n );\n }\n}\n","/**\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","/**\n * Handler context for @toon-protocol/sdk.\n *\n * Provides a context object passed to kind-based handlers with methods\n * for accessing TOON data, shallow-parsed metadata, and accept/reject actions.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { ToonRoutingMeta } from '@toon-protocol/core/toon';\nimport type {\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n} from '@toon-protocol/core';\n\n// Re-export core response types so SDK consumers don't need to import from core\nexport type { HandlePacketAcceptResponse, HandlePacketRejectResponse };\n\n/**\n * The handler context passed to each kind-based handler.\n */\nexport interface HandlerContext {\n /** Raw TOON string (base64-encoded). */\n readonly toon: string;\n /** Event kind from shallow parse. */\n readonly kind: number;\n /** Event pubkey from shallow parse. */\n readonly pubkey: string;\n /** Payment amount in the ILP packet. */\n readonly amount: bigint;\n /** ILP destination address. */\n readonly destination: string;\n /** Lazy-decode the TOON payload into a full NostrEvent. */\n decode(): NostrEvent;\n /** Accept the packet with optional metadata. */\n accept(metadata?: Record<string, unknown>): HandlePacketAcceptResponse;\n /** Reject the packet with an ILP error code and message. */\n reject(code: string, message: string): HandlePacketRejectResponse;\n}\n\nexport interface CreateHandlerContextOptions {\n toon: string;\n meta: ToonRoutingMeta;\n amount: bigint;\n destination: string;\n toonDecoder: (toon: string) => NostrEvent;\n}\n\n/**\n * Creates a HandlerContext from the given options.\n */\nexport function createHandlerContext(\n options: CreateHandlerContextOptions\n): HandlerContext {\n let cachedEvent: NostrEvent | undefined;\n\n return {\n get toon() {\n return options.toon;\n },\n get kind() {\n return options.meta.kind;\n },\n get pubkey() {\n return options.meta.pubkey;\n },\n get amount() {\n return options.amount;\n },\n get destination() {\n return options.destination;\n },\n decode(): NostrEvent {\n if (!cachedEvent) {\n cachedEvent = options.toonDecoder(options.toon);\n }\n return cachedEvent;\n },\n accept(metadata?: Record<string, unknown>): HandlePacketAcceptResponse {\n return {\n accept: true,\n ...(metadata ? { metadata } : {}),\n };\n },\n reject(code: string, message: string): HandlePacketRejectResponse {\n return {\n accept: false,\n code,\n message,\n };\n },\n };\n}\n","/**\n * Handler registry for @toon-protocol/sdk.\n *\n * Maps event kinds to handler functions for dispatching incoming ILP packets.\n */\n\nimport { JOB_REQUEST_KIND_BASE } from '@toon-protocol/core';\nimport type {\n HandlerContext,\n HandlePacketAcceptResponse,\n HandlePacketRejectResponse,\n} from './handler-context.js';\n\nexport type HandlerResponse =\n | HandlePacketAcceptResponse\n | HandlePacketRejectResponse;\n\nexport type Handler = (ctx: HandlerContext) => Promise<HandlerResponse>;\n\n/**\n * Registry that maps Nostr event kinds to handler functions.\n */\nexport class HandlerRegistry {\n private handlers = new Map<number, Handler>();\n private defaultHandler: Handler | undefined;\n\n /**\n * Register a handler for a specific event kind.\n * Replaces any existing handler for that kind.\n */\n on(kind: number, handler: Handler): this {\n this.handlers.set(kind, handler);\n return this;\n }\n\n /**\n * Register a default handler for unrecognized kinds.\n */\n onDefault(handler: Handler): this {\n this.defaultHandler = handler;\n return this;\n }\n\n /**\n * Returns all registered kind numbers, sorted ascending.\n */\n getRegisteredKinds(): number[] {\n return [...this.handlers.keys()].sort((a, b) => a - b);\n }\n\n /**\n * Returns the handler registered for `kind`, or `undefined` if none.\n * Added for Story 12.7 AC-10 (handler-registration verification).\n */\n get(kind: number): Handler | undefined {\n return this.handlers.get(kind);\n }\n\n /**\n * Returns registered kinds in the DVM request range (5000-5999), sorted ascending.\n * Uses JOB_REQUEST_KIND_BASE (5000) as the range start.\n */\n getDvmKinds(): number[] {\n const dvmRangeStart = JOB_REQUEST_KIND_BASE;\n const dvmRangeEnd = JOB_REQUEST_KIND_BASE + 999;\n return this.getRegisteredKinds().filter(\n (k) => k >= dvmRangeStart && k <= dvmRangeEnd\n );\n }\n\n /**\n * Dispatch a context to the appropriate handler based on kind.\n */\n async dispatch(ctx: HandlerContext): Promise<HandlerResponse> {\n const handler = this.handlers.get(ctx.kind);\n if (handler) {\n return handler(ctx);\n }\n if (this.defaultHandler) {\n return this.defaultHandler(ctx);\n }\n return {\n accept: false,\n code: 'F00',\n message: `No handler registered for kind ${ctx.kind}`,\n };\n }\n}\n","/**\n * Pricing validator for @toon-protocol/sdk.\n *\n * Validates that incoming ILP payments meet the required price based on\n * TOON payload size and configured price per byte.\n */\n\nimport type { ToonRoutingMeta } from '@toon-protocol/core/toon';\n\nexport interface PricingValidatorConfig {\n basePricePerByte?: bigint;\n ownPubkey: string;\n kindPricing?: Record<number, bigint>;\n}\n\nexport interface PricingValidationResult {\n accepted: boolean;\n rejection?: {\n accept: false;\n code: string;\n message: string;\n metadata?: Record<string, string>;\n };\n}\n\n/**\n * Creates a pricing validator that checks payment amounts against TOON size.\n */\nexport function createPricingValidator(config: PricingValidatorConfig) {\n const basePricePerByte = config.basePricePerByte ?? 10n;\n\n return {\n validate(meta: ToonRoutingMeta, amount: bigint): PricingValidationResult {\n // Self-write bypass\n if (meta.pubkey === config.ownPubkey) {\n return { accepted: true };\n }\n\n // Kind-specific pricing override (use Object.hasOwn for prototype-safe lookup)\n const kindOverride =\n config.kindPricing && Object.hasOwn(config.kindPricing, meta.kind)\n ? config.kindPricing[meta.kind]\n : undefined;\n const pricePerByte = kindOverride ?? basePricePerByte;\n\n const requiredAmount = BigInt(meta.rawBytes.length) * pricePerByte;\n\n if (amount < requiredAmount) {\n return {\n accepted: false,\n rejection: {\n accept: false,\n code: 'F04',\n message: 'Insufficient payment',\n metadata: {\n required: requiredAmount.toString(),\n received: amount.toString(),\n },\n },\n };\n }\n\n return { accepted: true };\n },\n };\n}\n","/**\n * Verification pipeline for @toon-protocol/sdk.\n *\n * Verifies Schnorr signatures on incoming TOON payloads before dispatching\n * to handlers. In dev mode, verification is skipped.\n */\n\nimport type { ToonRoutingMeta } from '@toon-protocol/core/toon';\nimport { schnorr } from '@noble/curves/secp256k1.js';\nimport { hexToBytes } from '@noble/hashes/utils.js';\n\nexport interface VerificationResult {\n verified: boolean;\n rejection?: {\n accept: false;\n code: string;\n message: string;\n };\n}\n\nexport interface VerificationPipelineConfig {\n devMode: boolean;\n}\n\n/**\n * Creates a verification pipeline that checks Schnorr signatures.\n */\nexport function createVerificationPipeline(config: VerificationPipelineConfig) {\n return {\n async verify(\n meta: ToonRoutingMeta,\n _toonData: string\n ): Promise<VerificationResult> {\n if (config.devMode) {\n return { verified: true };\n }\n\n try {\n const sigBytes = hexToBytes(meta.sig);\n const msgBytes = hexToBytes(meta.id);\n const pubkeyBytes = hexToBytes(meta.pubkey);\n const valid = schnorr.verify(sigBytes, msgBytes, pubkeyBytes);\n if (!valid) {\n return {\n verified: false,\n rejection: {\n accept: false,\n code: 'F06',\n message: 'Invalid Schnorr signature',\n },\n };\n }\n return { verified: true };\n } catch {\n return {\n verified: false,\n rejection: {\n accept: false,\n code: 'F06',\n message: 'Signature verification failed',\n },\n };\n }\n },\n };\n}\n","/**\n * Payment handler bridge for @toon-protocol/sdk.\n *\n * Bridges between ILP packet handling and the handler registry,\n * using isTransit to distinguish fire-and-forget from await semantics.\n */\n\nimport type { HandlerRegistry } from './handler-registry.js';\nimport { createHandlerContext } from './handler-context.js';\nimport type { ToonRoutingMeta } from '@toon-protocol/core/toon';\n\nexport interface PaymentHandlerBridgeConfig {\n registry: HandlerRegistry;\n devMode: boolean;\n ownPubkey: string;\n basePricePerByte: bigint;\n}\n\nexport interface PaymentRequest {\n paymentId: string;\n destination: string;\n amount: string;\n data: string;\n isTransit: boolean;\n}\n\nexport interface PaymentResponse {\n accept: boolean;\n code?: string;\n message?: string;\n data?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Creates a payment handler bridge that connects ILP packets to the handler registry.\n */\nexport function createPaymentHandlerBridge(config: PaymentHandlerBridgeConfig) {\n const { registry } = config;\n\n return {\n async handlePayment(request: PaymentRequest): Promise<PaymentResponse> {\n let amount: bigint;\n try {\n amount = BigInt(request.amount);\n } catch {\n return {\n accept: false,\n code: 'T00',\n message: 'Invalid payment amount',\n };\n }\n\n const ctx = createHandlerContext({\n toon: request.data,\n meta: {\n kind: 0,\n pubkey: '',\n id: '',\n sig: '',\n rawBytes: new Uint8Array(0),\n } as ToonRoutingMeta,\n amount,\n destination: request.destination,\n toonDecoder: () => {\n throw new Error('decode not available in bridge');\n },\n });\n\n if (request.isTransit) {\n void registry.dispatch(ctx).catch((err: unknown) => {\n // Log only the error message, not the full error object (which may contain payload data)\n const errMsg = err instanceof Error ? err.message : 'Unknown error';\n console.error('Transit handler error:', errMsg);\n });\n return { accept: true };\n }\n\n try {\n return await registry.dispatch(ctx);\n } catch (err: unknown) {\n // Log only the error message, not the full error object (which may contain payload data)\n const errMsg = err instanceof Error ? err.message : 'Unknown error';\n console.error('Handler error:', errMsg);\n return {\n accept: false,\n code: 'T00',\n message: 'Internal error',\n };\n }\n },\n };\n}\n","/**\n * Event storage handler stub for @toon-protocol/sdk.\n *\n * This is a stub that throws. The real implementation lives in\n * `@toon-protocol/relay` -- see `createEventStorageHandler` from that package.\n *\n * The SDK is the framework; Relay is the relay implementation. SDK consumers\n * building relay functionality should use `@toon-protocol/relay` directly.\n */\n\n/**\n * Creates an event storage handler.\n *\n * **Stub** -- throws \"not yet implemented\". See `@toon-protocol/relay` for the\n * real relay implementation of this handler.\n *\n * @see {@link https://github.com/toon-protocol/relay | @toon-protocol/relay}\n */\nexport function createEventStorageHandler(_config: unknown): unknown {\n throw new Error(\n 'createEventStorageHandler is not yet implemented in @toon-protocol/sdk. ' +\n 'Use @toon-protocol/relay for the relay implementation.'\n );\n}\n","/**\n * createNode() composition for @toon-protocol/sdk.\n *\n * Wires the full ILP packet processing pipeline:\n * shallow TOON parse -> Schnorr signature verification -> pricing validation -> handler dispatch\n *\n * Provides start() / stop() lifecycle management by delegating to\n * the core ToonNode composition (embedded mode) or manual HTTP\n * composition (standalone mode).\n *\n * ## Deployment Modes\n *\n * - **Embedded** (`connector`): Pass an EmbeddableConnectorLike directly.\n * Zero-latency packet delivery via direct function calls.\n * - **Standalone** (`connectorUrl` + `handlerPort`): Connect to an external\n * connector via HTTP. The SDK starts an HTTP server to receive packets.\n */\n\nimport { createServer, type Server as HttpServer } from 'node:http';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type {\n EmbeddableConnectorLike,\n HandlePacketRequest,\n HandlePacketResponse,\n ConnectorChannelClient,\n} from '@toon-protocol/core';\nimport type { TransportConfig } from '@toon-protocol/connector';\nimport type {\n KnownPeer,\n BootstrapResult,\n BootstrapEventListener,\n} from '@toon-protocol/core';\nimport type { SettlementConfig } from '@toon-protocol/core';\nimport {\n createToonNode,\n BootstrapService,\n createDiscoveryTracker,\n createHttpIlpClient,\n createHttpConnectorAdmin,\n createHttpChannelClient,\n resolveChainConfig,\n buildIlpPrepare,\n buildJobFeedbackEvent,\n buildJobResultEvent,\n parseJobResult,\n deriveChildAddress,\n checkAddressCollision,\n AddressRegistry,\n ILP_ROOT_PREFIX,\n calculateRouteAmount,\n resolveRouteFees,\n buildPrefixClaimEvent,\n validatePrefix,\n ilpCodeToSemantic,\n} from '@toon-protocol/core';\nimport type { ChainProviderConfigEntry } from '@toon-protocol/core';\nimport type { DvmJobStatus, IlpSendResult } from '@toon-protocol/core';\nimport type {\n IlpClient,\n ConnectorAdminClient,\n DiscoveryTracker,\n} from '@toon-protocol/core';\nimport {\n shallowParseToon,\n decodeEventFromToon,\n encodeEventToToon,\n} from '@toon-protocol/core/toon';\n\nimport type { SkillDescriptor } from '@toon-protocol/core';\nimport { fromSecretKey } from './identity.js';\nimport { HandlerRegistry, type Handler } from './handler-registry.js';\nimport { createHandlerContext } from './handler-context.js';\nimport { createVerificationPipeline } from './verification-pipeline.js';\nimport { createPricingValidator } from './pricing-validator.js';\nimport {\n buildSkillDescriptor,\n type BuildSkillDescriptorConfig,\n} from './skill-descriptor.js';\nimport { NodeError } from './errors.js';\n\n/**\n * Maximum base64-encoded payload size (in characters) accepted by the pipeline.\n * Defense-in-depth against DoS via oversized payloads. 1MB of base64 decodes\n * to ~750KB of raw TOON data, which is far beyond any legitimate Nostr event.\n * The pay-per-byte pricing model provides an additional economic disincentive.\n */\nconst MAX_PAYLOAD_BASE64_LENGTH = 1_048_576;\n\n/**\n * Configuration for creating a ServiceNode via createNode().\n *\n * Supports three deployment modes:\n * - **Default** (no connector args): Auto-creates an embedded ConnectorNode via\n * dynamic import. Requires `@toon-protocol/connector` as peer dependency.\n * - **Embedded** (`connector`): Pass a pre-configured EmbeddableConnectorLike.\n * - **Standalone** (`connectorUrl` + `handlerPort`): Connect to an external\n * connector via HTTP. The SDK starts an HTTP server to receive packets.\n *\n * Provide `connector` OR (`connectorUrl` + `handlerPort`), or neither (auto-create).\n */\nexport interface NodeConfig {\n /** 32-byte secp256k1 secret key */\n secretKey: Uint8Array;\n\n /** Chain preset name (default: 'anvil'). See resolveChainConfig(). */\n chain?: string;\n\n // --- Connector (optional — defaults to auto-created embedded connector) ---\n\n /**\n * Embedded connector instance for zero-latency mode.\n * If neither `connector` nor `connectorUrl` is provided, an embedded\n * ConnectorNode is auto-created (requires @toon-protocol/connector peer dep).\n */\n connector?: EmbeddableConnectorLike;\n /**\n * External connector admin URL for standalone mode (e.g., \"http://localhost:8081\").\n * Must be provided with `handlerPort`.\n */\n connectorUrl?: string;\n /**\n * Port for the HTTP server that receives ILP packets from the external connector.\n * Must be provided with `connectorUrl`.\n */\n handlerPort?: number;\n\n /**\n * BTP server port for the auto-created embedded connector (default: 3000).\n * Only used when neither `connector` nor `connectorUrl` is provided.\n */\n btpServerPort?: number;\n\n /**\n * EVM private key for settlement infrastructure.\n * Only used when auto-creating an embedded connector.\n * If not set, the identity's secp256k1 key is used.\n */\n settlementPrivateKey?: string;\n\n // --- Network ---\n\n /**\n * Upstream peer's ILP address prefix for address derivation.\n * When set, the node derives its ILP address as\n * `deriveChildAddress(upstreamPrefix, pubkey)` and ignores `ilpAddress`.\n */\n upstreamPrefix?: string;\n /**\n * Multiple upstream peer prefixes for multi-peered nodes (Story 7.3).\n * When set, derives one ILP address per upstream prefix. Takes priority\n * over `upstreamPrefix` (singular) when both are set.\n */\n upstreamPrefixes?: string[];\n /** ILP address (default: derived from pubkey under ILP_ROOT_PREFIX) */\n ilpAddress?: string;\n /** BTP endpoint URL advertised in kind:10032 announcements */\n btpEndpoint?: string;\n /**\n * ILP-over-HTTP endpoint URL (RFC-0035) advertised in kind:10032, for\n * stateless one-shot writes (`POST /ilp`). With the shared transport server,\n * this is typically the same host/port as `btpEndpoint` (e.g.\n * `http://host:3000/ilp`).\n */\n httpEndpoint?: string;\n /** Whether `httpEndpoint` accepts an HTTP Upgrade to BTP (default: false). */\n supportsUpgrade?: boolean;\n /** Asset code (default: 'USD') */\n assetCode?: string;\n /** Asset scale (default: 6) */\n assetScale?: number;\n /**\n * Base price per byte for pricing validation (default: 10n).\n *\n * Amounts are in USDC micro-units (6 decimals) for production.\n * Default 10n = 10 micro-USDC per byte = $0.00001/byte.\n * A 1KB event costs 10,240 micro-USDC = ~$0.01.\n */\n basePricePerByte?: bigint;\n /**\n * Routing fee per byte charged by this node as an intermediary (default: 0n = free routing).\n * Advertised in kind:10032 peer info events as a non-negative integer string.\n */\n feePerByte?: bigint;\n /** Dev mode skips signature verification (default: false) */\n devMode?: boolean;\n /** TOON encoder function */\n toonEncoder?: (event: NostrEvent) => Uint8Array;\n /** TOON decoder function */\n toonDecoder?: (bytes: Uint8Array) => NostrEvent;\n /** Initial known peers for bootstrap */\n knownPeers?: KnownPeer[];\n /** Relay WebSocket URL */\n relayUrl?: string;\n /** Settlement info for peer registration */\n settlementInfo?: SettlementConfig;\n /** Enable ArDrive peer lookup */\n ardriveEnabled?: boolean;\n /** Per-kind pricing overrides */\n kindPricing?: Record<number, bigint>;\n /**\n * Multi-chain provider configuration for the embedded connector.\n * When provided, passed directly to ConnectorNode as `chainProviders`.\n * Takes priority over the legacy `settlementInfra` auto-configuration.\n * Only used when auto-creating an embedded connector.\n */\n chainProviders?: ChainProviderConfigEntry[];\n\n /**\n * Transport configuration for the embedded connector (ator/SOCKS5 privacy overlay).\n * When provided, passed directly to ConnectorNode as `transport`.\n * Only used when auto-creating an embedded connector.\n */\n transport?: TransportConfig;\n\n /**\n * NIP-59 transport privacy configuration for the embedded connector.\n * When enabled, per-packet claims are wrapped in three-layer encryption.\n * Only used when auto-creating an embedded connector.\n */\n nip59?: { enabled: boolean };\n /**\n * Per-peer NIP-59 public keys for claim encryption.\n * Map of peer ID to compressed secp256k1 public key (hex, 66 chars).\n * Applied to peer configs when registering peers with the connector.\n * Only used when auto-creating an embedded connector.\n */\n peerNip59PublicKeys?: Record<string, string>;\n /** Config-based handler registration (alternative to post-creation .on()) */\n handlers?: Record<number, Handler>;\n /** Config-based default handler (alternative to post-creation .onDefault()) */\n defaultHandler?: Handler;\n /**\n * Optional skill descriptor configuration overrides.\n * When DVM handlers are registered, these values override the auto-derived\n * defaults in the skill descriptor. See buildSkillDescriptor().\n */\n skillConfig?: Omit<\n BuildSkillDescriptorConfig,\n 'basePricePerByte' | 'kindPricing'\n >;\n}\n\n/**\n * Result returned by ServiceNode.start().\n */\nexport interface StartResult {\n /** Number of peers successfully bootstrapped */\n peerCount: number;\n /** Number of payment channels opened */\n channelCount: number;\n /** Detailed results from the bootstrap phase */\n bootstrapResults: BootstrapResult[];\n}\n\n/**\n * Result returned by ServiceNode.publishEvent().\n */\nexport interface PublishEventResult {\n success: boolean;\n eventId: string;\n /** Response data from the ILP FULFILL packet (e.g., Arweave tx ID for kind:5094). */\n data?: string;\n code?: string;\n message?: string;\n}\n\n/**\n * A fully wired TOON node with lifecycle management.\n */\nexport interface ServiceNode {\n /** Nostr x-only public key (32 bytes, 64 hex chars) */\n readonly pubkey: string;\n /** EVM address derived from the same secp256k1 key */\n readonly evmAddress: string;\n /** Pass-through to the underlying connector (null in standalone mode) */\n readonly connector: EmbeddableConnectorLike | null;\n /** Channel client (null if connector lacks channel support or in standalone mode) */\n readonly channelClient: ConnectorChannelClient | null;\n /** Register a handler for a specific event kind (builder pattern) */\n on(kind: number, handler: Handler): ServiceNode;\n /** Register a lifecycle event listener */\n on(event: 'bootstrap', listener: BootstrapEventListener): ServiceNode;\n /** Register a default handler for unrecognized kinds (builder pattern) */\n onDefault(handler: Handler): ServiceNode;\n /** Start the node: wire packet handler, run bootstrap, start discovery */\n start(): Promise<StartResult>;\n /** Stop the node: clean up lifecycle state */\n stop(): Promise<void>;\n /** Initiate peering with a discovered peer (register + settlement) */\n peerWith(pubkey: string): Promise<void>;\n /**\n * Publish a Nostr event to a remote peer via the embedded connector.\n *\n * TOON-encodes the event, computes payment amount, and sends as an\n * ILP PREPARE packet via the runtime client.\n *\n * @param event - The Nostr event to publish\n * @param options - Must include destination ILP address. Optional `amount`\n * overrides the default basePricePerByte * toonData.length calculation\n * (prepaid model: send exact destination amount). Optional `bid` is a\n * client-side safety cap: if the destination amount exceeds `bid`, the\n * SDK throws before sending any ILP packet.\n * @returns Result with success/failure info and event ID\n */\n publishEvent(\n event: NostrEvent,\n options?: { destination: string; amount?: bigint; bid?: bigint }\n ): Promise<PublishEventResult>;\n\n /**\n * Publish a Kind 7000 DVM job feedback event via ILP PREPARE.\n *\n * Builds a signed Kind 7000 feedback event with NIP-90 tags (e, p, status)\n * and delegates to publishEvent() for TOON encoding and ILP delivery.\n * Standard relay write fee applies: basePricePerByte * toonData.length.\n *\n * @param requestEventId - 64-char hex event ID of the original Kind 5xxx request\n * @param customerPubkey - 64-char hex pubkey of the customer who posted the request\n * @param status - Job status value ('processing', 'error', 'success', 'partial')\n * @param content - Optional status details or error message\n * @param options - Must include destination ILP address\n * @returns Result with success/failure info and event ID\n */\n publishFeedback(\n requestEventId: string,\n customerPubkey: string,\n status: DvmJobStatus,\n content?: string,\n options?: { destination: string }\n ): Promise<PublishEventResult>;\n\n /**\n * Publish a Kind 6xxx DVM job result event via ILP PREPARE.\n *\n * Builds a signed Kind 6xxx result event with NIP-90 tags (e, p, amount)\n * and delegates to publishEvent() for TOON encoding and ILP delivery.\n * Standard relay write fee applies: basePricePerByte * toonData.length.\n *\n * @param requestEventId - 64-char hex event ID of the original Kind 5xxx request\n * @param customerPubkey - 64-char hex pubkey of the customer who posted the request\n * @param amount - Compute cost in USDC micro-units as string\n * @param content - Result data (text, URL, etc.)\n * @param options - Must include destination; optional kind (default: 6100)\n * @returns Result with success/failure info and event ID\n */\n publishResult(\n requestEventId: string,\n customerPubkey: string,\n amount: string,\n content: string,\n options?: { destination: string; kind?: number }\n ): Promise<PublishEventResult>;\n\n /**\n * Returns the computed skill descriptor for this node's DVM capabilities.\n * Returns `undefined` if no DVM handlers (kinds 5000-5999) are registered.\n * The descriptor is computed from the handler registry and node config.\n */\n getSkillDescriptor(): SkillDescriptor | undefined;\n\n /**\n * Send an ILP payment to a provider for compute settlement.\n *\n * Extracts the compute cost from the result event's `amount` tag via\n * parseJobResult(), optionally validates against the original bid amount\n * (E5-R005 bid validation), and sends a pure ILP value transfer\n * (empty data field) to the provider's ILP address.\n *\n * This is a payment-only operation -- no TOON encoding, no relay write.\n * The payment routes through the ILP mesh using the same infrastructure\n * as relay write fees.\n *\n * @deprecated Use publishEvent() with amount option instead. Prepaid model:\n * send job request + payment in one packet via\n * `publishEvent(event, { destination, amount: computePrice })`.\n *\n * @param resultEvent - Kind 6xxx result event with amount tag\n * @param providerIlpAddress - Provider's ILP address from kind:10035\n * @param options - Optional originalBid for bid validation (E5-R005)\n * @returns ILP send result with accepted/rejected status\n */\n settleCompute(\n resultEvent: NostrEvent,\n providerIlpAddress: string,\n options?: { originalBid?: string }\n ): Promise<IlpSendResult>;\n\n /**\n * Add a new upstream peer, deriving and registering its ILP address.\n *\n * Updates the AddressRegistry, registers a self-route in the embedded\n * connector, and triggers kind:10032 republication via BootstrapService.\n *\n * @param upstreamPrefix - The upstream peer's ILP address prefix\n */\n addUpstreamPeer(upstreamPrefix: string): void;\n\n /**\n * Remove an upstream peer, unregistering its derived ILP address.\n *\n * Updates the AddressRegistry, removes the self-route from the embedded\n * connector, and triggers kind:10032 republication via BootstrapService.\n *\n * @param upstreamPrefix - The upstream prefix to remove\n */\n removeUpstreamPeer(upstreamPrefix: string): void;\n\n /**\n * Claim a prefix from an upstream peer by sending a prefix claim event\n * with payment via ILP.\n *\n * Builds a Kind 10034 prefix claim event, reads the upstream's prefix\n * pricing from its kind:10032 advertisement, and calls publishEvent()\n * with the appropriate amount.\n *\n * @param prefix - The prefix string to claim (e.g., 'useast')\n * @param upstreamDestination - ILP address of the upstream node\n * @param options - Optional prefixPrice override (defaults to upstream's prefixPricing.basePrice from discovery)\n * @returns Result with success/failure info and event ID\n */\n claimPrefix(\n prefix: string,\n upstreamDestination: string,\n options?: { prefixPrice?: bigint }\n ): Promise<PublishEventResult>;\n}\n\n/**\n * Creates a fully wired ServiceNode from configuration.\n *\n * The returned node has the full ILP packet processing pipeline wired in the\n * correct order:\n * 1. Shallow TOON parse (extract routing metadata)\n * 2. Schnorr signature verification (reject with F06 if invalid)\n * 3. Pricing validation (reject with F04 if underpaid)\n * 4. Handler dispatch (route to kind-specific or default handler)\n *\n * Supports two deployment modes:\n * - **Embedded** (`connector`): Uses createToonNode for zero-latency\n * packet delivery via direct function calls.\n * - **Standalone** (`connectorUrl` + `handlerPort`): Starts an HTTP server\n * to receive packets and uses HTTP clients for the connector API.\n *\n * Handlers can be registered via config or post-creation .on()/.onDefault().\n */\nexport function createNode(config: NodeConfig): ServiceNode {\n // 0. Validate connector mode\n const hasConnector = config.connector !== undefined;\n const hasConnectorUrl = config.connectorUrl !== undefined;\n\n if (hasConnector && hasConnectorUrl) {\n throw new NodeError(\n 'NodeConfig: provide either connector or connectorUrl, not both'\n );\n }\n if (hasConnectorUrl && config.handlerPort === undefined) {\n throw new NodeError(\n 'NodeConfig: handlerPort is required when using connectorUrl (standalone mode)'\n );\n }\n const autoCreateConnector = !hasConnector && !hasConnectorUrl;\n const embeddedMode = hasConnector || autoCreateConnector;\n\n // 0b. Resolve chain config and auto-populate settlementInfo if not set\n const chainConfig = resolveChainConfig(config.chain);\n let effectiveSettlementInfo = config.settlementInfo;\n if (!effectiveSettlementInfo) {\n const chainKey = `evm:base:${chainConfig.chainId}`;\n const supportedChains = [chainKey];\n const preferredTokens: Record<string, string> = {\n [chainKey]: chainConfig.usdcAddress,\n };\n const tokenNetworks: Record<string, string> | undefined =\n chainConfig.tokenNetworkAddress\n ? { [chainKey]: chainConfig.tokenNetworkAddress }\n : undefined;\n\n effectiveSettlementInfo = {\n supportedChains,\n preferredTokens,\n ...(tokenNetworks && { tokenNetworks }),\n };\n }\n\n // 1. Derive identity from secretKey\n let identity;\n try {\n identity = fromSecretKey(config.secretKey);\n } catch (error: unknown) {\n throw new NodeError(\n `Invalid secretKey: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n const { pubkey, evmAddress } = identity;\n\n // 2. Create handler registry\n const registry = new HandlerRegistry();\n\n // 3. Register config-based handlers (with kind validation matching node.on())\n if (config.handlers) {\n for (const [kind, handler] of Object.entries(config.handlers)) {\n const kindNum = Number(kind);\n if (!Number.isInteger(kindNum) || kindNum < 0) {\n throw new NodeError(\n `Invalid event kind in handlers config: expected a non-negative integer, got '${kind}'`\n );\n }\n registry.on(kindNum, handler);\n }\n }\n\n // 4. Register config-based default handler\n if (config.defaultHandler) {\n registry.onDefault(config.defaultHandler);\n }\n\n // 5. Create verification pipeline\n const verifier = createVerificationPipeline({\n devMode: config.devMode ?? false,\n });\n\n // 6. Create pricing validator\n const pricer = createPricingValidator({\n basePricePerByte: config.basePricePerByte ?? 10n,\n ownPubkey: pubkey,\n kindPricing: config.kindPricing,\n });\n\n // 7. Set up TOON codec with defaults\n const encoder = config.toonEncoder ?? encodeEventToToon;\n const decoder = config.toonDecoder ?? decodeEventFromToon;\n\n // Context decoder: converts base64 TOON string to NostrEvent\n const contextDecoder = (toon: string): NostrEvent => {\n const bytes = Buffer.from(toon, 'base64');\n return decoder(bytes);\n };\n\n // Mutable ref so the packet handler closure can access the tracker after it's created.\n const trackerRef: { current?: { processEvent(event: NostrEvent): void } } =\n {};\n\n // 8. Build the pipelined packet handler (Option A: directly on HandlePacketRequest)\n const pipelinedHandler = async (\n request: HandlePacketRequest\n ): Promise<HandlePacketResponse> => {\n // Step 0: Reject oversized payloads before allocating memory (DoS mitigation)\n if (request.data.length > MAX_PAYLOAD_BASE64_LENGTH) {\n return {\n accept: false,\n code: 'F06',\n message: `Payload too large: ${request.data.length} bytes exceeds maximum ${MAX_PAYLOAD_BASE64_LENGTH}`,\n };\n }\n\n // Step 1: Shallow TOON parse\n const toonBytes = Buffer.from(request.data, 'base64');\n let meta;\n try {\n meta = shallowParseToon(toonBytes);\n } catch {\n // Corrupted TOON data cannot be parsed -- reject as invalid payload\n return {\n accept: false,\n code: 'F06',\n message: 'Invalid TOON payload: failed to parse routing metadata',\n };\n }\n\n // Dev mode: log packet details\n // Sanitize user-controlled fields (amount, destination) to prevent log injection\n // via newlines or control characters. meta.kind (integer) and meta.pubkey (validated\n // hex) are safe; request.data preview is base64 (safe character set).\n if (config.devMode ?? false) {\n const toonPreview =\n request.data.length > 80\n ? request.data.substring(0, 80) + '...'\n : request.data;\n // eslint-disable-next-line no-control-regex\n const sanitize = (s: string) => s.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.log(\n '[toon:dev]',\n `kind=${meta.kind}`,\n `pubkey=${meta.pubkey.substring(0, 16)}...`,\n `amount=${sanitize(request.amount)}`,\n `dest=${sanitize(request.destination)}`,\n `toon=${toonPreview}`\n );\n }\n\n // Step 2: Verify signature\n const verifyResult = await verifier.verify(meta, request.data);\n if (!verifyResult.verified) {\n // VerificationResult.rejection is always set when verified=false per\n // createVerificationPipeline contract. Guard defensively anyway.\n if (verifyResult.rejection) {\n return verifyResult.rejection;\n }\n return { accept: false, code: 'F06', message: 'Verification failed' };\n }\n\n // Step 3: Validate pricing (skip in dev mode)\n let amount: bigint;\n try {\n amount = BigInt(request.amount);\n } catch {\n if (config.devMode ?? false) {\n amount = 0n;\n } else {\n return {\n accept: false,\n code: 'T00',\n message: 'Invalid payment amount',\n };\n }\n }\n if (!(config.devMode ?? false)) {\n const priceResult = pricer.validate(meta, amount);\n if (!priceResult.accepted) {\n // PricingValidationResult.rejection is always set when accepted=false per\n // createPricingValidator contract. Guard defensively anyway.\n if (priceResult.rejection) {\n return priceResult.rejection;\n }\n return {\n accept: false,\n code: 'F04',\n message: 'Pricing validation failed',\n };\n }\n }\n\n // Step 4: Build HandlerContext with real metadata\n const ctx = createHandlerContext({\n toon: request.data,\n meta,\n amount,\n destination: request.destination,\n toonDecoder: contextDecoder,\n });\n\n // Step 5: Dispatch to handler (T00 error boundary)\n try {\n const result = await registry.dispatch(ctx);\n\n // Feed accepted kind:10032 events to discovery tracker for peer discovery.\n // Uses late-binding reference since the tracker is created after this handler.\n if (result.accept && meta.kind === 10032 && trackerRef.current) {\n const decoded = ctx.decode();\n if (decoded) {\n trackerRef.current.processEvent(decoded);\n }\n }\n\n // Connector v3.3.2 breaking change: the embedded ConnectorNode's\n // PaymentHandlerAdapter consumes `response.data` (base64) and ignores\n // `response.metadata`. The SDK's `ctx.accept(metadata)` historically\n // returned `{ accept: true, metadata }`. To preserve handler ergonomics\n // (handlers still pass an object), serialize `metadata` → base64 JSON\n // into `data` here when the handler did not already populate `data`.\n // The streamSwap FULFILL decoder expects this exact base64-JSON shape.\n if (result.accept && result.metadata && !result.data) {\n try {\n const metadataJson = JSON.stringify(result.metadata);\n (result as { data?: string }).data = Buffer.from(\n metadataJson,\n 'utf8'\n ).toString('base64');\n } catch (err) {\n console.error(\n 'Failed to serialize handler metadata to FULFILL data:',\n err instanceof Error ? err.message : err\n );\n }\n }\n\n // Connector v3.3.2 breaking change: the adapter's reject path reads\n // `response.rejectReason.{code,message}` and feeds `rejectReason.code`\n // through `mapRejectCode()` — a SEMANTIC-reason → ILP-code lookup\n // (e.g. `'internal_error'` → `'T00'`). The SDK's `ctx.reject(ilpCode,\n // message)` returns the ILP code directly, so without translation\n // every reject collapses to F99 (the `mapRejectCode()` fallback).\n //\n // Reverse-map the handler's ILP code back to the connector's expected\n // semantic reason. Canonical mapping lives in\n // `@toon-protocol/core` (utils/reject-code.ts).\n if (\n !result.accept &&\n !(result as { rejectReason?: unknown }).rejectReason &&\n (result as { code?: string }).code\n ) {\n const ilpCode = (result as { code: string }).code;\n (\n result as {\n rejectReason?: { code: string; message: string };\n }\n ).rejectReason = {\n code: ilpCodeToSemantic(ilpCode),\n message:\n (result as { message?: string }).message ?? 'Payment rejected',\n };\n }\n\n return result;\n } catch (err: unknown) {\n // Log only the error message, not the full error object (which may contain payload data)\n const errMsg = err instanceof Error ? err.message : 'Unknown error';\n console.error('Handler dispatch failed:', errMsg);\n return { accept: false, code: 'T00', message: 'Internal error' };\n }\n };\n\n // ILP info shared between both modes\n // Address resolution priority (Story 7.3 extends with upstreamPrefixes):\n // 1. upstreamPrefixes set -> derive one address per prefix, primary = first\n // 2. upstreamPrefix set -> derive single address (ignore ilpAddress)\n // 3. ilpAddress set -> use directly (includes genesis node with 'g.toon')\n // 4. neither set -> derive from ILP_ROOT_PREFIX + pubkey (default)\n let resolvedIlpAddress: string;\n let resolvedIlpAddresses: string[];\n\n if (\n config.upstreamPrefixes !== undefined &&\n config.upstreamPrefixes.length > 0\n ) {\n // upstreamPrefixes takes priority over upstreamPrefix (singular)\n if (config.upstreamPrefix !== undefined) {\n console.warn(\n '[toon:warn] Both upstreamPrefixes and upstreamPrefix set; upstreamPrefixes takes priority'\n );\n }\n resolvedIlpAddresses = config.upstreamPrefixes.map((p) =>\n deriveChildAddress(p, pubkey)\n );\n // Task 4.4: Check for truncation collisions across upstream prefixes\n for (let i = 0; i < resolvedIlpAddresses.length; i++) {\n const others = resolvedIlpAddresses.filter((_, j) => j !== i);\n checkAddressCollision(resolvedIlpAddresses[i] as string, others);\n }\n resolvedIlpAddress = resolvedIlpAddresses[0] as string;\n } else if (config.upstreamPrefix !== undefined) {\n resolvedIlpAddress = deriveChildAddress(config.upstreamPrefix, pubkey);\n resolvedIlpAddresses = [resolvedIlpAddress];\n } else if (config.ilpAddress !== undefined) {\n resolvedIlpAddress = config.ilpAddress;\n resolvedIlpAddresses = [resolvedIlpAddress];\n } else {\n resolvedIlpAddress = deriveChildAddress(ILP_ROOT_PREFIX, pubkey);\n resolvedIlpAddresses = [resolvedIlpAddress];\n }\n\n // Task 6.2: Initialize AddressRegistry with initial upstream prefixes\n const addressRegistry = new AddressRegistry();\n if (\n config.upstreamPrefixes !== undefined &&\n config.upstreamPrefixes.length > 0\n ) {\n for (let i = 0; i < config.upstreamPrefixes.length; i++) {\n addressRegistry.addAddress(\n config.upstreamPrefixes[i] as string,\n resolvedIlpAddresses[i] as string\n );\n }\n } else if (config.upstreamPrefix !== undefined) {\n addressRegistry.addAddress(config.upstreamPrefix, resolvedIlpAddress);\n } else if (config.ilpAddress !== undefined) {\n addressRegistry.addAddress('explicit', resolvedIlpAddress);\n } else {\n addressRegistry.addAddress(ILP_ROOT_PREFIX, resolvedIlpAddress);\n }\n\n const ilpInfo = {\n ilpAddress: resolvedIlpAddress,\n ilpAddresses: resolvedIlpAddresses,\n btpEndpoint: config.btpEndpoint ?? '',\n ...(config.httpEndpoint !== undefined && { httpEndpoint: config.httpEndpoint }),\n ...(config.supportsUpgrade !== undefined && { supportsUpgrade: config.supportsUpgrade }),\n assetCode: config.assetCode ?? 'USD',\n assetScale: config.assetScale ?? 6,\n feePerByte: String(config.feePerByte ?? 0n),\n };\n\n // 9. Branch: embedded mode vs standalone mode\n let ilpClient: IlpClient;\n let adminClient: ConnectorAdminClient;\n let channelClient: ConnectorChannelClient | null = null;\n let bootstrapServiceInstance: BootstrapService;\n let discoveryTrackerInstance: DiscoveryTracker;\n let httpServer: HttpServer | null = null;\n\n // Lifecycle delegates (start/stop differ per mode)\n let doStart: () => Promise<{\n bootstrapResults: BootstrapResult[];\n peerCount: number;\n channelCount: number;\n }>;\n let doStop: () => Promise<void>;\n\n // Track auto-created connector for cleanup\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let autoCreatedConnector: any = null;\n\n if (embeddedMode && hasConnector) {\n // --- EMBEDDED MODE (user-provided connector): delegate to createToonNode ---\n const toonNode = createToonNode({\n connector: config.connector as NonNullable<typeof config.connector>,\n handlePacket: pipelinedHandler,\n secretKey: config.secretKey,\n ilpInfo,\n toonEncoder: encoder,\n toonDecoder: decoder,\n relayUrl: config.relayUrl,\n knownPeers: config.knownPeers,\n settlementInfo: effectiveSettlementInfo,\n basePricePerByte: config.basePricePerByte,\n ardriveEnabled: config.ardriveEnabled,\n });\n\n ilpClient = toonNode.ilpClient;\n channelClient = toonNode.channelClient;\n bootstrapServiceInstance = toonNode.bootstrapService;\n discoveryTrackerInstance = toonNode.discoveryTracker;\n adminClient = {\n addPeer: () => Promise.resolve(),\n removePeer: () => Promise.resolve(),\n };\n\n trackerRef.current = discoveryTrackerInstance;\n\n doStart = async () => toonNode.start();\n doStop = async () => toonNode.stop();\n } else if (autoCreateConnector) {\n // --- AUTO-CREATE EMBEDDED MODE: deferred ConnectorNode creation in doStart() ---\n\n // Create placeholder bootstrap/discovery instances (wired during start)\n bootstrapServiceInstance = new BootstrapService(\n {\n knownPeers: config.knownPeers ?? [],\n ardriveEnabled: config.ardriveEnabled ?? false,\n defaultRelayUrl: config.relayUrl ?? '',\n settlementInfo: effectiveSettlementInfo,\n ownIlpAddress: ilpInfo.ilpAddress,\n toonEncoder: encoder,\n toonDecoder: decoder,\n basePricePerByte: config.basePricePerByte ?? 10n,\n },\n config.secretKey,\n ilpInfo\n );\n\n discoveryTrackerInstance = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo: effectiveSettlementInfo,\n });\n\n // Placeholder clients (replaced during start)\n ilpClient = {\n sendIlpPacket: () =>\n Promise.reject(new NodeError('Node not started. Call start() first.')),\n };\n adminClient = {\n addPeer: () => Promise.resolve(),\n removePeer: () => Promise.resolve(),\n };\n\n trackerRef.current = discoveryTrackerInstance;\n\n doStart = async () => {\n // Dynamic import to keep @toon-protocol/connector as optional peer dep\n // eslint-disable-next-line @typescript-eslint/consistent-type-imports\n let ConnectorNodeClass: typeof import('@toon-protocol/connector').ConnectorNode;\n // eslint-disable-next-line @typescript-eslint/consistent-type-imports\n let createConnectorLogger: typeof import('@toon-protocol/connector').createLogger;\n try {\n const mod = await import('@toon-protocol/connector');\n ConnectorNodeClass = mod.ConnectorNode;\n createConnectorLogger = mod.createLogger;\n } catch {\n throw new NodeError(\n 'Auto-create connector requires @toon-protocol/connector as a peer dependency. ' +\n 'Install it with: pnpm add @toon-protocol/connector'\n );\n }\n\n const nodeId = `toon-${pubkey.slice(0, 16)}`;\n const btpServerPort = config.btpServerPort ?? 3000;\n const connectorLogger = createConnectorLogger(nodeId, 'warn');\n\n // Derive settlement private key from identity if not explicitly set\n let settlementPrivateKey = config.settlementPrivateKey;\n if (!settlementPrivateKey) {\n const keyBuffer = Buffer.from(config.secretKey);\n settlementPrivateKey = `0x${keyBuffer.toString('hex')}`;\n keyBuffer.fill(0);\n }\n\n const hasSettlementAddresses =\n chainConfig.registryAddress && chainConfig.tokenNetworkAddress;\n\n // Build connector config: chainProviders for settlement (v2.3.0+)\n const hasChainProviders =\n config.chainProviders !== undefined && config.chainProviders.length > 0;\n\n // When chainProviders not explicitly provided, build from legacy config\n const effectiveChainProviders = hasChainProviders\n ? config.chainProviders\n : hasSettlementAddresses\n ? [\n {\n chainType: 'evm' as const,\n chainId: `evm:${chainConfig.chainId ?? '31337'}`,\n rpcUrl: chainConfig.rpcUrl,\n registryAddress: chainConfig.registryAddress,\n tokenAddress: chainConfig.usdcAddress,\n privateKey: settlementPrivateKey,\n },\n ]\n : undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connectorConfig: any = {\n nodeId,\n btpServerPort,\n environment: 'development',\n deploymentMode: 'embedded',\n peers: [],\n routes: [],\n localDelivery: { enabled: false },\n };\n if (effectiveChainProviders) {\n connectorConfig.chainProviders = effectiveChainProviders;\n }\n if (config.transport) {\n connectorConfig.transport = config.transport;\n }\n if (config.nip59) {\n connectorConfig.nip59 = config.nip59;\n }\n\n autoCreatedConnector = new ConnectorNodeClass(\n connectorConfig,\n connectorLogger\n );\n\n await autoCreatedConnector.start();\n\n // Now wire the real connector into createToonNode\n const connector =\n autoCreatedConnector as unknown as EmbeddableConnectorLike;\n const toonNode = createToonNode({\n connector,\n handlePacket: pipelinedHandler,\n secretKey: config.secretKey,\n ilpInfo,\n toonEncoder: encoder,\n toonDecoder: decoder,\n relayUrl: config.relayUrl,\n knownPeers: config.knownPeers,\n settlementInfo: effectiveSettlementInfo,\n basePricePerByte: config.basePricePerByte,\n ardriveEnabled: config.ardriveEnabled,\n });\n\n // Replace placeholder clients with real ones\n ilpClient = toonNode.ilpClient;\n channelClient = toonNode.channelClient;\n\n // Wire bootstrap and discovery with the real admin client\n bootstrapServiceInstance.setIlpClient(toonNode.ilpClient);\n bootstrapServiceInstance.setConnectorAdmin({\n addPeer: () => Promise.resolve(),\n removePeer: () => Promise.resolve(),\n });\n if (toonNode.channelClient) {\n bootstrapServiceInstance.setChannelClient(toonNode.channelClient);\n }\n\n discoveryTrackerInstance.setConnectorAdmin({\n addPeer: () => Promise.resolve(),\n removePeer: () => Promise.resolve(),\n });\n if (toonNode.channelClient) {\n discoveryTrackerInstance.setChannelClient(toonNode.channelClient);\n }\n\n trackerRef.current = discoveryTrackerInstance;\n\n // Add self-routes for all addresses (Task 5.3)\n for (const addr of ilpInfo.ilpAddresses) {\n autoCreatedConnector.addRoute({\n prefix: addr,\n nextHop: nodeId,\n priority: 100,\n });\n }\n\n // Start the toonNode (wires packet handler, runs bootstrap)\n const result = await toonNode.start();\n return result;\n };\n\n doStop = async () => {\n if (autoCreatedConnector) {\n await autoCreatedConnector.stop();\n autoCreatedConnector = null;\n }\n };\n } else {\n // --- STANDALONE MODE: manual composition with HTTP clients ---\n const connectorUrl = config.connectorUrl as string;\n const handlerPort = config.handlerPort as number;\n\n ilpClient = createHttpIlpClient(connectorUrl);\n adminClient = createHttpConnectorAdmin(connectorUrl, '');\n\n // Channel client via HTTP if settlement is configured\n if (effectiveSettlementInfo) {\n channelClient = createHttpChannelClient(connectorUrl);\n }\n\n // Create BootstrapService\n bootstrapServiceInstance = new BootstrapService(\n {\n knownPeers: config.knownPeers ?? [],\n ardriveEnabled: config.ardriveEnabled ?? false,\n defaultRelayUrl: config.relayUrl ?? '',\n settlementInfo: effectiveSettlementInfo,\n ownIlpAddress: ilpInfo.ilpAddress,\n toonEncoder: encoder,\n toonDecoder: decoder,\n basePricePerByte: config.basePricePerByte ?? 10n,\n },\n config.secretKey,\n ilpInfo\n );\n\n bootstrapServiceInstance.setIlpClient(ilpClient);\n bootstrapServiceInstance.setConnectorAdmin(adminClient);\n if (channelClient) {\n bootstrapServiceInstance.setChannelClient(channelClient);\n }\n\n // Create DiscoveryTracker\n discoveryTrackerInstance = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo: effectiveSettlementInfo,\n });\n discoveryTrackerInstance.setConnectorAdmin(adminClient);\n if (channelClient) {\n discoveryTrackerInstance.setChannelClient(channelClient);\n }\n\n trackerRef.current = discoveryTrackerInstance;\n\n doStart = async () => {\n // Start HTTP server for receiving ILP packets\n httpServer = createServer(async (req, res) => {\n if (req.method === 'GET' && req.url === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'healthy', pubkey }));\n return;\n }\n\n if (req.method === 'POST' && req.url === '/handle-packet') {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on('end', async () => {\n try {\n const request = JSON.parse(body) as HandlePacketRequest;\n if (\n request.amount === undefined ||\n request.amount === null ||\n request.destination === undefined ||\n request.destination === null ||\n request.data === undefined ||\n request.data === null\n ) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n accept: false,\n code: 'F00',\n message: 'Missing required fields',\n })\n );\n return;\n }\n const result = await pipelinedHandler(request);\n res.writeHead(result.accept ? 200 : 400, {\n 'Content-Type': 'application/json',\n });\n res.end(JSON.stringify(result));\n } catch (err: unknown) {\n console.error('[SDK] handle-packet error:', err);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n accept: false,\n code: 'T00',\n message: 'Internal server error',\n })\n );\n }\n });\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n await new Promise<void>((resolve) => {\n httpServer?.listen(handlerPort, () => resolve());\n });\n\n // Run bootstrap\n const results = await bootstrapServiceInstance.bootstrap();\n const bootstrapPeerPubkeys = results.map((r) => r.knownPeer.pubkey);\n discoveryTrackerInstance.addExcludedPubkeys(bootstrapPeerPubkeys);\n\n return {\n bootstrapResults: results,\n peerCount: results.length,\n channelCount: results.filter((r) => r.channelId).length,\n };\n };\n\n doStop = async () => {\n const server = httpServer;\n if (server) {\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n httpServer = null;\n }\n };\n }\n\n // 10. Track SDK-level lifecycle state\n let started = false;\n let lastBootstrapResults: BootstrapResult[] = [];\n\n // 11. Build and return ServiceNode\n const node: ServiceNode = {\n get pubkey() {\n return pubkey;\n },\n get evmAddress() {\n return evmAddress;\n },\n get connector() {\n return (\n config.connector ??\n (autoCreatedConnector as unknown as EmbeddableConnectorLike) ??\n null\n );\n },\n get channelClient() {\n return channelClient;\n },\n\n getSkillDescriptor(): SkillDescriptor | undefined {\n return buildSkillDescriptor(registry, {\n basePricePerByte: config.basePricePerByte ?? 10n,\n kindPricing: config.kindPricing,\n ...config.skillConfig,\n });\n },\n\n on(\n kindOrEvent: number | string,\n handlerOrListener: Handler | BootstrapEventListener\n ): ServiceNode {\n if (typeof kindOrEvent === 'number') {\n // Handler registration (existing behavior)\n if (!Number.isInteger(kindOrEvent) || kindOrEvent < 0) {\n throw new NodeError(\n `Invalid event kind: expected a non-negative integer, got ${String(kindOrEvent)}`\n );\n }\n registry.on(kindOrEvent, handlerOrListener as Handler);\n } else if (kindOrEvent === 'bootstrap') {\n // Lifecycle event listener -- forward to bootstrapService AND discoveryTracker\n const listener = handlerOrListener as BootstrapEventListener;\n bootstrapServiceInstance.on(listener);\n discoveryTrackerInstance.on(listener);\n } else {\n // Sanitize event name to prevent log injection via control characters\n // eslint-disable-next-line no-control-regex\n const sanitized = String(kindOrEvent).replace(/[\\x00-\\x1f\\x7f]/g, '');\n throw new NodeError(\n `Unknown lifecycle event: '${sanitized}'. Supported: 'bootstrap'`\n );\n }\n return node;\n },\n\n onDefault(handler: Handler): ServiceNode {\n registry.onDefault(handler);\n return node;\n },\n\n async start(): Promise<StartResult> {\n if (started) {\n throw new NodeError('Node already started');\n }\n\n try {\n const result = await doStart();\n started = true;\n lastBootstrapResults = result.bootstrapResults;\n return {\n peerCount: result.peerCount,\n channelCount: result.channelCount,\n bootstrapResults: result.bootstrapResults,\n };\n } catch (error: unknown) {\n if (error instanceof NodeError) {\n throw error;\n }\n throw new NodeError(\n `Failed to start node: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async stop(): Promise<void> {\n if (!started) {\n return; // No-op if not started\n }\n\n await doStop();\n started = false;\n },\n\n async peerWith(targetPubkey: string): Promise<void> {\n if (!started) {\n throw new NodeError(\n 'Cannot peer: node not started. Call start() first.'\n );\n }\n // Defense-in-depth: validate pubkey format before delegating to core\n if (\n typeof targetPubkey !== 'string' ||\n targetPubkey.length !== 64 ||\n !/^[0-9a-f]{64}$/.test(targetPubkey)\n ) {\n throw new NodeError(\n 'Invalid pubkey: expected a 64-character lowercase hex string'\n );\n }\n return discoveryTrackerInstance.peerWith(targetPubkey);\n },\n\n async publishEvent(\n event: NostrEvent,\n options?: { destination: string; amount?: bigint; bid?: bigint }\n ): Promise<PublishEventResult> {\n // Guard: node must be started\n if (!started) {\n throw new NodeError(\n 'Cannot publish: node not started. Call start() first.'\n );\n }\n\n // Guard: destination is required\n if (!options?.destination) {\n throw new NodeError(\n \"Cannot publish: destination is required. Pass { destination: 'g.peer.address' }.\"\n );\n }\n\n try {\n // TOON-encode the event\n const toonData = encoder(event);\n\n // Resolve intermediary routing fees from discovered peers\n const { hopFees, warnings } = resolveRouteFees({\n destination: options.destination,\n ownIlpAddress: ilpInfo.ilpAddress,\n discoveredPeers: discoveryTrackerInstance.getAllDiscoveredPeers(),\n });\n\n // Log warnings about unknown intermediaries\n for (const warning of warnings) {\n console.warn(`[publishEvent] ${warning}`);\n }\n\n // Compute destination amount: use override or default basePricePerByte * bytes\n const destinationAmount =\n options.amount ??\n (config.basePricePerByte ?? 10n) * BigInt(toonData.length);\n\n // Bid safety cap: if bid is provided, reject if destination amount exceeds bid\n if (options.bid !== undefined && destinationAmount > options.bid) {\n throw new NodeError(\n `Cannot publish: destination amount ${destinationAmount} exceeds bid safety cap ${options.bid}`\n );\n }\n\n // Compute total amount: destination amount + intermediary route fees\n let amount: bigint;\n if (options.amount !== undefined) {\n // When amount override is provided, add only hop fees (no basePricePerByte)\n const hopFeesTotal = calculateRouteAmount({\n basePricePerByte: 0n,\n packetByteLength: toonData.length,\n hopFees,\n });\n amount = options.amount + hopFeesTotal;\n } else {\n // Default: basePricePerByte * bytes + hop fees\n amount = calculateRouteAmount({\n basePricePerByte: config.basePricePerByte ?? 10n,\n packetByteLength: toonData.length,\n hopFees,\n });\n }\n\n // Build ILP PREPARE packet using shared construction (packet equivalence\n // with x402 rail -- the destination relay cannot distinguish between\n // packets sent via publishEvent() and the x402 /publish endpoint).\n const packet = buildIlpPrepare({\n destination: options.destination,\n amount,\n data: toonData,\n });\n\n // Send via ILP client\n const result = await ilpClient.sendIlpPacket(packet);\n\n // Map IlpSendResult to PublishEventResult\n if (result.accepted) {\n return {\n success: true,\n eventId: event.id,\n ...(result.data !== undefined ? { data: result.data } : {}),\n };\n }\n\n return {\n success: false,\n eventId: event.id,\n code: result.code ?? 'T00',\n message: result.message ?? 'Unknown error',\n };\n } catch (error: unknown) {\n // Propagate NodeError directly\n if (error instanceof NodeError) {\n throw error;\n }\n throw new NodeError(\n `Failed to publish event: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async publishFeedback(\n requestEventId: string,\n customerPubkey: string,\n status: DvmJobStatus,\n content?: string,\n options?: { destination: string }\n ): Promise<PublishEventResult> {\n // Build Kind 7000 feedback event using provider's secretKey\n const feedbackEvent = buildJobFeedbackEvent(\n { requestEventId, customerPubkey, status, content },\n config.secretKey\n );\n\n // Delegate to publishEvent() for TOON encoding, pricing, and ILP delivery\n return node.publishEvent(feedbackEvent, options);\n },\n\n async publishResult(\n requestEventId: string,\n customerPubkey: string,\n amount: string,\n content: string,\n options?: { destination: string; kind?: number }\n ): Promise<PublishEventResult> {\n // Default result kind is 6100 (text generation result = 5100 + 1000)\n const resultKind = options?.kind ?? 6100;\n\n // Build Kind 6xxx result event using provider's secretKey\n const resultEvent = buildJobResultEvent(\n { kind: resultKind, requestEventId, customerPubkey, amount, content },\n config.secretKey\n );\n\n // Delegate to publishEvent() for TOON encoding, pricing, and ILP delivery\n return node.publishEvent(resultEvent, options);\n },\n\n async settleCompute(\n resultEvent: NostrEvent,\n providerIlpAddress: string,\n options?: { originalBid?: string }\n ): Promise<IlpSendResult> {\n // Deprecation warning\n console.warn(\n '[settleCompute] DEPRECATED: Use publishEvent() with { amount } option instead. The prepaid model sends job request + payment in one packet.'\n );\n\n // Guard: node must be started\n if (!started) {\n throw new NodeError(\n 'Cannot settle compute: node not started. Call start() first.'\n );\n }\n\n // Guard: providerIlpAddress must be a non-empty, non-whitespace string\n if (\n !providerIlpAddress ||\n typeof providerIlpAddress !== 'string' ||\n providerIlpAddress.trim() === ''\n ) {\n throw new NodeError(\n \"Cannot settle compute: providerIlpAddress is required. Resolve it from the provider's kind:10035 service discovery event.\"\n );\n }\n\n // Extract amount from result event via parseJobResult()\n const parsed = parseJobResult(resultEvent);\n if (!parsed) {\n throw new NodeError(\n 'Cannot settle compute: failed to parse result event. Ensure the event is a valid Kind 6xxx with an amount tag.'\n );\n }\n\n const computeAmount = parsed.amount;\n\n // Validate computeAmount is a valid non-negative numeric string before any\n // further processing. Without this, a non-numeric amount would propagate to\n // ilpClient.sendIlpPacket() and cause a BootstrapError instead of a clear\n // NodeError. Negative amounts would cause undefined behavior in the ILP layer.\n let amountBigInt: bigint;\n try {\n amountBigInt = BigInt(computeAmount);\n } catch {\n throw new NodeError(\n `Cannot settle compute: result event amount ('${computeAmount}') is not a valid numeric string.`\n );\n }\n if (amountBigInt < 0n) {\n throw new NodeError(\n `Cannot settle compute: result event amount ('${computeAmount}') must be non-negative.`\n );\n }\n\n // E5-R005 bid validation: if originalBid is provided, validate amount <= bid\n if (options?.originalBid !== undefined) {\n let bidBigInt: bigint;\n try {\n bidBigInt = BigInt(options.originalBid);\n } catch {\n throw new NodeError(\n `Cannot settle compute: originalBid ('${options.originalBid}') is not a valid numeric string.`\n );\n }\n if (amountBigInt > bidBigInt) {\n throw new NodeError(\n `Cannot settle compute: result amount (${computeAmount}) exceeds original bid (${options.originalBid}). Potential provider overcharge.`\n );\n }\n }\n\n // Send pure ILP value transfer (empty data = no event payload)\n // This is a payment-only operation -- no TOON encoding, no relay write.\n return ilpClient.sendIlpPacket({\n destination: providerIlpAddress,\n amount: computeAmount,\n data: '',\n });\n },\n\n // Task 6.3: Address lifecycle management for multi-peered nodes\n addUpstreamPeer(upstreamPrefix: string): void {\n const derivedAddress = deriveChildAddress(upstreamPrefix, pubkey);\n\n // Check for collision against existing addresses\n checkAddressCollision(derivedAddress, addressRegistry.getAddresses());\n\n // Update registry\n addressRegistry.addAddress(upstreamPrefix, derivedAddress);\n\n // Update ilpInfo for kind:10032 republication\n ilpInfo.ilpAddresses = addressRegistry.getAddresses();\n ilpInfo.ilpAddress =\n addressRegistry.getPrimaryAddress() ?? ilpInfo.ilpAddress;\n\n // Register self-route in embedded connector (if available)\n if (autoCreatedConnector) {\n const nodeId = `toon-${pubkey.slice(0, 16)}`;\n autoCreatedConnector.addRoute({\n prefix: derivedAddress,\n nextHop: nodeId,\n priority: 100,\n });\n }\n\n // Trigger kind:10032 republication to propagate updated address list\n if (started && lastBootstrapResults.length > 0) {\n void bootstrapServiceInstance.republish(lastBootstrapResults);\n }\n },\n\n removeUpstreamPeer(upstreamPrefix: string): void {\n // Guard: cannot remove the last address -- a node must always have at least one\n // ILP address (AC #3: empty ilpAddresses array is rejected at construction time).\n if (\n addressRegistry.hasPrefix(upstreamPrefix) &&\n addressRegistry.size <= 1\n ) {\n throw new NodeError(\n 'Cannot remove last upstream peer: a node must have at least one ILP address'\n );\n }\n\n const removedAddress = addressRegistry.removeAddress(upstreamPrefix);\n if (removedAddress === undefined) {\n return; // No-op if prefix not found\n }\n\n // Update ilpInfo for kind:10032 republication\n ilpInfo.ilpAddresses = addressRegistry.getAddresses();\n const newPrimary = addressRegistry.getPrimaryAddress();\n if (newPrimary !== undefined) {\n ilpInfo.ilpAddress = newPrimary;\n }\n\n // Remove self-route from embedded connector (if available)\n if (autoCreatedConnector && autoCreatedConnector.removeRoute) {\n autoCreatedConnector.removeRoute(removedAddress);\n }\n\n // Trigger kind:10032 republication to propagate updated address list\n if (started && lastBootstrapResults.length > 0) {\n void bootstrapServiceInstance.republish(lastBootstrapResults);\n }\n },\n\n async claimPrefix(\n prefix: string,\n upstreamDestination: string,\n options?: { prefixPrice?: bigint }\n ): Promise<PublishEventResult> {\n // Guard: node must be started\n if (!started) {\n throw new NodeError(\n 'Cannot claim prefix: node not started. Call start() first.'\n );\n }\n\n // Validate prefix format before sending payment (fail-fast, like bid safety cap)\n const prefixValidation = validatePrefix(prefix);\n if (!prefixValidation.valid) {\n throw new NodeError(\n `Cannot claim prefix: ${prefixValidation.reason ?? 'invalid prefix'}`\n );\n }\n\n // Determine the amount: explicit override, or look up upstream's prefixPricing\n let claimAmount = options?.prefixPrice;\n if (claimAmount === undefined) {\n // Look up from discovered peers\n const peers = discoveryTrackerInstance.getAllDiscoveredPeers();\n const upstreamPeer = peers.find(\n (p) =>\n p.peerInfo.ilpAddress === upstreamDestination ||\n (p.peerInfo.ilpAddresses &&\n p.peerInfo.ilpAddresses.includes(upstreamDestination))\n );\n if (upstreamPeer?.peerInfo.prefixPricing?.basePrice) {\n claimAmount = BigInt(upstreamPeer.peerInfo.prefixPricing.basePrice);\n } else {\n throw new NodeError(\n 'Cannot claim prefix: no amount provided and upstream peer prefix pricing not found in discovery'\n );\n }\n }\n\n // Build the prefix claim event\n const claimEvent = buildPrefixClaimEvent(\n { requestedPrefix: prefix },\n config.secretKey\n );\n\n // Delegate to publishEvent with the amount override\n return node.publishEvent(claimEvent, {\n destination: upstreamDestination,\n amount: claimAmount,\n });\n },\n };\n\n return node;\n}\n","/**\n * Skill descriptor builder for DVM service discovery.\n *\n * Computes a SkillDescriptor from the handler registry and node configuration.\n * The descriptor is embedded in kind:10035 events to advertise DVM capabilities.\n *\n * Returns `undefined` when no DVM handlers are registered (backward compatible\n * with pre-DVM kind:10035 events).\n */\n\nimport { ToonError } from '@toon-protocol/core';\nimport type { SkillDescriptor, ReputationScore } from '@toon-protocol/core';\nimport type { HandlerRegistry } from './handler-registry.js';\n\n/** Regex for 64-char lowercase hex string. */\nconst HEX_64_REGEX = /^[0-9a-f]{64}$/;\n\n/** Configuration for building a skill descriptor. */\nexport interface BuildSkillDescriptorConfig {\n /** Base price per byte in USDC micro-units (default: 10n). */\n basePricePerByte?: bigint;\n /** Per-kind pricing overrides (kind number -> price in USDC micro-units). */\n kindPricing?: Record<number, bigint>;\n /** Service name override (default: 'toon-dvm'). */\n name?: string;\n /** Schema version override (default: '1.0'). */\n version?: string;\n /** Feature list override. */\n features?: string[];\n /** JSON Schema draft-07 object for job request parameters. */\n inputSchema?: Record<string, unknown>;\n /** Available AI models. */\n models?: string[];\n /** Pre-computed reputation score for the skill descriptor. */\n reputation?: ReputationScore;\n /** TEE attestation metadata for the skill descriptor. */\n attestation?: {\n /** 64-char hex event ID of the provider's latest kind:10033 attestation event. */\n eventId: string;\n /** Enclave image hash for the TEE environment. */\n enclaveImageHash: string;\n };\n}\n\n/**\n * Builds a SkillDescriptor from the handler registry and configuration.\n *\n * Returns `undefined` when the registry has no DVM handlers (kinds 5000-5999).\n * When DVM kinds exist, auto-populates the descriptor:\n * - `kinds` from `registry.getDvmKinds()`\n * - `pricing` from `config.kindPricing` overrides or `config.basePricePerByte` fallback\n * - `name`, `version`, `features`, `inputSchema`, `models` from config or defaults\n *\n * @param registry - The handler registry to read DVM kinds from.\n * @param config - Node configuration for pricing and optional overrides.\n * @returns A SkillDescriptor, or undefined if no DVM handlers are registered.\n */\nexport function buildSkillDescriptor(\n registry: HandlerRegistry,\n config: BuildSkillDescriptorConfig = {}\n): SkillDescriptor | undefined {\n const dvmKinds = registry.getDvmKinds();\n if (dvmKinds.length === 0) {\n return undefined;\n }\n\n // Validate attestation eventId if provided\n if (config.attestation !== undefined) {\n if (!HEX_64_REGEX.test(config.attestation.eventId)) {\n throw new ToonError(\n 'Skill descriptor attestation eventId must be a 64-character lowercase hex string',\n 'DVM_SKILL_INVALID_ATTESTATION_EVENT_ID'\n );\n }\n }\n\n const basePricePerByte = config.basePricePerByte ?? 10n;\n const kindPricing = config.kindPricing ?? {};\n\n // Derive pricing: per-kind overrides or basePricePerByte fallback\n const pricing: Record<string, string> = {};\n for (const kind of dvmKinds) {\n if (Object.hasOwn(kindPricing, kind)) {\n pricing[String(kind)] = String(kindPricing[kind]);\n } else {\n pricing[String(kind)] = String(basePricePerByte);\n }\n }\n\n const descriptor: SkillDescriptor = {\n name: config.name ?? 'toon-dvm',\n version: config.version ?? '1.0',\n kinds: dvmKinds,\n features: config.features ?? [],\n inputSchema: config.inputSchema ?? { type: 'object', properties: {} },\n pricing,\n };\n\n if (config.models !== undefined) {\n descriptor.models = config.models;\n }\n\n if (config.reputation !== undefined) {\n descriptor.reputation = config.reputation;\n }\n\n if (config.attestation !== undefined) {\n descriptor.attestation = {\n eventId: config.attestation.eventId,\n enclaveImageHash: config.attestation.enclaveImageHash,\n };\n }\n\n return descriptor;\n}\n","/**\n * Workflow Orchestrator for multi-step DVM pipelines (Story 6.1).\n *\n * Manages the lifecycle of a workflow chain: creates step job requests,\n * detects step completion/failure, advances to the next step, handles\n * timeouts, and settles compute payments per step.\n *\n * The orchestrator uses the TOON relay as the orchestration layer --\n * there is no separate workflow engine. Step completion is detected\n * via relay event subscriptions (Kind 6xxx results, Kind 7000 feedback).\n *\n * State machine states:\n * - `pending`: Workflow created but not yet started\n * - `step_N_running`: Step N's job request published, waiting for result\n * - `step_N_failed`: Step N failed (error or timeout), workflow aborted\n * - `completed`: All steps finished successfully\n *\n * Settlement: Each step settles individually via settleCompute().\n * The orchestrator validates sum(step_amounts) <= total_bid before settlement.\n *\n * Forward-compatible with Epic 7 prepaid protocol: settlement logic is\n * isolated in handleStepResult() so swapping to prepaid per-step payment\n * requires changes only in that method.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { buildJobRequestEvent, parseJobFeedback } from '@toon-protocol/core';\nimport type { ParsedWorkflowDefinition } from '@toon-protocol/core';\nimport type { ServiceNode } from './create-node.js';\n\n/**\n * Workflow state type. Uses template literal types for step-indexed states.\n * Note: Timeout is represented as `step_N_failed` (timeout is a failure mode,\n * not a separate terminal state). The timeout cause is communicated in the\n * customer notification content.\n */\nexport type WorkflowState =\n | 'pending'\n | `step_${number}_running`\n | `step_${number}_failed`\n | 'completed';\n\n/**\n * Event store interface for workflow state persistence.\n * Minimal interface -- only store() and query() are needed.\n */\nexport interface WorkflowEventStore {\n store(event: NostrEvent): Promise<void>;\n query(filter: { kinds?: number[]; '#e'?: string[] }): Promise<NostrEvent[]>;\n}\n\n/**\n * Configuration options for the WorkflowOrchestrator.\n */\nexport interface WorkflowOrchestratorOptions {\n /** Secret key for signing step job request events (32-byte Uint8Array). */\n secretKey?: Uint8Array;\n /** Per-step timeout in milliseconds (default: 300000 = 5 minutes). */\n stepTimeoutMs?: number;\n /** Injectable time source for deterministic testing. */\n now?: () => number;\n /**\n * Injectable timer factory for deterministic testing.\n * Defaults to global setTimeout. Inject a custom implementation\n * to control timer advancement in tests without vi.useFakeTimers().\n */\n setTimer?: (\n callback: () => void,\n ms: number\n ) => ReturnType<typeof setTimeout>;\n /**\n * Injectable timer cancellation for deterministic testing.\n * Defaults to global clearTimeout. Must pair with setTimer.\n */\n clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;\n /** Optional event store for workflow state persistence. */\n eventStore?: WorkflowEventStore;\n /** Default destination ILP address for publishing events. */\n destination?: string;\n /** Workflow definition event ID (for referencing in notifications). */\n workflowEventId?: string;\n /** Customer pubkey (for directing notifications). */\n customerPubkey?: string;\n}\n\n/**\n * Per-step state tracking.\n */\ninterface StepState {\n /** Index of this step (0-based). */\n index: number;\n /** The job request event ID published for this step. */\n requestEventId?: string;\n /** The result event received for this step. */\n resultEvent?: NostrEvent;\n /** Whether this step has been settled. */\n settled: boolean;\n}\n\n/**\n * Orchestrates a multi-step DVM workflow chain.\n *\n * Each instance manages a single workflow. For concurrent workflows,\n * create multiple WorkflowOrchestrator instances sharing the same\n * ServiceNode.\n */\nexport class WorkflowOrchestrator {\n private readonly node: ServiceNode;\n private readonly options: Required<\n Pick<WorkflowOrchestratorOptions, 'stepTimeoutMs'>\n > &\n WorkflowOrchestratorOptions;\n\n private state: WorkflowState = 'pending';\n private definition: ParsedWorkflowDefinition | null = null;\n private workflowEventId: string = '0'.repeat(64);\n private customerPubkey: string = '0'.repeat(64);\n private currentStepIndex = 0;\n private stepStates: StepState[] = [];\n private timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n private processedResultIds = new Set<string>();\n\n constructor(node: ServiceNode, options?: WorkflowOrchestratorOptions) {\n this.node = node;\n this.options = {\n stepTimeoutMs: 300_000, // 5 minutes default\n ...options,\n };\n if (options?.workflowEventId) {\n this.workflowEventId = options.workflowEventId;\n }\n if (options?.customerPubkey) {\n this.customerPubkey = options.customerPubkey;\n }\n }\n\n /**\n * Returns the current workflow state.\n */\n getState(): WorkflowState {\n return this.state;\n }\n\n /**\n * Returns per-step state tracking data (for testing/debugging).\n */\n getStepStates(): readonly Readonly<StepState>[] {\n return this.stepStates;\n }\n\n /**\n * Starts a workflow: parses the definition, creates and publishes\n * the Kind 5xxx job request for step 1, sets state to step_1_running.\n */\n async startWorkflow(definition: ParsedWorkflowDefinition): Promise<void> {\n // Guard: prevent re-entrance -- calling startWorkflow on an already-started\n // orchestrator would silently overwrite state without cleaning up timeouts.\n if (this.state !== 'pending') {\n throw new Error(\n 'WorkflowOrchestrator.startWorkflow() called on a non-pending orchestrator. ' +\n 'Create a new WorkflowOrchestrator instance for each workflow.'\n );\n }\n\n // Guard: validate steps non-empty (defense-in-depth -- the builder validates\n // this too, but the orchestrator may receive ParsedWorkflowDefinition from\n // sources other than the builder, e.g. deserialized from EventStore).\n if (!definition.steps || definition.steps.length === 0) {\n throw new Error(\n 'WorkflowOrchestrator.startWorkflow() requires at least one step in the definition.'\n );\n }\n\n this.definition = definition;\n\n // Initialize step states\n this.stepStates = definition.steps.map((_, index) => ({\n index,\n settled: false,\n }));\n\n // Store the workflow definition event if eventStore is available\n if (this.options.eventStore) {\n // Create a synthetic event representing the workflow state.\n // Use a hash-like ID derived from pubkey + timestamp to avoid collisions\n // across concurrent workflows.\n const now = this.options.now ? this.options.now() : Date.now();\n const syntheticId = `wf${now.toString(16).padStart(16, '0')}${this.node.pubkey.slice(0, 48)}`;\n const stateEvent: NostrEvent = {\n id: syntheticId.padEnd(64, '0').slice(0, 64),\n pubkey: this.node.pubkey,\n created_at: Math.floor(now / 1000),\n kind: 10040,\n content: JSON.stringify({\n state: 'started',\n definition,\n }),\n tags: [],\n sig: '0'.repeat(128),\n };\n await this.options.eventStore.store(stateEvent);\n }\n\n // Create and publish step 1\n await this.publishStepRequest(\n 0,\n definition.initialInput.data,\n definition.initialInput.type\n );\n\n // Set state\n this.state = 'step_1_running';\n this.currentStepIndex = 0;\n\n // Start timeout for step 1\n this.startStepTimeout();\n }\n\n /**\n * Handles a Kind 6xxx result event for the current step.\n *\n * If the current step matches, extracts the result content and either:\n * - Advances to the next step (publishes Kind 5xxx for step N+1)\n * - Marks workflow as completed (if this was the final step)\n *\n * Idempotent: re-processing a result for an already-advanced step is a no-op.\n */\n async handleStepResult(resultEvent: NostrEvent): Promise<void> {\n if (!this.definition) return;\n\n // Validate result event kind is in the 6000-6999 range (Kind 6xxx).\n // Without this check, any event kind could be injected as a step result.\n if (resultEvent.kind < 6000 || resultEvent.kind > 6999) return;\n\n // Idempotency: skip already-processed result events\n if (this.processedResultIds.has(resultEvent.id)) return;\n\n // Idempotency: ignore if we've already moved past this step\n const currentStep = this.stepStates[this.currentStepIndex];\n if (!currentStep) return;\n if (currentStep.resultEvent) return; // Already processed\n\n // Ignore if workflow is in a terminal state\n if (this.state === 'completed' || this.state.endsWith('_failed')) return;\n\n // NOTE: The orchestrator trusts that the caller pre-filters result events\n // to match the current step's request (via relay subscription with `e` tag\n // filter on currentStep.requestEventId). Defense-in-depth validation of the\n // `e` tag could be added here but would require callers to ensure result\n // events always reference the correct request ID.\n\n // Mark this result as processed\n this.processedResultIds.add(resultEvent.id);\n\n // Clear timeout\n this.clearStepTimeout();\n\n // Record the result\n currentStep.resultEvent = resultEvent;\n\n // Settle compute for this step\n await this.settleStep(this.currentStepIndex, resultEvent);\n\n // Store step result in eventStore if available\n if (this.options.eventStore) {\n await this.options.eventStore.store(resultEvent);\n }\n\n // Check if this was the final step\n const isLastStep =\n this.currentStepIndex === this.definition.steps.length - 1;\n\n if (isLastStep) {\n // Workflow complete\n this.state = 'completed';\n\n // Notify customer with Kind 7000 success\n await this.publishWorkflowNotification(\n 'success',\n 'Workflow completed successfully'\n );\n } else {\n // Advance to next step\n const nextIndex = this.currentStepIndex + 1;\n const resultContent = resultEvent.content;\n\n await this.publishStepRequest(nextIndex, resultContent, 'text');\n\n this.currentStepIndex = nextIndex;\n this.state = `step_${nextIndex + 1}_running`;\n\n // Start timeout for next step\n this.startStepTimeout();\n }\n }\n\n /**\n * Handles a Kind 7000 feedback event for the current step.\n *\n * If the feedback status is 'error', marks the workflow as failed\n * and notifies the customer.\n */\n async handleStepFeedback(feedbackEvent: NostrEvent): Promise<void> {\n if (!this.definition) return;\n\n // Ignore if workflow is in a terminal state\n if (this.state === 'completed' || this.state.endsWith('_failed')) return;\n\n const parsed = parseJobFeedback(feedbackEvent);\n if (!parsed) return;\n\n if (parsed.status === 'error') {\n // Clear timeout\n this.clearStepTimeout();\n\n // Mark failed\n this.state = `step_${this.currentStepIndex + 1}_failed`;\n\n // Store failure in eventStore if available\n if (this.options.eventStore) {\n await this.options.eventStore.store(feedbackEvent);\n }\n\n // Notify customer\n await this.publishWorkflowNotification(\n 'error',\n `Workflow failed at step ${this.currentStepIndex + 1}: ${parsed.content || 'Unknown error'}`\n );\n }\n }\n\n /**\n * Cleans up resources (timeout handles).\n */\n destroy(): void {\n this.clearStepTimeout();\n }\n\n // ---------- Private Methods ----------\n\n /**\n * Publishes a Kind 5xxx job request for the given step.\n */\n private async publishStepRequest(\n stepIndex: number,\n inputData: string,\n inputType: string\n ): Promise<void> {\n if (!this.definition) return;\n\n const step = this.definition.steps[stepIndex];\n if (!step) return;\n\n // Compute bid for this step\n const stepBid = this.getStepBid(stepIndex);\n\n // Build the job request event\n const jobRequest = buildJobRequestEvent(\n {\n kind: step.kind,\n input: { data: inputData, type: inputType },\n bid: stepBid,\n output: 'text/plain',\n content: step.description,\n targetProvider: step.targetProvider,\n },\n this.getSecretKey()\n );\n\n // Publish via the node's publishEvent (which handles TOON encoding + ILP)\n const destination = this.options.destination ?? 'g.toon.local';\n await this.node.publishEvent(jobRequest, { destination });\n\n // Record the request event ID\n const stepState = this.stepStates[stepIndex];\n if (stepState) {\n stepState.requestEventId = jobRequest.id;\n }\n }\n\n /**\n * Settles compute payment for a completed step.\n */\n private async settleStep(\n stepIndex: number,\n resultEvent: NostrEvent\n ): Promise<void> {\n if (!this.definition) return;\n\n const stepState = this.stepStates[stepIndex];\n if (!stepState || stepState.settled) return;\n\n const step = this.definition.steps[stepIndex];\n if (!step) return;\n\n // Determine provider ILP address from result event pubkey or fallback.\n // In production, this would be resolved from the provider's kind:10035\n // service discovery event. For now, derive from the destination prefix.\n const providerIlpAddress = this.options.destination ?? 'g.toon.provider';\n\n try {\n await this.node.settleCompute(resultEvent, providerIlpAddress, {\n originalBid: this.getStepBid(stepIndex),\n });\n stepState.settled = true;\n } catch (_err: unknown) {\n // Settlement failure does not block workflow advancement.\n // The orchestrator prioritizes workflow progress over payment atomicity.\n // In production, the caller should monitor stepState.settled per step\n // and reconcile unsettled steps out-of-band.\n }\n }\n\n /**\n * Returns the bid allocation for a specific step.\n * Uses explicit bidAllocation if set, otherwise proportional split.\n */\n private getStepBid(stepIndex: number): string {\n if (!this.definition) return '0';\n\n const step = this.definition.steps[stepIndex];\n if (!step) return '0';\n\n // Use explicit allocation if available\n if (step.bidAllocation !== undefined) {\n return step.bidAllocation;\n }\n\n // Proportional split -- wrap BigInt conversion in try/catch since\n // totalBid may come from deserialized EventStore data that was not\n // validated by the builder.\n try {\n const totalBid = BigInt(this.definition.totalBid);\n const stepCount = BigInt(this.definition.steps.length);\n const perStep = totalBid / stepCount;\n return perStep.toString();\n } catch {\n return '0';\n }\n }\n\n /**\n * Publishes a Kind 7000 workflow notification to the customer.\n */\n private async publishWorkflowNotification(\n status: 'success' | 'error',\n content: string\n ): Promise<void> {\n const destination = this.options.destination ?? 'g.toon.local';\n try {\n await this.node.publishFeedback(\n this.workflowEventId,\n this.customerPubkey,\n status,\n content,\n { destination }\n );\n } catch (_err: unknown) {\n // Best-effort notification -- don't fail the workflow.\n // Notification delivery is not guaranteed; callers should\n // check orchestrator.getState() for authoritative status.\n }\n }\n\n /**\n * Starts a timeout timer for the current step.\n */\n private startStepTimeout(): void {\n this.clearStepTimeout();\n\n const timeoutMs = this.options.stepTimeoutMs;\n const timerFn = this.options.setTimer ?? setTimeout;\n\n this.timeoutHandle = timerFn(() => {\n // Mark as failed due to timeout\n this.state = `step_${this.currentStepIndex + 1}_failed`;\n\n // Notify customer\n void this.publishWorkflowNotification(\n 'error',\n `Workflow timed out at step ${this.currentStepIndex + 1} after ${timeoutMs}ms`\n );\n }, timeoutMs);\n }\n\n /**\n * Returns the secret key for signing step events.\n * Falls back to a deterministic key derived from the node pubkey\n * (for testing -- production should always pass secretKey in options).\n */\n private getSecretKey(): Uint8Array {\n if (this.options.secretKey) {\n return this.options.secretKey;\n }\n // Fallback: use a fixed test key (this path should not be hit in production)\n throw new Error(\n 'WorkflowOrchestrator requires secretKey in options to sign step events'\n );\n }\n\n /**\n * Clears the current step timeout.\n */\n private clearStepTimeout(): void {\n if (this.timeoutHandle !== null) {\n const cancelFn = this.options.clearTimer ?? clearTimeout;\n cancelFn(this.timeoutHandle);\n this.timeoutHandle = null;\n }\n }\n}\n","/**\n * Swarm Coordinator for competitive DVM bidding (Story 6.2).\n *\n * Manages the lifecycle of a competitive swarm: collects provider\n * submissions, handles timeout-based judging deadlines, validates\n * winner selection, and settles compute payment to the winner only.\n *\n * The coordinator uses the TOON relay as the orchestration layer --\n * there is no separate swarm engine. Submission arrival is detected\n * via relay event subscriptions (Kind 6xxx results).\n *\n * State machine states:\n * - `collecting`: Waiting for provider submissions (Kind 6xxx results)\n * - `judging`: Timeout or max submissions reached; customer selects winner\n * - `settled`: Winner paid via settleCompute(); swarm complete\n * - `failed`: No submissions within timeout or error\n *\n * Settlement: Only the winning provider receives compute payment.\n * Losing providers paid relay write fees (sunk cost) but receive\n * no compute payment -- this is by design to incentivize quality.\n *\n * Forward-compatible with Epic 7 prepaid protocol: settlement logic\n * is isolated in selectWinner() so swapping to prepaid per-winner\n * payment requires changes only in that method.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport {\n ToonError,\n parseSwarmRequest,\n parseSwarmSelection,\n} from '@toon-protocol/core';\nimport type { ServiceNode } from './create-node.js';\nimport type { WorkflowEventStore } from './workflow-orchestrator.js';\n\n/**\n * Swarm state type.\n * - `collecting`: Waiting for provider submissions.\n * - `judging`: Timeout or max submissions reached; awaiting winner selection.\n * - `settled`: Winner paid; swarm complete.\n * - `failed`: No submissions within timeout or error.\n */\nexport type SwarmState = 'collecting' | 'judging' | 'settled' | 'failed';\n\n/**\n * Configuration options for the SwarmCoordinator.\n */\nexport interface SwarmCoordinatorOptions {\n /** Secret key for signing events (32-byte Uint8Array). */\n secretKey?: Uint8Array;\n /** Swarm collection timeout in milliseconds (default: 600000 = 10 minutes). */\n timeoutMs?: number;\n /** Injectable time source for deterministic testing. */\n now?: () => number;\n /**\n * Injectable timer factory for deterministic testing.\n * Defaults to global setTimeout. Inject a custom implementation\n * to control timer advancement in tests without vi.useFakeTimers().\n */\n setTimer?: (\n callback: () => void,\n ms: number\n ) => ReturnType<typeof setTimeout>;\n /**\n * Injectable timer cancellation for deterministic testing.\n * Defaults to global clearTimeout. Must pair with setTimer.\n */\n clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;\n /** Optional event store for swarm state persistence. */\n eventStore?: WorkflowEventStore;\n /** Default destination ILP address for publishing events. */\n destination?: string;\n}\n\n/**\n * Coordinates a competitive DVM swarm.\n *\n * Each instance manages a single swarm. For concurrent swarms,\n * create multiple SwarmCoordinator instances sharing the same\n * ServiceNode.\n */\nexport class SwarmCoordinator {\n private readonly node: ServiceNode;\n private readonly options: Required<\n Pick<SwarmCoordinatorOptions, 'timeoutMs'>\n > &\n SwarmCoordinatorOptions;\n\n private state: SwarmState = 'collecting';\n private swarmRequestId = '';\n private customerPubkey = '';\n private maxProviders = 0;\n private submissions: NostrEvent[] = [];\n private timeoutHandle: ReturnType<typeof setTimeout> | null = null;\n private started = false;\n private settlementSucceeded = false;\n private submissionIds = new Set<string>();\n\n constructor(node: ServiceNode, options?: SwarmCoordinatorOptions) {\n this.node = node;\n this.options = {\n timeoutMs: 600_000, // 10 minutes default\n ...options,\n };\n }\n\n /**\n * Returns the current swarm state.\n */\n getState(): SwarmState {\n return this.state;\n }\n\n /**\n * Returns whether settlement was successfully completed.\n * Only meaningful when state is 'settled' (returns true).\n * If settlement fails, the state remains 'judging' and this returns false.\n */\n isSettlementSucceeded(): boolean {\n return this.settlementSucceeded;\n }\n\n /**\n * Returns the collected eligible submissions.\n */\n getSubmissions(): readonly NostrEvent[] {\n return this.submissions;\n }\n\n /**\n * Starts a swarm: parses the swarm request, initializes collection,\n * and starts the timeout timer.\n */\n async startSwarm(swarmRequest: NostrEvent): Promise<void> {\n const parsed = parseSwarmRequest(swarmRequest);\n if (!parsed) {\n throw new ToonError(\n 'Invalid swarm request event: missing swarm tag or invalid Kind 5xxx',\n 'DVM_INVALID_KIND'\n );\n }\n\n this.swarmRequestId = swarmRequest.id;\n this.customerPubkey = swarmRequest.pubkey;\n this.maxProviders = parsed.maxProviders;\n this.state = 'collecting';\n this.submissions = [];\n this.submissionIds = new Set<string>();\n this.started = true;\n\n // Store the swarm request in EventStore if available\n if (this.options.eventStore) {\n await this.options.eventStore.store(swarmRequest);\n }\n\n // Start timeout timer\n this.startTimeout();\n }\n\n /**\n * Handles a Kind 6xxx result event (provider submission).\n *\n * Validates the `e` tag references the swarm request, adds to the\n * submissions list, and checks if max providers is reached.\n *\n * Late submissions (after timeout or max reached) are ignored.\n * Submissions before startSwarm() are silently ignored.\n */\n async handleSubmission(resultEvent: NostrEvent): Promise<void> {\n if (!this.started) return;\n\n // Only accept submissions during collecting state\n if (this.state !== 'collecting') return;\n\n // Validate result event kind is in the 6000-6999 range\n if (resultEvent.kind < 6000 || resultEvent.kind > 6999) return;\n\n // Validate e tag references our swarm request\n const eTag = resultEvent.tags.find((t: string[]) => t[0] === 'e');\n if (!eTag) return;\n const requestEventId = eTag[1];\n if (requestEventId === undefined || requestEventId !== this.swarmRequestId)\n return;\n\n // Deduplicate by event ID -- relay normally handles dedup, but the\n // coordinator must also guard against duplicate submissions reaching\n // the maxProviders threshold with the same event repeated.\n if (this.submissionIds.has(resultEvent.id)) return;\n this.submissionIds.add(resultEvent.id);\n\n // Add to submissions\n this.submissions.push(resultEvent);\n\n // Store in EventStore if available\n if (this.options.eventStore) {\n await this.options.eventStore.store(resultEvent);\n }\n\n // Check if max providers reached\n if (this.submissions.length >= this.maxProviders) {\n this.clearTimeout();\n this.state = 'judging';\n }\n }\n\n /**\n * Selects the winner and settles compute payment.\n *\n * Validates:\n * - Swarm is in `judging` state (not `collecting`, `settled`, or `failed`)\n * - Selection references a submission in the collected set\n * - Swarm has not already been settled (idempotency guard)\n *\n * @throws ToonError with code DVM_SWARM_ALREADY_SETTLED if already settled\n * @throws ToonError with code DVM_SWARM_INVALID_SELECTION if winner not in submissions\n */\n async selectWinner(selectionEvent: NostrEvent): Promise<void> {\n // Idempotency guard: reject if already settled\n if (this.state === 'settled') {\n throw new ToonError(\n 'Swarm has already been settled; duplicate selection rejected',\n 'DVM_SWARM_ALREADY_SETTLED'\n );\n }\n\n // Must be in judging state\n if (this.state !== 'judging') {\n throw new ToonError(\n `Cannot select winner in state '${this.state}'; swarm must be in 'judging' state`,\n 'DVM_SWARM_INVALID_SELECTION'\n );\n }\n\n // Parse the selection event to extract the winner\n // Validate that the selection event is from the swarm's customer\n if (selectionEvent.pubkey !== this.customerPubkey) {\n throw new ToonError(\n 'Selection event pubkey does not match the swarm request customer pubkey',\n 'DVM_SWARM_INVALID_SELECTION'\n );\n }\n\n const parsed = parseSwarmSelection(selectionEvent);\n if (!parsed) {\n throw new ToonError(\n 'Invalid swarm selection event: missing winner tag or invalid Kind 7000',\n 'DVM_SWARM_INVALID_SELECTION'\n );\n }\n\n // Validate that the selection references this swarm (not a different one)\n if (parsed.swarmRequestEventId !== this.swarmRequestId) {\n throw new ToonError(\n `Selection event references swarm '${parsed.swarmRequestEventId}' but this swarm is '${this.swarmRequestId}'`,\n 'DVM_SWARM_INVALID_SELECTION'\n );\n }\n\n // Validate the winner references a collected submission\n const winnerSubmission = this.submissions.find(\n (s) => s.id === parsed.winnerResultEventId\n );\n if (!winnerSubmission) {\n throw new ToonError(\n `Winner result event ID '${parsed.winnerResultEventId}' not found in collected submissions`,\n 'DVM_SWARM_INVALID_SELECTION'\n );\n }\n\n // Settle compute payment to the winning provider only\n const providerIlpAddress = this.options.destination ?? 'g.toon.provider';\n try {\n await this.node.settleCompute(winnerSubmission, providerIlpAddress);\n this.settlementSucceeded = true;\n } catch (_err: unknown) {\n // Settlement failed -- remain in 'judging' state so the caller can\n // retry selectWinner() with the same or different selection event.\n // Do NOT transition to 'settled' because no payment was made.\n this.settlementSucceeded = false;\n throw new ToonError(\n 'Settlement failed for winning provider; swarm remains in judging state for retry',\n 'DVM_SWARM_SETTLEMENT_FAILED'\n );\n }\n\n // Store selection in EventStore if available\n if (this.options.eventStore) {\n await this.options.eventStore.store(selectionEvent);\n }\n\n this.state = 'settled';\n }\n\n /**\n * Cleans up resources (timeout handles).\n */\n destroy(): void {\n this.clearTimeout();\n }\n\n // ---------- Private Methods ----------\n\n /**\n * Starts the collection timeout timer.\n */\n private startTimeout(): void {\n this.clearTimeout();\n\n const timeoutMs = this.options.timeoutMs;\n const timerFn = this.options.setTimer ?? setTimeout;\n\n this.timeoutHandle = timerFn(() => {\n if (this.state !== 'collecting') return;\n\n if (this.submissions.length === 0) {\n // No submissions -- transition to failed\n this.state = 'failed';\n\n // Publish \"no submissions\" Kind 7000 feedback to customer\n void this.publishNoSubmissionsFeedback();\n } else {\n // Some submissions collected -- transition to judging\n this.state = 'judging';\n }\n }, timeoutMs);\n }\n\n /**\n * Publishes a Kind 7000 feedback event indicating no submissions were received.\n */\n private async publishNoSubmissionsFeedback(): Promise<void> {\n const destination = this.options.destination ?? 'g.toon.local';\n try {\n await this.node.publishFeedback(\n this.swarmRequestId,\n this.customerPubkey,\n 'error',\n 'Swarm timed out with no submissions',\n { destination }\n );\n } catch (_err: unknown) {\n // Best-effort notification -- don't fail the state transition.\n }\n }\n\n /**\n * Clears the current timeout timer.\n */\n private clearTimeout(): void {\n if (this.timeoutHandle !== null) {\n const cancelFn = this.options.clearTimer ?? clearTimeout;\n cancelFn(this.timeoutHandle);\n this.timeoutHandle = null;\n }\n }\n}\n","/**\n * Prefix claim handler for the TOON prefix marketplace.\n *\n * Creates a handler that processes kind 10034 prefix claim events,\n * validates payment and prefix availability, and publishes grant\n * confirmations. Lives in SDK (not core) because it depends on\n * the SDK's Handler type and HandlerContext interface.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport {\n parsePrefixClaimEvent,\n buildPrefixGrantEvent,\n validatePrefix,\n} from '@toon-protocol/core';\nimport type { Handler, HandlerResponse } from './handler-registry.js';\nimport type { HandlerContext } from './handler-context.js';\n\n/**\n * Options for creating a prefix claim handler.\n */\nexport interface PrefixClaimHandlerOptions {\n /** Pricing configuration for prefix claims. */\n prefixPricing: { basePrice: bigint };\n /** Secret key for signing grant events. */\n secretKey: Uint8Array;\n /** Returns the current map of claimed prefixes (prefix -> claimer pubkey). */\n getClaimedPrefixes: () => Map<string, string>;\n /**\n * Atomically claim a prefix for a pubkey. Returns true if the claim\n * succeeded, false if the prefix was already taken. This is the\n * serialization point for race condition defense.\n */\n claimPrefix: (prefix: string, claimerPubkey: string) => boolean;\n /** Publish the grant event (e.g., to the local relay). */\n publishGrant: (grantEvent: NostrEvent) => Promise<void>;\n /** ILP address prefix to include in the grant event (default: 'g.toon'). */\n ilpAddressPrefix?: string;\n}\n\n/**\n * Creates a handler for kind 10034 prefix claim events.\n *\n * The handler validates payment, prefix availability, and prefix format,\n * then atomically claims the prefix and publishes a grant confirmation.\n *\n * @param options - Handler configuration\n * @returns A Handler function for kind 10034 events\n */\nexport function createPrefixClaimHandler(\n options: PrefixClaimHandlerOptions\n): Handler {\n return async (ctx: HandlerContext): Promise<HandlerResponse> => {\n // 1. Parse the incoming event as PrefixClaimContent\n const event = ctx.decode();\n const claim = parsePrefixClaimEvent(event);\n if (!claim) {\n return ctx.reject('F06', 'Invalid prefix claim event: malformed content');\n }\n\n // 2. Validate the prefix format\n const validation = validatePrefix(claim.requestedPrefix);\n if (!validation.valid) {\n return ctx.reject(\n 'F06',\n `Invalid prefix: ${validation.reason ?? 'unknown validation error'}`\n );\n }\n\n // 3. Check payment is sufficient\n if (ctx.amount < options.prefixPricing.basePrice) {\n return ctx.reject(\n 'F06',\n `Insufficient payment: received ${ctx.amount}, required ${options.prefixPricing.basePrice}`\n );\n }\n\n // 4. Check prefix availability\n const claimedPrefixes = options.getClaimedPrefixes();\n if (claimedPrefixes.has(claim.requestedPrefix)) {\n return ctx.reject(\n 'F06',\n `PREFIX_TAKEN: prefix \"${claim.requestedPrefix}\" is already claimed`\n );\n }\n\n // 5. Atomically claim the prefix (serialization point for race conditions)\n const claimed = options.claimPrefix(claim.requestedPrefix, event.pubkey);\n if (!claimed) {\n return ctx.reject(\n 'F06',\n `PREFIX_TAKEN: prefix \"${claim.requestedPrefix}\" is already claimed`\n );\n }\n\n // 6. Build and publish grant event\n const prefix = options.ilpAddressPrefix ?? 'g.toon';\n const ilpAddress = `${prefix}.${claim.requestedPrefix}`;\n const grantEvent = buildPrefixGrantEvent(\n {\n grantedPrefix: claim.requestedPrefix,\n claimerPubkey: event.pubkey,\n ilpAddress,\n },\n options.secretKey\n );\n await options.publishGrant(grantEvent);\n\n // 7. Accept the ILP packet\n return ctx.accept();\n };\n}\n","/**\n * Arweave DVM handler for kind:5094 blob storage requests.\n *\n * Creates a handler function that:\n * 1. Parses the kind:5094 event from the HandlerContext\n * 2. For single-packet uploads: uploads directly to Arweave via the adapter\n * 3. For chunked uploads: accumulates via ChunkManager, uploads on completion\n *\n * The handler does NOT validate pricing -- the SDK pricing validator\n * (packages/sdk/src/pricing-validator.ts) runs BEFORE the handler is invoked.\n *\n * Returns HandlePacketAcceptResponse with `data` field containing the Arweave\n * tx ID -- NOT via ctx.accept() which only supports metadata.\n */\n\nimport { parseBlobStorageRequest } from '@toon-protocol/core';\nimport type { HandlerContext } from '../handler-context.js';\nimport type { HandlerResponse } from '../handler-registry.js';\nimport type { ArweaveUploadAdapter } from './turbo-adapter.js';\nimport type { ChunkManager } from './chunk-manager.js';\n\n/**\n * Validates and sanitizes a MIME content type string.\n * Returns sanitized type or 'application/octet-stream' for invalid values.\n *\n * Defends against header injection (CWE-113) and ensures only well-formed\n * MIME types are forwarded to the Arweave upload adapter.\n */\nconst MIME_REGEX =\n /^[a-zA-Z0-9][a-zA-Z0-9!#$&\\-^_.+]*\\/[a-zA-Z0-9][a-zA-Z0-9!#$&\\-^_.+]*$/;\nfunction sanitizeContentType(contentType: string): string {\n // Strip any parameters (e.g., \"; charset=utf-8\") and whitespace\n const base = (contentType.split(';')[0] ?? '').trim();\n if (base.length > 0 && base.length <= 255 && MIME_REGEX.test(base)) {\n return base;\n }\n return 'application/octet-stream';\n}\n\nexport interface ArweaveDvmConfig {\n /** Arweave upload adapter (wraps @ardrive/turbo-sdk). */\n turboAdapter: ArweaveUploadAdapter;\n /** Chunk state manager for multi-packet uploads. */\n chunkManager: ChunkManager;\n /** Optional default Arweave data item tags (e.g., Content-Type). */\n arweaveTags?: Record<string, string>;\n}\n\n/**\n * Creates an Arweave DVM handler for kind:5094 blob storage requests.\n *\n * @param config - Handler configuration with adapter and chunk manager.\n * @returns A handler function compatible with HandlerRegistry.on().\n */\nexport function createArweaveDvmHandler(\n config: ArweaveDvmConfig\n): (ctx: HandlerContext) => Promise<HandlerResponse> {\n const { turboAdapter, chunkManager, arweaveTags } = config;\n\n return async (ctx: HandlerContext): Promise<HandlerResponse> => {\n // Decode the TOON payload into a full NostrEvent\n const event = ctx.decode();\n\n // Parse the kind:5094 event\n const parsed = parseBlobStorageRequest(event);\n if (!parsed) {\n return {\n accept: false,\n code: 'F00',\n message: 'Malformed kind:5094 blob storage request',\n };\n }\n\n // Build tags for Arweave upload.\n // Event-level Content-Type takes precedence over config-level arweaveTags.\n // Sanitize to prevent header injection (CWE-113) from user-controlled input.\n const uploadTags: Record<string, string> = {\n ...arweaveTags,\n 'Content-Type': sanitizeContentType(parsed.contentType),\n };\n\n // Check if this is a chunked upload\n if (\n parsed.uploadId !== undefined &&\n parsed.chunkIndex !== undefined &&\n parsed.totalChunks !== undefined\n ) {\n try {\n const result = chunkManager.addChunk(\n parsed.uploadId,\n parsed.chunkIndex,\n parsed.totalChunks,\n parsed.blobData\n );\n\n if (!result.complete) {\n // Intermediate chunk -- acknowledge receipt\n return {\n accept: true,\n data: Buffer.from(`ack:${parsed.chunkIndex}`).toString('base64'),\n };\n }\n\n // All chunks received -- upload assembled blob\n // assembled is guaranteed to be present when complete is true\n const assembled = result.assembled ?? Buffer.alloc(0);\n const { txId } = await turboAdapter.upload(assembled, uploadTags);\n\n // Base64-encode txId for ILP FULFILL data field (connector validates base64)\n return {\n accept: true,\n data: Buffer.from(txId).toString('base64'),\n };\n } catch (error) {\n // Log full error internally but return generic message to client (CWE-209)\n console.error(\n '[ArweaveDvmHandler] Chunked upload failed:',\n error instanceof Error ? (error.stack ?? error.message) : error\n );\n const internalMsg =\n error instanceof Error ? error.message : 'Unknown chunk error';\n // Only surface safe, expected error categories to callers\n const safeMsg = internalMsg.includes('Duplicate chunkIndex')\n ? 'Duplicate chunk rejected'\n : internalMsg.includes('Max active uploads')\n ? 'Server busy, too many concurrent uploads'\n : internalMsg.includes('exceeds max bytes')\n ? 'Upload size limit exceeded'\n : 'Chunk processing error';\n return {\n accept: false,\n code: 'F00',\n message: safeMsg,\n };\n }\n }\n\n // Single-packet upload\n try {\n const { txId } = await turboAdapter.upload(parsed.blobData, uploadTags);\n // Base64-encode txId for ILP FULFILL data field (connector validates base64)\n return {\n accept: true,\n data: Buffer.from(txId).toString('base64'),\n };\n } catch (error) {\n // Log the full underlying error server-side for operator/gate diagnostics\n // (e.g. Turbo upload-service rejection, network timeout to upload.ardrive.io,\n // a >100 KB payload exceeding the unauthenticated free-tier limit, or a\n // throwing/no-credential adapter — see #146). The stub adapter used to\n // swallow the cause as a bare \"Arweave upload failed\"; we now always emit\n // the real reason here so the failure self-diagnoses from the dvm logs.\n // The client still only receives a generic message (CWE-209): we never\n // forward SDK internals over the wire.\n const cause =\n error instanceof Error ? (error.stack ?? error.message) : String(error);\n console.error(\n `[ArweaveDvmHandler] Arweave upload failed (${parsed.blobData.length} bytes). ` +\n `Cause: ${cause}`\n );\n return {\n accept: false,\n code: 'T00',\n message: 'Arweave upload failed',\n };\n }\n };\n}\n","/**\n * Arweave upload adapter wrapping @ardrive/turbo-sdk.\n *\n * This is the ONLY file that imports @ardrive/turbo-sdk, isolating the\n * external dependency behind an interface (risk E8-R002).\n */\n\nimport { Readable } from 'node:stream';\n\n/**\n * Interface for uploading data to Arweave.\n * Implementations wrap a specific Arweave upload SDK.\n */\nexport interface ArweaveUploadAdapter {\n upload(\n data: Buffer,\n tags?: Record<string, string>\n ): Promise<{ txId: string }>;\n}\n\n/**\n * Turbo SDK upload adapter using @ardrive/turbo-sdk.\n *\n * - Dev/free tier (<=100KB): pass no turboClient. getClient() lazily generates an\n * ephemeral RSA JWK and builds a TurboFactory.authenticated() client. The upload\n * service grants free small-data-item uploads to any valid signer regardless of\n * balance, so no deposit is required. NOTE: TurboFactory.unauthenticated() is NOT\n * usable here — in @ardrive/turbo-sdk >=1.40 uploadFile() exists only on the\n * authenticated client; the unauthenticated client exposes only uploadSignedDataItem().\n * - Prod (paid, uncapped): pass a TurboAuthenticatedClient from a funded wallet.\n *\n * The turboClient is typed as `unknown` to avoid importing @ardrive/turbo-sdk types\n * in this interface. The actual TurboAuthenticatedClient is duck-typed at runtime.\n */\nexport class TurboUploadAdapter implements ArweaveUploadAdapter {\n private turboClient: unknown;\n private initialized = false;\n\n constructor(turboClient?: unknown) {\n if (turboClient) {\n this.turboClient = turboClient;\n this.initialized = true;\n }\n }\n\n private async getClient(): Promise<unknown> {\n if (!this.initialized) {\n // Lazy-import to avoid loading @ardrive/turbo-sdk unless actually used\n const { TurboFactory } = await import('@ardrive/turbo-sdk/node');\n const Arweave = (await import('arweave')).default;\n const arweave = Arweave.init({});\n const jwk = await arweave.crypto.generateJWK();\n this.turboClient = TurboFactory.authenticated({ privateKey: jwk });\n this.initialized = true;\n }\n return this.turboClient;\n }\n\n async upload(\n data: Buffer,\n tags?: Record<string, string>\n ): Promise<{ txId: string }> {\n const client = (await this.getClient()) as {\n uploadFile(params: {\n fileStreamFactory: () => Readable;\n fileSizeFactory: () => number;\n dataItemOpts?: { tags: { name: string; value: string }[] };\n }): Promise<{ id: string }>;\n };\n\n // Convert Record<string, string> to tag array format\n const tagArray: { name: string; value: string }[] = [];\n if (tags) {\n for (const [name, value] of Object.entries(tags)) {\n tagArray.push({ name, value });\n }\n }\n\n const result = await client.uploadFile({\n fileStreamFactory: () => Readable.from(data),\n fileSizeFactory: () => data.length,\n ...(tagArray.length > 0 ? { dataItemOpts: { tags: tagArray } } : {}),\n });\n\n return { txId: result.id };\n }\n}\n","/**\n * Chunk state manager for multi-packet blob uploads.\n *\n * Accumulates chunks for a given uploadId, assembles them in order\n * when all chunks have arrived, and enforces timeout and memory caps.\n */\n\nexport interface ChunkManagerConfig {\n /** Timeout in milliseconds before partial uploads are discarded (default: 300_000 = 5 min). */\n timeoutMs?: number;\n /** Maximum number of concurrent active uploads (default: 100). */\n maxActiveUploads?: number;\n /** Maximum total bytes accumulated per upload before rejection (default: 50MB). */\n maxBytesPerUpload?: number;\n}\n\nexport interface AddChunkResult {\n /** Whether all chunks have been received and the blob is fully assembled. */\n complete: boolean;\n /** The assembled blob (only present when complete is true). */\n assembled?: Buffer;\n /** Error message if the chunk was rejected. */\n error?: string;\n}\n\ninterface UploadState {\n chunks: Map<number, Buffer>;\n totalChunks: number;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Manages chunked upload state for the Arweave DVM handler.\n */\nexport class ChunkManager {\n private uploads = new Map<string, UploadState>();\n private readonly timeoutMs: number;\n private readonly maxActiveUploads: number;\n private readonly maxBytesPerUpload: number;\n\n constructor(config: ChunkManagerConfig = {}) {\n this.timeoutMs = config.timeoutMs ?? 300_000;\n this.maxActiveUploads = config.maxActiveUploads ?? 100;\n this.maxBytesPerUpload = config.maxBytesPerUpload ?? 50 * 1024 * 1024; // 50MB\n }\n\n /**\n * Add a chunk for a given uploadId.\n *\n * @param uploadId - Unique identifier for the upload session.\n * @param chunkIndex - Zero-based index of this chunk.\n * @param totalChunks - Total number of chunks expected.\n * @param data - The chunk data.\n * @returns Result indicating completion status or error.\n * @throws Error if memory cap reached or duplicate chunk.\n */\n addChunk(\n uploadId: string,\n chunkIndex: number,\n totalChunks: number,\n data: Buffer\n ): AddChunkResult {\n let state = this.uploads.get(uploadId);\n\n if (!state) {\n // Check memory cap before creating new upload\n if (this.uploads.size >= this.maxActiveUploads) {\n throw new Error(\n `Max active uploads reached (${this.maxActiveUploads}). Cannot accept new uploadId.`\n );\n }\n\n // Create new upload state with timeout\n const timer = setTimeout(() => {\n this.cleanup(uploadId);\n }, this.timeoutMs);\n\n state = {\n chunks: new Map(),\n totalChunks,\n timer,\n };\n this.uploads.set(uploadId, state);\n }\n\n // Validate chunkIndex is within bounds\n if (chunkIndex < 0 || chunkIndex >= state.totalChunks) {\n throw new Error(\n `chunkIndex ${chunkIndex} out of bounds for totalChunks ${state.totalChunks} (uploadId ${uploadId})`\n );\n }\n\n // Check for duplicate chunk\n if (state.chunks.has(chunkIndex)) {\n throw new Error(\n `Duplicate chunkIndex ${chunkIndex} for uploadId ${uploadId}`\n );\n }\n\n // Check per-upload byte limit before storing\n let currentBytes = 0;\n for (const chunk of state.chunks.values()) {\n currentBytes += chunk.length;\n }\n if (currentBytes + data.length > this.maxBytesPerUpload) {\n this.cleanup(uploadId);\n throw new Error(\n `Upload ${uploadId} exceeds max bytes per upload (${this.maxBytesPerUpload})`\n );\n }\n\n // Store chunk\n state.chunks.set(chunkIndex, data);\n\n // Check if all chunks received\n if (state.chunks.size === state.totalChunks) {\n // Assemble in correct order (0, 1, 2, ...)\n const orderedChunks: Buffer[] = [];\n for (let i = 0; i < state.totalChunks; i++) {\n const chunk = state.chunks.get(i);\n if (!chunk) {\n throw new Error(`Missing chunk ${i} for upload`);\n }\n orderedChunks.push(chunk);\n }\n const assembled = Buffer.concat(orderedChunks);\n\n // Clean up state\n this.cleanup(uploadId);\n\n return { complete: true, assembled };\n }\n\n return { complete: false };\n }\n\n /**\n * Check if an upload is complete (all chunks received).\n * Returns false if the uploadId is unknown (cleaned up or never started).\n */\n isComplete(uploadId: string): boolean {\n const state = this.uploads.get(uploadId);\n if (!state) return false;\n return state.chunks.size === state.totalChunks;\n }\n\n /**\n * Clean up state for a given uploadId, clearing the timeout timer.\n */\n cleanup(uploadId: string): void {\n const state = this.uploads.get(uploadId);\n if (state) {\n clearTimeout(state.timer);\n this.uploads.delete(uploadId);\n }\n }\n\n /**\n * Clean up all active uploads and their timers.\n * Call this on node shutdown to prevent timer leaks.\n */\n destroyAll(): void {\n for (const [, state] of this.uploads) {\n clearTimeout(state.timer);\n }\n this.uploads.clear();\n }\n}\n","/**\n * Client-side helpers for uploading blobs to an Arweave DVM provider.\n *\n * - uploadBlob(): single-packet upload for blobs under the chunk threshold\n * - uploadBlobChunked(): multi-packet upload that splits large blobs into chunks\n *\n * Both use publishEvent() with the amount override (Story 7.6, D7-007).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { buildBlobStorageRequest } from '@toon-protocol/core';\nimport type { PublishEventResult } from '../create-node.js';\n\n/**\n * Minimal interface for publishing events. Matches ServiceNode.publishEvent().\n */\nexport interface PublishableNode {\n publishEvent(\n event: NostrEvent,\n options?: { destination: string; amount?: bigint }\n ): Promise<\n Pick<PublishEventResult, 'success' | 'eventId' | 'data' | 'message'>\n >;\n}\n\n/**\n * Options for single-packet blob upload.\n */\nexport interface UploadBlobOptions {\n /** MIME type of the blob (default: 'application/octet-stream'). */\n contentType?: string;\n /** Secret key for signing the event. */\n secretKey: Uint8Array;\n /** Price per byte in USDC micro-units for amount calculation. */\n pricePerByte?: bigint;\n}\n\n/**\n * Options for chunked blob upload.\n */\nexport interface UploadBlobChunkedOptions {\n /** Chunk size in bytes (default: 500_000, under 512KB threshold). */\n chunkSize?: number;\n /** MIME type of the blob (default: 'application/octet-stream'). */\n contentType?: string;\n /** Secret key for signing events. */\n secretKey: Uint8Array;\n /** Price per byte in USDC micro-units for amount calculation. */\n pricePerByte?: bigint;\n}\n\n/**\n * Upload a blob to an Arweave DVM provider in a single ILP packet.\n *\n * Uses publishEvent() with amount override (D7-007) to send the blob\n * and payment in one ILP PREPARE.\n *\n * @param node - The ServiceNode (or compatible) to publish through.\n * @param blob - The raw blob data to upload.\n * @param destination - The provider's ILP address.\n * @param options - Upload options including secretKey for signing.\n * @returns The Arweave transaction ID from the FULFILL data field.\n */\nexport async function uploadBlob(\n node: PublishableNode,\n blob: Buffer,\n destination: string,\n options: UploadBlobOptions\n): Promise<string> {\n const contentType = options.contentType ?? 'application/octet-stream';\n const pricePerByte = options.pricePerByte ?? 10n;\n const amount = BigInt(blob.length) * pricePerByte;\n\n const event = buildBlobStorageRequest(\n {\n blobData: blob,\n contentType,\n bid: amount.toString(),\n },\n options.secretKey\n );\n\n const result = await node.publishEvent(event, { destination, amount });\n\n if (!result.success) {\n throw new Error(`Blob upload failed: ${result.message ?? 'unknown error'}`);\n }\n\n // The tx ID is returned base64-encoded in the FULFILL data field.\n // Decode it to get the raw Arweave tx ID string.\n if (result.data) {\n return Buffer.from(result.data, 'base64').toString('utf-8');\n }\n return result.eventId;\n}\n\n/**\n * Upload a large blob to an Arweave DVM provider using chunked uploads.\n *\n * Splits the blob into chunks, each sent as a separate kind:5094 ILP PREPARE\n * with uploadId, chunkIndex, and totalChunks params. Each chunk carries its\n * own payment. The final chunk's FULFILL data contains the Arweave tx ID.\n *\n * @param node - The ServiceNode (or compatible) to publish through.\n * @param blob - The raw blob data to upload.\n * @param destination - The provider's ILP address.\n * @param options - Chunked upload options including secretKey for signing.\n * @returns The Arweave transaction ID from the final chunk's FULFILL data.\n */\nexport async function uploadBlobChunked(\n node: PublishableNode,\n blob: Buffer,\n destination: string,\n options: UploadBlobChunkedOptions\n): Promise<string> {\n if (blob.length === 0) {\n throw new Error('Cannot upload empty blob via chunked upload');\n }\n\n const chunkSize = options.chunkSize ?? 500_000;\n const contentType = options.contentType ?? 'application/octet-stream';\n const pricePerByte = options.pricePerByte ?? 10n;\n const uploadId = randomUUID();\n\n // Split blob into chunks\n const totalChunks = Math.ceil(blob.length / chunkSize);\n let lastResult: PublishEventResult | undefined;\n\n for (let i = 0; i < totalChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, blob.length);\n const chunkData = blob.subarray(start, end);\n const amount = BigInt(chunkData.length) * pricePerByte;\n\n const event = buildBlobStorageRequest(\n {\n blobData: Buffer.from(chunkData),\n contentType,\n bid: amount.toString(),\n params: [\n { key: 'uploadId', value: uploadId },\n { key: 'chunkIndex', value: String(i) },\n { key: 'totalChunks', value: String(totalChunks) },\n ],\n },\n options.secretKey\n );\n\n lastResult = await node.publishEvent(event, { destination, amount });\n\n if (!lastResult.success) {\n throw new Error(\n `Chunk ${i}/${totalChunks} upload failed: ${lastResult.message ?? 'unknown error'}`\n );\n }\n }\n\n // The final chunk's FULFILL data contains the Arweave tx ID (base64-encoded)\n // lastResult is guaranteed to be set since totalChunks >= 1\n if (!lastResult) {\n throw new Error('No chunks were uploaded');\n }\n if (lastResult.data) {\n return Buffer.from(lastResult.data, 'base64').toString('utf-8');\n }\n return lastResult.eventId;\n}\n","/**\n * Adaptive δ/W controller for rolling swaps (issue #83, rolling-swap spec §6).\n *\n * Two knobs, managed separately:\n * - `δ` (delta) — packet size in source micro-units — bounds *per-packet\n * pick-off risk*.\n * - `W` (window) — max unfulfilled packets in flight — bounds *timing /\n * liveness risk* and the worst-case unrecovered exposure `δ·W`.\n *\n * Normative behavior implemented here (spec §6):\n * - **The cap.** `delta_cap = ε / (v·τ)`, recomputed per packet from measured\n * state, and `δ ≤ delta_cap` always. δ and `delta_cap` are fractions of the\n * session's remaining notional; `v·τ` is the expected fractional rate drift\n * while one packet is in flight; ε is the per-packet slippage budget as a\n * fraction of the maker's advertised half-spread (default `ε = 0.5 ×\n * halfSpread`). ε is spread-denominated, never an absolute rate.\n * - **Inputs are measured, not trusted.** `v` is an EWMA of\n * `abs(R_i − R_{i−1})/R_{i−1}` per second read off the quote tape\n * (`PacketProgress.rate` / `rateTimestamp`, issue #82); `τ` is an EWMA of\n * observed round-trip times. Both update on every observed packet.\n * - **Asymmetric adjustment, one knob per step.** On a shrink signal (a\n * `stale_rate` reject, any other reject/verification failure, or realized\n * per-packet slip `> ε`): multiplicative — `δ ← max(δ_min, δ/2)`; if the\n * signal was a timeout/expiry, `W ← max(1, ⌈W/2⌉)` instead. On a clean\n * streak of `K = 16` consecutive fulfills: additive — `δ ← min(caps, δ +\n * δ_0)` or `W ← min(W_max, W + 1)`, alternating, never both in one step.\n * - **Cold start ramps.** With no persisted state for the tuple:\n * `δ_0 = min(delta_cap, notional/256, maker maxAmount)`, `W_0 = 1`. Until\n * the first shrink signal ever observed for the tuple, the δ widen step is\n * multiplicative (`δ ← min(caps, 2δ)` per clean streak) — slow-start —\n * dropping to additive permanently after the first loss event.\n * - **State is per-(chain, maker, pair) and persisted** via a pluggable\n * {@link SwapControllerStateStore} (the SDK is isomorphic; the JSON-file\n * implementation is Node-only and lazily imports `node:fs`).\n *\n * INVARIANT (spec §5): the controller is an *efficiency* mechanism only. It\n * never sees, computes, or relaxes the `minExchangeRate` floor — the floor\n * check in `stream-swap.ts` runs before the controller observes anything and\n * consults nothing but the floor itself. A calm (or adversarially painted)\n * tape can widen δ up to the caps, but can never worsen the sender's declared\n * worst case. NOTE: the adversarial-quote-tape question (toon-meta#146 — a\n * maker quoting calm to coax δ wide, then gapping one large packet) is open;\n * its resolution may tighten the cap logic here. The floor plus the absolute\n * `maxPacketAmount` cap are the uncompromising backstops in the meantime.\n *\n * @module\n */\n\nimport type { SwapPair } from '@toon-protocol/core';\nimport { ToonError } from '@toon-protocol/core';\n\nimport { applyRate } from './swap-handler.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Error thrown for invalid adaptive-controller configuration or a\n * persistence-layer failure surfaced through {@link SwapControllerStateStore}.\n */\nexport class SwapControllerError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'SWAP_CONTROLLER_ERROR', cause);\n this.name = 'SwapControllerError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Persisted state\n// ---------------------------------------------------------------------------\n\n/**\n * Persisted per-(chain, maker, pair) controller state (spec §6 value shape\n * `{delta, W, vEwma, tauEwma, cleanStreak, everShrunk, updatedAt}` plus the\n * additive `lastWidened` alternation marker).\n *\n * JSON-serializable by construction: `delta` is a decimal string because\n * `bigint` does not survive `JSON.stringify`.\n */\nexport interface SwapControllerState {\n /** Schema version for forward migration. Currently `1`. */\n v: 1;\n /**\n * Current ramp value of δ in source micro-units (decimal string).\n * `'0'` means \"not yet initialized\" — the first `nextDelta()` call seeds it\n * with the cold-start `δ_0`.\n */\n delta: string;\n /** In-flight window W: max unfulfilled packets outstanding. Always ≥ 1. */\n W: number;\n /** EWMA of `abs(ΔR)/R` per second, measured from the quote tape. */\n vEwma: number;\n /** EWMA of observed packet round-trip time, in seconds. */\n tauEwma: number;\n /** Consecutive clean fulfills since the last knob adjustment. */\n cleanStreak: number;\n /**\n * True once ANY shrink signal has ever been observed for this tuple.\n * Ends the multiplicative slow-start widen ramp permanently (spec §6).\n */\n everShrunk: boolean;\n /** Which knob the most recent widen step adjusted (alternation marker). */\n lastWidened: 'delta' | 'window';\n /** Unix ms of the last state mutation. */\n updatedAt: number;\n}\n\nconst DECIMAL_UINT_REGEX = /^(0|[1-9]\\d*)$/;\n\n/** Runtime shape check for a (possibly foreign) persisted state blob. */\nexport function isSwapControllerState(\n value: unknown\n): value is SwapControllerState {\n if (!value || typeof value !== 'object') return false;\n const s = value as Record<string, unknown>;\n return (\n s['v'] === 1 &&\n typeof s['delta'] === 'string' &&\n DECIMAL_UINT_REGEX.test(s['delta'] as string) &&\n typeof s['W'] === 'number' &&\n Number.isInteger(s['W']) &&\n (s['W'] as number) >= 1 &&\n typeof s['vEwma'] === 'number' &&\n Number.isFinite(s['vEwma']) &&\n (s['vEwma'] as number) >= 0 &&\n typeof s['tauEwma'] === 'number' &&\n Number.isFinite(s['tauEwma']) &&\n (s['tauEwma'] as number) >= 0 &&\n typeof s['cleanStreak'] === 'number' &&\n Number.isInteger(s['cleanStreak']) &&\n (s['cleanStreak'] as number) >= 0 &&\n typeof s['everShrunk'] === 'boolean' &&\n (s['lastWidened'] === 'delta' || s['lastWidened'] === 'window') &&\n typeof s['updatedAt'] === 'number'\n );\n}\n\n/**\n * Canonical persistence key for a controller tuple (spec §6):\n * `${chain}:${makerPubkey}:${from}:${to}` with `from`/`to` as\n * `assetCode@chain` so cross-chain pairs sharing an asset code stay distinct.\n *\n * The leading `chain` segment is the SOURCE chain — per-tuple state is how\n * the same code runs fast on Base and cautious on Mina.\n */\nexport function swapControllerStateKey(params: {\n makerPubkey: string;\n pair: SwapPair;\n}): string {\n const { makerPubkey, pair } = params;\n const from = `${pair.from.assetCode}@${pair.from.chain}`;\n const to = `${pair.to.assetCode}@${pair.to.chain}`;\n return `${pair.from.chain}:${makerPubkey}:${from}:${to}`;\n}\n\n// ---------------------------------------------------------------------------\n// Pluggable state store\n// ---------------------------------------------------------------------------\n\n/**\n * Pluggable persistence seam for controller state, keyed by\n * {@link swapControllerStateKey}. The SDK is isomorphic, so the interface is\n * environment-neutral (same pattern as `WorkflowEventStore`); pick\n * {@link JsonFileSwapControllerStateStore} on Node or supply your own\n * (e.g. IndexedDB / daemon-side store in toon-client).\n */\nexport interface SwapControllerStateStore {\n load(key: string): Promise<SwapControllerState | undefined>;\n save(key: string, state: SwapControllerState): Promise<void>;\n}\n\n/** Volatile in-memory store — the default when no store is configured. */\nexport class InMemorySwapControllerStateStore implements SwapControllerStateStore {\n private readonly states = new Map<string, SwapControllerState>();\n\n async load(key: string): Promise<SwapControllerState | undefined> {\n const hit = this.states.get(key);\n return hit === undefined ? undefined : { ...hit };\n }\n\n async save(key: string, state: SwapControllerState): Promise<void> {\n this.states.set(key, { ...state });\n }\n}\n\n/**\n * Node-only JSON-file store (the toon-client `JsonFileChannelStore` pattern —\n * controller state persists beside the channel store). One JSON file holds a\n * `{ [key]: SwapControllerState }` map; writes are atomic\n * (temp-file + rename). `node:fs` / `node:path` are imported lazily so\n * bundling this module for the browser stays safe as long as the class is\n * not instantiated there.\n */\nexport class JsonFileSwapControllerStateStore implements SwapControllerStateStore {\n constructor(private readonly filePath: string) {\n if (typeof filePath !== 'string' || filePath.length === 0) {\n throw new SwapControllerError(\n 'JsonFileSwapControllerStateStore requires a non-empty filePath'\n );\n }\n }\n\n private async readAll(): Promise<Record<string, unknown>> {\n const fs = await import('node:fs/promises');\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new SwapControllerError(\n `Failed to read controller state file ${this.filePath}`,\n err instanceof Error ? err : undefined\n );\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Corrupt file → treat as empty (cold start) rather than bricking every\n // future swap on this box. The next save rewrites it whole.\n }\n return {};\n }\n\n async load(key: string): Promise<SwapControllerState | undefined> {\n const all = await this.readAll();\n const candidate = all[key];\n return isSwapControllerState(candidate) ? { ...candidate } : undefined;\n }\n\n async save(key: string, state: SwapControllerState): Promise<void> {\n const fs = await import('node:fs/promises');\n const path = await import('node:path');\n const all = await this.readAll();\n all[key] = { ...state };\n const dir = path.dirname(this.filePath);\n await fs.mkdir(dir, { recursive: true });\n const tmp = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;\n await fs.writeFile(tmp, JSON.stringify(all, null, 2), 'utf8');\n await fs.rename(tmp, this.filePath);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Observations\n// ---------------------------------------------------------------------------\n\n/**\n * Resolution class of one packet, as measured by the sender (spec §6):\n *\n * - `'fulfill'` — packet fulfilled and its claim accepted. Clean unless the\n * realized slip exceeds ε (computed internally from `rate` +\n * `sourceAmount` + `targetAmount`), in which case it is a shrink signal.\n * - `'reject-stale'` — maker staleness reject (`T99 stale_rate`, spec §4).\n * Shrink signal → δ halves.\n * - `'reject'` — any other reject or verification failure (R5-class: floor\n * breach, recipient substitution, bad claim). Shrink signal → δ halves.\n * - `'timeout'` — packet expiry / transport timeout. Shrink signal → W\n * halves (timing risk, not pricing risk).\n * - `'error'` — transport or decode error. Shrink signal → δ halves.\n */\nexport type PacketResolution =\n | 'fulfill'\n | 'reject-stale'\n | 'reject'\n | 'timeout'\n | 'error';\n\n/** One per-packet observation fed to {@link AdaptiveDeltaController.observe}. */\nexport interface PacketObservation {\n /** Resolution class for this packet (see {@link PacketResolution}). */\n resolution: PacketResolution;\n /** Measured round-trip time for this packet in ms (send → resolve). */\n rttMs?: number;\n /** Quote-tape rate `R_i` for this packet (decimal string, issue #82). */\n rate?: string;\n /** Unix ms when the maker's rate source produced {@link rate}. */\n rateTimestamp?: number;\n /** Source amount sent for this packet (micro-units). Used for slip. */\n sourceAmount?: bigint;\n /** Delivered target amount (micro-units). Used for slip. */\n targetAmount?: bigint;\n /**\n * Remaining session notional AFTER this packet (source micro-units).\n * When provided, widen steps are additionally clamped to the current\n * `delta_cap` fraction of it (spec §6 `δ ← min(delta_cap, δ + δ_0)`).\n * `nextDelta()` re-enforces the cap at issuance regardless.\n */\n remaining?: bigint;\n}\n\n// ---------------------------------------------------------------------------\n// The controller\n// ---------------------------------------------------------------------------\n\n/**\n * The seam `streamSwap` consumes (kept structural so alternative controller\n * implementations — or a test double — can be plugged in).\n */\nexport interface StreamSwapAdaptiveController {\n /**\n * Decide the size of the next packet, in source micro-units, given the\n * session's remaining notional. MUST return a value in `[1, remaining]`\n * (callers clamp defensively). Enforces `δ ≤ delta_cap` at issuance.\n */\n nextDelta(remaining: bigint): bigint;\n /** Current in-flight window W (max unfulfilled packets outstanding, ≥ 1). */\n readonly window: number;\n /** Feed one packet observation. May persist state (hence async). */\n observe(observation: PacketObservation): void | Promise<void>;\n}\n\n/** Configuration for {@link AdaptiveDeltaController.create}. */\nexport interface AdaptiveDeltaControllerConfig {\n /** Maker's 64-char hex pubkey — part of the persistence key. */\n makerPubkey: string;\n /** The pair being executed — chain + asset parts of the persistence key. */\n pair: SwapPair;\n /**\n * Maker's advertised two-sided spread as a fraction (e.g. `0.004` = 40 bps).\n * ε is denominated off this — NEVER an absolute rate (spec §6): the\n * half-spread self-calibrates ε per chain and per maker. Today the spread\n * is caller-supplied (from the maker's board / RFQ response once toon#145's\n * RFQ lands); there is deliberately no default — an invented spread would\n * rot and silently mis-size ε.\n */\n advertisedSpread: number;\n /**\n * ε as a fraction of the advertised HALF-spread. Default `0.5`\n * (spec §6 default `ε = 0.5 × halfSpread`).\n */\n epsilonHalfSpreadFraction?: number;\n /**\n * Absolute per-packet ceiling in source micro-units (the maker's advertised\n * `maxAmount`). Applied to δ at issuance AND to every widen step — the\n * uncompromising absolute cap alongside the measured `v·τ` bound.\n */\n maxPacketAmount?: bigint;\n /** Floor on δ in source micro-units (`δ_min`). Default `1n`. */\n minPacketAmount?: bigint;\n /** Ceiling on W (`W_max`). Default `8`. */\n maxWindow?: number;\n /** Clean-fulfill streak length K per widen step. Default `16` (spec §6). */\n cleanStreakLength?: number;\n /**\n * Cold-start divisor: `δ_0 = notional / coldStartDivisor` (further clamped\n * by `delta_cap` and `maxPacketAmount`). Default `256` (spec §6).\n */\n coldStartDivisor?: number;\n /** EWMA smoothing factor α for both `v` and `τ`, in (0, 1]. Default `0.2`. */\n ewmaAlpha?: number;\n /**\n * Pluggable persistence. Default: a fresh volatile\n * {@link InMemorySwapControllerStateStore} (state survives the session\n * only). Pass {@link JsonFileSwapControllerStateStore} (Node) or a custom\n * store to persist ramp/trust across swaps.\n */\n store?: SwapControllerStateStore;\n /** Injectable clock for deterministic tests. Default `Date.now`. */\n now?: () => number;\n}\n\nconst DEFAULT_EPSILON_HALF_SPREAD_FRACTION = 0.5;\nconst DEFAULT_MAX_WINDOW = 8;\nconst DEFAULT_CLEAN_STREAK_LENGTH = 16;\nconst DEFAULT_COLD_START_DIVISOR = 256;\nconst DEFAULT_EWMA_ALPHA = 0.2;\n/** Fixed-point scale for applying the fractional delta_cap to a bigint. */\nconst CAP_SCALE = 1_000_000_000_000n; // 1e12\nconst CAP_SCALE_NUM = 1e12;\n\n/** Parse a positive decimal-string rate to a JS number (measurement-grade). */\nfunction rateToNumber(rate: string): number {\n const n = Number(rate);\n return Number.isFinite(n) && n > 0 ? n : NaN;\n}\n\n/**\n * Per-(chain, maker, pair) adaptive δ/W controller (spec §6). Construct via\n * {@link AdaptiveDeltaController.create} (async: loads persisted state).\n *\n * Concurrency note: one instance drives one swap session. Running two\n * concurrent sessions against the same tuple is last-write-wins on the\n * persisted state (same as concurrent channel use).\n */\nexport class AdaptiveDeltaController implements StreamSwapAdaptiveController {\n /** Persistence key for this tuple (see {@link swapControllerStateKey}). */\n readonly key: string;\n\n private readonly store: SwapControllerStateStore;\n private readonly pair: SwapPair;\n private readonly epsilon: number;\n private readonly maxPacketAmount: bigint | undefined;\n private readonly minPacketAmount: bigint;\n private readonly maxWindow: number;\n private readonly cleanStreakLength: number;\n private readonly coldStartDivisor: bigint;\n private readonly alpha: number;\n private readonly now: () => number;\n\n private readonly stateInternal: SwapControllerState;\n /** δ in-memory as bigint (mirrors `stateInternal.delta`). */\n private delta: bigint;\n /** Cold-start δ_0 — also the additive widen increment. Set on first nextDelta(). */\n private delta0: bigint | null = null;\n /** Previous tape entry for the per-second volatility measurement. */\n private lastRate: number | null = null;\n private lastRateTimestamp: number | null = null;\n\n private constructor(\n config: AdaptiveDeltaControllerConfig,\n key: string,\n store: SwapControllerStateStore,\n state: SwapControllerState\n ) {\n this.key = key;\n this.store = store;\n this.pair = config.pair;\n const epsilonFraction =\n config.epsilonHalfSpreadFraction ?? DEFAULT_EPSILON_HALF_SPREAD_FRACTION;\n this.epsilon = epsilonFraction * (config.advertisedSpread / 2);\n this.maxPacketAmount = config.maxPacketAmount;\n this.minPacketAmount = config.minPacketAmount ?? 1n;\n this.maxWindow = config.maxWindow ?? DEFAULT_MAX_WINDOW;\n this.cleanStreakLength =\n config.cleanStreakLength ?? DEFAULT_CLEAN_STREAK_LENGTH;\n this.coldStartDivisor = BigInt(\n config.coldStartDivisor ?? DEFAULT_COLD_START_DIVISOR\n );\n this.alpha = config.ewmaAlpha ?? DEFAULT_EWMA_ALPHA;\n this.now = config.now ?? Date.now;\n this.stateInternal = state;\n this.delta = BigInt(state.delta);\n }\n\n /**\n * Create a controller for one swap session: loads the persisted state for\n * the tuple from the store, or starts cold (`δ` unset until the first\n * `nextDelta()`, `W = 1`).\n */\n static async create(\n config: AdaptiveDeltaControllerConfig\n ): Promise<AdaptiveDeltaController> {\n validateConfig(config);\n const key = swapControllerStateKey({\n makerPubkey: config.makerPubkey,\n pair: config.pair,\n });\n const store = config.store ?? new InMemorySwapControllerStateStore();\n const now = config.now ?? Date.now;\n const loaded = await store.load(key);\n const state: SwapControllerState = isSwapControllerState(loaded)\n ? loaded\n : {\n v: 1,\n delta: '0',\n W: 1,\n vEwma: 0,\n tauEwma: 0,\n cleanStreak: 0,\n everShrunk: false,\n // 'window' so the FIRST widen step hits δ (alternation starts on δ).\n lastWidened: 'window',\n updatedAt: now(),\n };\n return new AdaptiveDeltaController(config, key, store, state);\n }\n\n /** Defensive snapshot of the current controller state. */\n get state(): SwapControllerState {\n return { ...this.stateInternal, delta: this.delta.toString() };\n }\n\n /** Current in-flight window W. */\n get window(): number {\n return this.stateInternal.W;\n }\n\n /**\n * Current `delta_cap = ε/(v·τ)` as a fraction of remaining notional.\n * `Infinity` while `v·τ` has no measurement yet (cold tape) — the\n * cold-start `δ_0` and absolute caps bound δ in that regime.\n */\n get deltaCapFraction(): number {\n const vTau = this.stateInternal.vEwma * this.stateInternal.tauEwma;\n if (!(vTau > 0)) return Infinity;\n return this.epsilon / vTau;\n }\n\n /** `delta_cap` in absolute source micro-units for a given remaining notional. */\n private deltaCapAbsolute(remaining: bigint): bigint {\n const frac = this.deltaCapFraction;\n if (!Number.isFinite(frac) || frac >= 1) return remaining;\n if (frac <= 0) return this.minPacketAmount;\n const scaled = BigInt(Math.floor(frac * CAP_SCALE_NUM));\n return (remaining * scaled) / CAP_SCALE;\n }\n\n /**\n * Decide the next packet size (source micro-units) for a session with\n * `remaining` notional left. Enforces, in order: the measured\n * `delta_cap = ε/(v·τ)` bound, the absolute `maxPacketAmount` cap, the\n * remaining notional, and the `minPacketAmount` floor. Seeds the\n * cold-start `δ_0 = min(delta_cap, notional/256, maxPacketAmount)` on\n * first call.\n */\n nextDelta(remaining: bigint): bigint {\n if (typeof remaining !== 'bigint' || remaining <= 0n) return 0n;\n\n if (this.delta0 === null) {\n // δ_0 is derived from the FIRST observed remaining (the session\n // notional) and reused as the additive widen increment all session.\n let d0 = remaining / this.coldStartDivisor;\n const capAbs = this.deltaCapAbsolute(remaining);\n if (d0 > capAbs) d0 = capAbs;\n if (this.maxPacketAmount !== undefined && d0 > this.maxPacketAmount) {\n d0 = this.maxPacketAmount;\n }\n if (d0 < this.minPacketAmount) d0 = this.minPacketAmount;\n this.delta0 = d0;\n if (this.delta <= 0n) {\n // Cold start: seed the ramp value. Persisted on the next observe().\n this.delta = d0;\n this.stateInternal.delta = d0.toString();\n }\n }\n\n let d = this.delta;\n const capAbs = this.deltaCapAbsolute(remaining);\n if (d > capAbs) d = capAbs;\n if (this.maxPacketAmount !== undefined && d > this.maxPacketAmount) {\n d = this.maxPacketAmount;\n }\n if (d > remaining) d = remaining;\n const floor =\n this.minPacketAmount < remaining ? this.minPacketAmount : remaining;\n if (d < floor) d = floor;\n return d;\n }\n\n /**\n * Feed one packet observation: updates the measured EWMAs (`v`, `τ`),\n * applies at most ONE knob adjustment (asymmetric: multiplicative shrink /\n * additive widen), and persists the state.\n */\n async observe(observation: PacketObservation): Promise<void> {\n const s = this.stateInternal;\n\n // --- Measurements (not knobs): τ and v EWMAs update on every packet ---\n if (\n typeof observation.rttMs === 'number' &&\n Number.isFinite(observation.rttMs) &&\n observation.rttMs > 0\n ) {\n const tauSec = observation.rttMs / 1000;\n s.tauEwma =\n s.tauEwma === 0\n ? tauSec\n : this.alpha * tauSec + (1 - this.alpha) * s.tauEwma;\n }\n if (\n typeof observation.rate === 'string' &&\n typeof observation.rateTimestamp === 'number' &&\n Number.isFinite(observation.rateTimestamp)\n ) {\n const r = rateToNumber(observation.rate);\n if (!Number.isNaN(r)) {\n if (\n this.lastRate !== null &&\n this.lastRateTimestamp !== null &&\n observation.rateTimestamp > this.lastRateTimestamp\n ) {\n const dtSec =\n (observation.rateTimestamp - this.lastRateTimestamp) / 1000;\n const inst = Math.abs(r - this.lastRate) / this.lastRate / dtSec;\n if (Number.isFinite(inst)) {\n s.vEwma =\n s.vEwma === 0\n ? inst\n : this.alpha * inst + (1 - this.alpha) * s.vEwma;\n }\n }\n if (\n this.lastRateTimestamp === null ||\n observation.rateTimestamp >= this.lastRateTimestamp\n ) {\n this.lastRate = r;\n this.lastRateTimestamp = observation.rateTimestamp;\n }\n }\n }\n\n // --- Classify: shrink signal? (spec §6) ---\n const slip = this.realizedSlip(observation);\n const isShrink =\n observation.resolution !== 'fulfill' || slip > this.epsilon;\n\n // --- One knob per step ---\n if (isShrink) {\n if (observation.resolution === 'timeout') {\n // Timing/liveness signal → the window knob: W ← max(1, ⌈W/2⌉).\n s.W = Math.max(1, Math.ceil(s.W / 2));\n } else {\n // Pricing signal → the size knob: δ ← max(δ_min, δ/2).\n let d = this.delta / 2n;\n if (d < this.minPacketAmount) d = this.minPacketAmount;\n this.delta = d;\n s.delta = d.toString();\n }\n s.cleanStreak = 0;\n s.everShrunk = true;\n } else {\n s.cleanStreak += 1;\n if (s.cleanStreak >= this.cleanStreakLength) {\n s.cleanStreak = 0;\n const knob: 'delta' | 'window' =\n s.lastWidened === 'delta' ? 'window' : 'delta';\n if (knob === 'window') {\n s.W = Math.min(this.maxWindow, s.W + 1);\n } else {\n // Slow-start (never shrunk for this tuple): multiplicative ×2.\n // After the first loss event ever: additive +δ_0, permanently.\n const increment = this.delta0 ?? this.minPacketAmount;\n let d = s.everShrunk ? this.delta + increment : this.delta * 2n;\n if (this.maxPacketAmount !== undefined && d > this.maxPacketAmount) {\n d = this.maxPacketAmount;\n }\n if (\n observation.remaining !== undefined &&\n observation.remaining > 0n\n ) {\n const capAbs = this.deltaCapAbsolute(observation.remaining);\n if (d > capAbs) d = capAbs;\n }\n if (d < this.minPacketAmount) d = this.minPacketAmount;\n // Widen must never shrink (cap clamps can undershoot current δ).\n if (d < this.delta) d = this.delta;\n this.delta = d;\n s.delta = d.toString();\n }\n s.lastWidened = knob;\n }\n }\n\n s.updatedAt = this.now();\n await this.store.save(this.key, { ...s });\n }\n\n /**\n * Realized per-packet slip as a fraction: shortfall of the delivered\n * target amount vs the amount implied by the packet's own tape rate `R_i`\n * (spec §7.1 `Δcumulative` vs `⌊δ · R_i⌋` cross-check). `0` when the\n * inputs are unavailable or delivery met the tape.\n */\n private realizedSlip(observation: PacketObservation): number {\n if (\n observation.resolution !== 'fulfill' ||\n observation.rate === undefined ||\n observation.sourceAmount === undefined ||\n observation.targetAmount === undefined ||\n observation.sourceAmount <= 0n\n ) {\n return 0;\n }\n let expected: bigint;\n try {\n expected = applyRate({\n sourceAmount: observation.sourceAmount,\n fromScale: this.pair.from.assetScale,\n toScale: this.pair.to.assetScale,\n rate: observation.rate,\n });\n } catch {\n return 0;\n }\n if (expected <= 0n || observation.targetAmount >= expected) return 0;\n const shortfall = expected - observation.targetAmount;\n return Number((shortfall * 1_000_000n) / expected) / 1_000_000;\n }\n}\n\nfunction validateConfig(config: AdaptiveDeltaControllerConfig): void {\n if (\n typeof config.makerPubkey !== 'string' ||\n config.makerPubkey.length === 0\n ) {\n throw new SwapControllerError('makerPubkey must be a non-empty string');\n }\n if (\n !config.pair ||\n typeof config.pair !== 'object' ||\n !config.pair.from ||\n !config.pair.to ||\n typeof config.pair.from.chain !== 'string' ||\n typeof config.pair.to.chain !== 'string'\n ) {\n throw new SwapControllerError(\n 'pair must be a SwapPair with from/to chain descriptors'\n );\n }\n if (\n typeof config.advertisedSpread !== 'number' ||\n !Number.isFinite(config.advertisedSpread) ||\n config.advertisedSpread <= 0\n ) {\n throw new SwapControllerError(\n `advertisedSpread must be a positive finite fraction, got ${String(\n config.advertisedSpread\n )}`\n );\n }\n if (\n config.epsilonHalfSpreadFraction !== undefined &&\n (typeof config.epsilonHalfSpreadFraction !== 'number' ||\n !Number.isFinite(config.epsilonHalfSpreadFraction) ||\n config.epsilonHalfSpreadFraction <= 0)\n ) {\n throw new SwapControllerError(\n 'epsilonHalfSpreadFraction must be a positive finite number'\n );\n }\n if (\n config.maxPacketAmount !== undefined &&\n (typeof config.maxPacketAmount !== 'bigint' || config.maxPacketAmount <= 0n)\n ) {\n throw new SwapControllerError('maxPacketAmount must be a positive bigint');\n }\n if (\n config.minPacketAmount !== undefined &&\n (typeof config.minPacketAmount !== 'bigint' || config.minPacketAmount <= 0n)\n ) {\n throw new SwapControllerError('minPacketAmount must be a positive bigint');\n }\n if (\n config.minPacketAmount !== undefined &&\n config.maxPacketAmount !== undefined &&\n config.minPacketAmount > config.maxPacketAmount\n ) {\n throw new SwapControllerError('minPacketAmount must be <= maxPacketAmount');\n }\n if (\n config.maxWindow !== undefined &&\n (!Number.isInteger(config.maxWindow) || config.maxWindow < 1)\n ) {\n throw new SwapControllerError('maxWindow must be an integer >= 1');\n }\n if (\n config.cleanStreakLength !== undefined &&\n (!Number.isInteger(config.cleanStreakLength) ||\n config.cleanStreakLength < 1)\n ) {\n throw new SwapControllerError('cleanStreakLength must be an integer >= 1');\n }\n if (\n config.coldStartDivisor !== undefined &&\n (!Number.isInteger(config.coldStartDivisor) || config.coldStartDivisor < 1)\n ) {\n throw new SwapControllerError('coldStartDivisor must be an integer >= 1');\n }\n if (\n config.ewmaAlpha !== undefined &&\n (typeof config.ewmaAlpha !== 'number' ||\n !(config.ewmaAlpha > 0) ||\n config.ewmaAlpha > 1)\n ) {\n throw new SwapControllerError('ewmaAlpha must be in (0, 1]');\n }\n}\n","/**\n * EVM-specific settlement tx construction + signature verification\n * (Story 12.6 AC-7).\n *\n * @module\n * @since 12.6\n * @see _bmad-output/implementation-artifacts/12-6-build-settlement-tx.md\n */\n\nimport { keccak_256 } from '@noble/hashes/sha3.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport { recoverEvmSigner } from '@toon-protocol/settlement-digest';\n\nimport { SettlementTxError } from '../errors.js';\nimport type { AccumulatedClaim } from '../stream-swap.js';\nimport {\n balanceProofHashEvm,\n bigintToBytes32BE,\n concatBytes,\n hexToBytes,\n} from './hashes.js';\nimport type { SwapSignerConfig, SettlementBundle } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Function selector + event signature\n// ---------------------------------------------------------------------------\n\n// TODO(12.6 follow-up): verify against the TokenNetwork contract in\n// ../connector/packages/contracts/. Story 12.8 E2E will catch drift if the\n// real contract uses a different name/arity.\nconst EVM_SETTLEMENT_FUNCTION_SIGNATURE =\n 'updateBalance(bytes32,uint256,uint256,address,bytes)';\nconst EVM_SETTLEMENT_EVENT_SIGNATURE =\n 'SettlementSucceeded(bytes32,uint256,uint256,address)';\n\n/** 4-byte keccak256 function selector for the settlement call. */\nexport const EVM_SETTLEMENT_FUNCTION_SELECTOR: Uint8Array = keccak_256(\n new TextEncoder().encode(EVM_SETTLEMENT_FUNCTION_SIGNATURE)\n).slice(0, 4);\n\nconst EVM_SETTLEMENT_EVENT_TOPIC: string =\n '0x' +\n bytesToHex(\n keccak_256(new TextEncoder().encode(EVM_SETTLEMENT_EVENT_SIGNATURE))\n );\n\n// ---------------------------------------------------------------------------\n// Recover EVM signer address\n// ---------------------------------------------------------------------------\n\n/**\n * Recover the EVM signer address from an `AccumulatedClaim`'s 65-byte\n * `r||s||v` signature. Returns lowercase `0x`-prefixed 40-hex-char address.\n *\n * Reconstructs the v2 EIP-712 balance-proof digest via `balanceProofHashEvm`\n * using the claim's settlement-context fields (channelId, cumulativeAmount,\n * nonce, recipient) PLUS the EIP-712 domain inputs (`chainId`,\n * `verifyingContract`) and recovers the secp256k1 public key. `chainId` +\n * `verifyingContract` are REQUIRED by the v2 digest (refs connector#324\n * finding #1) — a signature is valid on exactly one (chain, contract) pair.\n *\n * @param chainId settlement chain id (e.g. `8453` for Base).\n * @param verifyingContract deployed `RollingSwapChannel` address (0x + 40 hex).\n * @throws {SettlementTxError} INVALID_SIGNATURE_LENGTH / INVALID_SIGNATURE_V /\n * MISSING_SETTLEMENT_METADATA / INVALID_INPUT.\n * @since 12.6\n */\nexport function recoverEvmSignerAddress(\n claim: AccumulatedClaim,\n chainId: number,\n verifyingContract: string\n): string {\n if (\n typeof chainId !== 'number' ||\n !Number.isInteger(chainId) ||\n chainId <= 0\n ) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `EVM v2 digest requires a positive integer chainId, got ${chainId}`\n );\n }\n if (!verifyingContract) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n 'EVM v2 digest requires verifyingContract (RollingSwapChannel address)'\n );\n }\n if (\n claim.channelId === undefined ||\n claim.cumulativeAmount === undefined ||\n claim.nonce === undefined ||\n claim.recipient === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'Claim missing channelId/cumulativeAmount/nonce/recipient for EVM signer recovery'\n );\n }\n if (claim.claimBytes.length !== 65) {\n throw new SettlementTxError(\n 'INVALID_SIGNATURE_LENGTH',\n `EVM signature must be 65 bytes (r||s||v), got ${claim.claimBytes.length}`\n );\n }\n const v = claim.claimBytes[64];\n if (v !== 27 && v !== 28) {\n throw new SettlementTxError(\n 'INVALID_SIGNATURE_V',\n `EVM signature v must be 27 or 28, got ${v}`\n );\n }\n try {\n const channelIdBytes = hexToBytes(claim.channelId);\n const recipientBytes = hexToBytes(claim.recipient);\n const verifyingContractBytes = hexToBytes(verifyingContract);\n if (channelIdBytes.length !== 32) {\n throw new Error(\n `channelId must be 32 bytes (got ${channelIdBytes.length})`\n );\n }\n if (recipientBytes.length !== 20) {\n throw new Error(\n `recipient must be 20 bytes (got ${recipientBytes.length})`\n );\n }\n if (verifyingContractBytes.length !== 20) {\n throw new Error(\n `verifyingContract must be 20 bytes (got ${verifyingContractBytes.length})`\n );\n }\n const msgHash = balanceProofHashEvm(\n channelIdBytes,\n BigInt(claim.cumulativeAmount),\n BigInt(claim.nonce),\n recipientBytes,\n BigInt(chainId),\n verifyingContractBytes\n );\n // Delegate the secp256k1 recover + keccak address derivation to the\n // dependency-light leaf (byte-identical to the previous inline impl).\n // Length/`v` were already validated above; the leaf re-checks harmlessly.\n return recoverEvmSigner(msgHash, claim.claimBytes);\n } catch (err) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `EVM signer recovery failed: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err }\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Minimal ABI encoder (bytes32, uint256, uint256, address, bytes)\n// ---------------------------------------------------------------------------\n\nfunction padLeft32(bytes: Uint8Array): Uint8Array {\n if (bytes.length > 32) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `cannot pad bytes of length ${bytes.length} to 32`\n );\n }\n const out = new Uint8Array(32);\n out.set(bytes, 32 - bytes.length);\n return out;\n}\n\nfunction padRight32(bytes: Uint8Array): Uint8Array {\n const padded = Math.ceil(bytes.length / 32) * 32;\n const out = new Uint8Array(padded);\n out.set(bytes, 0);\n return out;\n}\n\nfunction encodeUpdateBalanceCallData(\n channelIdBytes: Uint8Array,\n cumulativeAmount: bigint,\n nonce: bigint,\n recipientBytes: Uint8Array,\n signature: Uint8Array\n): Uint8Array {\n if (channelIdBytes.length !== 32) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `channelId must be 32 bytes (got ${channelIdBytes.length})`\n );\n }\n if (recipientBytes.length !== 20) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `recipient must be 20 bytes (got ${recipientBytes.length})`\n );\n }\n\n // Solidity ABI layout for `(bytes32, uint256, uint256, address, bytes)`:\n // head = channelId(32) || cumulative(32) || nonce(32) || recipient(32) || offset(32) = 160 bytes\n // tail = sigLen(32) || sigPadded(ceil(sigLen/32)*32)\n // Only `bytes` is dynamic; its head slot is the offset (160) into the\n // head+tail region (selector is NOT counted in the offset per spec).\n const channelIdWord = channelIdBytes; // already 32\n const cumulativeWord = bigintToBytes32BE(cumulativeAmount);\n const nonceWord = bigintToBytes32BE(nonce);\n const recipientWord = padLeft32(recipientBytes);\n const offsetWord = bigintToBytes32BE(160n);\n const sigLenWord = bigintToBytes32BE(BigInt(signature.length));\n const sigPadded = padRight32(signature);\n\n return concatBytes(\n EVM_SETTLEMENT_FUNCTION_SELECTOR,\n channelIdWord,\n cumulativeWord,\n nonceWord,\n recipientWord,\n offsetWord,\n sigLenWord,\n sigPadded\n );\n}\n\n// ---------------------------------------------------------------------------\n// Minimal RLP encoder (for EIP-155 unsigned tx)\n// ---------------------------------------------------------------------------\n\nfunction rlpEncodeBytes(bytes: Uint8Array): Uint8Array {\n if (bytes.length === 1 && bytes[0] !== undefined && bytes[0] < 0x80) {\n return bytes;\n }\n if (bytes.length < 56) {\n return concatBytes(new Uint8Array([0x80 + bytes.length]), bytes);\n }\n const lenBytes = bigintToMinimalBytes(BigInt(bytes.length));\n return concatBytes(new Uint8Array([0xb7 + lenBytes.length]), lenBytes, bytes);\n}\n\nfunction rlpEncodeList(items: Uint8Array[]): Uint8Array {\n const payload = concatBytes(...items);\n if (payload.length < 56) {\n return concatBytes(new Uint8Array([0xc0 + payload.length]), payload);\n }\n const lenBytes = bigintToMinimalBytes(BigInt(payload.length));\n return concatBytes(\n new Uint8Array([0xf7 + lenBytes.length]),\n lenBytes,\n payload\n );\n}\n\nfunction bigintToMinimalBytes(x: bigint): Uint8Array {\n if (x < 0n) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'bigintToMinimalBytes: negative input'\n );\n }\n if (x === 0n) return new Uint8Array(0);\n let hex = x.toString(16);\n if (hex.length % 2) hex = '0' + hex;\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n\nfunction rlpEncodeUint(x: bigint): Uint8Array {\n return rlpEncodeBytes(bigintToMinimalBytes(x));\n}\n\n/**\n * RLP-encode an EIP-155 unsigned transaction:\n * [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]\n */\nfunction rlpEncodeUnsignedTx(params: {\n nonce: bigint;\n gasPrice: bigint;\n gasLimit: bigint;\n to: Uint8Array; // 20 bytes\n value: bigint;\n data: Uint8Array;\n chainId: bigint;\n}): Uint8Array {\n return rlpEncodeList([\n rlpEncodeUint(params.nonce),\n rlpEncodeUint(params.gasPrice),\n rlpEncodeUint(params.gasLimit),\n rlpEncodeBytes(params.to),\n rlpEncodeUint(params.value),\n rlpEncodeBytes(params.data),\n rlpEncodeUint(params.chainId),\n rlpEncodeUint(0n),\n rlpEncodeUint(0n),\n ]);\n}\n\n// ---------------------------------------------------------------------------\n// buildEvmSettlementTx\n// ---------------------------------------------------------------------------\n\n/**\n * Produce a `SettlementBundle` for a winning EVM `AccumulatedClaim`.\n *\n * The bundle's `unsignedTxBytes` is RLP-encoded with placeholder gas fields\n * (tx-nonce / gasPrice / gasLimit = 0). Callers (direct sender OR a Chain\n * Bridge DVM) fill in real gas via {@link fillEvmSettlementTxGas} before\n * signing + broadcasting.\n *\n * @stable\n * @since 12.6\n */\nexport function buildEvmSettlementTx(\n winner: AccumulatedClaim,\n signer: SwapSignerConfig,\n recipient: string,\n selectedClaimIndex: number,\n claimsMerged: number\n): SettlementBundle {\n if (\n winner.channelId === undefined ||\n winner.cumulativeAmount === undefined ||\n winner.nonce === undefined ||\n winner.recipient === undefined ||\n winner.swapSignerAddress === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'EVM winner claim missing settlement-context fields'\n );\n }\n if (!signer.contractAddress) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `EVM SwapSignerConfig.contractAddress is required for chain ${winner.pair.to.chain}`\n );\n }\n if (\n typeof signer.chainId !== 'number' ||\n !Number.isInteger(signer.chainId) ||\n signer.chainId <= 0\n ) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `EVM SwapSignerConfig.chainId must be a positive integer, got ${signer.chainId}`\n );\n }\n\n const channelIdBytes = hexToBytes(winner.channelId);\n const recipientBytes = hexToBytes(recipient);\n const contractBytes = hexToBytes(signer.contractAddress);\n\n const calldata = encodeUpdateBalanceCallData(\n channelIdBytes,\n BigInt(winner.cumulativeAmount),\n BigInt(winner.nonce),\n recipientBytes,\n winner.claimBytes\n );\n\n const unsignedTxBytes = rlpEncodeUnsignedTx({\n nonce: 0n,\n gasPrice: 0n,\n gasLimit: 0n,\n to: contractBytes,\n value: 0n,\n data: calldata,\n chainId: BigInt(signer.chainId),\n });\n\n return {\n chain: winner.pair.to.chain,\n chainKind: 'evm',\n channelId: winner.channelId,\n cumulativeAmount: winner.cumulativeAmount,\n nonce: winner.nonce,\n recipient,\n swapSignerAddress: winner.swapSignerAddress,\n unsignedTxBytes,\n expectedEventSignature: EVM_SETTLEMENT_EVENT_TOPIC,\n claimsMerged,\n selectedClaimIndex,\n sourceChain: winner.pair.from.chain,\n sourceAssetCode: winner.pair.from.assetCode,\n };\n}\n\n// ---------------------------------------------------------------------------\n// fillEvmSettlementTxGas\n// ---------------------------------------------------------------------------\n\n/**\n * Re-encode an EVM settlement bundle's RLP with real tx-nonce / gasPrice /\n * gasLimit values. The `to`, `value`, `data`, `chainId` fields are preserved\n * from the original bundle.\n *\n * @stable\n * @since 12.6\n */\nexport function fillEvmSettlementTxGas(\n bundle: SettlementBundle,\n gas: { nonce: bigint; gasPrice: bigint; gasLimit: bigint },\n signer: SwapSignerConfig\n): Uint8Array {\n if (bundle.chainKind !== 'evm') {\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `fillEvmSettlementTxGas requires chainKind=evm, got ${bundle.chainKind}`\n );\n }\n if (!signer.contractAddress) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n 'EVM SwapSignerConfig.contractAddress is required for gas-fill'\n );\n }\n if (typeof signer.chainId !== 'number' || signer.chainId <= 0) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n 'EVM SwapSignerConfig.chainId must be a positive integer'\n );\n }\n // Decode calldata from the original bundle's RLP: we know calldata starts\n // at a fixed offset in our own encoding, but safer to re-construct from\n // bundle fields using encodeUpdateBalanceCallData again.\n const channelIdBytes = hexToBytes(bundle.channelId);\n const recipientBytes = hexToBytes(bundle.recipient);\n const contractBytes = hexToBytes(signer.contractAddress);\n // We need the 65-byte signature back; extract it from the original bundle's\n // calldata payload, which is the last `signature.length` bytes before the\n // zero-padding. Simplest: re-build from settlement fields alone cannot\n // reproduce the signature — so we require the caller to pass it via the\n // bundle's embedded claim. Instead of complicating the API, parse the\n // signature out of bundle.unsignedTxBytes calldata.\n const sig = extractSignatureFromBundle(bundle);\n const calldata = encodeUpdateBalanceCallData(\n channelIdBytes,\n BigInt(bundle.cumulativeAmount),\n BigInt(bundle.nonce),\n recipientBytes,\n sig\n );\n return rlpEncodeUnsignedTx({\n nonce: gas.nonce,\n gasPrice: gas.gasPrice,\n gasLimit: gas.gasLimit,\n to: contractBytes,\n value: 0n,\n data: calldata,\n chainId: BigInt(signer.chainId),\n });\n}\n\n/**\n * Decode the 65-byte balance-proof signature from a bundle's RLP-encoded\n * calldata. The calldata layout is:\n * selector(4) || channelId(32) || cumulative(32) || nonce(32) ||\n * recipient(32) || offset(32) || sigLen(32) || sigPadded(ceil(sigLen/32)*32)\n */\nfunction extractSignatureFromBundle(bundle: SettlementBundle): Uint8Array {\n // We re-parse our own RLP encoding. For robustness, locate the data field\n // via a minimal RLP walk.\n const tx = bundle.unsignedTxBytes;\n const list = rlpDecodeList(tx);\n if (list.length < 6) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'unsignedTxBytes is not a 9-element RLP list'\n );\n }\n const data = list[5];\n if (!data) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'unsignedTxBytes missing data field'\n );\n }\n // data = selector(4) || head(5 * 32) || sigLen(32) || sigPadded\n const sigLenOffset = 4 + 5 * 32;\n if (data.length < sigLenOffset + 32) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'calldata too short to contain signature length word'\n );\n }\n const sigLenBytes = data.slice(sigLenOffset, sigLenOffset + 32);\n let sigLen = 0n;\n for (const b of sigLenBytes) sigLen = (sigLen << 8n) | BigInt(b);\n const sigStart = sigLenOffset + 32;\n const sigEnd = sigStart + Number(sigLen);\n if (sigEnd > data.length) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'calldata truncated before end of signature bytes'\n );\n }\n return data.slice(sigStart, sigEnd);\n}\n\n// Minimal RLP decoder (items as raw bytes; does NOT recurse into sublists).\nfunction rlpDecodeList(buf: Uint8Array): Uint8Array[] {\n if (buf.length === 0) {\n throw new SettlementTxError('ENCODING_FAILED', 'empty RLP input');\n }\n const first = buf[0] ?? 0;\n let offset: number;\n let listEnd: number;\n if (first >= 0xc0 && first <= 0xf7) {\n offset = 1;\n listEnd = 1 + (first - 0xc0);\n } else if (first >= 0xf8 && first <= 0xff) {\n const lenOfLen = first - 0xf7;\n let listLen = 0;\n for (let i = 0; i < lenOfLen; i++) {\n listLen = (listLen << 8) | (buf[1 + i] ?? 0);\n }\n offset = 1 + lenOfLen;\n listEnd = offset + listLen;\n } else {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `rlpDecodeList: input is not a list (first byte 0x${first.toString(16)})`\n );\n }\n const items: Uint8Array[] = [];\n let p = offset;\n while (p < listEnd) {\n const b = buf[p] ?? 0;\n if (b < 0x80) {\n items.push(buf.slice(p, p + 1));\n p += 1;\n } else if (b <= 0xb7) {\n const len = b - 0x80;\n items.push(buf.slice(p + 1, p + 1 + len));\n p += 1 + len;\n } else if (b <= 0xbf) {\n const lenOfLen = b - 0xb7;\n let len = 0;\n for (let i = 0; i < lenOfLen; i++) {\n len = (len << 8) | (buf[p + 1 + i] ?? 0);\n }\n items.push(buf.slice(p + 1 + lenOfLen, p + 1 + lenOfLen + len));\n p += 1 + lenOfLen + len;\n } else {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'rlpDecodeList: nested list not supported in minimal decoder'\n );\n }\n }\n return items;\n}\n\n/**\n * Verify an EVM claim's signature by recovering the signer and comparing to\n * `expectedAddress`. Returns `{ valid, recovered }`. The v2 EIP-712 digest\n * binds `chainId` + `verifyingContract`, so both must be supplied (refs\n * connector#324 finding #1).\n *\n * @since 12.6\n */\nexport function verifyEvmClaimSignature(\n claim: AccumulatedClaim,\n expectedAddress: string,\n chainId: number,\n verifyingContract: string\n): { valid: boolean; recovered: string } {\n const recovered = recoverEvmSignerAddress(claim, chainId, verifyingContract);\n const expected = expectedAddress.toLowerCase();\n return {\n valid: recovered.toLowerCase() === expected,\n recovered,\n };\n}\n","/**\n * Shared balance-proof hash helpers — re-exported from `@toon-protocol/core`.\n *\n * The canonical hash/field layouts (the single source of truth for every\n * signer and verifier) now live in `@toon-protocol/core`'s settlement module so\n * the `@toon-protocol/client` package can consume them without depending on the\n * SDK. This file preserves the historical `@toon-protocol/sdk` import surface\n * (Story 12.6 AC-6) so existing SDK consumers and `@toon-protocol/swap` (which\n * import these names via the SDK root export) are unaffected.\n *\n * Any change to a hash layout must be made in `@toon-protocol/core` — it\n * applies to the Swap signer, the SDK verifiers, and the client signers at once.\n *\n * @module\n * @since 12.6\n * @see _bmad-output/epics/epic-12-token-swap-primitive.md\n */\n\nexport {\n hexToBytes,\n bigintToBytes32BE,\n concatBytes,\n balanceProofHashEvm,\n coopCloseHashEvm,\n eip712DomainSeparatorEvm,\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n} from '@toon-protocol/core';\n","/**\n * Mina-specific settlement: balance-proof signature verification +\n * settlement-bundle construction (Story 12.8 follow-up to 12.6 AC-9).\n *\n * ## What is implemented (and unit-tested)\n *\n * - {@link verifyMinaSignature}: verifies the Swap's off-chain balance-proof\n * signature using `mina-signer` (`verifyFields`). This is the EXACT inverse\n * of the Swap's {@link MinaPaymentChannelSigner} (`packages/swap/src/payment-channel-signer.ts`),\n * which signs `balanceProofFieldsMina(channelId, cumulativeAmount, nonce, recipient)`\n * via `mina-signer`'s `signFields` and emits the base58 signature string as\n * the claim's `claimBytes` (UTF-8). The field-element derivation lives in\n * `hashes.ts` so signer and verifier cannot drift (same single-source-of-\n * truth pattern as EVM/Solana).\n * - {@link buildMinaSettlementTx}: constructs a {@link SettlementBundle}\n * envelope (chain metadata + the verified balance proof bytes) so a Chain\n * Bridge DVM / direct sender has everything needed to drive the on-chain\n * claim. The signature is re-emitted verbatim in `unsignedTxBytes`.\n *\n * ## What remains (documented gap — NOT silently stubbed)\n *\n * The final on-chain step — submitting a Mina zkApp `claimFromChannel`\n * transaction that settles the channel — requires **o1js circuit compilation\n * + zk-SNARK proof generation** (Poseidon balance-commitment, zkApp method\n * proving). The connector already implements this in\n * `MinaPaymentChannelSDK.claimFromChannel()` (o1js, heavyweight). That proof\n * generation is intentionally NOT done here: o1js circuit compilation is far\n * too heavy for a unit-testable SDK helper and would pull a multi-hundred-MB\n * WASM dependency into every SDK consumer. Instead, the bundle carries the\n * verified balance proof so a Mina-capable settler (the connector's\n * `MinaPaymentChannelProvider`, or a future o1js-backed Chain Bridge DVM) can\n * generate the proof + broadcast. See the on-chain settlement note below\n * (connector 3.8.1 wires the dual-party claim path — connector#84).\n *\n * Note also: the Swap↔sender wire proof here (a Schnorr signature over four\n * field elements) is a DIFFERENT object than the connector's on-chain\n * `MinaPaymentChannelSDK` Poseidon-commitment proof shape\n * (`{ commitment, signature, nonce }`). The verifier matches the Swap's\n * actual emitted format (`MinaPaymentChannelSigner`), which is what a sender\n * receives on-wire.\n *\n * @module\n * @since 12.8\n */\n\nimport { SettlementTxError } from '../errors.js';\nimport type { AccumulatedClaim } from '../stream-swap.js';\nimport { balanceProofFieldsMina } from './hashes.js';\nimport type { SwapSignerConfig, SettlementBundle } from './types.js';\n\n/**\n * Network id the Swap signs with (`MinaPaymentChannelSigner` uses\n * `network: 'mainnet'`). The signature itself is network-agnostic for the\n * `signFields`/`verifyFields` path — `mina-signer` only folds the network id\n * into message-string hashing, not pre-hashed field arrays — but we keep this\n * aligned with the Swap for clarity and future-proofing.\n */\nconst MINA_NETWORK = 'mainnet';\n\n/**\n * Minimal structural type for the slice of the `mina-signer` `Client` we use.\n * Declared locally so the SDK does not hard-depend on the optional peer dep's\n * full type surface (the ambient `mina-signer.d.ts` only declares\n * `derivePublicKey`). `verifyFields` is synchronous.\n */\nexport interface MinaSignerClientLike {\n verifyFields(input: {\n data: bigint[];\n signature: string;\n publicKey: string;\n }): boolean;\n}\n\n/**\n * Constructor shape of the `mina-signer` default export.\n */\ntype MinaSignerClientCtor = new (opts: {\n network: string;\n}) => MinaSignerClientLike;\n\n/**\n * Lazily load `mina-signer` (optional peer dep) and instantiate a `Client`\n * bound to {@link MINA_NETWORK}. Returns `undefined` when the peer dep is not\n * installed — callers MUST treat that as \"cannot verify Mina claims\" rather\n * than \"claim is valid\".\n *\n * Mirrors the dynamic-import pattern in `identity.ts` (`deriveMinaIdentity`).\n *\n * @since 12.8\n */\nexport async function loadMinaSignerClient(): Promise<\n MinaSignerClientLike | undefined\n> {\n try {\n // `mina-signer` is an optional peer dep — dynamic import so the SDK\n // type-checks and runs without it installed.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const lib: any = await import('mina-signer');\n const Ctor: MinaSignerClientCtor = 'default' in lib ? lib.default : lib;\n return new Ctor({ network: MINA_NETWORK });\n } catch {\n return undefined;\n }\n}\n\n/**\n * Verify a Mina balance-proof Schnorr signature carried in an\n * `AccumulatedClaim`.\n *\n * Re-derives the signed field-element message via {@link balanceProofFieldsMina}\n * (identical to the Swap signer), decodes the claim's `claimBytes` to the\n * base58 signature string the Swap emitted, and verifies it against\n * `expectedSignerAddress` using a `mina-signer` `Client`.\n *\n * `client` MUST be a pre-loaded `mina-signer` `Client` (see\n * {@link loadMinaSignerClient}). It is injected (rather than imported inline)\n * so the synchronous `buildSettlementTx()` pipeline can verify Mina claims\n * without becoming async.\n *\n * @returns `true` iff the signature is valid for the given signer address.\n * @throws {SettlementTxError} on missing settlement metadata or a malformed\n * (non-UTF-8 / empty) signature payload.\n * @since 12.8\n */\nexport function verifyMinaSignature(\n claim: AccumulatedClaim,\n expectedSignerAddress: string,\n client: MinaSignerClientLike\n): boolean {\n if (\n claim.channelId === undefined ||\n claim.cumulativeAmount === undefined ||\n claim.nonce === undefined ||\n claim.recipient === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'Claim missing channelId/cumulativeAmount/nonce/recipient for Mina signature verify'\n );\n }\n if (claim.claimBytes.length === 0) {\n throw new SettlementTxError(\n 'INVALID_SIGNATURE_LENGTH',\n 'Mina claimBytes is empty (expected UTF-8 base58 signature string)'\n );\n }\n\n // The Swap emits the `mina-signer` base58 signature string as UTF-8 bytes.\n const signature = new TextDecoder().decode(claim.claimBytes);\n\n const fields = balanceProofFieldsMina(\n claim.channelId,\n BigInt(claim.cumulativeAmount),\n BigInt(claim.nonce),\n claim.recipient\n );\n\n try {\n return client.verifyFields({\n data: fields,\n signature,\n publicKey: expectedSignerAddress,\n });\n } catch {\n // `verifyFields` throws on a structurally invalid signature / publicKey.\n // Treat that as \"not valid\" rather than crashing the settlement pipeline.\n return false;\n }\n}\n\n/**\n * Discriminator for the Mina on-chain `claimFromChannel` zkApp method.\n *\n * On-chain settlement: the actual transaction is a Mina zkApp method call\n * requiring o1js circuit compilation + zk-SNARK proof generation. As of\n * `@toon-protocol/connector` 3.8.1 the connector's settlement executor wires\n * the dual-party `MinaPaymentChannelSDK.claimFromChannel()` path with the real\n * counterparty balance, salt, and both party signatures (connector#84 — earlier\n * builds passed single-sig/zeroed placeholders that left Mina claims\n * un-settleable). Proof generation + broadcast remain the connector-side\n * settler's responsibility and are intentionally out of scope for this SDK\n * helper — see the module docblock. The bundle below therefore carries the\n * verified balance-proof bytes + channel metadata for that settler to consume.\n */\n\n/**\n * Build a Mina {@link SettlementBundle} from a winning `AccumulatedClaim`.\n *\n * Unlike the EVM/Solana builders (which produce a chain-native unsigned tx),\n * this builder produces a settlement ENVELOPE: it validates the winning\n * claim's settlement context and re-emits the Swap's verified balance-proof\n * signature bytes in `unsignedTxBytes`. The actual on-chain `claimFromChannel`\n * zkApp transaction (o1js proof generation) is the responsibility of a\n * Mina-capable settler — see the module docblock + the on-chain settlement note.\n *\n * The signature carried here is the SAME `claimBytes` the sender already\n * verified via {@link verifyMinaSignature}, so a downstream settler does not\n * need to re-derive it.\n *\n * @since 12.8\n */\nexport function buildMinaSettlementTx(\n winner: AccumulatedClaim,\n signer: SwapSignerConfig,\n recipient: string,\n selectedClaimIndex: number,\n claimsMerged: number\n): SettlementBundle {\n if (\n winner.channelId === undefined ||\n winner.cumulativeAmount === undefined ||\n winner.nonce === undefined ||\n winner.recipient === undefined ||\n winner.swapSignerAddress === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'Mina winner claim missing settlement-context fields'\n );\n }\n if (!signer.address) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Mina SwapSignerConfig.address is required for chain ${winner.pair.to.chain}`\n );\n }\n if (winner.claimBytes.length === 0) {\n throw new SettlementTxError(\n 'INVALID_SIGNATURE_LENGTH',\n 'Mina winner claimBytes is empty (expected UTF-8 base58 signature string)'\n );\n }\n\n // Envelope: the verified balance-proof signature bytes. A Mina-capable\n // settler generates the o1js claimFromChannel proof from (channelId,\n // cumulativeAmount, nonce, recipient, signature) — connector 3.8.1, #84.\n const unsignedTxBytes = winner.claimBytes;\n\n return {\n chain: winner.pair.to.chain,\n chainKind: 'mina',\n channelId: winner.channelId,\n cumulativeAmount: winner.cumulativeAmount,\n nonce: winner.nonce,\n recipient,\n swapSignerAddress: winner.swapSignerAddress,\n unsignedTxBytes,\n claimsMerged,\n selectedClaimIndex,\n sourceChain: winner.pair.from.chain,\n sourceAssetCode: winner.pair.from.assetCode,\n };\n}\n","/**\n * Solana-specific settlement tx construction + signature verification\n * (Story 12.6 AC-9).\n *\n * @module\n * @since 12.6\n * @see _bmad-output/implementation-artifacts/12-6-build-settlement-tx.md\n */\n\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { SettlementTxError } from '../errors.js';\nimport { base58Decode, base58Encode } from '../identity.js';\nimport type { AccumulatedClaim } from '../stream-swap.js';\nimport { balanceProofHashSolana, concatBytes } from './hashes.js';\nimport type { SwapSignerConfig, SettlementBundle } from './types.js';\n\n/**\n * Verify a Solana Ed25519 balance-proof signature.\n *\n * @since 12.6\n */\nexport function verifyEd25519Signature(\n claim: AccumulatedClaim,\n expectedSignerAddress: string\n): boolean {\n if (\n claim.channelId === undefined ||\n claim.cumulativeAmount === undefined ||\n claim.nonce === undefined ||\n claim.recipient === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'Claim missing channelId/cumulativeAmount/nonce/recipient for Solana signature verify'\n );\n }\n if (claim.claimBytes.length !== 64) {\n throw new SettlementTxError(\n 'INVALID_SIGNATURE_LENGTH',\n `Solana signature must be 64 bytes, got ${claim.claimBytes.length}`\n );\n }\n const msgHash = balanceProofHashSolana(\n claim.channelId,\n BigInt(claim.cumulativeAmount),\n BigInt(claim.nonce),\n claim.recipient\n );\n let pubkeyBytes: Uint8Array;\n try {\n pubkeyBytes = base58Decode(expectedSignerAddress);\n } catch (err) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana expected signer address is not valid base58: ${expectedSignerAddress}`,\n { cause: err }\n );\n }\n if (pubkeyBytes.length !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana expected signer pubkey must be 32 bytes, got ${pubkeyBytes.length}`\n );\n }\n try {\n return ed25519.verify(claim.claimBytes, msgHash, pubkeyBytes);\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Anchor discriminator (default — may be overridden if the real program is\n// non-Anchor. TODO(12.6 follow-up): confirm via ../connector/packages/solana-program/.\n// Story 12.8 E2E against a real program will catch drift.)\n// ---------------------------------------------------------------------------\n\nconst SOLANA_UPDATE_BALANCE_DISCRIMINATOR: Uint8Array = sha256(\n new TextEncoder().encode('global:update_balance')\n).slice(0, 8);\n\n/**\n * Write an 8-byte little-endian representation of a non-negative bigint.\n * Throws if value > 2^64 - 1.\n */\nfunction bigintToBytes8LE(x: bigint): Uint8Array {\n if (x < 0n) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'bigintToBytes8LE: negative input'\n );\n }\n if (x > 0xffffffffffffffffn) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n 'bigintToBytes8LE: value exceeds 64 bits'\n );\n }\n const out = new Uint8Array(8);\n let v = x;\n for (let i = 0; i < 8; i++) {\n out[i] = Number(v & 0xffn);\n v >>= 8n;\n }\n return out;\n}\n\n/**\n * Build a Solana `SettlementBundle` from a winning AccumulatedClaim.\n *\n * Produces a serialized legacy `Message` (NOT `Transaction`, since Transaction\n * requires signatures). The Message header + account keys + placeholder\n * blockhash + single instruction invoking `signer.programId`.\n *\n * NOTE: This Message is a TEMPLATE. The caller (direct sender OR a Chain\n * Bridge DVM) MUST patch in a real recent blockhash before signing — the\n * current bundle carries an all-zero blockhash placeholder.\n *\n * @stable\n * @since 12.6\n */\nexport function buildSolanaSettlementTx(\n winner: AccumulatedClaim,\n signer: SwapSignerConfig,\n recipient: string,\n selectedClaimIndex: number,\n claimsMerged: number\n): SettlementBundle {\n if (\n winner.channelId === undefined ||\n winner.cumulativeAmount === undefined ||\n winner.nonce === undefined ||\n winner.recipient === undefined ||\n winner.swapSignerAddress === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'Solana winner claim missing settlement-context fields'\n );\n }\n if (!signer.programId) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana SwapSignerConfig.programId is required for chain ${winner.pair.to.chain}`\n );\n }\n\n let programIdBytes: Uint8Array;\n let recipientBytes: Uint8Array;\n let swapBytes: Uint8Array;\n let channelIdBytes: Uint8Array;\n try {\n programIdBytes = base58Decode(signer.programId);\n recipientBytes = base58Decode(recipient);\n swapBytes = base58Decode(winner.swapSignerAddress);\n channelIdBytes = base58Decode(winner.channelId);\n } catch (err) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `Solana settlement tx: base58 decode failed (${err instanceof Error ? err.message : String(err)})`,\n { cause: err }\n );\n }\n if (programIdBytes.length !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana programId must be 32 bytes, got ${programIdBytes.length}`\n );\n }\n if (recipientBytes.length !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana recipient must decode to 32 bytes, got ${recipientBytes.length}`\n );\n }\n if (swapBytes.length !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana swapSignerAddress must decode to 32 bytes, got ${swapBytes.length}`\n );\n }\n if (channelIdBytes.length !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana channelId must decode to 32 bytes, got ${channelIdBytes.length}`\n );\n }\n\n // Instruction data: discriminator(8) || cumulative(8 LE) || nonce(8 LE) || signature(64)\n const instructionData = concatBytes(\n SOLANA_UPDATE_BALANCE_DISCRIMINATOR,\n bigintToBytes8LE(BigInt(winner.cumulativeAmount)),\n bigintToBytes8LE(BigInt(winner.nonce)),\n winner.claimBytes\n );\n\n // Accounts: [recipient (signer), swap, channel-state, system-program, programId]\n // We construct a minimal Message. For simplicity, use 4 accounts:\n // [0] recipient (signer, writable)\n // [1] swap (writable)\n // [2] channel-state (writable, derived — we approximate via channelIdBytes)\n // [3] program (readonly)\n const accounts = [recipientBytes, swapBytes, channelIdBytes, programIdBytes];\n\n // Message header: [numRequiredSignatures, numReadonlySigned, numReadonlyUnsigned]\n const header = new Uint8Array([1, 0, 1]); // 1 signer (recipient), 1 readonly unsigned (program)\n\n // Compact-u16 length encoding (for small counts, one byte suffices)\n const accountsCountByte = new Uint8Array([accounts.length]);\n const recentBlockhash = new Uint8Array(32); // placeholder — caller patches\n const instructionsCountByte = new Uint8Array([1]);\n\n // Instruction: programIdIndex (u8) || accountsLen (compact-u16) ||\n // accountIndices(u8 each) || dataLen (compact-u16) || data\n const programIdIndex = new Uint8Array([3]); // index of program in accounts\n const instrAccountsLen = new Uint8Array([3]); // recipient, swap, channel-state\n const instrAccountIndices = new Uint8Array([0, 1, 2]);\n // Instruction data length as compact-u16 (assume <127)\n if (instructionData.length >= 0x80) {\n throw new SettlementTxError(\n 'ENCODING_FAILED',\n `Solana instruction data too large for simple compact-u16 encoding: ${instructionData.length}`\n );\n }\n const instrDataLen = new Uint8Array([instructionData.length]);\n\n const instruction = concatBytes(\n programIdIndex,\n instrAccountsLen,\n instrAccountIndices,\n instrDataLen,\n instructionData\n );\n\n const unsignedTxBytes = concatBytes(\n header,\n accountsCountByte,\n ...accounts,\n recentBlockhash,\n instructionsCountByte,\n instruction\n );\n\n return {\n chain: winner.pair.to.chain,\n chainKind: 'solana',\n channelId: winner.channelId,\n cumulativeAmount: winner.cumulativeAmount,\n nonce: winner.nonce,\n recipient,\n swapSignerAddress: winner.swapSignerAddress,\n unsignedTxBytes,\n claimsMerged,\n selectedClaimIndex,\n sourceChain: winner.pair.from.chain,\n sourceAssetCode: winner.pair.from.assetCode,\n };\n}\n\n/**\n * Re-export base58 helpers so callers (e.g., a Chain Bridge DVM) can round-\n * trip Solana addresses without a second base58 impl.\n *\n * @since 12.6\n */\nexport { base58Decode, base58Encode };\n","/**\n * `buildSettlementTx()` — public entrypoint for Story 12.6.\n *\n * Takes the output `claims` array from `streamSwap()` + per-chain signer\n * config + per-chain recipient addresses, and produces one\n * {@link SettlementBundle} per unique `(chain, channelId)` group.\n *\n * @module\n * @since 12.6\n * @see _bmad-output/implementation-artifacts/12-6-build-settlement-tx.md\n */\n\nimport { SettlementTxError } from '../errors.js';\nimport { base58Decode } from '../identity.js';\nimport type { AccumulatedClaim } from '../stream-swap.js';\nimport {\n buildEvmSettlementTx,\n recoverEvmSignerAddress,\n verifyEvmClaimSignature,\n} from './evm.js';\nimport { buildMinaSettlementTx, verifyMinaSignature } from './mina.js';\nimport type { MinaSignerClientLike } from './mina.js';\nimport { buildSolanaSettlementTx, verifyEd25519Signature } from './solana.js';\nimport type {\n BuildSettlementTxParams,\n BuildSettlementTxResult,\n SwapSignerConfig,\n SettlementBundle,\n} from './types.js';\n\nconst EVM_ADDRESS_REGEX = /^0x[0-9a-f]{40}$/;\n\nfunction chainKindOf(chain: string): 'evm' | 'solana' | 'mina' | 'unknown' {\n if (chain.startsWith('evm:')) return 'evm';\n if (chain.startsWith('solana:')) return 'solana';\n if (chain.startsWith('mina:')) return 'mina';\n return 'unknown';\n}\n\n/**\n * Build raw unsigned settlement transactions from a bag of accumulated\n * swap claims.\n *\n * Algorithm:\n * 1. Validate params (synchronous throws on malformed input).\n * 2. Optionally verify each claim's signature. Rejected claims land in\n * `result.rejected[]` and are dropped from further processing.\n * 3. Group surviving claims by `(chain, channelId)`. Within each group,\n * assert recipient + swapSignerAddress consensus, unique nonces, and\n * non-decreasing cumulativeAmount with nonce.\n * 4. Pick the winning claim per group (highest nonce).\n * 5. Dispatch each winner to its chain-specific tx builder.\n *\n * @stable\n * @since 12.6\n * @throws {SettlementTxError} Synchronously on malformed input, group-level\n * inconsistency, or chain dispatch failures.\n *\n * @example\n * ```ts\n * const result = await streamSwap({ ... });\n * const { bundles } = buildSettlementTx({\n * claims: result.claims,\n * signers: {\n * 'evm:base:8453': {\n * address: '0xswap...',\n * contractAddress: '0xtokennetwork...',\n * chainId: 8453,\n * },\n * },\n * recipients: { 'evm:base:8453': '0xsender...' },\n * });\n * // Feed bundles[0].unsignedTxBytes into fillEvmSettlementTxGas → sign → eth_sendRawTransaction.\n * ```\n */\nexport function buildSettlementTx(\n params: BuildSettlementTxParams\n): BuildSettlementTxResult {\n // ---- 1. Validate ----\n if (!Array.isArray(params.claims) || params.claims.length === 0) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n 'claims array is empty or not an array'\n );\n }\n if (!params.signers || typeof params.signers !== 'object') {\n throw new SettlementTxError('INVALID_INPUT', 'signers map is required');\n }\n if (!params.recipients || typeof params.recipients !== 'object') {\n throw new SettlementTxError('INVALID_INPUT', 'recipients map is required');\n }\n\n const logger = params.logger;\n const verifySignatures = params.verifySignatures ?? true;\n\n // Every claim must carry the five settlement-context fields.\n for (let i = 0; i < params.claims.length; i++) {\n const c = params.claims[i];\n if (c === undefined) continue; // bounded by loop — defensive for TS narrowing\n if (\n c.channelId === undefined ||\n c.nonce === undefined ||\n c.cumulativeAmount === undefined ||\n c.recipient === undefined ||\n c.swapSignerAddress === undefined\n ) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n `claims[${i}] missing one or more of { channelId, nonce, cumulativeAmount, recipient, swapSignerAddress }`\n );\n }\n }\n\n // Every distinct chain must have a signer + recipient + per-chain config validity.\n const distinctChains = new Set<string>();\n for (const c of params.claims) distinctChains.add(c.pair.to.chain);\n for (const chain of distinctChains) {\n const signer = params.signers[chain];\n if (!signer) {\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `signers map missing entry for chain ${chain}`\n );\n }\n if (!(chain in params.recipients)) {\n throw new SettlementTxError(\n 'MISSING_RECIPIENT',\n `recipients map missing entry for chain ${chain}`\n );\n }\n const kind = chainKindOf(chain);\n if (kind === 'evm') {\n if (\n !signer.contractAddress ||\n !EVM_ADDRESS_REGEX.test(signer.contractAddress)\n ) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `EVM signers[${chain}].contractAddress must match 0x + 40 lowercase hex`\n );\n }\n if (\n typeof signer.chainId !== 'number' ||\n !Number.isInteger(signer.chainId) ||\n signer.chainId <= 0\n ) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `EVM signers[${chain}].chainId must be a positive integer`\n );\n }\n } else if (kind === 'solana') {\n if (!signer.programId || signer.programId.length === 0) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana signers[${chain}].programId is required`\n );\n }\n // AC-4: programId MUST be a valid base58 string that decodes to 32 bytes.\n let programIdLen: number;\n try {\n programIdLen = base58Decode(signer.programId).length;\n } catch (err) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana signers[${chain}].programId is not valid base58`,\n { cause: err }\n );\n }\n if (programIdLen !== 32) {\n throw new SettlementTxError(\n 'INVALID_INPUT',\n `Solana signers[${chain}].programId must decode to 32 bytes (got ${programIdLen})`\n );\n }\n }\n }\n\n // ---- 2. Verify signatures (optional) ----\n const rejected: BuildSettlementTxResult['rejected'] = [];\n const survivors: AccumulatedClaim[] = [];\n for (const claim of params.claims) {\n if (!verifySignatures) {\n survivors.push(claim);\n continue;\n }\n const chain = claim.pair.to.chain;\n const signer = params.signers[chain];\n if (!signer) {\n // Already validated above; this branch is defensive for TS narrowing.\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `signers map missing entry for chain ${chain} (unreachable — validated)`\n );\n }\n const kind = chainKindOf(chain);\n if (kind === 'evm') {\n try {\n // EVM signer config validated above to carry chainId + contractAddress;\n // both are REQUIRED for the v2 EIP-712 balance-proof digest\n // (connector#324 finding #1).\n const { valid, recovered } = verifyEvmClaimSignature(\n claim,\n signer.address,\n signer.chainId as number,\n signer.contractAddress as string\n );\n if (!valid) {\n rejected.push({\n claim,\n reason: 'SIGNER_MISMATCH',\n details: `recovered=${recovered} expected=${signer.address.toLowerCase()}`,\n });\n continue;\n }\n survivors.push(claim);\n } catch (err) {\n rejected.push({\n claim,\n reason: 'SIGNATURE_INVALID',\n details: err instanceof Error ? err.message : String(err),\n });\n }\n } else if (kind === 'solana') {\n try {\n const ok = verifyEd25519Signature(claim, signer.address);\n if (!ok) {\n rejected.push({\n claim,\n reason: 'SIGNER_MISMATCH',\n details: `ed25519.verify returned false against ${signer.address}`,\n });\n continue;\n }\n survivors.push(claim);\n } catch (err) {\n rejected.push({\n claim,\n reason: 'SIGNATURE_INVALID',\n details: err instanceof Error ? err.message : String(err),\n });\n }\n } else if (kind === 'mina') {\n if (!params.minaSignerClient) {\n // mina-signer is an optional peer dep loaded via async import; the\n // sync pipeline cannot load it itself. Reject (rather than pass\n // through unverified) so an absent client never lets an unverified\n // Mina claim settle.\n rejected.push({\n claim,\n reason: 'MINA_VERIFICATION_UNSUPPORTED',\n details:\n 'minaSignerClient not provided — load mina-signer via loadMinaSignerClient() and pass it in params.minaSignerClient to verify mina:* claims',\n });\n continue;\n }\n try {\n const ok = verifyMinaSignature(\n claim,\n signer.address,\n params.minaSignerClient\n );\n if (!ok) {\n rejected.push({\n claim,\n reason: 'SIGNER_MISMATCH',\n details: `mina-signer verifyFields returned false against ${signer.address}`,\n });\n continue;\n }\n survivors.push(claim);\n } catch (err) {\n rejected.push({\n claim,\n reason: 'SIGNATURE_INVALID',\n details: err instanceof Error ? err.message : String(err),\n });\n }\n } else {\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `Unknown chain kind for ${chain}`\n );\n }\n }\n\n logger?.debug?.({\n event: 'build_settlement_tx.verified',\n survivorCount: survivors.length,\n rejectedCount: rejected.length,\n });\n\n // ---- 3. Group by (chain, channelId) ----\n interface Group {\n chain: string;\n channelId: string;\n claims: { claim: AccumulatedClaim; originalIndex: number }[];\n }\n const groups = new Map<string, Group>();\n // Need the original input index for bundle.selectedClaimIndex.\n const originalIndex = new Map<AccumulatedClaim, number>();\n for (let i = 0; i < params.claims.length; i++) {\n const c = params.claims[i];\n if (c !== undefined) originalIndex.set(c, i);\n }\n for (const claim of survivors) {\n const chain = claim.pair.to.chain;\n const channelId = claim.channelId;\n if (channelId === undefined) {\n // Validated earlier — defensive branch.\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'claim.channelId undefined after validation (unreachable)'\n );\n }\n const key = `${chain}::${channelId}`;\n let g = groups.get(key);\n if (!g) {\n g = { chain, channelId, claims: [] };\n groups.set(key, g);\n }\n const idx = originalIndex.get(claim);\n g.claims.push({ claim, originalIndex: idx ?? -1 });\n }\n\n // ---- 3b. Validate each group ----\n const superseded: AccumulatedClaim[] = [];\n const bundles: SettlementBundle[] = [];\n for (const g of groups.values()) {\n if (g.claims.length === 0) continue;\n const first = g.claims[0];\n if (!first) continue;\n const firstRecipient = first.claim.recipient;\n const firstSwapSigner = first.claim.swapSignerAddress;\n if (firstRecipient === undefined || firstSwapSigner === undefined) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'winner claim missing recipient/swapSignerAddress (unreachable after validation)'\n );\n }\n for (let i = 1; i < g.claims.length; i++) {\n const entry = g.claims[i];\n if (!entry) continue;\n const c = entry.claim;\n if (c.recipient !== firstRecipient) {\n throw new SettlementTxError(\n 'RECIPIENT_MISMATCH',\n `claims in channel ${g.channelId} disagree on recipient: ${firstRecipient} vs ${String(c.recipient)} (claim indices ${first.originalIndex}, ${entry.originalIndex})`\n );\n }\n if (c.swapSignerAddress !== firstSwapSigner) {\n throw new SettlementTxError(\n 'SWAP_SIGNER_MISMATCH',\n `claims in channel ${g.channelId} disagree on swapSignerAddress: ${firstSwapSigner} vs ${String(c.swapSignerAddress)}`\n );\n }\n }\n // Nonces strictly unique within group.\n const nonceSeen = new Set<string>();\n for (const entry of g.claims) {\n const nonceStr = entry.claim.nonce;\n if (nonceStr === undefined) {\n throw new SettlementTxError(\n 'MISSING_SETTLEMENT_METADATA',\n 'claim.nonce undefined (unreachable after validation)'\n );\n }\n if (nonceSeen.has(nonceStr)) {\n throw new SettlementTxError(\n 'DUPLICATE_NONCE',\n `channel ${g.channelId} has two claims with nonce ${nonceStr}`\n );\n }\n nonceSeen.add(nonceStr);\n }\n // Sort by nonce ascending, then enforce non-decreasing cumulativeAmount.\n const sorted = [...g.claims].sort((a, b) => {\n const an = BigInt(a.claim.nonce ?? '0');\n const bn = BigInt(b.claim.nonce ?? '0');\n return an < bn ? -1 : an > bn ? 1 : 0;\n });\n for (let i = 1; i < sorted.length; i++) {\n const prevE = sorted[i - 1];\n const currE = sorted[i];\n if (!prevE || !currE) continue;\n const prev = BigInt(prevE.claim.cumulativeAmount ?? '0');\n const curr = BigInt(currE.claim.cumulativeAmount ?? '0');\n if (curr < prev) {\n throw new SettlementTxError(\n 'NON_MONOTONIC_CUMULATIVE',\n `channel ${g.channelId} nonce ${String(currE.claim.nonce)} has cumulativeAmount ${curr} < previous nonce ${String(prevE.claim.nonce)} cumulativeAmount ${prev}`\n );\n }\n }\n\n // Winner = highest nonce = last entry in sorted array.\n const winnerEntry = sorted[sorted.length - 1];\n if (!winnerEntry) continue;\n const winner = winnerEntry.claim;\n\n if (params.includeSuperseded) {\n for (let i = 0; i < sorted.length - 1; i++) {\n const e = sorted[i];\n if (e) superseded.push(e.claim);\n }\n }\n\n // Dispatch per chain kind.\n const signer = params.signers[g.chain];\n const recipient = params.recipients[g.chain];\n if (!signer || recipient === undefined) {\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `signers/recipients missing entry for ${g.chain} (unreachable after validation)`\n );\n }\n const kind = chainKindOf(g.chain);\n let bundle: SettlementBundle;\n if (kind === 'evm') {\n // Recipient address consistency check: bundle recipient MUST match\n // the claim's recipient. The recipients map is the sender's declared\n // address — any disagreement with the Swap-reported recipient signals\n // an adversarial Swap or caller misconfiguration.\n if (recipient.toLowerCase() !== firstRecipient.toLowerCase()) {\n throw new SettlementTxError(\n 'RECIPIENT_MISMATCH',\n `recipients[${g.chain}] (${recipient}) does not match Swap-reported recipient (${firstRecipient})`\n );\n }\n bundle = buildEvmSettlementTx(\n winner,\n signer,\n recipient.toLowerCase(),\n winnerEntry.originalIndex,\n g.claims.length\n );\n } else if (kind === 'solana') {\n if (recipient !== firstRecipient) {\n throw new SettlementTxError(\n 'RECIPIENT_MISMATCH',\n `recipients[${g.chain}] (${recipient}) does not match Swap-reported recipient (${firstRecipient})`\n );\n }\n bundle = buildSolanaSettlementTx(\n winner,\n signer,\n recipient,\n winnerEntry.originalIndex,\n g.claims.length\n );\n } else if (kind === 'mina') {\n // Mina addresses are case-sensitive base58 (no lowercasing, like Solana).\n if (recipient !== firstRecipient) {\n throw new SettlementTxError(\n 'RECIPIENT_MISMATCH',\n `recipients[${g.chain}] (${recipient}) does not match Swap-reported recipient (${firstRecipient})`\n );\n }\n bundle = buildMinaSettlementTx(\n winner,\n signer,\n recipient,\n winnerEntry.originalIndex,\n g.claims.length\n );\n } else {\n throw new SettlementTxError(\n 'UNSUPPORTED_CHAIN',\n `Unknown chain kind for ${g.chain}`\n );\n }\n bundles.push(bundle);\n }\n\n logger?.info?.({\n event: 'build_settlement_tx.complete',\n bundleCount: bundles.length,\n rejectedCount: rejected.length,\n supersededCount: superseded.length,\n });\n\n return { bundles, rejected, superseded };\n}\n\n/**\n * Standalone utility: verify a single `AccumulatedClaim`'s signature against\n * a `SwapSignerConfig` without running the full grouping/winner pipeline.\n *\n * Useful inside a `streamSwap()` `onPacket` callback for mid-stream claim\n * validation.\n *\n * For `mina:*` claims, pass a pre-loaded `mina-signer` `Client` as\n * `minaSignerClient` (see `loadMinaSignerClient()`); without it, Mina claims\n * return `MINA_VERIFICATION_UNSUPPORTED` (same contract as\n * `buildSettlementTx`).\n *\n * @stable\n * @since 12.6\n */\nexport function verifyAccumulatedClaim(\n claim: AccumulatedClaim,\n signer: SwapSignerConfig,\n minaSignerClient?: MinaSignerClientLike\n): { valid: true } | { valid: false; reason: string } {\n const kind = chainKindOf(claim.pair.to.chain);\n if (kind === 'evm') {\n if (\n typeof signer.chainId !== 'number' ||\n !Number.isInteger(signer.chainId) ||\n signer.chainId <= 0 ||\n !signer.contractAddress\n ) {\n return {\n valid: false,\n reason:\n 'INVALID_INPUT: EVM signer requires chainId (positive integer) + contractAddress for the v2 EIP-712 digest (connector#324 finding #1)',\n };\n }\n try {\n const { valid, recovered } = verifyEvmClaimSignature(\n claim,\n signer.address,\n signer.chainId,\n signer.contractAddress\n );\n if (valid) return { valid: true };\n return {\n valid: false,\n reason: `SIGNER_MISMATCH: recovered=${recovered} expected=${signer.address.toLowerCase()}`,\n };\n } catch (err) {\n return {\n valid: false,\n reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (kind === 'solana') {\n try {\n const ok = verifyEd25519Signature(claim, signer.address);\n return ok\n ? { valid: true }\n : {\n valid: false,\n reason: 'SIGNER_MISMATCH: ed25519.verify returned false',\n };\n } catch (err) {\n return {\n valid: false,\n reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (kind === 'mina') {\n if (!minaSignerClient) {\n return {\n valid: false,\n reason:\n 'MINA_VERIFICATION_UNSUPPORTED: pass a mina-signer Client (loadMinaSignerClient()) as minaSignerClient to verify mina:* claims',\n };\n }\n try {\n const ok = verifyMinaSignature(claim, signer.address, minaSignerClient);\n return ok\n ? { valid: true }\n : {\n valid: false,\n reason: 'SIGNER_MISMATCH: mina-signer verifyFields returned false',\n };\n } catch (err) {\n return {\n valid: false,\n reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n return {\n valid: false,\n reason: `UNSUPPORTED_CHAIN: ${claim.pair.to.chain}`,\n };\n}\n\n// Re-export helper for tests + external callers that need address recovery\n// without running the full pipeline.\nexport { recoverEvmSignerAddress };\n","/**\n * SHA3 (keccak) hash function, based on a new \"Sponge function\" design.\n * Different from older hashes, the internal state is bigger than output size.\n *\n * Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),\n * [Website](https://keccak.team/keccak.html),\n * [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).\n *\n * Check out `sha3-addons` module for cSHAKE, k12, and others.\n * @module\n */\nimport { rotlBH, rotlBL, rotlSH, rotlSL, split } from './_u64.ts';\n// prettier-ignore\nimport {\n abytes, aexists, anumber, aoutput,\n clean, createHasher, createXOFer, Hash,\n swap32IfBE,\n toBytes, u32,\n type CHash, type CHashXO, type HashXOF, type Input\n} from './utils.ts';\n\n// No __PURE__ annotations in sha3 header:\n// EVERYTHING is in fact used on every export.\n// Various per round constants calculations\nconst _0n = BigInt(0);\nconst _1n = BigInt(1);\nconst _2n = BigInt(2);\nconst _7n = BigInt(7);\nconst _256n = BigInt(256);\nconst _0x71n = BigInt(0x71);\nconst SHA3_PI: number[] = [];\nconst SHA3_ROTL: number[] = [];\nconst _SHA3_IOTA: bigint[] = [];\nfor (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {\n // Pi\n [x, y] = [y, (2 * x + 3 * y) % 5];\n SHA3_PI.push(2 * (5 * y + x));\n // Rotational\n SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);\n // Iota\n let t = _0n;\n for (let j = 0; j < 7; j++) {\n R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;\n if (R & _2n) t ^= _1n << ((_1n << /* @__PURE__ */ BigInt(j)) - _1n);\n }\n _SHA3_IOTA.push(t);\n}\nconst IOTAS = split(_SHA3_IOTA, true);\nconst SHA3_IOTA_H = IOTAS[0];\nconst SHA3_IOTA_L = IOTAS[1];\n\n// Left rotation (without 0, 32, 64)\nconst rotlH = (h: number, l: number, s: number) => (s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s));\nconst rotlL = (h: number, l: number, s: number) => (s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s));\n\n/** `keccakf1600` internal function, additionally allows to adjust round count. */\nexport function keccakP(s: Uint32Array, rounds: number = 24): void {\n const B = new Uint32Array(5 * 2);\n // NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)\n for (let round = 24 - rounds; round < 24; round++) {\n // Theta θ\n for (let x = 0; x < 10; x++) B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];\n for (let x = 0; x < 10; x += 2) {\n const idx1 = (x + 8) % 10;\n const idx0 = (x + 2) % 10;\n const B0 = B[idx0];\n const B1 = B[idx0 + 1];\n const Th = rotlH(B0, B1, 1) ^ B[idx1];\n const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];\n for (let y = 0; y < 50; y += 10) {\n s[x + y] ^= Th;\n s[x + y + 1] ^= Tl;\n }\n }\n // Rho (ρ) and Pi (π)\n let curH = s[2];\n let curL = s[3];\n for (let t = 0; t < 24; t++) {\n const shift = SHA3_ROTL[t];\n const Th = rotlH(curH, curL, shift);\n const Tl = rotlL(curH, curL, shift);\n const PI = SHA3_PI[t];\n curH = s[PI];\n curL = s[PI + 1];\n s[PI] = Th;\n s[PI + 1] = Tl;\n }\n // Chi (χ)\n for (let y = 0; y < 50; y += 10) {\n for (let x = 0; x < 10; x++) B[x] = s[y + x];\n for (let x = 0; x < 10; x++) s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];\n }\n // Iota (ι)\n s[0] ^= SHA3_IOTA_H[round];\n s[1] ^= SHA3_IOTA_L[round];\n }\n clean(B);\n}\n\n/** Keccak sponge function. */\nexport class Keccak extends Hash<Keccak> implements HashXOF<Keccak> {\n protected state: Uint8Array;\n protected pos = 0;\n protected posOut = 0;\n protected finished = false;\n protected state32: Uint32Array;\n protected destroyed = false;\n\n public blockLen: number;\n public suffix: number;\n public outputLen: number;\n protected enableXOF = false;\n protected rounds: number;\n\n // NOTE: we accept arguments in bytes instead of bits here.\n constructor(\n blockLen: number,\n suffix: number,\n outputLen: number,\n enableXOF = false,\n rounds: number = 24\n ) {\n super();\n this.blockLen = blockLen;\n this.suffix = suffix;\n this.outputLen = outputLen;\n this.enableXOF = enableXOF;\n this.rounds = rounds;\n // Can be passed from user as dkLen\n anumber(outputLen);\n // 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes\n // 0 < blockLen < 200\n if (!(0 < blockLen && blockLen < 200))\n throw new Error('only keccak-f1600 function is supported');\n this.state = new Uint8Array(200);\n this.state32 = u32(this.state);\n }\n clone(): Keccak {\n return this._cloneInto();\n }\n protected keccak(): void {\n swap32IfBE(this.state32);\n keccakP(this.state32, this.rounds);\n swap32IfBE(this.state32);\n this.posOut = 0;\n this.pos = 0;\n }\n update(data: Input): this {\n aexists(this);\n data = toBytes(data);\n abytes(data);\n const { blockLen, state } = this;\n const len = data.length;\n for (let pos = 0; pos < len; ) {\n const take = Math.min(blockLen - this.pos, len - pos);\n for (let i = 0; i < take; i++) state[this.pos++] ^= data[pos++];\n if (this.pos === blockLen) this.keccak();\n }\n return this;\n }\n protected finish(): void {\n if (this.finished) return;\n this.finished = true;\n const { state, suffix, pos, blockLen } = this;\n // Do the padding\n state[pos] ^= suffix;\n if ((suffix & 0x80) !== 0 && pos === blockLen - 1) this.keccak();\n state[blockLen - 1] ^= 0x80;\n this.keccak();\n }\n protected writeInto(out: Uint8Array): Uint8Array {\n aexists(this, false);\n abytes(out);\n this.finish();\n const bufferOut = this.state;\n const { blockLen } = this;\n for (let pos = 0, len = out.length; pos < len; ) {\n if (this.posOut >= blockLen) this.keccak();\n const take = Math.min(blockLen - this.posOut, len - pos);\n out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);\n this.posOut += take;\n pos += take;\n }\n return out;\n }\n xofInto(out: Uint8Array): Uint8Array {\n // Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF\n if (!this.enableXOF) throw new Error('XOF is not possible for this instance');\n return this.writeInto(out);\n }\n xof(bytes: number): Uint8Array {\n anumber(bytes);\n return this.xofInto(new Uint8Array(bytes));\n }\n digestInto(out: Uint8Array): Uint8Array {\n aoutput(out, this);\n if (this.finished) throw new Error('digest() was already called');\n this.writeInto(out);\n this.destroy();\n return out;\n }\n digest(): Uint8Array {\n return this.digestInto(new Uint8Array(this.outputLen));\n }\n destroy(): void {\n this.destroyed = true;\n clean(this.state);\n }\n _cloneInto(to?: Keccak): Keccak {\n const { blockLen, suffix, outputLen, rounds, enableXOF } = this;\n to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);\n to.state32.set(this.state32);\n to.pos = this.pos;\n to.posOut = this.posOut;\n to.finished = this.finished;\n to.rounds = rounds;\n // Suffix can change in cSHAKE\n to.suffix = suffix;\n to.outputLen = outputLen;\n to.enableXOF = enableXOF;\n to.destroyed = this.destroyed;\n return to;\n }\n}\n\nconst gen = (suffix: number, blockLen: number, outputLen: number) =>\n createHasher(() => new Keccak(blockLen, suffix, outputLen));\n\n/** SHA3-224 hash function. */\nexport const sha3_224: CHash = /* @__PURE__ */ (() => gen(0x06, 144, 224 / 8))();\n/** SHA3-256 hash function. Different from keccak-256. */\nexport const sha3_256: CHash = /* @__PURE__ */ (() => gen(0x06, 136, 256 / 8))();\n/** SHA3-384 hash function. */\nexport const sha3_384: CHash = /* @__PURE__ */ (() => gen(0x06, 104, 384 / 8))();\n/** SHA3-512 hash function. */\nexport const sha3_512: CHash = /* @__PURE__ */ (() => gen(0x06, 72, 512 / 8))();\n\n/** keccak-224 hash function. */\nexport const keccak_224: CHash = /* @__PURE__ */ (() => gen(0x01, 144, 224 / 8))();\n/** keccak-256 hash function. Different from SHA3-256. */\nexport const keccak_256: CHash = /* @__PURE__ */ (() => gen(0x01, 136, 256 / 8))();\n/** keccak-384 hash function. */\nexport const keccak_384: CHash = /* @__PURE__ */ (() => gen(0x01, 104, 384 / 8))();\n/** keccak-512 hash function. */\nexport const keccak_512: CHash = /* @__PURE__ */ (() => gen(0x01, 72, 512 / 8))();\n\nexport type ShakeOpts = { dkLen?: number };\n\nconst genShake = (suffix: number, blockLen: number, outputLen: number) =>\n createXOFer<HashXOF<Keccak>, ShakeOpts>(\n (opts: ShakeOpts = {}) =>\n new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true)\n );\n\n/** SHAKE128 XOF with 128-bit security. */\nexport const shake128: CHashXO = /* @__PURE__ */ (() => genShake(0x1f, 168, 128 / 8))();\n/** SHAKE256 XOF with 256-bit security. */\nexport const shake256: CHashXO = /* @__PURE__ */ (() => genShake(0x1f, 136, 256 / 8))();\n","/**\n * Arweave gateway redundancy — single source of truth.\n *\n * Media bytes are content-addressed by Arweave tx id, so every gateway serves\n * the same bytes. This module owns the ordered gateway preference list and the\n * URL helpers used on BOTH sides of the wire:\n * - upload (client-mcp daemon): stamp a primary `url` + `fallback` mirrors.\n * - render (views/rig browser): re-point imeta URLs + fail over on error.\n *\n * Previously hand-duplicated in `views`, `rig`, and `client-mcp`; those now all\n * import from here. The default list can be overridden per call (e.g. from a\n * daemon env var) — pass `gateways` to the helpers.\n */\n\n/** Ordered Arweave gateways to try (primary first, then fallbacks). */\nexport const ARWEAVE_GATEWAYS = [\n 'https://ar-io.dev',\n 'https://arweave.net',\n 'https://permagate.io',\n] as const;\n\n/** Timeout for individual Arweave fetch requests in milliseconds. */\nexport const ARWEAVE_FETCH_TIMEOUT_MS = 15000;\n\n/** Arweave transaction IDs are 43-character base64url strings. */\nconst TX_ID_RE = /^[a-zA-Z0-9_-]{43}$/;\n\n/** Hosts recognized as Arweave gateways (path-addressed). */\nconst ARWEAVE_HOST_RE =\n /(^|\\.)(arweave\\.net|ar-io\\.dev|permagate\\.io|g8way\\.io|ar\\.io)$/i;\n\n/**\n * Extract an Arweave tx id from a media URL, or null if it is not\n * Arweave-addressable. Handles `ar://<txid>` and path-style\n * `https://<gateway>/<txid>`.\n *\n * Sandbox-subdomain URLs (`https://<txid>.<gateway>`) are deliberately NOT\n * decoded: tx ids are case-sensitive base64url, but `URL` (and DNS) lower-case\n * the hostname, which would corrupt the id. Real gateway sandboxing uses a\n * base32 label, not the raw id — the canonical id always travels in the path.\n */\nexport function arweaveTxId(rawUrl: string): string | null {\n const ar = /^ar:\\/\\/([a-zA-Z0-9_-]{43})(?:[/?#]|$)/.exec(rawUrl);\n if (ar?.[1]) return ar[1];\n\n let u: URL;\n try {\n u = new URL(rawUrl);\n } catch {\n return null;\n }\n // Only re-point hosts we actually recognize as Arweave gateways, so a stray\n // 43-char path segment on some other CDN is never misread as a tx id.\n if (!ARWEAVE_HOST_RE.test(u.hostname)) return null;\n\n // Path style: https://arweave.net/<txid>\n const seg = u.pathname.split('/').find(Boolean);\n if (seg && TX_ID_RE.test(seg)) return seg;\n\n return null;\n}\n\n/**\n * Primary URL + fallback mirror URLs for an Arweave tx id, one per gateway in\n * preference order. Used by the upload path to stamp `imeta` `url` + `fallback`.\n */\nexport function arweaveUrls(\n txId: string,\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): { url: string; fallbacks: string[] } {\n const all = (gateways.length ? gateways : ARWEAVE_GATEWAYS).map(\n (g) => `${g}/${txId}`\n );\n const [url, ...fallbacks] = all;\n return { url: url ?? `${ARWEAVE_GATEWAYS[0]}/${txId}`, fallbacks };\n}\n\n/**\n * Ordered candidate URLs for a media URL. Arweave-addressable URLs expand to the\n * full gateway-preference list (primary first); anything else is returned\n * unchanged. `extraFallbacks` (e.g. publisher-supplied `imeta` mirrors) are\n * appended last, de-duplicated. Used by the render path to fail over on error.\n */\nexport function arweaveGatewayCandidates(\n rawUrl: string,\n extraFallbacks: string[] = [],\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): string[] {\n const txId = arweaveTxId(rawUrl);\n const candidates = txId\n ? (gateways.length ? gateways : ARWEAVE_GATEWAYS).map((g) => `${g}/${txId}`)\n : [rawUrl];\n const seen = new Set(candidates);\n for (const f of extraFallbacks) {\n if (f && !seen.has(f)) {\n seen.add(f);\n candidates.push(f);\n }\n }\n return candidates;\n}\n","/**\n * Daemon configuration: resolved from a JSON config file and/or environment\n * variables, then expanded into a `ToonClientConfig` (BTP + channels + signer)\n * plus daemon-only settings (HTTP port, relay URL, apex negotiation).\n *\n * The mnemonic is sourced from (in precedence order):\n * 1. `TOON_CLIENT_MNEMONIC` env var,\n * 2. an encrypted keystore (#207) at `keystorePath`, decrypted with\n * `TOON_CLIENT_KEYSTORE_PASSWORD`,\n * 3. the `mnemonic` field of the config file (discouraged — plaintext on disk).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { loadKeystore } from '@toon-protocol/client';\nimport {\n encodeEventToToon,\n decodeEventFromToon,\n GenesisPeerLoader,\n} from '@toon-protocol/core';\nimport { ARWEAVE_GATEWAYS } from '@toon-protocol/arweave';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport type {\n SettlementChain,\n SwapControllerParams,\n} from '../control-api.js';\n\n/** Apex/relay settlement parameters injected as a peer negotiation. */\nexport interface ApexNegotiationConfig {\n /** ILP destination address, e.g. `g.proxy`. */\n destination: string;\n /** Peer id key used in the negotiation map (last ILP segment, e.g. `proxy`). */\n peerId: string;\n /** Settlement chain family. */\n chain: SettlementChain;\n /** Negotiated chain key, e.g. `evm:base:84532`. */\n chainKey: string;\n /** Numeric chain id (EVM only; 0 for solana/mina). */\n chainId: number;\n /** The apex's settlement (receive) address on `chain`. */\n settlementAddress: string;\n /** Token contract / mint / zkApp address. */\n tokenAddress?: string;\n /** EVM TokenNetwork / Solana programId / Mina zkApp address. */\n tokenNetwork?: string;\n}\n\nexport interface DaemonConfigFile {\n /** Named network tier (drives settlement presets, #209). */\n network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';\n mnemonic?: string;\n mnemonicAccountIndex?: number;\n keystorePath?: string;\n /**\n * Set when the daemon auto-generated the keystore (#251 first-run onboarding).\n * Such a keystore is encrypted with a default password so the identity reloads\n * across restarts without `TOON_CLIENT_KEYSTORE_PASSWORD`. A user-imported\n * keystore leaves this unset and still requires the env password.\n */\n keystoreAutoPassword?: boolean;\n /** Human-facing onboarding notes written by first-run scaffolding (ignored at runtime). */\n _help?: Record<string, string>;\n /** BTP WebSocket URL of the apex/connector. */\n btpUrl?: string;\n /**\n * Connector-PROXY base URL (devnet payment-proxy, e.g.\n * `https://proxy.devnet.toonprotocol.dev`). When set, the daemon routes paid\n * writes through the proxy's `POST /ilp` (ILP-over-HTTP) WITHOUT a BTP socket;\n * `btpUrl` then becomes optional. Env override: `TOON_CLIENT_PROXY_URL`.\n */\n proxyUrl?: string;\n /**\n * Devnet faucet base URL (e.g. `https://faucet.devnet.toonprotocol.dev`),\n * carried through to the ToonClient config for tooling/e2e funding. Env\n * override: `TOON_CLIENT_FAUCET_URL`.\n */\n faucetUrl?: string;\n /**\n * Faucet HTTP request timeout in milliseconds. When set it overrides the\n * chain-aware default (fast 30s for evm/solana, 120s for the slow-settling\n * mina faucet). Env override: `TOON_CLIENT_FAUCET_TIMEOUT_MS`.\n */\n faucetTimeoutMs?: number;\n /** Relay WS URL for FREE reads. */\n relayUrl?: string;\n /**\n * Apex CHANNEL ANCHOR (settlement peer). Defaults to the genesis seed apex's\n * ILP anchor (core's `genesis-peers.json`); its last segment becomes the apex\n * `peerId` the channel keys under. This is NOT a write route — see\n * `publishDestination`/`storeDestination`.\n */\n destination?: string;\n /**\n * ILP route for PUBLISHES (relay writes → `POST /write`). When unset it is\n * DERIVED from the `….relay.store` anchor (`g.proxy.relay.store` →\n * `g.proxy.relay`) — never the bare anchor, which the apex forwards to the\n * store and which 404s a `/write`. Env: `TOON_CLIENT_PUBLISH_DESTINATION`.\n */\n publishDestination?: string;\n /**\n * ILP route for UPLOADS (kind:5094 blob → `POST /store` → Arweave). When unset\n * it is DERIVED from the `….relay.store` anchor (`g.proxy.relay.store` →\n * `g.proxy.store`). Env: `TOON_CLIENT_STORE_DESTINATION`.\n */\n storeDestination?: string;\n /** Default fee per paid write, base units. Default `1`. */\n feePerEvent?: string;\n /** Channel nonce-watermark persistence file. Default `<dir>/channels.json`. */\n channelStorePath?: string;\n /**\n * Received swap-claim (chain-B) watermark persistence file (#352). Default\n * `<dir>/received-claims.json`. Verified claims harvested from `toon_swap`\n * survive a daemon restart here.\n */\n receivedClaimStorePath?: string;\n /** Localhost control API port. Default 8787. */\n httpPort?: number;\n /**\n * Active settlement chain for paid writes to the apex. A single daemon settles\n * to a given peer on ONE chain (the `ChannelManager` keys channels per peer +\n * each `ToonClient` owns one BTP session). Default `evm`. Override with\n * `TOON_CLIENT_CHAIN`. For simultaneous multi-chain, run one daemon per chain\n * (distinct `httpPort` + `channelStorePath`).\n */\n chain?: SettlementChain;\n /** Manual apex negotiation (HS / direct-apex mode where bootstrap finds 0 peers). */\n apex?: ApexNegotiationConfig;\n /**\n * Per-chain apex negotiations. The entry for the active `chain` is used; the\n * others are retained so switching chains needs only a `chain`/restart change.\n */\n apexChains?: Partial<Record<SettlementChain, ApexNegotiationConfig>>;\n /**\n * Additional apex CHILD peers (last ILP segment, e.g. `[\"store\",\"swap\"]`)\n * reachable via the SAME apex channel — used when publishing/swapping to\n * `g.proxy.store` / `g.proxy.swap`. The runner injects the apex\n * negotiation under each and points it at the open apex channel (no extra\n * on-chain channel). The apex `peerId` itself is always handled.\n */\n apexChildPeers?: string[];\n /** Extra settlement overrides passed straight through to ToonClient. */\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n /** Solana on-chain payment-channel params (required when `chain` is solana). */\n solanaChannel?: ToonClientConfig['solanaChannel'];\n /** Mina on-chain payment-channel params (required when `chain` is mina). */\n minaChannel?: ToonClientConfig['minaChannel'];\n /**\n * Ordered Arweave gateways (primary first) used to stamp uploaded-media URLs:\n * the primary becomes the `imeta` `url`, the rest become `fallback` mirrors.\n * Default: the shared `ARWEAVE_GATEWAYS` (ar.io → arweave.net → permagate.io).\n * Env override: `TOON_CLIENT_ARWEAVE_GATEWAYS` (comma-separated).\n */\n arweaveGateways?: string[];\n /**\n * Optional allowed-root for `toon_upload`'s `filePath` reads. When set, a\n * supplied `filePath` is resolved and rejected unless it lies inside this\n * directory — bounding which filesystem locations the daemon will read on an\n * agent's behalf. When unset, any absolute path is read (the path is still\n * resolved). Env override: `TOON_CLIENT_UPLOAD_ROOT`.\n */\n uploadAllowedRoot?: string;\n /**\n * Daemon-level defaults for the rolling-swap sender defenses (#351). A\n * per-request `SwapRequest` field always wins; these apply when the request\n * leaves the knob unset.\n */\n swapDefaults?: SwapDefaultsConfig;\n}\n\n/** Daemon-level swap-defense defaults (rolling-swap toon-meta#145, #351). */\nexport interface SwapDefaultsConfig {\n /**\n * Default floor tolerance in basis points: every swap gets\n * `minExchangeRate = pair.rate × (1 − floorBps/10000)` unless the request\n * supplies an explicit `minExchangeRate` or its own `floorBps`.\n */\n floorBps?: number;\n /** Default per-packet PREPARE expiry window, ms (rolling-swap R7). */\n packetExpiryMs?: number;\n /**\n * Engage the adaptive δ/W controller by default for swaps that don't pin an\n * explicit `packetCount` or supply their own `controller` params.\n * `advertisedSpread` is required (the sdk deliberately has no default).\n */\n controller?: SwapControllerParams;\n}\n\nexport interface ResolvedDaemonConfig {\n httpPort: number;\n relayUrl: string;\n /**\n * Whether a write uplink (proxy or BTP) is configured. FREE reads work\n * without one; a write attempt with `hasUplink === false` is rejected at the\n * control API with a clear \"configure an uplink\" error (issue #69). Reads\n * (`subscribe`/`query`/`getEvents`) never consult this.\n */\n hasUplink: boolean;\n /** Connector-proxy base URL (devnet payment-proxy), when configured. */\n proxyUrl?: string;\n /** Devnet faucet base URL, when configured. */\n faucetUrl?: string;\n /**\n * Explicit faucet HTTP request timeout (ms), when configured. Overrides the\n * chain-aware default. When absent, {@link fundWallet} picks the per-chain\n * default (longer for mina).\n */\n faucetTimeoutMs?: number;\n destination: string;\n /** Resolved default destination for relay-write publishes (falls back to `destination`). */\n publishDestination: string;\n /** Resolved default destination for store/Arweave uploads (falls back to `destination`). */\n storeDestination: string;\n feePerEvent: bigint;\n apex?: ApexNegotiationConfig;\n /** Apex child peers reached via the same apex channel (e.g. store, swap). */\n apexChildPeers?: string[];\n /** The active settlement chain for paid writes. */\n chain: SettlementChain;\n /** File mapping (destination, chain) → on-chain channelId for restart resume. */\n apexChannelStorePath: string;\n /**\n * JSON file persisting per-(source chain, maker, pair) adaptive-controller\n * state (sdk `JsonFileSwapControllerStateStore`), beside the channel stores.\n * Optional only so manually-built configs (tests) may omit it — the runner\n * falls back to `<configDir>/swap-controller-state.json`.\n */\n swapControllerStatePath?: string;\n /** Daemon-level swap-defense defaults (#351), when configured. */\n swapDefaults?: SwapDefaultsConfig;\n /**\n * Durable store for verified RECEIVED swap claims (chain-B watermarks, #352).\n * Optional only so manually-built test configs may omit it (the runner then\n * falls back to an in-memory store); `resolveConfig` always sets it.\n */\n receivedClaimStorePath?: string;\n /** Fully-built config for the `ToonClient` constructor. */\n toonClientConfig: ToonClientConfig;\n network?: string;\n /**\n * Ordered Arweave gateways for stamping uploaded-media URLs (primary first).\n * Always populated by `resolveConfig` (default = shared `ARWEAVE_GATEWAYS`);\n * optional only so manually-built configs (tests) may omit it — consumers\n * fall back to the shared default when it is absent.\n */\n arweaveGateways?: string[];\n /**\n * Resolved allowed-root for `toon_upload` `filePath` reads, when configured.\n * Absent means no boundary (any absolute path is read, still resolved).\n */\n uploadAllowedRoot?: string;\n}\n\n/**\n * Password used to encrypt an auto-generated keystore (#251 first-run\n * onboarding) when `TOON_CLIENT_KEYSTORE_PASSWORD` is unset. At-rest\n * obfuscation only — its purpose is letting the daemon reload the identity\n * across restarts with no env var. Users wanting a real password re-import the\n * keystore and set the env var.\n */\nexport const DEFAULT_KEYSTORE_PASSWORD = 'toon-client-default';\n\n/** Default config directory: `~/.toon-client`. Overridable via env. */\nexport function configDir(): string {\n return process.env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\n/** Default config file path. */\nexport function defaultConfigPath(): string {\n return join(configDir(), 'config.json');\n}\n\n/** Read + parse the JSON config file, returning `{}` when absent. */\nexport function readConfigFile(path: string): DaemonConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as DaemonConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `Failed to read daemon config at ${path}: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n}\n\n/** Resolve the mnemonic from env / keystore / config (in precedence order). */\nexport function resolveMnemonic(file: DaemonConfigFile): string {\n const envMnemonic = process.env['TOON_CLIENT_MNEMONIC'];\n if (envMnemonic) return envMnemonic.trim();\n\n if (file.keystorePath) {\n // An auto-provisioned keystore (#251) falls back to the default password so\n // the identity reloads with no env var; a user-imported one still requires\n // TOON_CLIENT_KEYSTORE_PASSWORD.\n const password =\n process.env['TOON_CLIENT_KEYSTORE_PASSWORD'] ??\n (file.keystoreAutoPassword ? DEFAULT_KEYSTORE_PASSWORD : undefined);\n if (!password) {\n throw new Error(\n 'keystorePath is set but TOON_CLIENT_KEYSTORE_PASSWORD is not provided'\n );\n }\n return loadKeystore(file.keystorePath, password);\n }\n\n if (file.mnemonic) return file.mnemonic.trim();\n\n throw new Error(\n 'No mnemonic configured. Set TOON_CLIENT_MNEMONIC, configure a keystorePath ' +\n '(+ TOON_CLIENT_KEYSTORE_PASSWORD), or add `mnemonic` to the config file.'\n );\n}\n\n/**\n * Build the full resolved daemon config (file overlaid with env, mnemonic\n * resolved, ToonClientConfig assembled). Env overrides supported:\n * TOON_CLIENT_BTP_URL, TOON_CLIENT_PROXY_URL, TOON_CLIENT_FAUCET_URL,\n * TOON_CLIENT_RELAY_URL, TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK,\n * TOON_CLIENT_DESTINATION.\n */\n/** Parse a comma-separated env value into a trimmed, non-empty list (or undefined). */\nfunction parseCsvEnv(value: string | undefined): string[] | undefined {\n if (!value) return undefined;\n const items = value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n return items.length ? items : undefined;\n}\n\n/**\n * Derive the publish/upload ROUTES from the apex channel anchor. Behind the\n * devnet proxy the anchor follows `<base>.relay.store` (e.g. `g.proxy.relay.store`):\n * publishes terminate at the relay (`<base>.relay`) and uploads at the store\n * (`<base>.store`). Routing the bare anchor as a publish forwards the `/write`\n * to the store backend → HTTP 404. Anchors that don't match the convention fall\n * back to the anchor unchanged (back-compat for non-proxy / custom topologies).\n */\nfunction deriveRouteDestinations(anchor: string): {\n publish: string;\n store: string;\n} {\n const segs = anchor.split('.');\n if (segs.at(-1) === 'store' && segs.at(-2) === 'relay') {\n const base = segs.slice(0, -2).join('.'); // e.g. g.proxy\n return { publish: `${base}.relay`, store: `${base}.store` };\n }\n return { publish: anchor, store: anchor };\n}\n\nexport function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig {\n const mnemonic = resolveMnemonic(file);\n\n const proxyUrl = process.env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl;\n const faucetUrl = process.env['TOON_CLIENT_FAUCET_URL'] ?? file.faucetUrl;\n const faucetTimeoutEnv = process.env['TOON_CLIENT_FAUCET_TIMEOUT_MS'];\n const faucetTimeoutMs =\n faucetTimeoutEnv && Number.isFinite(Number(faucetTimeoutEnv))\n ? Number(faucetTimeoutEnv)\n : file.faucetTimeoutMs;\n const btpUrl = process.env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl;\n\n // A write uplink is OPTIONAL at resolve time: FREE relay reads need none.\n // A connector PROXY (devnet ILP-over-HTTP, no BTP socket) OR a BTP url enables\n // paid writes; with neither, the daemon still starts read-only and rejects a\n // write attempt at the control API (issue #69). When only a proxy is set,\n // paid writes route through `POST /ilp` via HttpIlpClient.\n const hasUplink = Boolean(btpUrl || proxyUrl);\n // Network defaults are bootstrapped from the committed genesis peer list\n // (`@toon-protocol/core` → discovery/genesis-peers.json) — the seed apex's\n // relay + ILP anchor — rather than hardcoded per-network literals here. The\n // seed is a pointer; the apex's kind:10032 announcement organically\n // distributes the rest. Env/file values still win; the trailing literals are\n // last-resort fallbacks for an empty genesis list.\n const genesisSeed = GenesisPeerLoader.loadGenesisPeers()[0];\n const relayUrl =\n process.env['TOON_CLIENT_RELAY_URL'] ??\n file.relayUrl ??\n genesisSeed?.relayUrl ??\n 'ws://localhost:7100';\n const httpPort = Number(\n process.env['TOON_CLIENT_HTTP_PORT'] ?? file.httpPort ?? 8787\n );\n const destination =\n process.env['TOON_CLIENT_DESTINATION'] ??\n file.destination ??\n genesisSeed?.ilpAddress ??\n 'g.proxy';\n // Publishes (relay writes) and uploads (store/Arweave) terminate at DIFFERENT\n // backends behind the proxy and so route to different ILP destinations. When\n // not set explicitly they're DERIVED from the channel anchor (see\n // deriveRouteDestinations) — NOT reused verbatim, which would forward a\n // `/write` to the store backend and 404. This makes a single-`destination`\n // config (old or auto-discovered) publish correctly with zero extra keys.\n const routes = deriveRouteDestinations(destination);\n const publishDestination =\n process.env['TOON_CLIENT_PUBLISH_DESTINATION'] ??\n file.publishDestination ??\n routes.publish;\n const storeDestination =\n process.env['TOON_CLIENT_STORE_DESTINATION'] ??\n file.storeDestination ??\n routes.store;\n const feePerEvent = BigInt(file.feePerEvent ?? '1');\n const arweaveGateways =\n parseCsvEnv(process.env['TOON_CLIENT_ARWEAVE_GATEWAYS']) ??\n file.arweaveGateways ??\n [...ARWEAVE_GATEWAYS];\n const uploadRoot =\n process.env['TOON_CLIENT_UPLOAD_ROOT'] ?? file.uploadAllowedRoot;\n const uploadAllowedRoot = uploadRoot ? resolve(uploadRoot) : undefined;\n const network = (process.env['TOON_CLIENT_NETWORK'] ?? file.network) as\n | ToonClientConfig['network']\n | undefined;\n\n // Active settlement chain + the matching apex negotiation.\n const chain = (process.env['TOON_CLIENT_CHAIN'] ??\n file.chain ??\n 'evm') as SettlementChain;\n // Negotiation precedence: explicit per-chain → explicit single apex → a\n // proxy-mode negotiation synthesized from the flat settlement config. The last\n // one lets a proxy-only daemon settle paid writes WITHOUT a manual `apex`\n // block or a relay kind:10032 announcement (issue #69) — the runner falls back\n // to live kind:10032 discovery when this returns undefined.\n const apex =\n file.apexChains?.[chain] ??\n file.apex ??\n buildProxyApexNegotiation(file, chain, destination);\n\n const channelStorePath =\n file.channelStorePath ?? join(configDir(), 'channels.json');\n const apexChannelStorePath = join(configDir(), 'apex-channels.json');\n const swapControllerStatePath = join(\n configDir(),\n 'swap-controller-state.json'\n );\n const receivedClaimStorePath =\n file.receivedClaimStorePath ?? join(configDir(), 'received-claims.json');\n\n const toonClientConfig: ToonClientConfig = {\n // validateConfig requires connectorUrl OR proxyUrl. When only BTP is set\n // we pass a dummy connectorUrl (unused at runtime — BTP transport is used);\n // when a proxy is configured, `proxyUrl` satisfies the requirement and the\n // client derives the `POST /ilp` endpoint + routes writes over HTTP.\n ...(proxyUrl ? { proxyUrl } : { connectorUrl: 'http://127.0.0.1:1' }),\n ...(faucetUrl ? { faucetUrl } : {}),\n mnemonic,\n mnemonicAccountIndex: file.mnemonicAccountIndex ?? 0,\n ilpInfo: {\n pubkey: '00'.repeat(32),\n ilpAddress: 'g.toon.client',\n btpEndpoint: btpUrl ?? '',\n assetCode: 'USD',\n assetScale: 6,\n },\n toonEncoder: encodeEventToToon,\n toonDecoder: decodeEventFromToon,\n ...(btpUrl ? { btpUrl, btpAuthToken: '' } : {}),\n destinationAddress: destination,\n relayUrl: '', // reads use our own RelaySubscription, not bootstrap discovery\n knownPeers: [],\n channelStorePath,\n ...(network ? { network } : {}),\n ...(file.supportedChains ? { supportedChains: file.supportedChains } : {}),\n ...(file.settlementAddresses\n ? { settlementAddresses: file.settlementAddresses }\n : {}),\n ...(file.preferredTokens ? { preferredTokens: file.preferredTokens } : {}),\n ...(file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}),\n ...(file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {}),\n ...(file.solanaChannel ? { solanaChannel: file.solanaChannel } : {}),\n ...(file.minaChannel ? { minaChannel: file.minaChannel } : {}),\n };\n\n return {\n httpPort,\n relayUrl,\n hasUplink,\n ...(proxyUrl ? { proxyUrl } : {}),\n ...(faucetUrl ? { faucetUrl } : {}),\n ...(faucetTimeoutMs !== undefined ? { faucetTimeoutMs } : {}),\n destination,\n publishDestination,\n storeDestination,\n feePerEvent,\n ...(apex ? { apex } : {}),\n ...(file.apexChildPeers ? { apexChildPeers: file.apexChildPeers } : {}),\n chain,\n apexChannelStorePath,\n swapControllerStatePath,\n ...(file.swapDefaults ? { swapDefaults: file.swapDefaults } : {}),\n receivedClaimStorePath,\n toonClientConfig,\n network,\n arweaveGateways,\n ...(uploadAllowedRoot ? { uploadAllowedRoot } : {}),\n };\n}\n\n/**\n * Default negotiated chain-key for a settlement family when none is configured\n * and none can be discovered. EVM carries `evm:<network>:<chainId>`; the devnet\n * Anvil chain is 31337. Solana/Mina carry no numeric id.\n */\nfunction defaultChainKey(chain: SettlementChain, chainId: number): string {\n switch (chain) {\n case 'evm':\n return `evm:devnet:${chainId}`;\n case 'solana':\n return 'solana:devnet';\n case 'mina':\n return 'mina:devnet';\n }\n}\n\n/**\n * Synthesize an apex negotiation for PROXY mode from the flat settlement config\n * (`settlementAddresses` / `tokenNetworks` / `preferredTokens`). Returns\n * undefined unless a proxy uplink AND the apex's settlement (receive) address\n * for the active chain are configured — the connector's on-chain counterparty\n * is REQUIRED to open a channel and is never fabricated (issue #69). When it\n * returns undefined the runner falls back to live kind:10032 discovery.\n *\n * The chainKey is taken from the first key in `settlementAddresses`/`tokenNetworks`\n * matching the chain family, else a sensible devnet default.\n */\nfunction buildProxyApexNegotiation(\n file: DaemonConfigFile,\n chain: SettlementChain,\n destination: string\n): ApexNegotiationConfig | undefined {\n const proxyUrl = process.env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl;\n if (!proxyUrl) return undefined;\n\n const settlementAddresses = file.settlementAddresses ?? {};\n // Prefer a chainKey that already carries settlement info for this family.\n const familyKeys = (rec: Record<string, string>): string[] =>\n Object.keys(rec).filter((k) => k.split(':')[0] === chain);\n const chainKey =\n familyKeys(settlementAddresses)[0] ??\n familyKeys(file.tokenNetworks ?? {})[0] ??\n familyKeys(file.preferredTokens ?? {})[0];\n\n // Without an explicit settlementAddress entry there is no on-chain\n // counterparty to open against — defer to relay discovery rather than guess.\n if (!chainKey) return undefined;\n const settlementAddress = settlementAddresses[chainKey];\n if (!settlementAddress) return undefined;\n\n const parts = chainKey.split(':');\n // Accept both 3-part `evm:{network}:{chainId}` and 2-part `evm:{chainId}`.\n const chainId =\n chain === 'evm' && parts.length >= 2\n ? Number(parts[2] ?? parts[1] ?? 0)\n : 0;\n const peerId = destination.split('.').at(-1) ?? destination;\n\n return {\n destination,\n peerId,\n chain,\n chainKey: chainKey || defaultChainKey(chain, chainId),\n chainId,\n settlementAddress,\n ...(file.preferredTokens?.[chainKey]\n ? { tokenAddress: file.preferredTokens[chainKey] }\n : {}),\n ...(file.tokenNetworks?.[chainKey]\n ? { tokenNetwork: file.tokenNetworks[chainKey] }\n : {}),\n };\n}\n","/**\n * Thin HTTP client for the `toon-clientd` localhost control API. Used by the\n * MCP server (and any other caller) to drive the daemon without holding any\n * chain keys or long-lived connections itself.\n */\n\nimport type {\n AddApexRequest,\n AddApexResponse,\n AddRelayRequest,\n BalancesResponse,\n ChannelDepositRequest,\n ChannelDepositResponse,\n CloseChannelRequest,\n CloseChannelResponse,\n SettleChannelRequest,\n SettleChannelResponse,\n ChannelsResponse,\n ErrorResponse,\n EventsQuery,\n EventsResponse,\n FundStatusResponse,\n FundWalletRequest,\n FundWalletResponse,\n GitCommentRequest,\n GitEstimateRequest,\n GitEstimateResponse,\n GitEventResponse,\n GitIssueRequest,\n GitPatchRequest,\n GitPushRequest,\n GitPushResponse,\n GitStatusRequest,\n HttpFetchPaidRequest,\n HttpFetchPaidResponse,\n OpenChannelRequest,\n PublishRequest,\n PublishResponse,\n PublishUnsignedRequest,\n QueryRequest,\n QueryResponse,\n RemoveApexRequest,\n RemoveRelayRequest,\n SettlementChain,\n SettleSwapClaimsRequest,\n SettleSwapClaimsResponse,\n ListSwapClaimsResponse,\n StatusResponse,\n SubscribeRequest,\n SubscribeResponse,\n SwapRequest,\n SwapResponse,\n TargetsResponse,\n UploadMediaRequest,\n UploadMediaResponse,\n} from './control-api.js';\n\n/** Error thrown when the daemon returns a non-2xx response. */\nexport class ControlApiError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly retryable: boolean,\n readonly detail?: string,\n /**\n * Structured payload for errors that carry data beyond a message — the\n * extra top-level fields of the error envelope (e.g. `refs` on\n * `non_fast_forward`, `objects` on `oversize_objects` from `/git/*`).\n */\n readonly data?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'ControlApiError';\n }\n}\n\n/** Thrown when the daemon is unreachable (not running / wrong port). */\nexport class DaemonUnreachableError extends Error {\n constructor(\n readonly baseUrl: string,\n readonly causedBy?: unknown\n ) {\n super(`toon-clientd not reachable at ${baseUrl}`);\n this.name = 'DaemonUnreachableError';\n }\n}\n\nexport interface ControlClientOptions {\n /** Base URL of the daemon, e.g. `http://127.0.0.1:8787`. */\n baseUrl: string;\n /** Per-request timeout, ms. Default 35000 (publishes can wait on FULFILL). */\n timeoutMs?: number;\n /** Inject a fetch implementation (tests). Defaults to global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport class ControlClient {\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ControlClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.timeoutMs = opts.timeoutMs ?? 35_000;\n this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n }\n\n /** True if the daemon answers `GET /status` (used as a liveness probe). */\n async ping(): Promise<boolean> {\n try {\n await this.request<StatusResponse>('GET', '/status');\n return true;\n } catch (err) {\n if (err instanceof DaemonUnreachableError) return false;\n // A reachable daemon that errored still counts as \"up\".\n return err instanceof ControlApiError;\n }\n }\n\n status(): Promise<StatusResponse> {\n return this.request<StatusResponse>('GET', '/status');\n }\n\n publish(body: PublishRequest): Promise<PublishResponse> {\n return this.request<PublishResponse>('POST', '/publish', body);\n }\n\n publishUnsigned(body: PublishUnsignedRequest): Promise<PublishResponse> {\n return this.request<PublishResponse>('POST', '/publish-unsigned', body);\n }\n\n uploadMedia(body: UploadMediaRequest): Promise<UploadMediaResponse> {\n return this.request<UploadMediaResponse>('POST', '/upload-media', body);\n }\n\n subscribe(body: SubscribeRequest): Promise<SubscribeResponse> {\n return this.request<SubscribeResponse>('POST', '/subscribe', body);\n }\n\n query(body: QueryRequest): Promise<QueryResponse> {\n return this.request<QueryResponse>('POST', '/query', body);\n }\n\n events(query: EventsQuery = {}): Promise<EventsResponse> {\n const qs = new URLSearchParams();\n if (query.subId) qs.set('subId', query.subId);\n if (query.cursor !== undefined) qs.set('cursor', String(query.cursor));\n if (query.limit !== undefined) qs.set('limit', String(query.limit));\n if (query.relayUrl) qs.set('relayUrl', query.relayUrl);\n const suffix = qs.toString() ? `?${qs.toString()}` : '';\n return this.request<EventsResponse>('GET', `/events${suffix}`);\n }\n\n openChannel(body: OpenChannelRequest = {}): Promise<{ channelId: string }> {\n return this.request<{ channelId: string }>('POST', '/channels', body);\n }\n\n channels(): Promise<ChannelsResponse> {\n return this.request<ChannelsResponse>('GET', '/channels');\n }\n\n balances(): Promise<BalancesResponse> {\n // On-chain balance reads can be slow on devnet RPCs. Cap this read well\n // below the default 35s so the wallet card resolves (or shows its Retry\n // state) in a few seconds instead of spinning — the read seam retries, so\n // the effective bound stays modest.\n return this.request<BalancesResponse>('GET', '/balances', undefined, { timeoutMs: 12_000 });\n }\n\n depositToChannel(body: ChannelDepositRequest): Promise<ChannelDepositResponse> {\n return this.request<ChannelDepositResponse>('POST', '/channels/deposit', body);\n }\n\n closeChannel(body: CloseChannelRequest): Promise<CloseChannelResponse> {\n return this.request<CloseChannelResponse>('POST', '/channels/close', body);\n }\n\n settleChannel(body: SettleChannelRequest): Promise<SettleChannelResponse> {\n return this.request<SettleChannelResponse>('POST', '/channels/settle', body);\n }\n\n swap(body: SwapRequest): Promise<SwapResponse> {\n return this.request<SwapResponse>('POST', '/swap', body);\n }\n\n /** List persisted received-claim watermarks (#352). */\n swapClaims(): Promise<ListSwapClaimsResponse> {\n return this.request<ListSwapClaimsResponse>('GET', '/swap/claims');\n }\n\n /**\n * Build/submit on-chain settlements for received swap claims (#352). An\n * EVM submission waits out gas estimation + a receipt, so give it headroom\n * beyond the default control timeout.\n */\n settleSwapClaims(\n body: SettleSwapClaimsRequest = {}\n ): Promise<SettleSwapClaimsResponse> {\n return this.request<SettleSwapClaimsResponse>(\n 'POST',\n '/swap/settle',\n body,\n { timeoutMs: 120_000 }\n );\n }\n\n httpFetchPaid(body: HttpFetchPaidRequest): Promise<HttpFetchPaidResponse> {\n return this.request<HttpFetchPaidResponse>(\n 'POST',\n '/http-fetch-paid',\n body\n );\n }\n\n targets(): Promise<TargetsResponse> {\n return this.request<TargetsResponse>('GET', '/targets');\n }\n\n addRelay(body: AddRelayRequest): Promise<TargetsResponse> {\n return this.request<TargetsResponse>('POST', '/relays', body);\n }\n\n removeRelay(body: RemoveRelayRequest): Promise<TargetsResponse> {\n return this.request<TargetsResponse>('DELETE', '/relays', body);\n }\n\n addApex(body: AddApexRequest): Promise<AddApexResponse> {\n return this.request<AddApexResponse>('POST', '/apex', body);\n }\n\n removeApex(body: RemoveApexRequest): Promise<TargetsResponse> {\n return this.request<TargetsResponse>('DELETE', '/apex', body);\n }\n\n fundWallet(body: FundWalletRequest = {}): Promise<FundWalletResponse> {\n // The drip is ASYNC: the daemon launches the faucet call in the background\n // and returns a 'pending' snapshot immediately, so this request resolves\n // fast regardless of chain. (Previously this had to out-wait the daemon's\n // chain-aware faucet budget — up to ~120s for Mina — which strained the\n // control + host timeouts; #199-class.) Poll `fundStatus` / re-read balances\n // for the terminal state.\n return this.request<FundWalletResponse>('POST', '/fund-wallet', body, {\n timeoutMs: 40_000,\n });\n }\n\n fundStatus(chain?: SettlementChain): Promise<FundStatusResponse> {\n const path = chain\n ? `/fund-wallet/status?chain=${encodeURIComponent(chain)}`\n : '/fund-wallet/status';\n return this.request<FundStatusResponse>('GET', path);\n }\n\n // ── Git write path (/git/*, epic #222 ticket #227) ─────────────────────────\n\n /**\n * Plan + price a push without paying. Runs local git plumbing plus a relay\n * remote-state read in the daemon, so allow more than the default budget on\n * large repositories.\n */\n gitEstimate(body: GitEstimateRequest): Promise<GitEstimateResponse> {\n return this.request<GitEstimateResponse>('POST', '/git/estimate', body, {\n timeoutMs: 120_000,\n });\n }\n\n /**\n * Execute a push (PAID: object uploads + event publishes). Uploads are\n * sequential single-packet store writes, so a large first push can\n * legitimately take minutes — budget accordingly rather than surfacing a\n * still-working push as a timeout.\n */\n gitPush(body: GitPushRequest): Promise<GitPushResponse> {\n return this.request<GitPushResponse>('POST', '/git/push', body, {\n timeoutMs: 600_000,\n });\n }\n\n gitIssue(body: GitIssueRequest): Promise<GitEventResponse> {\n return this.request<GitEventResponse>('POST', '/git/issue', body);\n }\n\n gitComment(body: GitCommentRequest): Promise<GitEventResponse> {\n return this.request<GitEventResponse>('POST', '/git/comment', body);\n }\n\n gitPatch(body: GitPatchRequest): Promise<GitEventResponse> {\n return this.request<GitEventResponse>('POST', '/git/patch', body);\n }\n\n gitStatus(body: GitStatusRequest): Promise<GitEventResponse> {\n return this.request<GitEventResponse>('POST', '/git/status', body);\n }\n\n /**\n * Whether an HTTP method is safe to transparently retry. Idempotent reads\n * (GET) and deletes can be replayed verbatim; a mutating POST cannot — the\n * daemon may have already applied it before the socket failed, so retrying\n * risks a double publish/fund/deposit.\n */\n private static isIdempotent(method: string): boolean {\n return method === 'GET' || method === 'DELETE';\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n opts?: { timeoutMs?: number }\n ): Promise<T> {\n // Transparently retry idempotent requests on a transient connection failure.\n // The MCP server is long-lived and calls the daemon infrequently, so a\n // pooled keep-alive socket can be reaped by the daemon between calls; the\n // next request reuses the now-dead socket and fails with ECONNRESET /\n // \"socket hang up\", surfacing as `DaemonUnreachableError` even though the\n // daemon is up. A fresh socket on retry succeeds — this is the root cause of\n // the intermittent \"daemon not reachable\" on `toon_balances` (toon-client\n // #186). A genuine ECONNREFUSED (daemon mid-restart) benefits from the same\n // brief retry. Timeouts are NOT retried here (a slow handler is classified\n // as a `ControlApiError` 504 below, not a connection failure).\n const attempts = ControlClient.isIdempotent(method) ? 3 : 1;\n let lastErr: unknown;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return await this.attemptOnce<T>(method, path, body, opts?.timeoutMs);\n } catch (err) {\n lastErr = err;\n if (attempt < attempts && err instanceof DaemonUnreachableError) {\n await new Promise((r) => setTimeout(r, 50 * attempt));\n continue;\n }\n throw err;\n }\n }\n throw lastErr;\n }\n\n private async attemptOnce<T>(\n method: string,\n path: string,\n body?: unknown,\n timeoutMs = this.timeoutMs\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers: body ? { 'content-type': 'application/json' } : undefined,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n // A request WE aborted is a timeout (the daemon is reachable but the\n // handler is slow — e.g. a hung on-chain balance read), NOT an unreachable\n // daemon. Classify it as a retryable 504 so callers surface \"retry\"\n // instead of the misleading \"the daemon failed to start — check the log\".\n if (controller.signal.aborted) {\n throw new ControlApiError(\n `control request ${method} ${path} timed out after ${timeoutMs}ms`,\n 504,\n true\n );\n }\n throw new DaemonUnreachableError(this.baseUrl, err);\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n const json = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const e = (json ?? {}) as ErrorResponse;\n // Surface any extra top-level envelope fields (structured error data,\n // e.g. /git/* `refs` / `objects`) on the thrown error.\n const { error, detail, retryable, ...extra } = e;\n throw new ControlApiError(\n error ?? `HTTP ${res.status}`,\n res.status,\n (typeof retryable === 'boolean' ? retryable : undefined) ??\n res.status === 503,\n typeof detail === 'string' ? detail : undefined,\n Object.keys(extra).length > 0 ? extra : undefined\n );\n }\n return json as T;\n }\n}\n\nfunction safeJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n","/**\n * Daemon process lifecycle: a single-instance PID lock, a detached spawn helper\n * (used by the MCP server to auto-start the daemon), and a readiness probe.\n *\n * Why single-instance matters: two daemons would open two BTP sessions against\n * the same channel and race the nonce watermark — corrupting the payment proof.\n * The lock is a PID file; a stale file (process gone) is reclaimed.\n */\n\nimport { spawn } from 'node:child_process';\nimport {\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { configDir } from './config.js';\nimport { ControlClient } from '../control-client.js';\n\n/** Default PID file path. */\nexport function pidFilePath(): string {\n return join(configDir(), 'daemon.pid');\n}\n\n/** Whether a process with `pid` is currently alive. */\nexport function isProcessAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n // Signal 0 performs error checking without actually sending a signal.\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means the process exists but we can't signal it — still alive.\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Read the recorded daemon PID, or null when absent/invalid. */\nexport function readPid(path = pidFilePath()): number | null {\n try {\n const pid = parseInt(readFileSync(path, 'utf8').trim(), 10);\n return Number.isInteger(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Acquire the single-instance lock for the current process. Throws if another\n * live daemon already holds it. Reclaims a stale lock (dead PID).\n */\nexport function acquireLock(path = pidFilePath()): void {\n const existing = readPid(path);\n if (\n existing !== null &&\n existing !== process.pid &&\n isProcessAlive(existing)\n ) {\n throw new Error(\n `Another toon-clientd is already running (pid ${existing}). ` +\n `Stop it first or remove ${path} if it is stale.`\n );\n }\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, String(process.pid), { mode: 0o600 });\n}\n\n/** Release the lock if it belongs to this process. */\nexport function releaseLock(path = pidFilePath()): void {\n const pid = readPid(path);\n if (pid === process.pid) {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n }\n}\n\n/** Whether a daemon is currently running per the PID lock. */\nexport function isDaemonRunning(path = pidFilePath()): boolean {\n const pid = readPid(path);\n return pid !== null && isProcessAlive(pid);\n}\n\nexport interface SpawnDaemonOptions {\n /** Path to the `toon-clientd` bin (defaults to this package's daemon entry). */\n daemonEntry?: string;\n /** Extra env for the spawned process. */\n env?: NodeJS.ProcessEnv;\n /** Directory for the detached stdout/stderr log. */\n logDir?: string;\n}\n\n/**\n * Spawn the daemon as a detached, fully background process (survives the\n * parent — e.g. the ephemeral MCP/agent session — exiting). Returns the child\n * PID. The caller should poll {@link waitForReady} before issuing requests.\n */\nexport function spawnDaemonDetached(opts: SpawnDaemonOptions = {}): number {\n const entry = opts.daemonEntry ?? defaultDaemonEntry();\n const logDir = opts.logDir ?? configDir();\n mkdirSync(logDir, { recursive: true });\n // A single appended log file; the detached child writes here so the parent\n // can close its handles and exit.\n const logPath = join(logDir, 'daemon.log');\n // Use 'a' so restarts append rather than truncate the operator's log.\n const out = openSync(logPath, 'a');\n const child = spawn(process.execPath, [entry, 'run'], {\n detached: true,\n stdio: ['ignore', out, out],\n env: { ...process.env, ...opts.env },\n });\n child.unref();\n if (child.pid === undefined) {\n throw new Error('Failed to spawn toon-clientd (no pid)');\n }\n return child.pid;\n}\n\n/** Resolve the path to this package's built daemon entry (`dist/daemon.js`). */\nexport function defaultDaemonEntry(): string {\n // When running from the built bin, the daemon entry sits next to this module.\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, 'daemon.js');\n}\n\n/**\n * Poll the control API until the daemon answers `GET /status`, up to\n * `timeoutMs`. Resolves true once reachable (NOT necessarily done\n * bootstrapping — the BTP session can take a moment; callers surface\n * `bootstrapping`).\n */\nexport async function waitForReady(\n baseUrl: string,\n timeoutMs = 15_000,\n intervalMs = 300\n): Promise<boolean> {\n const client = new ControlClient({ baseUrl, timeoutMs: intervalMs * 3 });\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await client.ping()) return true;\n await delay(intervalMs);\n }\n return false;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Whether the daemon entry file exists (used for friendlier error messages). */\nexport function daemonEntryExists(entry = defaultDaemonEntry()): boolean {\n return existsSync(entry);\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["verifyEvent","generateSecretKey","getPublicKey","generateSecretKey","getPublicKey","wordlist","sha256","bytesToHex","hexToBytes","hexToBytes","gen","concatBytes","_0n","_1n","_0n","_1n","_0n","_1n","_1n","_0n","_1n","key","_1n","_0n","_1n","window","Point","wbits","_0n","_2n","_0n","_1n","_3n","_4n","key","concatBytes","Point","endo","bytesToHex","Point","getPublicKey","utils","key","randomBytes","hmac","concatBytes","_1n","r","s","hexToBytes","_2n","bytesToHex","_0n","Point","_2n","_3n","secp256k1","sha256","secp256k1","sha256","bytesToHex","hexToBytes","concatBytes","key","privateKeyToAccount","toHex","createPublicClient","http","defineChain","getPublicKey","getPublicKey","base58Decode","key","RATE_REGEX","RATE_REGEX","HEX64_REGEX","DECIMAL_UINT_REGEX","base58Decode","split","noop","NOOP_LOGGER","getPublicKey","window","ToonError","DECIMAL_UINT_REGEX","key","capAbs","hexToBytes","base58Decode","EVM_ADDRESS_REGEX","key","createPublicClient","defineChain","http","readFileSync","writeFileSync","existsSync","_0n","_1n","_2n","_7n","keccak_256","finalizeEvent","getPublicKey","randomBytes","writeFileSync","readFileSync","generateMnemonic","validateMnemonic","getPublicKey","hmac","sha512","ed25519","key","BASE58_ALPHABET","validateConfig","validateMnemonic","generateSecretKey","toHex","isBase64","resolve","textEncoder","sha256","require","http","mod","sha256","TOKEN_PROGRAM_ID","key","STATE_MAP","base58Encode","ed25519","ensureOwnedMinaZkApp","status","toHex","privateKeyToAccount","bytesToHex","hexToMinaBase58PrivateKey","createPublicClient","defineChain","BASE58_ALPHABET","CRLF","findHeaderEnd","parseEvmChainId","Field","validateConfig","generateSecretKey","getPublicKey","existsSync","readFileSync","writeFileSync","hexToBytes","keccak_256","bytesToHex","verifyEvmClaimSignature","parseEvmChainId","encryptMnemonic","randomBytes","key","decryptMnemonic","generateMnemonic","readFileSync","decryptMnemonic","writeFileSync","readFileSync","readFileSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","join","join","readFileSync","mkdirSync","dirname","writeFileSync"]}
|