@toon-protocol/client 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/index.d.ts +514 -78
- package/dist/index.js +2080 -99
- package/dist/index.js.map +1 -1
- package/dist/mina-signer-J7GFWOGO.js +6317 -0
- package/dist/mina-signer-J7GFWOGO.js.map +1 -0
- package/package.json +1 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/modes/http.ts","../src/utils/retry.ts","../src/adapters/HttpRuntimeClient.ts","../src/adapters/BtpRuntimeClient.ts","../src/channel/OnChainChannelClient.ts","../src/signing/evm-signer.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/adapters/HttpConnectorAdmin.ts"],"sourcesContent":["import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type {\n BootstrapService,\n DiscoveryTracker,\n IlpSendResult,\n IlpClient,\n} from '@toon-protocol/core';\nimport { validateConfig, applyDefaults } from './config.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 { ChannelManager } from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.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 channelManager?: ChannelManager;\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 * 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 * 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\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\n const proof = await cm.signBalanceProof(channelId, amount);\n return EvmSigner.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 // Track any additional channels from bootstrap results\n if (this.channelManager) {\n for (const result of bootstrapResults) {\n if (\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\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?: { destination?: string; claim?: SignedBalanceProof }\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\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size\n const basePricePerByte = 10n;\n const amount = String(BigInt(toonData.length) * basePricePerByte);\n\n // Use provided destination or fall back to config default\n const destination =\n options?.destination ?? this.config.destinationAddress;\n\n // Require claim + BTP — plain sendIlpPacket is only valid for\n // node-to-node forwarding (town.ts), not client-to-node.\n if (!options?.claim) {\n throw new ToonClientError(\n 'Signed balance proof required. Call signBalanceProof() first.',\n 'MISSING_CLAIM'\n );\n }\n if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for publishing. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = EvmSigner.buildClaimMessage(\n options.claim,\n this.getPublicKey()\n );\n const response = await this.state.btpClient.sendIlpPacketWithClaim(\n {\n destination,\n amount,\n data: Buffer.from(toonData).toString('base64'),\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 throw new ToonClientError(\n 'Failed to publish event',\n 'PUBLISH_ERROR',\n error instanceof Error ? error : undefined\n );\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 * 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 * 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 if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for sending payments. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = EvmSigner.buildClaimMessage(\n params.claim,\n this.getPublicKey()\n );\n return this.state.btpClient.sendIlpPacketWithClaim(ilpParams, claimMessage);\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 { ValidationError } from './errors.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 * 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 connectorUrl for HTTP mode\n if (!config.connectorUrl) {\n throw new ValidationError(\n 'connectorUrl is required for HTTP mode. Example: \"http://localhost:8080\"'\n );\n }\n\n // Validate connectorUrl format\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 // 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 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 | 'evmPrivateKey'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\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 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 chainRpcUrls?: Record<string, string>;\n initialDeposit?: string;\n settlementTimeout?: number;\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(config: ToonClientConfig): ResolvedConfig {\n // Auto-generate Nostr keypair when secretKey is omitted\n const secretKey = config.secretKey ?? generateSecretKey();\n\n // Derive btpUrl from connectorUrl when not explicitly provided\n // http://host:8080 → ws://host:3000\n let btpUrl = config.btpUrl;\n if (!btpUrl && config.connectorUrl) {\n try {\n const url = new URL(config.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 && config.connectorUrl) {\n try {\n const url = new URL(config.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 connectorUrl: config.connectorUrl as string, // Already validated as required\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 config: ToonClientConfig\n): ClientSettlementInfo | undefined {\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 { 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 { 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 // Derive admin URL from connector URL (change port 8080 → 8081)\n const connectorUrl = config.connectorUrl;\n\n // Build settlement info from config\n const settlementInfo = buildSettlementInfo(config);\n\n // Create BTP runtime client — this is the primary transport for the client SDK.\n // The client connects to the connector via BTP WebSocket to send ILP packets.\n // HTTP is not used for ILP packet transport.\n let btpClient: BtpRuntimeClient | null = null;\n if (config.btpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: config.btpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n });\n await btpClient.connect();\n }\n\n // BTP is the runtime client for sending ILP packets\n const runtimeClient =\n btpClient ??\n new HttpRuntimeClient({\n connectorUrl,\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';\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 // Attempt to decode Base64 to validate format\n Buffer.from(params.data, 'base64');\n // Verify it's actually Base64 (not just any string Buffer accepts)\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(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","import { BTPClient } from '@toon-protocol/connector';\nimport type { ILPPreparePacket } from '@toon-protocol/connector';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport type { EVMClaimMessage } from '../signing/evm-signer.js';\nimport { withRetry } from '../utils/retry.js';\n\n/** BTP Peer — matches @toon-protocol/connector's Peer interface */\ninterface Peer {\n id: string;\n url: string;\n authToken: string;\n connected: boolean;\n lastSeen: Date;\n}\n\n/** BTP claim protocol constants — matches @toon-protocol/connector's BTP_CLAIM_PROTOCOL */\nconst BTP_CLAIM_PROTOCOL = {\n NAME: 'payment-channel-claim',\n CONTENT_TYPE: 1,\n} as const;\n\n/** Pino-compatible logger interface */\ninterface ConsoleLogger {\n level: string;\n silent: (...args: unknown[]) => void;\n info: typeof console.info;\n warn: typeof console.warn;\n error: typeof console.error;\n debug: typeof console.debug;\n trace: typeof console.debug;\n fatal: typeof console.error;\n child: () => ConsoleLogger;\n}\n\n/** Creates a pino-compatible logger wrapper around console */\nfunction createConsoleLogger(): ConsoleLogger {\n const noop = (..._args: unknown[]) => {\n // intentional no-op for pino's silent log level\n };\n const logger: ConsoleLogger = {\n level: 'info',\n silent: noop,\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console),\n trace: console.debug.bind(console),\n fatal: console.error.bind(console),\n child: () => createConsoleLogger(),\n };\n return logger;\n}\n\n/** ILP packet type constants — matches @toon-protocol/connector's PacketType enum */\nconst ILP_PACKET_TYPE = {\n PREPARE: 12,\n FULFILL: 13,\n REJECT: 14,\n} as const;\n\n/** Shape of a BTP fulfill response */\ninterface BtpFulfillResponse {\n type: typeof ILP_PACKET_TYPE.FULFILL;\n data: Buffer;\n}\n\n/** Shape of a BTP reject response */\ninterface BtpRejectResponse {\n type: typeof ILP_PACKET_TYPE.REJECT;\n code: string;\n message: string;\n data: Buffer;\n}\n\nexport interface BtpRuntimeClientConfig {\n btpUrl: string;\n peerId: string;\n authToken: string;\n logger?: ConsoleLogger;\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}\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 * Wraps BTPClient from @toon-protocol/connector with auto-reconnect on connection loss.\n */\nexport class BtpRuntimeClient implements IlpClient {\n private btpClient: BTPClient | null = null;\n private readonly config: BtpRuntimeClientConfig;\n private _isConnected = false;\n private readonly logger: ConsoleLogger;\n\n constructor(config: BtpRuntimeClientConfig) {\n this.config = config;\n this.logger = config.logger ?? createConsoleLogger();\n }\n\n /**\n * Connects to the BTP peer via WebSocket.\n */\n async connect(): Promise<void> {\n const peer: Peer = {\n id: this.config.peerId,\n url: this.config.btpUrl,\n authToken: this.config.authToken,\n connected: false,\n lastSeen: new Date(),\n };\n\n // Cast logger: ConsoleLogger implements the subset of pino's Logger\n // that BTPClient actually uses at runtime (info, warn, error, debug, child)\n this.btpClient = new BTPClient(\n peer,\n this.config.peerId,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.logger as any\n );\n\n await this.btpClient.connect();\n this._isConnected = true;\n }\n\n /**\n * Attempts to reconnect by creating a fresh BTPClient and connecting.\n */\n async reconnect(): Promise<void> {\n // Clean up old client if it exists\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 this.logger.info('[BtpRuntimeClient] Reconnecting...');\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 // Mark as disconnected so reconnect happens on next attempt\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: EVMClaimMessage\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 * 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 const packet = {\n type: ILP_PACKET_TYPE.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: Buffer.alloc(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: Buffer.from(params.data, 'base64'),\n } as ILPPreparePacket;\n\n const response = await this.btpClient?.sendPacket(packet);\n if (!response) {\n throw new Error('BTP client not connected');\n }\n\n if (response.type === ILP_PACKET_TYPE.FULFILL) {\n const fulfill = response as unknown as BtpFulfillResponse;\n return {\n accepted: true,\n data:\n fulfill.data.length > 0 ? fulfill.data.toString('base64') : undefined,\n };\n }\n\n // Reject packet — ILP application-level error, not a connection error\n const reject = response as unknown as BtpRejectResponse;\n return {\n accepted: false,\n code: reject.code,\n message: reject.message,\n data: reject.data.length > 0 ? reject.data.toString('base64') : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n * Embeds the claim in the same BTP message as the ILP PREPARE packet.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: EVMClaimMessage\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new Error('BTP client not connected');\n }\n\n const packet = {\n type: ILP_PACKET_TYPE.PREPARE,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: Buffer.alloc(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: Buffer.from(params.data, 'base64'),\n } as ILPPreparePacket;\n\n // Send ILP packet with claim embedded in the same BTP message\n const protocolData = [\n {\n protocolName: BTP_CLAIM_PROTOCOL.NAME,\n contentType: BTP_CLAIM_PROTOCOL.CONTENT_TYPE,\n data: Buffer.from(JSON.stringify(claim)),\n },\n ];\n\n const response = await this.btpClient.sendPacket(packet, protocolData);\n\n if (response.type === ILP_PACKET_TYPE.FULFILL) {\n const fulfill = response as unknown as BtpFulfillResponse;\n return {\n accepted: true,\n data:\n fulfill.data.length > 0 ? fulfill.data.toString('base64') : undefined,\n };\n }\n\n const reject = response as unknown as BtpRejectResponse;\n return {\n accepted: false,\n code: reject.code,\n message: reject.message,\n data: reject.data.length > 0 ? reject.data.toString('base64') : undefined,\n };\n }\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 type { EvmSigner } from '../signing/evm-signer.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 OnChainChannelClientConfig {\n evmSigner: EvmSigner;\n chainRpcUrls: Record<string, string>;\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 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 }\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 {\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 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","import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport { type Hex, toHex } from 'viem';\nimport type { BalanceProofParams, SignedBalanceProof } 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 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 /** 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 type { EvmSigner } from '../signing/evm-signer.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type { ChannelStore } from './ChannelStore.js';\n\ninterface ChannelTracking {\n nonce: number;\n cumulativeAmount: bigint;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n}\n\n/**\n * Local nonce tracking and claim signing.\n *\n * Does NOT make any network calls — it only manages state\n * and delegates signing to EvmSigner.\n */\nexport class ChannelManager {\n private readonly evmSigner: EvmSigner;\n private readonly channels = new Map<string, ChannelTracking>();\n private readonly store?: ChannelStore;\n\n constructor(evmSigner: EvmSigner, store?: ChannelStore) {\n this.evmSigner = evmSigner;\n this.store = store;\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 EIP-712 signing (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 chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\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 chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n });\n return;\n }\n }\n\n this.channels.set(channelId, {\n nonce: initialNonce,\n cumulativeAmount: initialAmount,\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n });\n }\n\n /**\n * Signs a balance proof for the given channel.\n * Auto-increments nonce and adds to cumulative amount.\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 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 /**\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","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"],"mappings":"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","names":["generateSecretKey","generateSecretKey"]}
|
|
1
|
+
{"version":3,"sources":["../src/ToonClient.ts","../src/config.ts","../src/errors.ts","../src/utils/binary.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/channel/OnChainChannelClient.ts","../src/signing/evm-signer.ts","../src/channel/ChannelManager.ts","../src/channel/ChannelStore.ts","../src/adapters/HttpConnectorAdmin.ts","../src/signing/solana-signer.ts","../src/signing/mina-signer.ts","../src/keys/KeyManager.ts","../src/keys/KeyDerivation.ts","../src/keys/PasskeyAuth.ts","../src/keys/encoding.ts","../src/keys/KeyVault.ts","../src/keys/BackupService.ts"],"sourcesContent":["import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type {\n BootstrapService,\n DiscoveryTracker,\n IlpSendResult,\n IlpClient,\n} from '@toon-protocol/core';\nimport { validateConfig, applyDefaults } from './config.js';\nimport { toBase64 } from './utils/binary.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 {\n ChannelManager,\n type PeerNegotiation,\n} from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { BtpRuntimeClient } from './adapters/BtpRuntimeClient.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 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 * 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 * 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\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\n const proof = await cm.signBalanceProof(channelId, amount);\n return EvmSigner.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\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?: { destination?: string; claim?: SignedBalanceProof }\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\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size\n const basePricePerByte = 10n;\n const amount = String(BigInt(toonData.length) * basePricePerByte);\n\n // Use provided destination or fall back to config default\n const destination =\n options?.destination ?? this.config.destinationAddress;\n\n if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for publishing. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\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 claimMessage = EvmSigner.buildClaimMessage(\n options.claim,\n this.getPublicKey()\n );\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 this.state.btpClient.sendIlpPacketWithClaim(\n {\n destination,\n amount,\n data: toBase64(\n toonData instanceof Uint8Array ? toonData : new Uint8Array(toonData)\n ),\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 * 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 * 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 if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for sending payments. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = EvmSigner.buildClaimMessage(\n params.claim,\n this.getPublicKey()\n );\n return this.state.btpClient.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 { ValidationError } from './errors.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 * 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 connectorUrl for HTTP mode\n if (!config.connectorUrl) {\n throw new ValidationError(\n 'connectorUrl is required for HTTP mode. Example: \"http://localhost:8080\"'\n );\n }\n\n // Validate connectorUrl format\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 // 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 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 | 'evmPrivateKey'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\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 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 chainRpcUrls?: Record<string, string>;\n initialDeposit?: string;\n settlementTimeout?: number;\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(config: ToonClientConfig): ResolvedConfig {\n // Auto-generate Nostr keypair when secretKey is omitted\n const secretKey = config.secretKey ?? generateSecretKey();\n\n // Derive btpUrl from connectorUrl when not explicitly provided\n // http://host:8080 → ws://host:3000\n let btpUrl = config.btpUrl;\n if (!btpUrl && config.connectorUrl) {\n try {\n const url = new URL(config.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 && config.connectorUrl) {\n try {\n const url = new URL(config.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 connectorUrl: config.connectorUrl as string, // Already validated as required\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 config: ToonClientConfig\n): ClientSettlementInfo | undefined {\n if (\n !config.supportedChains?.length &&\n !config.settlementAddresses &&\n !config.preferredTokens &&\n !config.tokenNetworks\n ) {\n return undefined;\n }\n\n return {\n ilpAddress: config.ilpInfo?.ilpAddress,\n supportedChains: config.supportedChains,\n settlementAddresses: config.settlementAddresses,\n preferredTokens: config.preferredTokens,\n tokenNetworks: config.tokenNetworks,\n };\n}\n","/**\n * Base error class for all TOON client errors.\n */\nexport class ToonClientError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n cause?: Error\n ) {\n super(message, { cause });\n this.name = 'ToonClientError';\n }\n}\n\n/**\n * Network error for connection failures (ECONNREFUSED, ETIMEDOUT).\n * These errors trigger retry logic with exponential backoff.\n */\nexport class NetworkError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Connector error for 5xx server errors.\n * These errors indicate the connector is unavailable or malfunctioning.\n */\nexport class ConnectorError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONNECTOR_ERROR', cause);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Validation error for invalid input parameters.\n * These errors are thrown before making any HTTP requests.\n */\nexport class ValidationError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'VALIDATION_ERROR', cause);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Unauthorized error for 401 responses from connector admin API.\n * Indicates missing or invalid authentication credentials.\n */\nexport class UnauthorizedError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'UNAUTHORIZED', cause);\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Peer not found error for 404 responses when removing a peer.\n * Indicates the specified peer ID does not exist in the connector.\n */\nexport class PeerNotFoundError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_NOT_FOUND', cause);\n this.name = 'PeerNotFoundError';\n }\n}\n\n/**\n * Peer already exists error for 409 responses when adding a peer.\n * Indicates a peer with the same ID already exists in the connector.\n */\nexport class PeerAlreadyExistsError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_ALREADY_EXISTS', cause);\n this.name = 'PeerAlreadyExistsError';\n }\n}\n","/**\n * 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","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 { 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 // Derive admin URL from connector URL (change port 8080 → 8081)\n const connectorUrl = config.connectorUrl;\n\n // Build settlement info from config\n const settlementInfo = buildSettlementInfo(config);\n\n // Create BTP runtime client — this is the primary transport for the client SDK.\n // The client connects to the connector via BTP WebSocket to send ILP packets.\n // HTTP is not used for ILP packet transport.\n let btpClient: BtpRuntimeClient | null = null;\n if (config.btpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: config.btpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n });\n await btpClient.connect();\n }\n\n // BTP is the runtime client for sending ILP packets\n const runtimeClient =\n btpClient ??\n new HttpRuntimeClient({\n connectorUrl,\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}\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<IsomorphicBtpClientConfig>;\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 = 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 this.ws.onerror = () => {\n reject(new BtpConnectionError('WebSocket connection error'));\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 // ─── 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(`Authentication failed: ${errData.code}`)\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}\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 });\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 * 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","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 type { EvmSigner } from '../signing/evm-signer.js';\nimport { toHex } from '../utils/binary.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 keypair: Uint8Array;\n programId: string;\n}\n\nexport interface MinaChannelConfig {\n graphqlUrl: string;\n privateKey: string;\n zkAppAddress: string;\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 readonly solanaConfig?: SolanaChannelConfig;\n private readonly 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 * 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 Solana payment channel (PDA creation).\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 // Derive deterministic channel ID from participants + program\n const encoder = new TextEncoder();\n const channelSeed = encoder.encode(\n `channel:${toHex(this.solanaConfig.keypair).slice(0, 32)}:${params.peerAddress}:${Date.now()}`\n );\n const channelIdBytes = new Uint8Array(\n await crypto.subtle.digest('SHA-256', channelSeed)\n );\n const channelId = '0x' + toHex(channelIdBytes);\n\n // Cache context\n this.channelContext.set(channelId, {\n chain: params.chain,\n tokenNetworkAddress: this.solanaConfig.programId,\n });\n\n return { channelId, status: 'opening' };\n }\n\n /**\n * Opens a Mina payment channel (zkApp state transition).\n * Dynamically imports o1js to avoid bundle bloat.\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\n // Derive deterministic channel ID\n const encoder = new TextEncoder();\n const channelSeed = encoder.encode(\n `channel:${this.minaConfig.privateKey.slice(0, 16)}:${params.peerAddress}:${Date.now()}`\n );\n const channelIdBytes = new Uint8Array(\n await crypto.subtle.digest('SHA-256', channelSeed)\n );\n const channelId = '0x' + toHex(channelIdBytes);\n\n // Cache context\n this.channelContext.set(channelId, {\n chain: params.chain,\n tokenNetworkAddress: this.minaConfig.zkAppAddress,\n });\n\n return { channelId, status: 'opening' };\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 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","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 { 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\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 });\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 },\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 });\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 });\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 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 metadata,\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","import type { ConnectorAdminClient } from '@toon-protocol/core';\nimport {\n ValidationError,\n NetworkError,\n ConnectorError,\n UnauthorizedError,\n PeerNotFoundError,\n PeerAlreadyExistsError,\n} from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration for HttpConnectorAdmin.\n */\nexport interface HttpConnectorAdminConfig {\n /** Admin API base URL (e.g., 'http://localhost:8081') */\n adminUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client for testing (default: global fetch) */\n httpClient?: typeof fetch;\n}\n\n/**\n * Result of a bulk peer operation.\n */\nexport interface PeerOperationResult {\n /** Peer ID that was operated on */\n peerId: string;\n /** Whether the operation succeeded */\n success: boolean;\n /** Error that occurred (if failed) */\n error?: Error;\n}\n\n/**\n * HTTP-based connector admin client for managing ILP peers via REST API.\n *\n * Implements the ConnectorAdminClient interface using HTTP requests to the\n * connector's admin API (typically port 8081).\n *\n * @example\n * ```typescript\n * // Embedded mode (DirectConnectorAdmin)\n * const adminClient = new DirectConnectorAdmin(connectorNode);\n *\n * // HTTP mode (HttpConnectorAdmin)\n * const adminClient = new HttpConnectorAdmin({\n * adminUrl: 'http://localhost:8081'\n * });\n *\n * // Add peer\n * await adminClient.addPeer({\n * id: 'nostr-abc123',\n * url: 'btp+ws://alice.example.com:3000',\n * authToken: 'secret-token',\n * routes: [{ prefix: 'g.toon.alice' }]\n * });\n *\n * // Remove peer\n * await adminClient.removePeer('nostr-abc123');\n * ```\n *\n * @throws {ValidationError} Input validation failed (before HTTP request)\n * @throws {NetworkError} Connection failed (ECONNREFUSED, ETIMEDOUT)\n * @throws {UnauthorizedError} Admin API returned 401 (missing/invalid auth)\n * @throws {PeerAlreadyExistsError} Admin API returned 409 (duplicate peer)\n * @throws {PeerNotFoundError} Admin API returned 404 (peer not found)\n * @throws {ConnectorError} Admin API returned 5xx (server error)\n */\nexport class HttpConnectorAdmin implements ConnectorAdminClient {\n private readonly adminUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpConnectorAdminConfig) {\n this.adminUrl = config.adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Add a peer to the connector via the admin API.\n *\n * Validates peer config parameters and sends HTTP POST to /admin/peers.\n *\n * @param config - Peer configuration\n * @param config.id - Unique peer identifier (non-empty string)\n * @param config.url - BTP WebSocket URL (must start with 'btp+ws://' or 'btp+wss://')\n * @param config.authToken - Authentication token (non-empty string)\n * @param config.routes - Optional routing table entries\n * @param config.settlement - Optional settlement configuration\n *\n * @throws {ValidationError} Invalid peer config (missing id, invalid url, etc.)\n * @throws {PeerAlreadyExistsError} Peer with same ID already exists (409 Conflict)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async addPeer(config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }): Promise<void> {\n // Validate required fields\n if (\n !config.id ||\n typeof config.id !== 'string' ||\n config.id.trim() === ''\n ) {\n throw new ValidationError('Peer id must be a non-empty string');\n }\n\n if (\n !config.url ||\n typeof config.url !== 'string' ||\n config.url.trim() === ''\n ) {\n throw new ValidationError('Peer url must be a non-empty string');\n }\n\n // Validate BTP URL format (accept both ws:// and btp+ws:// formats)\n const hasWsPrefix =\n config.url.startsWith('ws://') || config.url.startsWith('wss://');\n const hasBtpPrefix =\n config.url.startsWith('btp+ws://') || config.url.startsWith('btp+wss://');\n\n if (!hasWsPrefix && !hasBtpPrefix) {\n throw new ValidationError(\n `Invalid BTP URL format: \"${config.url}\". Must start with 'ws://', 'wss://', 'btp+ws://', or 'btp+wss://'`\n );\n }\n\n // authToken can be empty string for BTP (doesn't require authentication)\n if (\n config.authToken === undefined ||\n config.authToken === null ||\n typeof config.authToken !== 'string'\n ) {\n throw new ValidationError(\n 'Peer authToken must be a string (can be empty for no auth)'\n );\n }\n\n // Validate routes (if provided)\n if (config.routes !== undefined) {\n if (!Array.isArray(config.routes)) {\n throw new ValidationError('Peer routes must be an array');\n }\n\n for (const route of config.routes) {\n if (\n !route.prefix ||\n typeof route.prefix !== 'string' ||\n route.prefix.trim() === ''\n ) {\n throw new ValidationError('Route prefix must be a non-empty string');\n }\n if (\n route.priority !== undefined &&\n typeof route.priority !== 'number'\n ) {\n throw new ValidationError('Route priority must be a number');\n }\n }\n }\n\n // Validate settlement (if provided)\n if (config.settlement !== undefined) {\n if (typeof config.settlement !== 'object' || config.settlement === null) {\n throw new ValidationError('Peer settlement must be an object');\n }\n\n if (\n !config.settlement.preference ||\n typeof config.settlement.preference !== 'string'\n ) {\n throw new ValidationError(\n 'Settlement preference must be a non-empty string'\n );\n }\n }\n\n // Send HTTP POST request with retry logic\n const url = `${this.adminUrl}/admin/peers`;\n\n await withRetry(async () => this.sendAddPeerRequest(url, config), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Remove a peer from the connector via the admin API.\n *\n * Sends HTTP DELETE to /admin/peers/:id.\n *\n * @param peerId - Unique peer identifier to remove (non-empty string)\n *\n * @throws {ValidationError} Invalid peerId (empty string)\n * @throws {PeerNotFoundError} Peer does not exist (404 Not Found)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async removePeer(peerId: string): Promise<void> {\n // Validate peerId\n if (!peerId || typeof peerId !== 'string' || peerId.trim() === '') {\n throw new ValidationError('peerId must be a non-empty string');\n }\n\n // Send HTTP DELETE request with retry logic\n const url = `${this.adminUrl}/admin/peers/${encodeURIComponent(peerId)}`;\n\n await withRetry(async () => this.sendRemovePeerRequest(url, peerId), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Add multiple peers in parallel for efficient bootstrapping.\n *\n * Uses Promise.allSettled() to execute peer additions concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param configs - Array of peer configurations to add\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.addPeers([\n * { id: 'peer1', url: 'btp+ws://...', authToken: 'token1' },\n * { id: 'peer2', url: 'btp+ws://...', authToken: 'token2' },\n * ]);\n *\n * results.forEach(result => {\n * if (result.success) {\n * console.log(`Added peer: ${result.peerId}`);\n * } else {\n * console.error(`Failed to add ${result.peerId}:`, result.error);\n * }\n * });\n * ```\n */\n async addPeers(\n configs: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }[]\n ): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n configs.map((config) => this.addPeer(config))\n );\n\n return results.map((result, index) => {\n const config = configs[index];\n return {\n peerId: config ? config.id : 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Remove multiple peers in parallel.\n *\n * Uses Promise.allSettled() to execute peer removals concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param peerIds - Array of peer IDs to remove\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.removePeers(['peer1', 'peer2', 'peer3']);\n *\n * const succeeded = results.filter(r => r.success).length;\n * console.log(`Removed ${succeeded}/${results.length} peers`);\n * ```\n */\n async removePeers(peerIds: string[]): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n peerIds.map((peerId) => this.removePeer(peerId))\n );\n\n return results.map((result, index) => {\n const peerId = peerIds[index];\n return {\n peerId: peerId ?? 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Send HTTP POST request to add a peer.\n * Separated for retry logic wrapping.\n */\n private async sendAddPeerRequest(\n url: string,\n config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }\n ): Promise<void> {\n // Normalize URL for connector API (expects ws:// or wss://)\n // Strip btp+ prefix if present, or use as-is if already plain ws://\n let connectorUrl = config.url;\n if (connectorUrl.startsWith('btp+')) {\n connectorUrl = connectorUrl.replace(/^btp\\+/, '');\n }\n\n try {\n const response = await this.httpClient(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...config,\n url: connectorUrl,\n }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (201 Created)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `POST ${url}`, config.id);\n } catch (error) {\n this.handleNetworkError(error, url, 'addPeer');\n }\n }\n\n /**\n * Send HTTP DELETE request to remove a peer.\n * Separated for retry logic wrapping.\n */\n private async sendRemovePeerRequest(\n url: string,\n peerId: string\n ): Promise<void> {\n try {\n const response = await this.httpClient(url, {\n method: 'DELETE',\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (204 No Content)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `DELETE ${url}`, peerId);\n } catch (error) {\n this.handleNetworkError(error, url, 'removePeer');\n }\n }\n\n /**\n * Handle network errors from HTTP requests.\n *\n * Converts connection failures, timeouts, and unknown errors to NetworkError.\n * Re-throws existing ToonClientError instances.\n *\n * @param error - Error thrown by HTTP client\n * @param url - Request URL (for error messages)\n * @param operation - Operation name (for error messages)\n * @throws {NetworkError} Network connection or timeout error\n */\n private handleNetworkError(\n error: unknown,\n url: string,\n operation: string\n ): never {\n // Timeout errors (AbortSignal)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request to ${url} timed out after ${this.timeout}ms`,\n error\n );\n }\n\n // Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failures)\n if (\n error instanceof Error &&\n (error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('ENOTFOUND'))\n ) {\n throw new NetworkError(\n `Failed to connect to connector admin API at ${url}: ${error.message}`,\n error\n );\n }\n\n // Re-throw if already a ToonClientError\n if (\n error instanceof ValidationError ||\n error instanceof PeerAlreadyExistsError ||\n error instanceof PeerNotFoundError ||\n error instanceof UnauthorizedError ||\n error instanceof ConnectorError\n ) {\n throw error;\n }\n\n // Unknown error - wrap in NetworkError\n throw new NetworkError(\n `Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Handle HTTP error responses from the admin API.\n *\n * Converts HTTP status codes to appropriate error types.\n *\n * @param response - HTTP response from admin API\n * @param endpoint - Endpoint being called (for error messages)\n * @param peerId - Peer ID (for error messages)\n * @throws {UnauthorizedError} 401 Unauthorized\n * @throws {PeerNotFoundError} 404 Not Found\n * @throws {PeerAlreadyExistsError} 409 Conflict\n * @throws {ConnectorError} 5xx Server Error\n */\n private async handleErrorResponse(\n response: Response,\n endpoint: string,\n peerId: string\n ): Promise<never> {\n const status = response.status;\n const statusText = response.statusText;\n\n // Try to extract error message from response body\n let errorMessage = '';\n try {\n const body = await response.text();\n if (body) {\n errorMessage = ` - ${body}`;\n }\n } catch {\n // Ignore body parsing errors\n }\n\n switch (status) {\n case 401:\n throw new UnauthorizedError(\n `Admin API authentication failed for ${endpoint}: ${statusText}${errorMessage}`\n );\n\n case 404:\n throw new PeerNotFoundError(\n `Peer not found: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n case 409:\n throw new PeerAlreadyExistsError(\n `Peer already exists: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n default:\n if (status >= 500) {\n throw new ConnectorError(\n `Connector admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n\n // Other 4xx errors (400, 403, etc.)\n throw new ConnectorError(\n `Admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n }\n}\n","import 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';\n\n/**\n * Base58 encoding for Solana public keys.\n */\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\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// Lazy-loaded ed25519 module (optional dep — dynamically imported)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _ed25519: any = null;\nasync function getEd25519() {\n if (!_ed25519) {\n const mod = await import('@noble/curves/ed25519');\n _ed25519 = mod.ed25519;\n }\n return _ed25519;\n}\n\n/**\n * Solana signer for Ed25519 balance proofs.\n *\n * Signs channel state using Ed25519 (raw, not EIP-712).\n * Dynamically imports @noble/curves to avoid missing-dep errors for non-Solana users.\n */\nexport class SolanaSigner implements ChainSigner {\n readonly chainType = 'solana' as const;\n private readonly privateKey: Uint8Array;\n private publicKey?: Uint8Array;\n private pubkeyBase58Cache?: string;\n\n constructor(privateKey: Uint8Array) {\n this.privateKey = privateKey;\n }\n\n private async ensurePublicKey(): Promise<{\n publicKey: Uint8Array;\n base58: string;\n }> {\n if (this.publicKey && this.pubkeyBase58Cache) {\n return { publicKey: this.publicKey, base58: this.pubkeyBase58Cache };\n }\n const ed = await getEd25519();\n const pk: Uint8Array = ed.getPublicKey(this.privateKey);\n const b58 = toBase58(pk);\n this.publicKey = pk;\n this.pubkeyBase58Cache = b58;\n return { publicKey: pk, base58: b58 };\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 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 ed = await getEd25519();\n const { base58 } = await this.ensurePublicKey();\n\n // Construct message: channelId + nonce + transferredAmount + lockedAmount + locksRoot\n const encoder = new TextEncoder();\n const message = encoder.encode(\n `${params.channelId}:${params.nonce}:${params.transferredAmount}:${params.lockedAmount}:${params.locksRoot}`\n );\n\n const signature = ed.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 };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\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: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n signature: proof.signature,\n signerAddress: this.pubkeyBase58Cache ?? proof.signerAddress,\n programId: proof.tokenNetworkAddress,\n };\n return claim;\n }\n}\n","import type { SignedBalanceProof } from '../types.js';\nimport type {\n ChainSigner,\n ChainMetadata,\n ClaimMessage,\n MinaClaimMessage,\n} from './types.js';\n\n/**\n * Mina signer for Poseidon commitment balance proofs.\n *\n * Dynamically imports o1js to avoid pulling 50MB into the client bundle\n * for non-Mina users.\n */\nexport class MinaSigner implements ChainSigner {\n readonly chainType = 'mina' as const;\n private readonly privateKeyBase58: string;\n private publicKeyBase58 = 'uninitialized';\n\n constructor(privateKeyBase58: string) {\n this.privateKeyBase58 = privateKeyBase58;\n }\n\n get signerIdentifier(): string {\n return this.publicKeyBase58;\n }\n\n private async ensurePublicKey(): Promise<string> {\n if (this.publicKeyBase58 !== 'uninitialized') return this.publicKeyBase58;\n\n // @ts-expect-error -- o1js is an optional dependency\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const o1js = (await import('o1js')) as any;\n const pk = o1js.PrivateKey.fromBase58(this.privateKeyBase58);\n this.publicKeyBase58 = pk.toPublicKey().toBase58();\n return this.publicKeyBase58;\n }\n\n async signBalanceProof(params: {\n channelId: string;\n nonce: number;\n transferredAmount: bigint;\n lockedAmount: bigint;\n locksRoot: string;\n metadata: ChainMetadata;\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 // @ts-expect-error -- o1js is an optional dependency\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const o1js = (await import('o1js')) as any;\n const pubkey = await this.ensurePublicKey();\n\n // Compute Poseidon commitment over channel state fields\n const channelIdNum = BigInt(\n '0x' + params.channelId.replace(/^0x/, '').slice(0, 16)\n );\n const commitment = o1js.Poseidon.hash([\n o1js.Field(channelIdNum),\n o1js.Field(params.nonce),\n o1js.Field(params.transferredAmount),\n o1js.Field(params.lockedAmount),\n ]);\n\n // Sign the commitment\n const pk = o1js.PrivateKey.fromBase58(this.privateKeyBase58);\n const signature = o1js.Signature.create(pk, [commitment]);\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: signature.toBase58(),\n signerAddress: pubkey,\n chainId: 0,\n tokenNetworkAddress: params.metadata.zkAppAddress,\n };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\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 channelId: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n commitment: proof.signature,\n signerAddress: proof.signerAddress,\n zkAppAddress: proof.tokenNetworkAddress,\n };\n return claim;\n }\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 { 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 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 * Derive the Nostr secp256k1 key from mnemonic using NIP-06 path: m/44'/1237'/0'/0/0\n */\nfunction deriveNostrKey(seed: Uint8Array): {\n secretKey: Uint8Array;\n pubkey: string;\n} {\n const master = HDKey.fromMasterSeed(seed);\n const child = master.derive(\"m/44'/1237'/0'/0/0\");\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: m/44'/501'/0'/0'\n * Dynamically imports @noble/curves for Ed25519 operations.\n */\nasync function deriveSolanaKey(seed: Uint8Array): 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');\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'/0'/0' (all hardened)\n const indices = [\n 0x8000002c, // 44'\n 0x800001f5, // 501'\n 0x80000000, // 0'\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'/0'/0/0\n * Dynamically imports mina-signer.\n */\nasync function deriveMinaKey(seed: Uint8Array): 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'/0'/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 // Convert raw key bytes to Mina private key format\n // Mina uses Pallas curve; mina-signer accepts raw bytes or base58\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 // Derive a Mina keypair from the raw bytes by converting to hex\n const hexKey = Array.from(keyBytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n const keypair = client.derivePublicKey(hexKey);\n return {\n privateKey: hexKey,\n publicKey: keypair,\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 * Derive a full multi-chain ToonIdentity from a BIP-39 mnemonic.\n *\n * Chains derived:\n * - Nostr (secp256k1): m/44'/1237'/0'/0/0\n * - EVM (secp256k1): same key as Nostr\n * - Solana (Ed25519): m/44'/501'/0'/0' (SLIP-0010)\n * - Mina (Pallas): m/44'/12586'/0'/0/0\n */\nexport async function deriveFullIdentity(\n mnemonic: string\n): Promise<ToonIdentity> {\n const seed = mnemonicToSeedSync(mnemonic);\n\n const nostr = deriveNostrKey(seed);\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);\n } catch {\n solana = { secretKey: new Uint8Array(64), publicKey: '' };\n }\n\n let mina: ToonIdentity['mina'];\n try {\n mina = await deriveMinaKey(seed);\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","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"],"mappings":"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","names":["generateSecretKey","textEncoder","toHex","generateSecretKey","generateSecretKey","getPublicKey","privateKeyToAccount","toHex","toBase58","BASE58_ALPHABET","toBase64","fromBase64","toBase64","fromBase64","getPublicKey","fromBase64"]}
|