@toon-protocol/client 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -5
- package/dist/index.js +0 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/keys/KeyDerivation.ts","../src/utils/binary.ts","../src/utils/store-envelope.ts","../src/modes/http.ts","../src/utils/retry.ts","../src/adapters/HttpRuntimeClient.ts","../src/btp/protocol.ts","../src/btp/IsomorphicBtpClient.ts","../src/adapters/BtpRuntimeClient.ts","../src/adapters/HttpIlpClient.ts","../src/adapters/selectIlpTransport.ts","../src/channel/OnChainChannelClient.ts","../src/channel/solana-payment-channel.ts","../src/channel/mina-channel-open.ts","../src/signing/evm-signer.ts","../src/signing/solana-signer.ts","../src/signing/mina-signer.ts","../src/channel/mina-payment-channel.ts","../src/channel/mina-deposit.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/blob-storage.ts","../src/adapters/Http402Client.ts","../src/adapters/HttpConnectorAdmin.ts","../src/pet/filterPetDvmProviders.ts","../src/pet/buildPetInteractionRequest.ts","../src/pet/parsePetInteractionResult.ts","../src/pet/parsePetInteractionEvent.ts","../src/pet/buildPetListingEvent.ts","../src/pet/parsePetListing.ts","../src/pet/filterPetListings.ts","../src/pet/buildPetPurchaseRequest.ts","../src/faucet.ts","../src/keys/KeyManager.ts","../src/keys/PasskeyAuth.ts","../src/keys/encoding.ts","../src/keys/KeyVault.ts","../src/keys/BackupService.ts","../src/keys/keystore-node.ts"],"sourcesContent":["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 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 {\n requestBlobStorage,\n type RequestBlobStorageResult,\n} from './blob-storage.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.js';\nimport {\n Http402Client,\n type H402FetchOptions,\n} from './adapters/Http402Client.js';\nimport type {\n ToonClientConfig,\n ToonStartResult,\n PublishEventResult,\n SignedBalanceProof,\n} from './types.js';\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 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 townhouse 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 const chainId = parts.length >= 3 ? parseInt(parts[2] ?? '0', 10) : 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 ? parseInt(parts[2] ?? '0', 10) : 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.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 zkAppAddress: this.config.minaChannel.zkAppAddress,\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 }\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` (Mill swaps) is a separate\n // surface with a raw-TOON contract and is intentionally NOT wrapped here.\n const writeData = buildStoreWriteEnvelope(event);\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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let claimMessage: any;\n if (options?.claim) {\n // EXISTING PATH: Caller provides pre-signed claim (backwards compatible).\n // Build the envelope with the chain-appropriate signer so a Solana/Mina\n // balance proof is not mis-wrapped as an EVM claim (see F06 root cause).\n claimMessage = this.buildClaimMessageForProof(options.claim);\n } else if (this.channelManager) {\n // NEW PATH: Auto-open channel + auto-sign claim (lazy channels)\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 BigInt(amount)\n );\n const signer = this.channelManager.getSignerForChannel(channelId);\n claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());\n } else {\n throw new ToonClientError(\n 'No claim provided and no channel manager configured',\n 'MISSING_CLAIM'\n );\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 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 Mill 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 * @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 }): 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 },\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 sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution\n * to call this helper. Kept duplicated for now to minimize regression risk.\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 * 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 const chainIdPart = parts.length >= 3 ? parts[2] : 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 (town.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 townhouse 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 | '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 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 relayUrl: config.relayUrl ?? 'ws://localhost:7100',\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","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/mill's `deriveMillKeys`). 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/** Request-line + headers the deployed store accepts (proven against devnet). */\nconst REQUEST_LINE = 'POST /write HTTP/1.1';\nconst HEADERS = ['Host: relay', 'Content-Type: application/json'];\n\n/**\n * Wrap a signed Nostr event in the `POST /write` HTTP envelope the deployed\n * payment-proxy reverse-proxies to the relay store.\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 * @returns The envelope bytes to use as the ILP PREPARE `data`.\n */\nexport function buildStoreWriteEnvelope(event: NostrEvent): Uint8Array {\n const body = JSON.stringify({ event });\n const head = [REQUEST_LINE, ...HEADERS].join('\\r\\n');\n return encodeUtf8(head + '\\r\\n\\r\\n' + body);\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 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 // Skip 32-byte fulfillment (unused in TOON)\n offset += 32;\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.FULFILL, 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 pending.resolve({\n type: ILPPacketType.FULFILL,\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","import {\n IsomorphicBtpClient,\n BtpConnectionError,\n} from '../btp/IsomorphicBtpClient.js';\nimport { ILPPacketType, type BTPProtocolData } from '../btp/protocol.js';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { withRetry } from '../utils/retry.js';\nimport { toBase64, fromBase64, encodeUtf8 } from '../utils/binary.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 async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): 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 async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed by reconnect() above\n const response = await this.btpClient!.sendPacket({\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n });\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n // Reject\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 response = await this.btpClient.sendPacket(\n {\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n },\n protocolData\n );\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\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';\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 async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): 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 async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim?: unknown\n ): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n\n const prepare = serializeIlpPrepare({\n type: ILPPacketType.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + 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);\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 private async mapResponse(response: Response): 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 const ilp = deserializeIlpPacket(buf);\n if (ilp.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: ilp.data.length > 0 ? toBase64(ilp.data) : undefined,\n };\n }\n return {\n accepted: false,\n code: ilp.code,\n message: ilp.message,\n data: ilp.data.length > 0 ? toBase64(ilp.data) : undefined,\n };\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 {\n openSolanaChannel as openSolanaChannelOnChain,\n getChannelAccountState as getSolanaChannelAccountState,\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: '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: '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 zkAppAddress: string;\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 }\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 < 3) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainIdStr = parts[2];\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 * 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 payerTokenAccount: cfg.deposit.payerTokenAccount,\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 deployed zkApp address IS the channel id (claim `zkAppAddress`).\n const zkAppAddress = this.minaConfig.zkAppAddress;\n if (!zkAppAddress) {\n throw new Error(\n 'Mina channel requires a deployed zkAppAddress (minaConfig.zkAppAddress)'\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 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\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.\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 openEvmChannel(\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\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\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 * Mill ↔ 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 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 // deposit: discriminator(8) + amount(8 LE)\n const depositData = new Uint8Array(16);\n depositData.set(IX_DEPOSIT, 0);\n writeU64LE(depositData, 8, params.deposit.amount);\n\n depositTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: false },\n {\n pubkey: params.deposit.payerTokenAccount,\n isSigner: false,\n isWritable: true,\n },\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 }\n\n return { channelPDA, opened: true, initTxSignature, depositTxSignature };\n}\n\n// Internal helpers exported for unit tests (parity assertions).\nexport const __testing = { padTo32, sortPubkeys, isOnCurve };\n","/**\n * On-chain Mina payment-channel open — connector-parity.\n *\n * Opens (initializes) — and optionally deposits into — a REAL on-chain Mina\n * payment channel on the deployed `PaymentChannel` zkApp, so the connector's\n * `MinaPaymentChannelSDK.getChannelState(zkAppAddress)` finds a channel whose\n * on-chain `channelState == OPEN` (status `'opened'`) and the Mina claim\n * verifies + stores. This is the Mina analog of `openSolanaChannel`\n * (`solana-payment-channel.ts`, connector#105): the client opens its own\n * per-channel on-chain state rather than relying on a pre-initialized channel.\n *\n * ## Why this is separate from `mina-payment-channel.ts`\n *\n * `mina-payment-channel.ts` builds the OFF-chain balance-proof claim with\n * `mina-signer` (no o1js — keeps the client lightweight). But INITIALIZING a\n * zkApp channel requires producing a zkApp method proof, which is heavyweight\n * o1js WASM circuit work. So this module lazily imports `o1js` +\n * `@toon-protocol/mina-zkapp` ONLY when an on-chain open is actually requested\n * (the e2e client / Node settlement path), mirroring the connector's own\n * `getO1js()` lazy-require. Both are OPTIONAL dependencies and are kept out of\n * the bundle via `tsup` `external` so npm consumers that never open a Mina\n * channel don't pay the o1js cost.\n *\n * ## Contract call (must match the connector's `MinaPaymentChannelSDK.openChannel`)\n *\n * `PaymentChannel.initializeChannel(participantA, participantB, nonce, timeout, tokenId)`\n * sets `channelState = OPEN` (1). The deployed zkApp address IS the channel id\n * (`MinaClaimMessage.zkAppAddress`), identical to the claim's channel-hash\n * preimage in `mina-payment-channel.ts`. `deposit(amount, depositor)` then\n * bumps `depositTotal` (only valid while `channelState == OPEN`).\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address); this module assumes the\n * account exists and only INITIALIZES the channel on it (idempotent — if the\n * channel is already `OPEN`, it returns without re-initializing).\n *\n * @module\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\n\n/** Result shape of o1js `fetchAccount` (the bits we read). */\ninterface FetchAccountResult {\n error?: unknown;\n account?: {\n zkapp?: { appState?: { toString(): string }[] };\n };\n}\n\n/** Minimal o1js surface this module uses (lazy-loaded). */\ninterface O1jsLike {\n Mina: any;\n PrivateKey: any;\n PublicKey: any;\n Field: any;\n AccountUpdate: any;\n fetchAccount: (args: { publicKey: any }) => Promise<FetchAccountResult>;\n}\n\nlet cachedO1js: O1jsLike | null = null;\nlet cachedPaymentChannel: any | null = null;\nlet compiledContract: any | null = null;\n\n/**\n * Test-only override for the o1js + contract loader. When set, `loadMinaRuntime`\n * returns this instead of doing the `createRequire` resolution — so unit tests\n * can inject fakes WITHOUT pulling the real o1js WASM runtime (vitest's\n * `vi.mock` cannot intercept the CJS `require` path the production loader uses).\n */\nlet runtimeOverride:\n | (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>)\n | null = null;\n\n/** Test hook: inject a fake o1js + PaymentChannel runtime. */\nexport function _setMinaRuntimeForTests(\n loader: (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>) | null\n): void {\n runtimeOverride = loader;\n}\n\n/**\n * Resolve `o1js` AND the `PaymentChannel` contract through ONE shared module\n * instance.\n *\n * ⚠️ o1js keeps its \"active Mina instance\" in a module-level closure\n * (`mina-instance.js`). `@toon-protocol/mina-zkapp` ships as CommonJS, so its\n * internal `import {Mina}` is emitted as `require('o1js')` → o1js's CJS build\n * (`dist/node/index.cjs`). A bare ESM `import('o1js')` from this module resolves\n * o1js's DIFFERENT ESM build (`dist/node/index.js`) — a SEPARATE module instance\n * with a SEPARATE `activeInstance` closure. Calling `setActiveInstance` on the\n * ESM instance while `PaymentChannel.initializeChannel` reads the CJS instance\n * throws `channelState.get() failed … Must call Mina.setActiveInstance first`\n * (observed in the local-HS Mina e2e on the FIRST publish). The connector's own\n * settlement path and `scripts/deploy-mina-zkapp.ts` both work around this by\n * requiring o1js through the same anchor the zkApp uses.\n *\n * Fix: anchor a `createRequire` at the `@toon-protocol/mina-zkapp` package and\n * `require('o1js')` + the contract from there, so both share the CJS o1js\n * instance and `setActiveInstance` is visible inside the contract method. Kept\n * lazy (and `external` in tsup) so the multi-hundred-MB WASM runtime is only\n * loaded when a Mina channel is actually opened.\n */\nasync function loadMinaRuntime(): Promise<{\n o1js: O1jsLike;\n PaymentChannel: any;\n}> {\n if (cachedO1js && cachedPaymentChannel) {\n return { o1js: cachedO1js, PaymentChannel: cachedPaymentChannel };\n }\n if (runtimeOverride) {\n const injected = await runtimeOverride();\n cachedO1js = injected.o1js;\n cachedPaymentChannel = injected.PaymentChannel;\n return injected;\n }\n const { createRequire } = await import('node:module');\n const nodePath = await import('node:path');\n // Anchor resolution at this module so the consumer's node_modules graph (where\n // both o1js and @toon-protocol/mina-zkapp are installed) resolves them, then\n // re-anchor at the mina-zkapp package so its CJS `require('o1js')` and ours are\n // the SAME physical module instance (shared active-instance closure).\n const requireHere = createRequire(import.meta.url);\n const mzkPkgPath = requireHere.resolve(\n '@toon-protocol/mina-zkapp/package.json'\n );\n const requireFromMzk = createRequire(mzkPkgPath);\n // o1js resolved from the mina-zkapp anchor → the SAME (CJS) instance the\n // contract's `require('o1js')` uses.\n const o1js = requireFromMzk('o1js') as O1jsLike;\n // ⚠️ A pnpm workspace package has NO self-referential symlink, so\n // `requireFromMzk('@toon-protocol/mina-zkapp')` fails with MODULE_NOT_FOUND.\n // Load the contract by PATH from the package's own `main` entry instead (the\n // same approach scripts/deploy-mina-zkapp.ts uses). This works in both the\n // workspace (no self-symlink) and the flat consumer node_modules layouts.\n const mzkPkgJson: { main?: string } = requireFromMzk(mzkPkgPath);\n const mzkDir = nodePath.dirname(mzkPkgPath);\n const mzkEntry = nodePath.join(mzkDir, mzkPkgJson.main ?? 'dist/index.js');\n const mzk: any = requireFromMzk(mzkEntry);\n const PaymentChannel = mzk.PaymentChannel ?? mzk.default?.PaymentChannel;\n if (!PaymentChannel) {\n throw new Error(\n '@toon-protocol/mina-zkapp does not export PaymentChannel — cannot open a Mina channel'\n );\n }\n cachedO1js = o1js;\n cachedPaymentChannel = PaymentChannel;\n return { o1js, PaymentChannel };\n}\n\n/** Lazily resolve `o1js` (shared CJS instance with the contract). */\nasync function getO1js(): Promise<O1jsLike> {\n return (await loadMinaRuntime()).o1js;\n}\n\n/**\n * Lazily resolve + compile the `PaymentChannel` contract. Compilation is the\n * expensive o1js step; cache the compiled artifact so repeated opens in the\n * same process don't recompile.\n */\nasync function getCompiledPaymentChannel(): Promise<any> {\n const { PaymentChannel } = await loadMinaRuntime();\n if (!compiledContract) {\n await PaymentChannel.compile();\n compiledContract = PaymentChannel;\n }\n return compiledContract;\n}\n\n/** Test hook: reset the cached o1js + compiled-contract state. */\nexport function _resetMinaChannelOpenCache(): void {\n cachedO1js = null;\n cachedPaymentChannel = null;\n compiledContract = null;\n}\n\n/** CHANNEL_STATE.OPEN from `@toon-protocol/mina-zkapp` constants. */\nconst MINA_CHANNEL_STATE_OPEN = 1n;\n/** CHANNEL_STATE.UNINITIALIZED. */\nconst MINA_CHANNEL_STATE_UNINITIALIZED = 0n;\n\nexport interface OpenMinaChannelParams {\n /** Mina GraphQL endpoint of the network the zkApp is deployed on. */\n graphqlUrl: string;\n /** Deployed payment-channel zkApp B62 address (the channel id). */\n zkAppAddress: string;\n /**\n * Fee-payer / participantA `EK…` base58 private key — the client's Mina key\n * (same key the off-chain claim is signed with). Pays the Mina tx fee and\n * authorizes the `initializeChannel` (+ `deposit`) transaction.\n */\n payerPrivateKey: string;\n /**\n * participantB B62 public key — the apex's Mina settlement address. When\n * omitted, the payer is used for both participants (single-party dev channel).\n */\n peerPublicKey?: string;\n /** Channel settlement timeout in slots. Default 86400. */\n timeout?: bigint;\n /** Mina token id field (decimal string). Default '1' (native MINA). */\n tokenId?: string;\n /** Optional on-chain deposit amount (base units) after initialization. */\n deposit?: { amount: bigint };\n /** Per-call network id for the Schnorr/account prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n /**\n * Transaction fee in nanomina for the `initializeChannel` + `deposit` zkApp\n * method calls. Lightnet/devnet REJECTS fee-less zkApp commands with\n * \"Insufficient fee\", so a non-zero fee is REQUIRED. Default 100_000_000\n * (0.1 MINA), matching scripts/deploy-mina-zkapp.ts.\n */\n feeNanomina?: bigint;\n}\n\nexport interface OpenMinaChannelResult {\n /** The zkApp address (channel id) — echoed for parity with the Solana opener. */\n zkAppAddress: string;\n /** True when a fresh `initializeChannel` tx was submitted this call. */\n opened: boolean;\n /** `initializeChannel` tx hash (absent when the channel was already OPEN). */\n initTxHash?: string;\n /** `deposit` tx hash, when a deposit was requested + submitted. */\n depositTxHash?: string;\n /** On-chain channelState after the call (0=UNINIT,1=OPEN,2=CLOSING,3=SETTLED). */\n channelState: number;\n /**\n * On-chain `depositTotal` (base units), read from the zkApp appState after the\n * open/deposit settled. The Mina balance-proof signer needs this so it can bind\n * `balanceB = depositTotal − balanceA` (toon-protocol/connector#133). A channel\n * can be re-deposited, so this is the CURRENT on-chain value, not a config one.\n */\n depositTotal: bigint;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Mina\n * payment channel on the already-deployed `PaymentChannel` zkApp.\n *\n * Idempotent: if the on-chain `channelState` is already `OPEN`, returns without\n * re-initializing (mirrors `openSolanaChannel`'s \"channel already exists\" path).\n * Throws if the zkApp account does not exist on-chain.\n */\nexport async function openMinaChannelOnChain(\n params: OpenMinaChannelParams\n): Promise<OpenMinaChannelResult> {\n const { Mina, PrivateKey, PublicKey, Field, AccountUpdate, fetchAccount } =\n await getO1js();\n\n // Use the plain-string `Mina.Network(graphqlUrl)` form (matching the\n // connector's MinaPaymentChannelSDK._setNetwork). The object form\n // (`{ networkId, mina }`) behaves inconsistently across o1js versions and left\n // the active-instance ledger unable to resolve fetched accounts\n // (`channelState.get()` → \"we can't find this zkapp account\"). The Schnorr\n // network prefix is governed by the off-chain signer (`networkId`), not this\n // on-chain endpoint binding.\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n // zkApp method txs MUST carry a fee on lightnet/devnet (\"Insufficient fee\"\n // otherwise). 0.1 MINA matches scripts/deploy-mina-zkapp.ts.\n const txFee = params.feeNanomina ?? 100_000_000n;\n\n // The client's mnemonic-derived Mina key is a big-endian hex scalar (the form\n // `deriveFullIdentity()` emits); o1js `PrivateKey.fromBase58` needs the Mina\n // `EK…` base58check form. Convert (idempotent — passes an already-`EK…` key\n // through unchanged), matching what the off-chain MinaSigner does.\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);\n const payerPublicKey = payerPrivateKey.toPublicKey();\n const zkAppPublicKey = PublicKey.fromBase58(params.zkAppAddress);\n\n // Read channelState (appState index 3) straight from the `fetchAccount`\n // result rather than `zkApp.channelState.get()`. `.get()` outside a\n // transaction is fragile here (it can throw \"Must call Mina.setActiveInstance\n // first\" / \"can't find this zkapp account\" even right after a successful\n // fetch); the network-fetched appState array is the reliable source. A\n // missing account is a hard error — the zkApp must be deployed out-of-band.\n const readChannelState = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n // PaymentChannel state field order: [channelHash, balanceCommitment,\n // nonceField, channelState, depositTotal, ...] → channelState is index 3.\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[3]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n // Read the on-chain `depositTotal` (appState index 4). The signer must bind\n // `balanceB = depositTotal − balanceA` against this CURRENT on-chain value\n // (connector#133); a channel can be re-deposited, so a stale config value\n // would fail the signatureA verification on settle. A missing account is a\n // hard error (same as readChannelState).\n const readDepositTotal = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[4]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n const currentState = await readChannelState();\n await fetchAccount({ publicKey: payerPublicKey });\n let opened = false;\n let initTxHash: string | undefined;\n let zkApp: any;\n const getZkApp = async () => {\n if (!zkApp) {\n const PaymentChannel = await getCompiledPaymentChannel();\n zkApp = new PaymentChannel(zkAppPublicKey);\n }\n return zkApp;\n };\n\n if (currentState === MINA_CHANNEL_STATE_UNINITIALIZED) {\n const channel = await getZkApp();\n const participantA = payerPublicKey;\n const participantB = params.peerPublicKey\n ? PublicKey.fromBase58(params.peerPublicKey)\n : payerPublicKey;\n const nonce = Field(0);\n const timeoutField = Field((params.timeout ?? 86400n).toString());\n const tokenIdField = Field(params.tokenId ?? '1');\n\n // `initializeChannel` reads `this.channelState.getAndRequireEquals()` as a\n // precondition, which needs the zkApp account in o1js's active-instance\n // cache. Re-fetch BOTH the zkApp and the fee-payer immediately before\n // building the transaction so the precondition read resolves (a stale or\n // missing cache surfaces as \"channelState.get() failed / Must call\n // setActiveInstance first\" — even though the network IS set).\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n\n const initTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.initializeChannel(\n participantA,\n participantB,\n nonce,\n timeoutField,\n tokenIdField\n );\n }\n );\n await initTx.prove();\n const sentInit = await initTx.sign([payerPrivateKey]).send();\n initTxHash = sentInit.hash ?? undefined;\n opened = true;\n // ALWAYS wait for the init tx to be INCLUDED in a block (and re-fetch the\n // account) before returning — NOT only when a deposit follows.\n //\n // Why this matters (issue #158): the two-party `channelHash =\n // Poseidon([client.x, apex.x, 0])` is only written to the zkApp's on-chain\n // state once `initializeChannel` is included in a block. If we fire-and-forget\n // the init tx, the publish proceeds immediately and the connector reads the\n // STILL-BARE zkApp (channelState=0, channelHash empty → `participants:[\"\",\"\"]`)\n // before the init lands, so its participant-form balance-proof reconstruction\n // mismatches → `mina_claim_verification_failed: \"Invalid balance proof\n // signature\"`. The EVM (`waitForTransactionReceipt`) and Solana\n // (`waitForConfirmation`) openers both confirm their open tx before returning;\n // Mina must do the same for parity. `.wait()` blocks until inclusion (lightnet\n // block time can be a few minutes).\n await sentInit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n } else if (currentState !== MINA_CHANNEL_STATE_OPEN) {\n // CLOSING (2) or SETTLED (3): cannot (re)open. Surface clearly.\n throw new Error(\n `Mina channel ${params.zkAppAddress} is in state ${currentState} (not UNINITIALIZED/OPEN) — cannot open`\n );\n }\n\n // Optional deposit (only valid while OPEN — which it now is).\n let depositTxHash: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n const channel = await getZkApp();\n // Re-fetch so the deposit tx sees the post-init state.\n await fetchAccount({ publicKey: zkAppPublicKey });\n const amountField = Field(params.deposit.amount.toString());\n const depositTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.deposit(amountField, payerPublicKey);\n }\n );\n await depositTx.prove();\n const sentDeposit = await depositTx.sign([payerPrivateKey]).send();\n depositTxHash = sentDeposit.hash ?? undefined;\n // ALWAYS wait for the deposit tx to be INCLUDED before returning — same\n // confirmation discipline as initializeChannel above (issue #158). The\n // connector's claimFromChannel runs a #126 balance-conservation gate that\n // reads the on-chain `depositTotal`; if we fire-and-forget the deposit, the\n // publish + claim race ahead and the connector reads depositTotal=0 (deposit\n // not yet in a block) → `PROOF_GENERATION_FAILED: Claim violates balance\n // conservation` and the settle aborts non-retryably. Blocking on inclusion\n // (and re-fetching) guarantees the funded depositTotal is on-chain before any\n // claim settles against it.\n await sentDeposit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n }\n\n // Read the resulting state from the network-fetched appState (best-effort —\n // may still reflect the pre-confirmation value on a slow node; the connector\n // re-reads at verification time). If we just opened, optimistically report\n // OPEN even if the node hasn't surfaced the new state yet.\n let finalState: number;\n try {\n finalState = Number(await readChannelState());\n } catch {\n finalState = opened\n ? Number(MINA_CHANNEL_STATE_OPEN)\n : Number(currentState);\n }\n if (opened && finalState === Number(MINA_CHANNEL_STATE_UNINITIALIZED)) {\n finalState = Number(MINA_CHANNEL_STATE_OPEN);\n }\n\n // Touch AccountUpdate so the (lazy) import is retained even if a future\n // refactor stops referencing it directly above; harmless no-op.\n void AccountUpdate;\n\n // Read the resulting on-chain depositTotal (post init+deposit confirmation, so\n // a fresh re-deposit is reflected). Best-effort: fall back to 0n if the read\n // throws on a slow node — the connector re-reads at verification time.\n let depositTotal: bigint;\n try {\n depositTotal = await readDepositTotal();\n } catch {\n depositTotal = 0n;\n }\n\n return {\n zkAppAddress: params.zkAppAddress,\n opened,\n initTxHash,\n depositTxHash,\n channelState: finalState,\n depositTotal,\n };\n}\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 Mill ↔ 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 */\nfunction 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 Mill ↔ 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 Mill ↔ 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\nconst DEPOSIT_TOTAL_STATE_INDEX = 4;\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","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}\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 });\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\n if (this.store) {\n this.store.save(channelId, {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount,\n });\n }\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 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}\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}\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 };\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 };\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 * 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 * For a successful single-packet (non-chunked) blob upload, the DVM provider\n * returns the Arweave transaction ID as a **UTF-8 string, base64-encoded** in\n * the ILP FULFILL `data` field (the connector validates that FULFILL data is\n * base64). An Arweave tx ID is a 43-character base64url string (32 raw bytes).\n *\n * So the decode is:\n * `txId = utf8(base64decode(result.data))`\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 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 });\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 base64-encoded Arweave tx ID',\n };\n }\n\n // FULFILL data contract: base64(utf8(arweaveTxId)).\n const txId = decodeUtf8(fromBase64(result.data));\n\n if (!ARWEAVE_TX_ID_REGEX.test(txId)) {\n return {\n success: false,\n eventId: event.id,\n error: `Decoded FULFILL data is not a valid Arweave tx ID: \"${txId}\"`,\n };\n }\n\n return {\n success: true,\n txId,\n eventId: event.id,\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(() => {});\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","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","/**\n * Pet DVM Provider Discovery\n *\n * Filters Kind 10035 service discovery events to find providers that\n * support Pet DVM interactions (Kind 5900).\n *\n * @module pet/filterPetDvmProviders\n */\n\nimport { parseServiceDiscovery } from '@toon-protocol/core';\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetDvmProvider } from './types.js';\n\n/**\n * Minimal Nostr event shape needed for filtering.\n * Using a local interface to avoid importing nostr-tools types.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Filter Kind 10035 service discovery events to find pet DVM providers.\n *\n * Accepts raw NostrEvent[] and internally parses content via parseServiceDiscovery.\n * Filters events where skill.kinds includes 5900 (PET_INTERACTION_REQUEST_KIND).\n * Returns provider metadata sorted by price ascending (cheapest first).\n *\n * Handles missing/malformed skill descriptors gracefully (returns empty array, no throw).\n *\n * @param events - Array of raw Nostr events (kind:10035)\n * @returns Array of PetDvmProvider metadata, sorted by price ascending\n */\nexport function filterPetDvmProviders(\n events: NostrEventLike[]\n): PetDvmProvider[] {\n const providers: PetDvmProvider[] = [];\n\n for (const event of events) {\n let parsed;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parsed = parseServiceDiscovery(event as any);\n } catch {\n continue;\n }\n\n if (!parsed) continue;\n\n const skill = parsed.skill;\n if (!skill) continue;\n\n // Check if this provider supports Kind 5900\n if (!skill.kinds.includes(PET_INTERACTION_REQUEST_KIND)) continue;\n\n const pricing = skill.pricing[String(PET_INTERACTION_REQUEST_KIND)] ?? '0';\n\n providers.push({\n ilpAddress: parsed.ilpAddress,\n pricing,\n pubkey: event.pubkey,\n features: skill.features,\n });\n }\n\n // Sort by price ascending (cheapest first)\n providers.sort((a, b) => {\n const priceA = Number(a.pricing) || 0;\n const priceB = Number(b.pricing) || 0;\n return priceA - priceB;\n });\n\n return providers;\n}\n","/**\n * Pet Interaction Request Builder (Kind 5900)\n *\n * Builds unsigned Kind 5900 Nostr events for pet DVM interaction requests.\n * Compatible with nostr-tools/pure finalizeEvent for signing.\n *\n * @module pet/buildPetInteractionRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport { ValidationError } from '../errors.js';\nimport type {\n PetInteractionRequestParams,\n UnsignedNostrEvent,\n} from './types.js';\n\n/** Maximum valid action type index (0-10 inclusive, ACTION_COUNT = 11) */\nconst MAX_ACTION_TYPE = 10;\n\n/**\n * Build an unsigned Kind 5900 pet interaction request event.\n *\n * All tag values are stringified per Nostr protocol convention.\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed interaction parameters\n * @returns Unsigned Nostr event ready for signing\n * @throws ValidationError for invalid input\n */\nexport function buildPetInteractionRequest(\n params: PetInteractionRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, actionType, itemId, tokenCost, isSleeping } = params;\n\n // Validate blobbiId\n if (!blobbiId || blobbiId.trim() === '') {\n throw new ValidationError('blobbiId must be a non-empty string');\n }\n\n // Validate actionType (0 <= actionType <= 10)\n if (\n !Number.isInteger(actionType) ||\n actionType < 0 ||\n actionType > MAX_ACTION_TYPE\n ) {\n throw new ValidationError(\n `actionType must be an integer between 0 and ${MAX_ACTION_TYPE}, got ${actionType}`\n );\n }\n\n // Validate itemId (>= 0)\n if (!Number.isInteger(itemId) || itemId < 0) {\n throw new ValidationError(\n `itemId must be a non-negative integer, got ${itemId}`\n );\n }\n\n // Validate tokenCost (>= 0)\n if (!Number.isFinite(tokenCost) || tokenCost < 0) {\n throw new ValidationError(\n `tokenCost must be a non-negative number, got ${tokenCost}`\n );\n }\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['action', String(actionType)],\n ['item', String(itemId)],\n ['cost', String(tokenCost)],\n ['sleeping', String(isSleeping)],\n ],\n content: '',\n };\n}\n","/**\n * Pet Interaction Result Parser (Kind 6900)\n *\n * Decodes base64-encoded JSON from IlpSendResult.data field.\n * Uses browser-safe atob() -- NOT Node.js Buffer -- for ditto React SPA compatibility.\n *\n * @module pet/parsePetInteractionResult\n */\n\nimport type { PetInteractionResultData, StatValues } from './types.js';\n\n/** Required stat field names */\nconst STAT_FIELDS: readonly (keyof StatValues)[] = [\n 'hunger',\n 'happiness',\n 'health',\n 'hygiene',\n 'energy',\n];\n\n/** Regex for validating 64-char hex string (BLAKE3 brain hash) */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/**\n * Validate that an object has all required StatValues fields as numbers.\n */\nfunction isValidStats(obj: unknown): obj is StatValues {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return STAT_FIELDS.every(\n (field) =>\n typeof record[field] === 'number' && Number.isFinite(record[field])\n );\n}\n\n/**\n * Parse base64-encoded JSON result data from a Kind 6900 DVM response.\n *\n * Uses atob() for browser compatibility (ditto React SPA).\n * Returns null for malformed/missing data (no throw).\n *\n * Validates:\n * - brainHash is 64-char hex\n * - stats has all 5 fields\n * - cycle >= 0\n * - stage 0-2\n *\n * @param data - Base64-encoded JSON string from IlpSendResult.data\n * @returns Parsed PetInteractionResultData or null if invalid\n */\nexport function parsePetInteractionResult(\n data: string\n): PetInteractionResultData | null {\n if (!data) return null;\n\n let json: string;\n try {\n // Use atob() for browser compatibility; works in Node 16+ too\n json = atob(data);\n } catch {\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return null;\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const record = parsed as Record<string, unknown>;\n\n // Validate stats\n if (!isValidStats(record['stats'])) return null;\n\n // Validate stage (0-2)\n const stage = record['stage'];\n if (\n typeof stage !== 'number' ||\n !Number.isInteger(stage) ||\n stage < 0 ||\n stage > 2\n ) {\n return null;\n }\n\n // Validate cycle (>= 0)\n const cycle = record['cycle'];\n if (typeof cycle !== 'number' || !Number.isInteger(cycle) || cycle < 0) {\n return null;\n }\n\n // Validate lastInteraction\n const lastInteraction = record['lastInteraction'];\n if (\n typeof lastInteraction !== 'number' ||\n !Number.isFinite(lastInteraction)\n ) {\n return null;\n }\n\n // Validate brainHash (64-char hex)\n const brainHash = record['brainHash'];\n if (typeof brainHash !== 'string' || !HEX_64_RE.test(brainHash)) {\n return null;\n }\n\n // Validate cooldownTimestamps (array of numbers)\n const cooldownTimestamps = record['cooldownTimestamps'];\n if (!Array.isArray(cooldownTimestamps)) return null;\n if (\n !cooldownTimestamps.every(\n (t): t is number => typeof t === 'number' && Number.isFinite(t)\n )\n ) {\n return null;\n }\n\n // Construct clean stat object to prevent prototype pollution from JSON.parse\n const validatedStats = record['stats'] as StatValues;\n const stats: StatValues = {\n hunger: validatedStats.hunger,\n happiness: validatedStats.happiness,\n health: validatedStats.health,\n hygiene: validatedStats.hygiene,\n energy: validatedStats.energy,\n };\n\n return {\n stats,\n stage,\n cycle,\n lastInteraction,\n brainHash,\n cooldownTimestamps: [...cooldownTimestamps],\n };\n}\n","/**\n * Pet Interaction Event Parser (Kind 14919)\n *\n * Parses Kind 14919 optimistic/proven pet interaction events.\n * Detects proof status from presence of 'proof' + 'mina_tx' tags.\n *\n * @module pet/parsePetInteractionEvent\n */\n\nimport type {\n PetInteractionEventData,\n InteractionResultContent,\n ProofStatus,\n StatValues,\n} from './types.js';\n\n/**\n * Minimal Nostr event shape needed for parsing.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Check that an object has the expected stat fields as numbers.\n */\nfunction isStatLike(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return false;\n const r = obj as Record<string, unknown>;\n return (\n typeof r['hunger'] === 'number' &&\n Number.isFinite(r['hunger']) &&\n typeof r['happiness'] === 'number' &&\n Number.isFinite(r['happiness']) &&\n typeof r['health'] === 'number' &&\n Number.isFinite(r['health']) &&\n typeof r['hygiene'] === 'number' &&\n Number.isFinite(r['hygiene']) &&\n typeof r['energy'] === 'number' &&\n Number.isFinite(r['energy'])\n );\n}\n\n/**\n * Construct a clean StatValues object from a validated stat-like object.\n * Prevents prototype pollution by extracting only known fields.\n */\nfunction cleanStats(obj: Record<string, unknown>): StatValues {\n return {\n hunger: obj['hunger'] as number,\n happiness: obj['happiness'] as number,\n health: obj['health'] as number,\n hygiene: obj['hygiene'] as number,\n energy: obj['energy'] as number,\n };\n}\n\n/**\n * Attempt to parse content JSON into InteractionResultContent.\n * Returns null if content is malformed.\n */\nfunction parseContent(content: string): InteractionResultContent | null {\n try {\n const parsed = JSON.parse(content);\n if (typeof parsed !== 'object' || parsed === null) return null;\n // Structural check -- must have stat objects with correct numeric fields\n if (\n !isStatLike(parsed.priorStats) ||\n !isStatLike(parsed.decayedStats) ||\n !isStatLike(parsed.finalStats)\n ) {\n return null;\n }\n if (\n typeof parsed.cycle !== 'number' ||\n typeof parsed.stage !== 'number' ||\n typeof parsed.tokenCost !== 'number'\n ) {\n return null;\n }\n // Construct clean object to prevent prototype pollution from JSON.parse\n return {\n priorStats: cleanStats(parsed.priorStats),\n decayedStats: cleanStats(parsed.decayedStats),\n finalStats: cleanStats(parsed.finalStats),\n cycle: parsed.cycle,\n stage: parsed.stage,\n tokenCost: parsed.tokenCost,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a Kind 14919 pet interaction event.\n *\n * Extracts all tag values and detects proof status:\n * - 'optimistic': no 'proof' tag\n * - 'proven': has 'proof' + 'mina_tx' tags\n *\n * Returns null if required tags are missing.\n *\n * @param event - A Nostr event (Kind 14919)\n * @returns Parsed PetInteractionEventData or null if malformed\n */\nexport function parsePetInteractionEvent(\n event: NostrEventLike\n): PetInteractionEventData | null {\n const tags = event.tags;\n\n // Extract required tags\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId) return null;\n\n const actionStr = getTagValue(tags, 'action');\n if (!actionStr) return null;\n const actionType = Number(actionStr);\n if (!Number.isFinite(actionType)) return null;\n\n const itemStr = getTagValue(tags, 'item');\n if (!itemStr) return null;\n const itemId = Number(itemStr);\n if (!Number.isFinite(itemId)) return null;\n\n const costStr = getTagValue(tags, 'cost');\n if (!costStr) return null;\n const tokenCost = Number(costStr);\n if (!Number.isFinite(tokenCost)) return null;\n\n const cycleStr = getTagValue(tags, 'cycle');\n if (!cycleStr) return null;\n const cycle = Number(cycleStr);\n if (!Number.isFinite(cycle)) return null;\n\n const stageStr = getTagValue(tags, 'stage');\n if (!stageStr) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n const brainHash = getTagValue(tags, 'brain_hash');\n if (!brainHash) return null;\n\n // Detect proof status\n const proof = getTagValue(tags, 'proof');\n const minaTx = getTagValue(tags, 'mina_tx');\n const proofStatus: ProofStatus = proof && minaTx ? 'proven' : 'optimistic';\n\n // Parse content\n const content = parseContent(event.content);\n\n const result: PetInteractionEventData = {\n blobbiId,\n actionType,\n itemId,\n tokenCost,\n cycle,\n stage,\n brainHash,\n proofStatus,\n content,\n };\n\n if (proof) result.proof = proof;\n if (minaTx) result.minaTx = minaTx;\n\n return result;\n}\n","/**\n * Pet Listing Event Builder (Kind 30402)\n *\n * Builds unsigned Kind 30402 (NIP-99 classified listing) Nostr events\n * for pet-for-sale marketplace listings. Every listing includes a\n * verified biography attachment (lifecycleHash + totalSpent) so buyers\n * can verify the listing against on-chain PetZkApp state.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetListingEvent\n */\n\nimport type { PetListingParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Kind 30402: NIP-99 parameterized replaceable classified listing.\n * Not yet defined in @toon-protocol/core (pet marketplace is new in Story 11-14).\n * Defined locally until core exports this constant.\n */\nconst PET_LISTING_KIND = 30402;\n\n/** Human-readable stage names for listing summaries */\nconst STAGE_NAMES: Record<number, string> = {\n 0: 'Egg',\n 1: 'Baby',\n 2: 'Adult',\n};\n\n/**\n * Build an unsigned Kind 30402 pet-for-sale classified listing event.\n *\n * The listing uses the NIP-99 classified listing format with TOON-specific\n * extension tags for verified biography (lifecycleHash, totalSpent).\n * The `d` tag is set to `blobbiId` for stable parameterized replaceability —\n * republishing with the same `d` tag updates the listing on relays.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed listing parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetListingEvent(\n params: PetListingParams\n): UnsignedNostrEvent {\n const {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n } = params;\n\n const stageName = STAGE_NAMES[stage] ?? 'Unknown';\n const summary = `${stageName} pet for sale — ${totalSpent} PET tokens spent (verified biography)`;\n\n return {\n kind: PET_LISTING_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['title', `Pet ${blobbiId} for sale`],\n ['price', String(askPriceUsdc), 'USDC', ''],\n ['summary', summary],\n ['t', 'pet'],\n ['t', 'toon-pet'],\n ['lifecycle_hash', lifecycleHash],\n ['total_spent', totalSpent],\n ['stage', String(stage)],\n ['expiration', String(expiresAt)],\n ['relay', relayUrl],\n ['p', sellerPubkey],\n ],\n content: JSON.stringify(stats),\n };\n}\n","/**\n * Pet Listing Parser (Kind 30402)\n *\n * Parses Kind 30402 (NIP-99 classified listing) Nostr events into\n * typed PetListing objects. Returns null for invalid or malformed events.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/parsePetListing\n */\n\nimport type { PetListing, StatValues } from './types.js';\n\n/** Regex for 64-char lowercase hex strings */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/** Minimal Nostr event shape required for parsing */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n * Uses noUncheckedIndexedAccess-safe bracket notation.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Default StatValues used when listing content is unparseable.\n * Note: values are 0 (outside the normal [1,100] game range) — this is\n * intentional as a sentinel for \"stats unknown / content malformed\".\n * Consumers should check for all-zero stats and display accordingly.\n */\nconst DEFAULT_STATS: StatValues = {\n hunger: 0,\n happiness: 0,\n health: 0,\n hygiene: 0,\n energy: 0,\n};\n\n/**\n * Attempt to parse content JSON into StatValues.\n * Returns DEFAULT_STATS if content is missing or malformed.\n */\nfunction parseStats(content: string): StatValues {\n try {\n const parsed = JSON.parse(content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return DEFAULT_STATS;\n const r = parsed as Record<string, unknown>;\n if (\n typeof r['hunger'] === 'number' &&\n typeof r['happiness'] === 'number' &&\n typeof r['health'] === 'number' &&\n typeof r['hygiene'] === 'number' &&\n typeof r['energy'] === 'number'\n ) {\n return {\n hunger: r['hunger'],\n happiness: r['happiness'],\n health: r['health'],\n hygiene: r['hygiene'],\n energy: r['energy'],\n };\n }\n return DEFAULT_STATS;\n } catch {\n return DEFAULT_STATS;\n }\n}\n\n/**\n * Parse a Kind 30402 pet classified listing event into a PetListing.\n *\n * Validation rules:\n * - event.kind must be 30402\n * - 'd' tag must be present and non-empty\n * - 'price' tag must be present with a valid positive numeric first element\n * - 'lifecycle_hash' tag must be a 64-char hex string\n * - 'total_spent' tag must be a valid non-negative numeric string\n * - 'stage' tag must be present\n *\n * Stats are parsed from content JSON; unparseable content falls back to DEFAULT_STATS.\n *\n * @param event - A Nostr event (expected Kind 30402)\n * @returns Parsed PetListing or null if invalid\n */\nexport function parsePetListing(event: NostrEventLike): PetListing | null {\n // Kind check\n if (event.kind !== 30402) return null;\n\n const { tags } = event;\n\n // Required: 'd' tag (blobbiId)\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId || blobbiId.trim() === '') return null;\n\n // Required: 'price' tag — must have at least 2 elements, first must be positive numeric\n let askPriceUsdc = 0;\n let foundPrice = false;\n for (const tag of tags) {\n if (tag[0] === 'price') {\n const priceStr = tag[1];\n if (priceStr === undefined) return null;\n const parsed = Number(priceStr);\n if (!Number.isFinite(parsed) || parsed <= 0) return null;\n askPriceUsdc = parsed;\n foundPrice = true;\n break;\n }\n }\n if (!foundPrice) return null;\n\n // Required: 'lifecycle_hash' tag — must be 64-char hex\n const lifecycleHash = getTagValue(tags, 'lifecycle_hash');\n if (!lifecycleHash) return null;\n if (!HEX_64_RE.test(lifecycleHash)) return null;\n\n // Required: 'total_spent' tag — must be a valid non-negative numeric string\n const totalSpent = getTagValue(tags, 'total_spent');\n if (totalSpent === undefined || totalSpent === '') return null;\n const totalSpentNum = Number(totalSpent);\n if (!Number.isFinite(totalSpentNum) || totalSpentNum < 0) return null;\n\n // Required: 'stage' tag\n const stageStr = getTagValue(tags, 'stage');\n if (stageStr === undefined) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n // Optional tags\n const sellerPubkey = getTagValue(tags, 'p') ?? '';\n const relayUrl = getTagValue(tags, 'relay') ?? '';\n const expiresAtStr = getTagValue(tags, 'expiration');\n const expiresAt = expiresAtStr !== undefined ? Number(expiresAtStr) : 0;\n\n // Parse stats from content (null-safe)\n const stats = parseStats(event.content);\n\n return {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n eventId: event.id,\n createdAt: event.created_at,\n };\n}\n","/**\n * Pet Listing Discovery Filter\n *\n * Filters and sorts Kind 30402 pet marketplace listing events into\n * typed PetListing objects. Handles expiry, stage, price, biography\n * value, and seller filtering. Results sorted by totalSpent descending\n * (highest biography value first) to surface the most battle-hardened pets.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/filterPetListings\n */\n\nimport { parsePetListing } from './parsePetListing.js';\nimport type { PetListing, PetListingFilterOptions } from './types.js';\n\n/** Minimal Nostr event shape accepted by the filter */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Compare two numeric strings. Returns negative if a < b, 0 if equal, positive if a > b.\n * Handles arbitrarily large integers (bigint comparison).\n */\nfunction compareNumericStrings(a: string, b: string): number {\n // Fast path for equal strings\n if (a === b) return 0;\n // Use BigInt for correct comparison of large token amounts\n try {\n const bigA = BigInt(a);\n const bigB = BigInt(b);\n if (bigA < bigB) return -1;\n if (bigA > bigB) return 1;\n return 0;\n } catch {\n // Fallback to float comparison for non-integer numerics\n const fa = Number(a);\n const fb = Number(b);\n // Guard against NaN — treat NaN as less than any valid number\n if (!Number.isFinite(fa) && !Number.isFinite(fb)) return 0;\n if (!Number.isFinite(fa)) return -1;\n if (!Number.isFinite(fb)) return 1;\n return fa - fb;\n }\n}\n\n/**\n * Filter and sort Kind 30402 pet marketplace listing events.\n *\n * Parsing is done via parsePetListing — invalid events are silently dropped.\n * Expired listings (expiration tag < current unix time) are excluded.\n * Options allow additional filtering by stage, price, biography value, and seller.\n * Results are sorted by totalSpent descending (highest biography value first).\n *\n * @param events - Array of raw Nostr events to filter\n * @param options - Optional filter criteria\n * @returns Filtered and sorted array of PetListing objects\n */\nexport function filterPetListings(\n events: NostrEventLike[],\n options?: PetListingFilterOptions\n): PetListing[] {\n const now = Math.floor(Date.now() / 1000);\n const listings: PetListing[] = [];\n\n for (const event of events) {\n // Parse and validate the listing\n const listing = parsePetListing(event);\n if (listing === null) continue;\n\n // Expiry filter — only exclude if expiresAt is set (> 0) AND has passed\n // listing.expiresAt is already parsed from the expiration tag by parsePetListing\n // (0 means no expiration tag was present — treat as never-expires)\n if (listing.expiresAt > 0 && listing.expiresAt < now) continue;\n\n // Stage filter\n if (options?.minStage !== undefined && listing.stage < options.minStage) {\n continue;\n }\n\n // Price filter\n if (\n options?.maxAskPriceUsdc !== undefined &&\n listing.askPriceUsdc > options.maxAskPriceUsdc\n ) {\n continue;\n }\n\n // Biography value filter (totalSpent numeric string comparison)\n if (options?.minTotalSpent !== undefined) {\n if (\n compareNumericStrings(listing.totalSpent, options.minTotalSpent) < 0\n ) {\n continue;\n }\n }\n\n // Seller pubkey filter\n if (\n options?.sellerPubkey !== undefined &&\n listing.sellerPubkey !== options.sellerPubkey\n ) {\n continue;\n }\n\n listings.push(listing);\n }\n\n // Sort by totalSpent descending (highest biography value first)\n listings.sort((a, b) => compareNumericStrings(b.totalSpent, a.totalSpent));\n\n return listings;\n}\n","/**\n * Pet Purchase Request Builder (Kind 5900, action type 9)\n *\n * Builds unsigned Kind 5900 Nostr events for pet transfer-ownership\n * purchase requests. Action type 9 is a reserved slot in the pet DVM\n * protocol — this event signals purchase intent and routes ILP payment\n * to the seller. The actual Mina on-chain ownership transfer (PetZkApp\n * .transferOperator) is handled by downstream stories.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetPurchaseRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetPurchaseRequestParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Action type 9 — transfer-ownership reserved slot.\n * Not currently handled server-side; defines the client-side protocol shape\n * for downstream Mina ownership transfer implementation.\n */\nconst TRANSFER_OWNERSHIP_ACTION = 9;\n\n/**\n * Build an unsigned Kind 5900 pet purchase request event.\n *\n * Reuses the existing pet interaction event kind (5900) with action type 9\n * (transfer-ownership). The `listing` tag references the kind:30402 listing\n * event being purchased. The `p` tag routes ILP payment to the seller.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed purchase request parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetPurchaseRequest(\n params: PetPurchaseRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, listingEventId, buyerPubkey, tokenCost, sellerPubkey } =\n params;\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['action', String(TRANSFER_OWNERSHIP_ACTION)],\n ['i', blobbiId],\n ['listing', listingEventId],\n ['buyer', buyerPubkey],\n ['p', sellerPubkey],\n ['cost', String(tokenCost)],\n ],\n content: '',\n };\n}\n","/**\n * Devnet faucet helper.\n *\n * The deployed TOON devnet exposes a faucet that drips test funds to a given\n * chain address so a client can open payment channels and pay for writes:\n *\n * EVM `POST {faucetUrl}/api/request` body `{ address }` → 100 ETH + 10k USDC\n * Solana `POST {faucetUrl}/api/solana/request` body `{ address }` → SOL + USDC\n * Mina `POST {faucetUrl}/api/mina/request` body `{ address }` → native MINA only\n *\n * Devnet edge (today): `https://faucet.devnet.toonprotocol.dev`.\n *\n * EVM is implemented fully. Solana/Mina are deferred to a later milestone (WS3)\n * and throw a clear error so callers don't silently assume funding happened.\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 /** Request timeout in milliseconds (default: 30000). */\n timeout?: number;\n}\n\n/** Map a chain to its faucet request path. */\nfunction faucetPath(chain: FaucetChain): string {\n switch (chain) {\n case 'evm':\n return '/api/request';\n case 'solana':\n return '/api/solana/request';\n case 'mina':\n return '/api/mina/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'` (implemented) | `'solana'` | `'mina'` (deferred — throw).\n * @throws {Error} If `faucetUrl`/`address` is missing, or the chain is deferred.\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 // Solana/Mina faucet flows are deferred (WS3). Fail loudly rather than\n // pretend a drip happened.\n if (chain === 'solana' || chain === 'mina') {\n throw new Error(\n `fundWallet: ${chain} faucet funding is deferred (WS3) — not yet implemented`\n );\n }\n\n const base = faucetUrl.replace(/\\/+$/, '');\n const url = `${base}${faucetPath(chain)}`;\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeout = options.timeout ?? 30000;\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 Townhouse node wallet crypto (`packages/townhouse/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 Townhouse'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 Townhouse 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"],"mappings":"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","names":["generateSecretKey","getPublicKey","generateSecretKey","ed25519","generateSecretKey","toHex","textEncoder","require","http","ed25519","base58Encode","hexToMinaBase58PrivateKey","hexToMinaBase58PrivateKey","STATE_MAP","base58Encode","ed25519","status","privateKeyToAccount","toHex","ed25519","base58Encode","ed25519","base58Encode","toHex","hexToMinaBase58PrivateKey","sha256","sha256","hexToMinaBase58PrivateKey","generateSecretKey","getPublicKey","PET_INTERACTION_REQUEST_KIND","PET_INTERACTION_REQUEST_KIND","HEX_64_RE","getTagValue","PET_INTERACTION_REQUEST_KIND","finalizeEvent","toBase64","fromBase64","bytesToHex","toBase64","fromBase64","getPublicKey","bytesToHex","fromBase64","finalizeEvent","writeFileSync","readFileSync","encryptMnemonic","decryptMnemonic","readFileSync","writeFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/keys/KeyDerivation.ts","../src/utils/binary.ts","../src/utils/store-envelope.ts","../src/modes/http.ts","../src/utils/retry.ts","../src/adapters/HttpRuntimeClient.ts","../src/btp/protocol.ts","../src/btp/IsomorphicBtpClient.ts","../src/adapters/BtpRuntimeClient.ts","../src/adapters/HttpIlpClient.ts","../src/adapters/selectIlpTransport.ts","../src/channel/OnChainChannelClient.ts","../src/channel/solana-payment-channel.ts","../src/channel/mina-channel-open.ts","../src/signing/evm-signer.ts","../src/signing/solana-signer.ts","../src/signing/mina-signer.ts","../src/channel/mina-payment-channel.ts","../src/channel/mina-deposit.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/blob-storage.ts","../src/adapters/Http402Client.ts","../src/adapters/HttpConnectorAdmin.ts","../src/pet/filterPetDvmProviders.ts","../src/pet/buildPetInteractionRequest.ts","../src/pet/parsePetInteractionResult.ts","../src/pet/parsePetInteractionEvent.ts","../src/pet/buildPetListingEvent.ts","../src/pet/parsePetListing.ts","../src/pet/filterPetListings.ts","../src/pet/buildPetPurchaseRequest.ts","../src/faucet.ts","../src/keys/KeyManager.ts","../src/keys/PasskeyAuth.ts","../src/keys/encoding.ts","../src/keys/KeyVault.ts","../src/keys/BackupService.ts","../src/keys/keystore-node.ts"],"sourcesContent":["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 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 {\n requestBlobStorage,\n type RequestBlobStorageResult,\n} from './blob-storage.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.js';\nimport {\n Http402Client,\n type H402FetchOptions,\n} from './adapters/Http402Client.js';\nimport type {\n ToonClientConfig,\n ToonStartResult,\n PublishEventResult,\n SignedBalanceProof,\n} from './types.js';\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 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 townhouse 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 const chainId = parts.length >= 3 ? parseInt(parts[2] ?? '0', 10) : 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 ? parseInt(parts[2] ?? '0', 10) : 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.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 zkAppAddress: this.config.minaChannel.zkAppAddress,\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 }\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` (Mill swaps) is a separate\n // surface with a raw-TOON contract and is intentionally NOT wrapped here.\n const writeData = buildStoreWriteEnvelope(event);\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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let claimMessage: any;\n if (options?.claim) {\n // EXISTING PATH: Caller provides pre-signed claim (backwards compatible).\n // Build the envelope with the chain-appropriate signer so a Solana/Mina\n // balance proof is not mis-wrapped as an EVM claim (see F06 root cause).\n claimMessage = this.buildClaimMessageForProof(options.claim);\n } else if (this.channelManager) {\n // NEW PATH: Auto-open channel + auto-sign claim (lazy channels)\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 BigInt(amount)\n );\n const signer = this.channelManager.getSignerForChannel(channelId);\n claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());\n } else {\n throw new ToonClientError(\n 'No claim provided and no channel manager configured',\n 'MISSING_CLAIM'\n );\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 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 Mill 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 * @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 }): 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 },\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 sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution\n * to call this helper. Kept duplicated for now to minimize regression risk.\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 * 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 const chainIdPart = parts.length >= 3 ? parts[2] : 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 (town.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 townhouse 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 | '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 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 relayUrl: config.relayUrl ?? 'ws://localhost:7100',\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","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/mill's `deriveMillKeys`). 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/** Request-line + headers the deployed store accepts (proven against devnet). */\nconst REQUEST_LINE = 'POST /write HTTP/1.1';\nconst HEADERS = ['Host: relay', 'Content-Type: application/json'];\n\n/**\n * Wrap a signed Nostr event in the `POST /write` HTTP envelope the deployed\n * payment-proxy reverse-proxies to the relay store.\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 * @returns The envelope bytes to use as the ILP PREPARE `data`.\n */\nexport function buildStoreWriteEnvelope(event: NostrEvent): Uint8Array {\n const body = JSON.stringify({ event });\n const head = [REQUEST_LINE, ...HEADERS].join('\\r\\n');\n return encodeUtf8(head + '\\r\\n\\r\\n' + body);\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 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 // Skip 32-byte fulfillment (unused in TOON)\n offset += 32;\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.FULFILL, 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 pending.resolve({\n type: ILPPacketType.FULFILL,\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","import {\n IsomorphicBtpClient,\n BtpConnectionError,\n} from '../btp/IsomorphicBtpClient.js';\nimport { ILPPacketType, type BTPProtocolData } from '../btp/protocol.js';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { withRetry } from '../utils/retry.js';\nimport { toBase64, fromBase64, encodeUtf8 } from '../utils/binary.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 async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): 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 async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed by reconnect() above\n const response = await this.btpClient!.sendPacket({\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n });\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n // Reject\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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 response = await this.btpClient.sendPacket(\n {\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n },\n protocolData\n );\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\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';\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 async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): 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 async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\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: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim?: unknown\n ): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n\n const prepare = serializeIlpPrepare({\n type: ILPPacketType.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + 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);\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 private async mapResponse(response: Response): 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 const ilp = deserializeIlpPacket(buf);\n if (ilp.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: ilp.data.length > 0 ? toBase64(ilp.data) : undefined,\n };\n }\n return {\n accepted: false,\n code: ilp.code,\n message: ilp.message,\n data: ilp.data.length > 0 ? toBase64(ilp.data) : undefined,\n };\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 {\n openSolanaChannel as openSolanaChannelOnChain,\n getChannelAccountState as getSolanaChannelAccountState,\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: '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: '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 zkAppAddress: string;\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 }\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 < 3) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainIdStr = parts[2];\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 * 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 payerTokenAccount: cfg.deposit.payerTokenAccount,\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 deployed zkApp address IS the channel id (claim `zkAppAddress`).\n const zkAppAddress = this.minaConfig.zkAppAddress;\n if (!zkAppAddress) {\n throw new Error(\n 'Mina channel requires a deployed zkAppAddress (minaConfig.zkAppAddress)'\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 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\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.\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 openEvmChannel(\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\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\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 * Mill ↔ 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 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 // deposit: discriminator(8) + amount(8 LE)\n const depositData = new Uint8Array(16);\n depositData.set(IX_DEPOSIT, 0);\n writeU64LE(depositData, 8, params.deposit.amount);\n\n depositTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: false },\n {\n pubkey: params.deposit.payerTokenAccount,\n isSigner: false,\n isWritable: true,\n },\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 }\n\n return { channelPDA, opened: true, initTxSignature, depositTxSignature };\n}\n\n// Internal helpers exported for unit tests (parity assertions).\nexport const __testing = { padTo32, sortPubkeys, isOnCurve };\n","/**\n * On-chain Mina payment-channel open — connector-parity.\n *\n * Opens (initializes) — and optionally deposits into — a REAL on-chain Mina\n * payment channel on the deployed `PaymentChannel` zkApp, so the connector's\n * `MinaPaymentChannelSDK.getChannelState(zkAppAddress)` finds a channel whose\n * on-chain `channelState == OPEN` (status `'opened'`) and the Mina claim\n * verifies + stores. This is the Mina analog of `openSolanaChannel`\n * (`solana-payment-channel.ts`, connector#105): the client opens its own\n * per-channel on-chain state rather than relying on a pre-initialized channel.\n *\n * ## Why this is separate from `mina-payment-channel.ts`\n *\n * `mina-payment-channel.ts` builds the OFF-chain balance-proof claim with\n * `mina-signer` (no o1js — keeps the client lightweight). But INITIALIZING a\n * zkApp channel requires producing a zkApp method proof, which is heavyweight\n * o1js WASM circuit work. So this module lazily imports `o1js` +\n * `@toon-protocol/mina-zkapp` ONLY when an on-chain open is actually requested\n * (the e2e client / Node settlement path), mirroring the connector's own\n * `getO1js()` lazy-require. Both are OPTIONAL dependencies and are kept out of\n * the bundle via `tsup` `external` so npm consumers that never open a Mina\n * channel don't pay the o1js cost.\n *\n * ## Contract call (must match the connector's `MinaPaymentChannelSDK.openChannel`)\n *\n * `PaymentChannel.initializeChannel(participantA, participantB, nonce, timeout, tokenId)`\n * sets `channelState = OPEN` (1). The deployed zkApp address IS the channel id\n * (`MinaClaimMessage.zkAppAddress`), identical to the claim's channel-hash\n * preimage in `mina-payment-channel.ts`. `deposit(amount, depositor)` then\n * bumps `depositTotal` (only valid while `channelState == OPEN`).\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address); this module assumes the\n * account exists and only INITIALIZES the channel on it (idempotent — if the\n * channel is already `OPEN`, it returns without re-initializing).\n *\n * @module\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\n\n/** Result shape of o1js `fetchAccount` (the bits we read). */\ninterface FetchAccountResult {\n error?: unknown;\n account?: {\n zkapp?: { appState?: { toString(): string }[] };\n };\n}\n\n/** Minimal o1js surface this module uses (lazy-loaded). */\ninterface O1jsLike {\n Mina: any;\n PrivateKey: any;\n PublicKey: any;\n Field: any;\n AccountUpdate: any;\n fetchAccount: (args: { publicKey: any }) => Promise<FetchAccountResult>;\n}\n\nlet cachedO1js: O1jsLike | null = null;\nlet cachedPaymentChannel: any | null = null;\nlet compiledContract: any | null = null;\n\n/**\n * Test-only override for the o1js + contract loader. When set, `loadMinaRuntime`\n * returns this instead of doing the `createRequire` resolution — so unit tests\n * can inject fakes WITHOUT pulling the real o1js WASM runtime (vitest's\n * `vi.mock` cannot intercept the CJS `require` path the production loader uses).\n */\nlet runtimeOverride:\n | (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>)\n | null = null;\n\n/** Test hook: inject a fake o1js + PaymentChannel runtime. */\nexport function _setMinaRuntimeForTests(\n loader: (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>) | null\n): void {\n runtimeOverride = loader;\n}\n\n/**\n * Resolve `o1js` AND the `PaymentChannel` contract through ONE shared module\n * instance.\n *\n * ⚠️ o1js keeps its \"active Mina instance\" in a module-level closure\n * (`mina-instance.js`). `@toon-protocol/mina-zkapp` ships as CommonJS, so its\n * internal `import {Mina}` is emitted as `require('o1js')` → o1js's CJS build\n * (`dist/node/index.cjs`). A bare ESM `import('o1js')` from this module resolves\n * o1js's DIFFERENT ESM build (`dist/node/index.js`) — a SEPARATE module instance\n * with a SEPARATE `activeInstance` closure. Calling `setActiveInstance` on the\n * ESM instance while `PaymentChannel.initializeChannel` reads the CJS instance\n * throws `channelState.get() failed … Must call Mina.setActiveInstance first`\n * (observed in the local-HS Mina e2e on the FIRST publish). The connector's own\n * settlement path and `scripts/deploy-mina-zkapp.ts` both work around this by\n * requiring o1js through the same anchor the zkApp uses.\n *\n * Fix: anchor a `createRequire` at the `@toon-protocol/mina-zkapp` package and\n * `require('o1js')` + the contract from there, so both share the CJS o1js\n * instance and `setActiveInstance` is visible inside the contract method. Kept\n * lazy (and `external` in tsup) so the multi-hundred-MB WASM runtime is only\n * loaded when a Mina channel is actually opened.\n */\nasync function loadMinaRuntime(): Promise<{\n o1js: O1jsLike;\n PaymentChannel: any;\n}> {\n if (cachedO1js && cachedPaymentChannel) {\n return { o1js: cachedO1js, PaymentChannel: cachedPaymentChannel };\n }\n if (runtimeOverride) {\n const injected = await runtimeOverride();\n cachedO1js = injected.o1js;\n cachedPaymentChannel = injected.PaymentChannel;\n return injected;\n }\n const { createRequire } = await import('node:module');\n const nodePath = await import('node:path');\n // Anchor resolution at this module so the consumer's node_modules graph (where\n // both o1js and @toon-protocol/mina-zkapp are installed) resolves them, then\n // re-anchor at the mina-zkapp package so its CJS `require('o1js')` and ours are\n // the SAME physical module instance (shared active-instance closure).\n const requireHere = createRequire(import.meta.url);\n const mzkPkgPath = requireHere.resolve(\n '@toon-protocol/mina-zkapp/package.json'\n );\n const requireFromMzk = createRequire(mzkPkgPath);\n // o1js resolved from the mina-zkapp anchor → the SAME (CJS) instance the\n // contract's `require('o1js')` uses.\n const o1js = requireFromMzk('o1js') as O1jsLike;\n // ⚠️ A pnpm workspace package has NO self-referential symlink, so\n // `requireFromMzk('@toon-protocol/mina-zkapp')` fails with MODULE_NOT_FOUND.\n // Load the contract by PATH from the package's own `main` entry instead (the\n // same approach scripts/deploy-mina-zkapp.ts uses). This works in both the\n // workspace (no self-symlink) and the flat consumer node_modules layouts.\n const mzkPkgJson: { main?: string } = requireFromMzk(mzkPkgPath);\n const mzkDir = nodePath.dirname(mzkPkgPath);\n const mzkEntry = nodePath.join(mzkDir, mzkPkgJson.main ?? 'dist/index.js');\n const mzk: any = requireFromMzk(mzkEntry);\n const PaymentChannel = mzk.PaymentChannel ?? mzk.default?.PaymentChannel;\n if (!PaymentChannel) {\n throw new Error(\n '@toon-protocol/mina-zkapp does not export PaymentChannel — cannot open a Mina channel'\n );\n }\n cachedO1js = o1js;\n cachedPaymentChannel = PaymentChannel;\n return { o1js, PaymentChannel };\n}\n\n/** Lazily resolve `o1js` (shared CJS instance with the contract). */\nasync function getO1js(): Promise<O1jsLike> {\n return (await loadMinaRuntime()).o1js;\n}\n\n/**\n * Lazily resolve + compile the `PaymentChannel` contract. Compilation is the\n * expensive o1js step; cache the compiled artifact so repeated opens in the\n * same process don't recompile.\n */\nasync function getCompiledPaymentChannel(): Promise<any> {\n const { PaymentChannel } = await loadMinaRuntime();\n if (!compiledContract) {\n await PaymentChannel.compile();\n compiledContract = PaymentChannel;\n }\n return compiledContract;\n}\n\n/** Test hook: reset the cached o1js + compiled-contract state. */\nexport function _resetMinaChannelOpenCache(): void {\n cachedO1js = null;\n cachedPaymentChannel = null;\n compiledContract = null;\n}\n\n/** CHANNEL_STATE.OPEN from `@toon-protocol/mina-zkapp` constants. */\nconst MINA_CHANNEL_STATE_OPEN = 1n;\n/** CHANNEL_STATE.UNINITIALIZED. */\nconst MINA_CHANNEL_STATE_UNINITIALIZED = 0n;\n\nexport interface OpenMinaChannelParams {\n /** Mina GraphQL endpoint of the network the zkApp is deployed on. */\n graphqlUrl: string;\n /** Deployed payment-channel zkApp B62 address (the channel id). */\n zkAppAddress: string;\n /**\n * Fee-payer / participantA `EK…` base58 private key — the client's Mina key\n * (same key the off-chain claim is signed with). Pays the Mina tx fee and\n * authorizes the `initializeChannel` (+ `deposit`) transaction.\n */\n payerPrivateKey: string;\n /**\n * participantB B62 public key — the apex's Mina settlement address. When\n * omitted, the payer is used for both participants (single-party dev channel).\n */\n peerPublicKey?: string;\n /** Channel settlement timeout in slots. Default 86400. */\n timeout?: bigint;\n /** Mina token id field (decimal string). Default '1' (native MINA). */\n tokenId?: string;\n /** Optional on-chain deposit amount (base units) after initialization. */\n deposit?: { amount: bigint };\n /** Per-call network id for the Schnorr/account prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n /**\n * Transaction fee in nanomina for the `initializeChannel` + `deposit` zkApp\n * method calls. Lightnet/devnet REJECTS fee-less zkApp commands with\n * \"Insufficient fee\", so a non-zero fee is REQUIRED. Default 100_000_000\n * (0.1 MINA), matching scripts/deploy-mina-zkapp.ts.\n */\n feeNanomina?: bigint;\n}\n\nexport interface OpenMinaChannelResult {\n /** The zkApp address (channel id) — echoed for parity with the Solana opener. */\n zkAppAddress: string;\n /** True when a fresh `initializeChannel` tx was submitted this call. */\n opened: boolean;\n /** `initializeChannel` tx hash (absent when the channel was already OPEN). */\n initTxHash?: string;\n /** `deposit` tx hash, when a deposit was requested + submitted. */\n depositTxHash?: string;\n /** On-chain channelState after the call (0=UNINIT,1=OPEN,2=CLOSING,3=SETTLED). */\n channelState: number;\n /**\n * On-chain `depositTotal` (base units), read from the zkApp appState after the\n * open/deposit settled. The Mina balance-proof signer needs this so it can bind\n * `balanceB = depositTotal − balanceA` (toon-protocol/connector#133). A channel\n * can be re-deposited, so this is the CURRENT on-chain value, not a config one.\n */\n depositTotal: bigint;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Mina\n * payment channel on the already-deployed `PaymentChannel` zkApp.\n *\n * Idempotent: if the on-chain `channelState` is already `OPEN`, returns without\n * re-initializing (mirrors `openSolanaChannel`'s \"channel already exists\" path).\n * Throws if the zkApp account does not exist on-chain.\n */\nexport async function openMinaChannelOnChain(\n params: OpenMinaChannelParams\n): Promise<OpenMinaChannelResult> {\n const { Mina, PrivateKey, PublicKey, Field, AccountUpdate, fetchAccount } =\n await getO1js();\n\n // Use the plain-string `Mina.Network(graphqlUrl)` form (matching the\n // connector's MinaPaymentChannelSDK._setNetwork). The object form\n // (`{ networkId, mina }`) behaves inconsistently across o1js versions and left\n // the active-instance ledger unable to resolve fetched accounts\n // (`channelState.get()` → \"we can't find this zkapp account\"). The Schnorr\n // network prefix is governed by the off-chain signer (`networkId`), not this\n // on-chain endpoint binding.\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n // zkApp method txs MUST carry a fee on lightnet/devnet (\"Insufficient fee\"\n // otherwise). 0.1 MINA matches scripts/deploy-mina-zkapp.ts.\n const txFee = params.feeNanomina ?? 100_000_000n;\n\n // The client's mnemonic-derived Mina key is a big-endian hex scalar (the form\n // `deriveFullIdentity()` emits); o1js `PrivateKey.fromBase58` needs the Mina\n // `EK…` base58check form. Convert (idempotent — passes an already-`EK…` key\n // through unchanged), matching what the off-chain MinaSigner does.\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);\n const payerPublicKey = payerPrivateKey.toPublicKey();\n const zkAppPublicKey = PublicKey.fromBase58(params.zkAppAddress);\n\n // Read channelState (appState index 3) straight from the `fetchAccount`\n // result rather than `zkApp.channelState.get()`. `.get()` outside a\n // transaction is fragile here (it can throw \"Must call Mina.setActiveInstance\n // first\" / \"can't find this zkapp account\" even right after a successful\n // fetch); the network-fetched appState array is the reliable source. A\n // missing account is a hard error — the zkApp must be deployed out-of-band.\n const readChannelState = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n // PaymentChannel state field order: [channelHash, balanceCommitment,\n // nonceField, channelState, depositTotal, ...] → channelState is index 3.\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[3]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n // Read the on-chain `depositTotal` (appState index 4). The signer must bind\n // `balanceB = depositTotal − balanceA` against this CURRENT on-chain value\n // (connector#133); a channel can be re-deposited, so a stale config value\n // would fail the signatureA verification on settle. A missing account is a\n // hard error (same as readChannelState).\n const readDepositTotal = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[4]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n const currentState = await readChannelState();\n await fetchAccount({ publicKey: payerPublicKey });\n let opened = false;\n let initTxHash: string | undefined;\n let zkApp: any;\n const getZkApp = async () => {\n if (!zkApp) {\n const PaymentChannel = await getCompiledPaymentChannel();\n zkApp = new PaymentChannel(zkAppPublicKey);\n }\n return zkApp;\n };\n\n if (currentState === MINA_CHANNEL_STATE_UNINITIALIZED) {\n const channel = await getZkApp();\n const participantA = payerPublicKey;\n const participantB = params.peerPublicKey\n ? PublicKey.fromBase58(params.peerPublicKey)\n : payerPublicKey;\n const nonce = Field(0);\n const timeoutField = Field((params.timeout ?? 86400n).toString());\n const tokenIdField = Field(params.tokenId ?? '1');\n\n // `initializeChannel` reads `this.channelState.getAndRequireEquals()` as a\n // precondition, which needs the zkApp account in o1js's active-instance\n // cache. Re-fetch BOTH the zkApp and the fee-payer immediately before\n // building the transaction so the precondition read resolves (a stale or\n // missing cache surfaces as \"channelState.get() failed / Must call\n // setActiveInstance first\" — even though the network IS set).\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n\n const initTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.initializeChannel(\n participantA,\n participantB,\n nonce,\n timeoutField,\n tokenIdField\n );\n }\n );\n await initTx.prove();\n const sentInit = await initTx.sign([payerPrivateKey]).send();\n initTxHash = sentInit.hash ?? undefined;\n opened = true;\n // ALWAYS wait for the init tx to be INCLUDED in a block (and re-fetch the\n // account) before returning — NOT only when a deposit follows.\n //\n // Why this matters (issue #158): the two-party `channelHash =\n // Poseidon([client.x, apex.x, 0])` is only written to the zkApp's on-chain\n // state once `initializeChannel` is included in a block. If we fire-and-forget\n // the init tx, the publish proceeds immediately and the connector reads the\n // STILL-BARE zkApp (channelState=0, channelHash empty → `participants:[\"\",\"\"]`)\n // before the init lands, so its participant-form balance-proof reconstruction\n // mismatches → `mina_claim_verification_failed: \"Invalid balance proof\n // signature\"`. The EVM (`waitForTransactionReceipt`) and Solana\n // (`waitForConfirmation`) openers both confirm their open tx before returning;\n // Mina must do the same for parity. `.wait()` blocks until inclusion (lightnet\n // block time can be a few minutes).\n await sentInit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n } else if (currentState !== MINA_CHANNEL_STATE_OPEN) {\n // CLOSING (2) or SETTLED (3): cannot (re)open. Surface clearly.\n throw new Error(\n `Mina channel ${params.zkAppAddress} is in state ${currentState} (not UNINITIALIZED/OPEN) — cannot open`\n );\n }\n\n // Optional deposit (only valid while OPEN — which it now is).\n let depositTxHash: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n const channel = await getZkApp();\n // Re-fetch so the deposit tx sees the post-init state.\n await fetchAccount({ publicKey: zkAppPublicKey });\n const amountField = Field(params.deposit.amount.toString());\n const depositTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.deposit(amountField, payerPublicKey);\n }\n );\n await depositTx.prove();\n const sentDeposit = await depositTx.sign([payerPrivateKey]).send();\n depositTxHash = sentDeposit.hash ?? undefined;\n // ALWAYS wait for the deposit tx to be INCLUDED before returning — same\n // confirmation discipline as initializeChannel above (issue #158). The\n // connector's claimFromChannel runs a #126 balance-conservation gate that\n // reads the on-chain `depositTotal`; if we fire-and-forget the deposit, the\n // publish + claim race ahead and the connector reads depositTotal=0 (deposit\n // not yet in a block) → `PROOF_GENERATION_FAILED: Claim violates balance\n // conservation` and the settle aborts non-retryably. Blocking on inclusion\n // (and re-fetching) guarantees the funded depositTotal is on-chain before any\n // claim settles against it.\n await sentDeposit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n }\n\n // Read the resulting state from the network-fetched appState (best-effort —\n // may still reflect the pre-confirmation value on a slow node; the connector\n // re-reads at verification time). If we just opened, optimistically report\n // OPEN even if the node hasn't surfaced the new state yet.\n let finalState: number;\n try {\n finalState = Number(await readChannelState());\n } catch {\n finalState = opened\n ? Number(MINA_CHANNEL_STATE_OPEN)\n : Number(currentState);\n }\n if (opened && finalState === Number(MINA_CHANNEL_STATE_UNINITIALIZED)) {\n finalState = Number(MINA_CHANNEL_STATE_OPEN);\n }\n\n // Touch AccountUpdate so the (lazy) import is retained even if a future\n // refactor stops referencing it directly above; harmless no-op.\n void AccountUpdate;\n\n // Read the resulting on-chain depositTotal (post init+deposit confirmation, so\n // a fresh re-deposit is reflected). Best-effort: fall back to 0n if the read\n // throws on a slow node — the connector re-reads at verification time.\n let depositTotal: bigint;\n try {\n depositTotal = await readDepositTotal();\n } catch {\n depositTotal = 0n;\n }\n\n return {\n zkAppAddress: params.zkAppAddress,\n opened,\n initTxHash,\n depositTxHash,\n channelState: finalState,\n depositTotal,\n };\n}\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 Mill ↔ 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 */\nfunction 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 Mill ↔ 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 Mill ↔ 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\nconst DEPOSIT_TOTAL_STATE_INDEX = 4;\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","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}\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 });\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\n if (this.store) {\n this.store.save(channelId, {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount,\n });\n }\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 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}\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}\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 };\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 };\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 * 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 * For a successful single-packet (non-chunked) blob upload, the DVM provider\n * returns the Arweave transaction ID as a **UTF-8 string, base64-encoded** in\n * the ILP FULFILL `data` field (the connector validates that FULFILL data is\n * base64). An Arweave tx ID is a 43-character base64url string (32 raw bytes).\n *\n * So the decode is:\n * `txId = utf8(base64decode(result.data))`\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 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 });\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 base64-encoded Arweave tx ID',\n };\n }\n\n // FULFILL data contract: base64(utf8(arweaveTxId)).\n const txId = decodeUtf8(fromBase64(result.data));\n\n if (!ARWEAVE_TX_ID_REGEX.test(txId)) {\n return {\n success: false,\n eventId: event.id,\n error: `Decoded FULFILL data is not a valid Arweave tx ID: \"${txId}\"`,\n };\n }\n\n return {\n success: true,\n txId,\n eventId: event.id,\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(() => {});\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","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","/**\n * Pet DVM Provider Discovery\n *\n * Filters Kind 10035 service discovery events to find providers that\n * support Pet DVM interactions (Kind 5900).\n *\n * @module pet/filterPetDvmProviders\n */\n\nimport { parseServiceDiscovery } from '@toon-protocol/core';\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetDvmProvider } from './types.js';\n\n/**\n * Minimal Nostr event shape needed for filtering.\n * Using a local interface to avoid importing nostr-tools types.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Filter Kind 10035 service discovery events to find pet DVM providers.\n *\n * Accepts raw NostrEvent[] and internally parses content via parseServiceDiscovery.\n * Filters events where skill.kinds includes 5900 (PET_INTERACTION_REQUEST_KIND).\n * Returns provider metadata sorted by price ascending (cheapest first).\n *\n * Handles missing/malformed skill descriptors gracefully (returns empty array, no throw).\n *\n * @param events - Array of raw Nostr events (kind:10035)\n * @returns Array of PetDvmProvider metadata, sorted by price ascending\n */\nexport function filterPetDvmProviders(\n events: NostrEventLike[]\n): PetDvmProvider[] {\n const providers: PetDvmProvider[] = [];\n\n for (const event of events) {\n let parsed;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parsed = parseServiceDiscovery(event as any);\n } catch {\n continue;\n }\n\n if (!parsed) continue;\n\n const skill = parsed.skill;\n if (!skill) continue;\n\n // Check if this provider supports Kind 5900\n if (!skill.kinds.includes(PET_INTERACTION_REQUEST_KIND)) continue;\n\n const pricing = skill.pricing[String(PET_INTERACTION_REQUEST_KIND)] ?? '0';\n\n providers.push({\n ilpAddress: parsed.ilpAddress,\n pricing,\n pubkey: event.pubkey,\n features: skill.features,\n });\n }\n\n // Sort by price ascending (cheapest first)\n providers.sort((a, b) => {\n const priceA = Number(a.pricing) || 0;\n const priceB = Number(b.pricing) || 0;\n return priceA - priceB;\n });\n\n return providers;\n}\n","/**\n * Pet Interaction Request Builder (Kind 5900)\n *\n * Builds unsigned Kind 5900 Nostr events for pet DVM interaction requests.\n * Compatible with nostr-tools/pure finalizeEvent for signing.\n *\n * @module pet/buildPetInteractionRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport { ValidationError } from '../errors.js';\nimport type {\n PetInteractionRequestParams,\n UnsignedNostrEvent,\n} from './types.js';\n\n/** Maximum valid action type index (0-10 inclusive, ACTION_COUNT = 11) */\nconst MAX_ACTION_TYPE = 10;\n\n/**\n * Build an unsigned Kind 5900 pet interaction request event.\n *\n * All tag values are stringified per Nostr protocol convention.\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed interaction parameters\n * @returns Unsigned Nostr event ready for signing\n * @throws ValidationError for invalid input\n */\nexport function buildPetInteractionRequest(\n params: PetInteractionRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, actionType, itemId, tokenCost, isSleeping } = params;\n\n // Validate blobbiId\n if (!blobbiId || blobbiId.trim() === '') {\n throw new ValidationError('blobbiId must be a non-empty string');\n }\n\n // Validate actionType (0 <= actionType <= 10)\n if (\n !Number.isInteger(actionType) ||\n actionType < 0 ||\n actionType > MAX_ACTION_TYPE\n ) {\n throw new ValidationError(\n `actionType must be an integer between 0 and ${MAX_ACTION_TYPE}, got ${actionType}`\n );\n }\n\n // Validate itemId (>= 0)\n if (!Number.isInteger(itemId) || itemId < 0) {\n throw new ValidationError(\n `itemId must be a non-negative integer, got ${itemId}`\n );\n }\n\n // Validate tokenCost (>= 0)\n if (!Number.isFinite(tokenCost) || tokenCost < 0) {\n throw new ValidationError(\n `tokenCost must be a non-negative number, got ${tokenCost}`\n );\n }\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['action', String(actionType)],\n ['item', String(itemId)],\n ['cost', String(tokenCost)],\n ['sleeping', String(isSleeping)],\n ],\n content: '',\n };\n}\n","/**\n * Pet Interaction Result Parser (Kind 6900)\n *\n * Decodes base64-encoded JSON from IlpSendResult.data field.\n * Uses browser-safe atob() -- NOT Node.js Buffer -- for ditto React SPA compatibility.\n *\n * @module pet/parsePetInteractionResult\n */\n\nimport type { PetInteractionResultData, StatValues } from './types.js';\n\n/** Required stat field names */\nconst STAT_FIELDS: readonly (keyof StatValues)[] = [\n 'hunger',\n 'happiness',\n 'health',\n 'hygiene',\n 'energy',\n];\n\n/** Regex for validating 64-char hex string (BLAKE3 brain hash) */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/**\n * Validate that an object has all required StatValues fields as numbers.\n */\nfunction isValidStats(obj: unknown): obj is StatValues {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return STAT_FIELDS.every(\n (field) =>\n typeof record[field] === 'number' && Number.isFinite(record[field])\n );\n}\n\n/**\n * Parse base64-encoded JSON result data from a Kind 6900 DVM response.\n *\n * Uses atob() for browser compatibility (ditto React SPA).\n * Returns null for malformed/missing data (no throw).\n *\n * Validates:\n * - brainHash is 64-char hex\n * - stats has all 5 fields\n * - cycle >= 0\n * - stage 0-2\n *\n * @param data - Base64-encoded JSON string from IlpSendResult.data\n * @returns Parsed PetInteractionResultData or null if invalid\n */\nexport function parsePetInteractionResult(\n data: string\n): PetInteractionResultData | null {\n if (!data) return null;\n\n let json: string;\n try {\n // Use atob() for browser compatibility; works in Node 16+ too\n json = atob(data);\n } catch {\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return null;\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const record = parsed as Record<string, unknown>;\n\n // Validate stats\n if (!isValidStats(record['stats'])) return null;\n\n // Validate stage (0-2)\n const stage = record['stage'];\n if (\n typeof stage !== 'number' ||\n !Number.isInteger(stage) ||\n stage < 0 ||\n stage > 2\n ) {\n return null;\n }\n\n // Validate cycle (>= 0)\n const cycle = record['cycle'];\n if (typeof cycle !== 'number' || !Number.isInteger(cycle) || cycle < 0) {\n return null;\n }\n\n // Validate lastInteraction\n const lastInteraction = record['lastInteraction'];\n if (\n typeof lastInteraction !== 'number' ||\n !Number.isFinite(lastInteraction)\n ) {\n return null;\n }\n\n // Validate brainHash (64-char hex)\n const brainHash = record['brainHash'];\n if (typeof brainHash !== 'string' || !HEX_64_RE.test(brainHash)) {\n return null;\n }\n\n // Validate cooldownTimestamps (array of numbers)\n const cooldownTimestamps = record['cooldownTimestamps'];\n if (!Array.isArray(cooldownTimestamps)) return null;\n if (\n !cooldownTimestamps.every(\n (t): t is number => typeof t === 'number' && Number.isFinite(t)\n )\n ) {\n return null;\n }\n\n // Construct clean stat object to prevent prototype pollution from JSON.parse\n const validatedStats = record['stats'] as StatValues;\n const stats: StatValues = {\n hunger: validatedStats.hunger,\n happiness: validatedStats.happiness,\n health: validatedStats.health,\n hygiene: validatedStats.hygiene,\n energy: validatedStats.energy,\n };\n\n return {\n stats,\n stage,\n cycle,\n lastInteraction,\n brainHash,\n cooldownTimestamps: [...cooldownTimestamps],\n };\n}\n","/**\n * Pet Interaction Event Parser (Kind 14919)\n *\n * Parses Kind 14919 optimistic/proven pet interaction events.\n * Detects proof status from presence of 'proof' + 'mina_tx' tags.\n *\n * @module pet/parsePetInteractionEvent\n */\n\nimport type {\n PetInteractionEventData,\n InteractionResultContent,\n ProofStatus,\n StatValues,\n} from './types.js';\n\n/**\n * Minimal Nostr event shape needed for parsing.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Check that an object has the expected stat fields as numbers.\n */\nfunction isStatLike(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return false;\n const r = obj as Record<string, unknown>;\n return (\n typeof r['hunger'] === 'number' &&\n Number.isFinite(r['hunger']) &&\n typeof r['happiness'] === 'number' &&\n Number.isFinite(r['happiness']) &&\n typeof r['health'] === 'number' &&\n Number.isFinite(r['health']) &&\n typeof r['hygiene'] === 'number' &&\n Number.isFinite(r['hygiene']) &&\n typeof r['energy'] === 'number' &&\n Number.isFinite(r['energy'])\n );\n}\n\n/**\n * Construct a clean StatValues object from a validated stat-like object.\n * Prevents prototype pollution by extracting only known fields.\n */\nfunction cleanStats(obj: Record<string, unknown>): StatValues {\n return {\n hunger: obj['hunger'] as number,\n happiness: obj['happiness'] as number,\n health: obj['health'] as number,\n hygiene: obj['hygiene'] as number,\n energy: obj['energy'] as number,\n };\n}\n\n/**\n * Attempt to parse content JSON into InteractionResultContent.\n * Returns null if content is malformed.\n */\nfunction parseContent(content: string): InteractionResultContent | null {\n try {\n const parsed = JSON.parse(content);\n if (typeof parsed !== 'object' || parsed === null) return null;\n // Structural check -- must have stat objects with correct numeric fields\n if (\n !isStatLike(parsed.priorStats) ||\n !isStatLike(parsed.decayedStats) ||\n !isStatLike(parsed.finalStats)\n ) {\n return null;\n }\n if (\n typeof parsed.cycle !== 'number' ||\n typeof parsed.stage !== 'number' ||\n typeof parsed.tokenCost !== 'number'\n ) {\n return null;\n }\n // Construct clean object to prevent prototype pollution from JSON.parse\n return {\n priorStats: cleanStats(parsed.priorStats),\n decayedStats: cleanStats(parsed.decayedStats),\n finalStats: cleanStats(parsed.finalStats),\n cycle: parsed.cycle,\n stage: parsed.stage,\n tokenCost: parsed.tokenCost,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a Kind 14919 pet interaction event.\n *\n * Extracts all tag values and detects proof status:\n * - 'optimistic': no 'proof' tag\n * - 'proven': has 'proof' + 'mina_tx' tags\n *\n * Returns null if required tags are missing.\n *\n * @param event - A Nostr event (Kind 14919)\n * @returns Parsed PetInteractionEventData or null if malformed\n */\nexport function parsePetInteractionEvent(\n event: NostrEventLike\n): PetInteractionEventData | null {\n const tags = event.tags;\n\n // Extract required tags\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId) return null;\n\n const actionStr = getTagValue(tags, 'action');\n if (!actionStr) return null;\n const actionType = Number(actionStr);\n if (!Number.isFinite(actionType)) return null;\n\n const itemStr = getTagValue(tags, 'item');\n if (!itemStr) return null;\n const itemId = Number(itemStr);\n if (!Number.isFinite(itemId)) return null;\n\n const costStr = getTagValue(tags, 'cost');\n if (!costStr) return null;\n const tokenCost = Number(costStr);\n if (!Number.isFinite(tokenCost)) return null;\n\n const cycleStr = getTagValue(tags, 'cycle');\n if (!cycleStr) return null;\n const cycle = Number(cycleStr);\n if (!Number.isFinite(cycle)) return null;\n\n const stageStr = getTagValue(tags, 'stage');\n if (!stageStr) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n const brainHash = getTagValue(tags, 'brain_hash');\n if (!brainHash) return null;\n\n // Detect proof status\n const proof = getTagValue(tags, 'proof');\n const minaTx = getTagValue(tags, 'mina_tx');\n const proofStatus: ProofStatus = proof && minaTx ? 'proven' : 'optimistic';\n\n // Parse content\n const content = parseContent(event.content);\n\n const result: PetInteractionEventData = {\n blobbiId,\n actionType,\n itemId,\n tokenCost,\n cycle,\n stage,\n brainHash,\n proofStatus,\n content,\n };\n\n if (proof) result.proof = proof;\n if (minaTx) result.minaTx = minaTx;\n\n return result;\n}\n","/**\n * Pet Listing Event Builder (Kind 30402)\n *\n * Builds unsigned Kind 30402 (NIP-99 classified listing) Nostr events\n * for pet-for-sale marketplace listings. Every listing includes a\n * verified biography attachment (lifecycleHash + totalSpent) so buyers\n * can verify the listing against on-chain PetZkApp state.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetListingEvent\n */\n\nimport type { PetListingParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Kind 30402: NIP-99 parameterized replaceable classified listing.\n * Not yet defined in @toon-protocol/core (pet marketplace is new in Story 11-14).\n * Defined locally until core exports this constant.\n */\nconst PET_LISTING_KIND = 30402;\n\n/** Human-readable stage names for listing summaries */\nconst STAGE_NAMES: Record<number, string> = {\n 0: 'Egg',\n 1: 'Baby',\n 2: 'Adult',\n};\n\n/**\n * Build an unsigned Kind 30402 pet-for-sale classified listing event.\n *\n * The listing uses the NIP-99 classified listing format with TOON-specific\n * extension tags for verified biography (lifecycleHash, totalSpent).\n * The `d` tag is set to `blobbiId` for stable parameterized replaceability —\n * republishing with the same `d` tag updates the listing on relays.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed listing parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetListingEvent(\n params: PetListingParams\n): UnsignedNostrEvent {\n const {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n } = params;\n\n const stageName = STAGE_NAMES[stage] ?? 'Unknown';\n const summary = `${stageName} pet for sale — ${totalSpent} PET tokens spent (verified biography)`;\n\n return {\n kind: PET_LISTING_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['title', `Pet ${blobbiId} for sale`],\n ['price', String(askPriceUsdc), 'USDC', ''],\n ['summary', summary],\n ['t', 'pet'],\n ['t', 'toon-pet'],\n ['lifecycle_hash', lifecycleHash],\n ['total_spent', totalSpent],\n ['stage', String(stage)],\n ['expiration', String(expiresAt)],\n ['relay', relayUrl],\n ['p', sellerPubkey],\n ],\n content: JSON.stringify(stats),\n };\n}\n","/**\n * Pet Listing Parser (Kind 30402)\n *\n * Parses Kind 30402 (NIP-99 classified listing) Nostr events into\n * typed PetListing objects. Returns null for invalid or malformed events.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/parsePetListing\n */\n\nimport type { PetListing, StatValues } from './types.js';\n\n/** Regex for 64-char lowercase hex strings */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/** Minimal Nostr event shape required for parsing */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n * Uses noUncheckedIndexedAccess-safe bracket notation.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Default StatValues used when listing content is unparseable.\n * Note: values are 0 (outside the normal [1,100] game range) — this is\n * intentional as a sentinel for \"stats unknown / content malformed\".\n * Consumers should check for all-zero stats and display accordingly.\n */\nconst DEFAULT_STATS: StatValues = {\n hunger: 0,\n happiness: 0,\n health: 0,\n hygiene: 0,\n energy: 0,\n};\n\n/**\n * Attempt to parse content JSON into StatValues.\n * Returns DEFAULT_STATS if content is missing or malformed.\n */\nfunction parseStats(content: string): StatValues {\n try {\n const parsed = JSON.parse(content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return DEFAULT_STATS;\n const r = parsed as Record<string, unknown>;\n if (\n typeof r['hunger'] === 'number' &&\n typeof r['happiness'] === 'number' &&\n typeof r['health'] === 'number' &&\n typeof r['hygiene'] === 'number' &&\n typeof r['energy'] === 'number'\n ) {\n return {\n hunger: r['hunger'],\n happiness: r['happiness'],\n health: r['health'],\n hygiene: r['hygiene'],\n energy: r['energy'],\n };\n }\n return DEFAULT_STATS;\n } catch {\n return DEFAULT_STATS;\n }\n}\n\n/**\n * Parse a Kind 30402 pet classified listing event into a PetListing.\n *\n * Validation rules:\n * - event.kind must be 30402\n * - 'd' tag must be present and non-empty\n * - 'price' tag must be present with a valid positive numeric first element\n * - 'lifecycle_hash' tag must be a 64-char hex string\n * - 'total_spent' tag must be a valid non-negative numeric string\n * - 'stage' tag must be present\n *\n * Stats are parsed from content JSON; unparseable content falls back to DEFAULT_STATS.\n *\n * @param event - A Nostr event (expected Kind 30402)\n * @returns Parsed PetListing or null if invalid\n */\nexport function parsePetListing(event: NostrEventLike): PetListing | null {\n // Kind check\n if (event.kind !== 30402) return null;\n\n const { tags } = event;\n\n // Required: 'd' tag (blobbiId)\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId || blobbiId.trim() === '') return null;\n\n // Required: 'price' tag — must have at least 2 elements, first must be positive numeric\n let askPriceUsdc = 0;\n let foundPrice = false;\n for (const tag of tags) {\n if (tag[0] === 'price') {\n const priceStr = tag[1];\n if (priceStr === undefined) return null;\n const parsed = Number(priceStr);\n if (!Number.isFinite(parsed) || parsed <= 0) return null;\n askPriceUsdc = parsed;\n foundPrice = true;\n break;\n }\n }\n if (!foundPrice) return null;\n\n // Required: 'lifecycle_hash' tag — must be 64-char hex\n const lifecycleHash = getTagValue(tags, 'lifecycle_hash');\n if (!lifecycleHash) return null;\n if (!HEX_64_RE.test(lifecycleHash)) return null;\n\n // Required: 'total_spent' tag — must be a valid non-negative numeric string\n const totalSpent = getTagValue(tags, 'total_spent');\n if (totalSpent === undefined || totalSpent === '') return null;\n const totalSpentNum = Number(totalSpent);\n if (!Number.isFinite(totalSpentNum) || totalSpentNum < 0) return null;\n\n // Required: 'stage' tag\n const stageStr = getTagValue(tags, 'stage');\n if (stageStr === undefined) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n // Optional tags\n const sellerPubkey = getTagValue(tags, 'p') ?? '';\n const relayUrl = getTagValue(tags, 'relay') ?? '';\n const expiresAtStr = getTagValue(tags, 'expiration');\n const expiresAt = expiresAtStr !== undefined ? Number(expiresAtStr) : 0;\n\n // Parse stats from content (null-safe)\n const stats = parseStats(event.content);\n\n return {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n eventId: event.id,\n createdAt: event.created_at,\n };\n}\n","/**\n * Pet Listing Discovery Filter\n *\n * Filters and sorts Kind 30402 pet marketplace listing events into\n * typed PetListing objects. Handles expiry, stage, price, biography\n * value, and seller filtering. Results sorted by totalSpent descending\n * (highest biography value first) to surface the most battle-hardened pets.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/filterPetListings\n */\n\nimport { parsePetListing } from './parsePetListing.js';\nimport type { PetListing, PetListingFilterOptions } from './types.js';\n\n/** Minimal Nostr event shape accepted by the filter */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Compare two numeric strings. Returns negative if a < b, 0 if equal, positive if a > b.\n * Handles arbitrarily large integers (bigint comparison).\n */\nfunction compareNumericStrings(a: string, b: string): number {\n // Fast path for equal strings\n if (a === b) return 0;\n // Use BigInt for correct comparison of large token amounts\n try {\n const bigA = BigInt(a);\n const bigB = BigInt(b);\n if (bigA < bigB) return -1;\n if (bigA > bigB) return 1;\n return 0;\n } catch {\n // Fallback to float comparison for non-integer numerics\n const fa = Number(a);\n const fb = Number(b);\n // Guard against NaN — treat NaN as less than any valid number\n if (!Number.isFinite(fa) && !Number.isFinite(fb)) return 0;\n if (!Number.isFinite(fa)) return -1;\n if (!Number.isFinite(fb)) return 1;\n return fa - fb;\n }\n}\n\n/**\n * Filter and sort Kind 30402 pet marketplace listing events.\n *\n * Parsing is done via parsePetListing — invalid events are silently dropped.\n * Expired listings (expiration tag < current unix time) are excluded.\n * Options allow additional filtering by stage, price, biography value, and seller.\n * Results are sorted by totalSpent descending (highest biography value first).\n *\n * @param events - Array of raw Nostr events to filter\n * @param options - Optional filter criteria\n * @returns Filtered and sorted array of PetListing objects\n */\nexport function filterPetListings(\n events: NostrEventLike[],\n options?: PetListingFilterOptions\n): PetListing[] {\n const now = Math.floor(Date.now() / 1000);\n const listings: PetListing[] = [];\n\n for (const event of events) {\n // Parse and validate the listing\n const listing = parsePetListing(event);\n if (listing === null) continue;\n\n // Expiry filter — only exclude if expiresAt is set (> 0) AND has passed\n // listing.expiresAt is already parsed from the expiration tag by parsePetListing\n // (0 means no expiration tag was present — treat as never-expires)\n if (listing.expiresAt > 0 && listing.expiresAt < now) continue;\n\n // Stage filter\n if (options?.minStage !== undefined && listing.stage < options.minStage) {\n continue;\n }\n\n // Price filter\n if (\n options?.maxAskPriceUsdc !== undefined &&\n listing.askPriceUsdc > options.maxAskPriceUsdc\n ) {\n continue;\n }\n\n // Biography value filter (totalSpent numeric string comparison)\n if (options?.minTotalSpent !== undefined) {\n if (\n compareNumericStrings(listing.totalSpent, options.minTotalSpent) < 0\n ) {\n continue;\n }\n }\n\n // Seller pubkey filter\n if (\n options?.sellerPubkey !== undefined &&\n listing.sellerPubkey !== options.sellerPubkey\n ) {\n continue;\n }\n\n listings.push(listing);\n }\n\n // Sort by totalSpent descending (highest biography value first)\n listings.sort((a, b) => compareNumericStrings(b.totalSpent, a.totalSpent));\n\n return listings;\n}\n","/**\n * Pet Purchase Request Builder (Kind 5900, action type 9)\n *\n * Builds unsigned Kind 5900 Nostr events for pet transfer-ownership\n * purchase requests. Action type 9 is a reserved slot in the pet DVM\n * protocol — this event signals purchase intent and routes ILP payment\n * to the seller. The actual Mina on-chain ownership transfer (PetZkApp\n * .transferOperator) is handled by downstream stories.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetPurchaseRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetPurchaseRequestParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Action type 9 — transfer-ownership reserved slot.\n * Not currently handled server-side; defines the client-side protocol shape\n * for downstream Mina ownership transfer implementation.\n */\nconst TRANSFER_OWNERSHIP_ACTION = 9;\n\n/**\n * Build an unsigned Kind 5900 pet purchase request event.\n *\n * Reuses the existing pet interaction event kind (5900) with action type 9\n * (transfer-ownership). The `listing` tag references the kind:30402 listing\n * event being purchased. The `p` tag routes ILP payment to the seller.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed purchase request parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetPurchaseRequest(\n params: PetPurchaseRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, listingEventId, buyerPubkey, tokenCost, sellerPubkey } =\n params;\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['action', String(TRANSFER_OWNERSHIP_ACTION)],\n ['i', blobbiId],\n ['listing', listingEventId],\n ['buyer', buyerPubkey],\n ['p', sellerPubkey],\n ['cost', String(tokenCost)],\n ],\n content: '',\n };\n}\n","/**\n * Devnet faucet helper.\n *\n * The deployed TOON devnet exposes a faucet that drips test funds to a given\n * chain address so a client can open payment channels and pay for writes:\n *\n * EVM `POST {faucetUrl}/api/request` body `{ address }` → 100 ETH + 10k USDC\n * Solana `POST {faucetUrl}/api/solana/request` body `{ address }` → SOL + USDC\n * Mina `POST {faucetUrl}/api/mina/request` body `{ address }` → native MINA + USDC\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.\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 /** Request timeout in milliseconds (default: 30000). */\n timeout?: number;\n}\n\n/** Map a chain to its faucet request path. */\nfunction faucetPath(chain: FaucetChain): string {\n switch (chain) {\n case 'evm':\n return '/api/request';\n case 'solana':\n return '/api/solana/request';\n case 'mina':\n return '/api/mina/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 ?? 30000;\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 Townhouse node wallet crypto (`packages/townhouse/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 Townhouse'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 Townhouse 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"],"mappings":"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","names":["generateSecretKey","getPublicKey","generateSecretKey","ed25519","generateSecretKey","toHex","textEncoder","require","http","ed25519","base58Encode","hexToMinaBase58PrivateKey","hexToMinaBase58PrivateKey","STATE_MAP","base58Encode","ed25519","status","privateKeyToAccount","toHex","ed25519","base58Encode","ed25519","base58Encode","toHex","hexToMinaBase58PrivateKey","sha256","sha256","hexToMinaBase58PrivateKey","generateSecretKey","getPublicKey","PET_INTERACTION_REQUEST_KIND","PET_INTERACTION_REQUEST_KIND","HEX_64_RE","getTagValue","PET_INTERACTION_REQUEST_KIND","finalizeEvent","toBase64","fromBase64","bytesToHex","toBase64","fromBase64","getPublicKey","bytesToHex","fromBase64","finalizeEvent","writeFileSync","readFileSync","encryptMnemonic","decryptMnemonic","readFileSync","writeFileSync"]}
|