@toon-protocol/client-mcp 0.26.2
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/LICENSE +190 -0
- package/README.md +261 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js +24 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js.map +1 -0
- package/dist/chunk-245J23EB.js +278 -0
- package/dist/chunk-245J23EB.js.map +1 -0
- package/dist/chunk-2SGZPDGE.js +625 -0
- package/dist/chunk-2SGZPDGE.js.map +1 -0
- package/dist/chunk-32QD72IL.js +83 -0
- package/dist/chunk-32QD72IL.js.map +1 -0
- package/dist/chunk-5YIZ2JQO.js +205 -0
- package/dist/chunk-5YIZ2JQO.js.map +1 -0
- package/dist/chunk-LR7W2ISE.js +657 -0
- package/dist/chunk-LR7W2ISE.js.map +1 -0
- package/dist/chunk-QTDCFXPF.js +2802 -0
- package/dist/chunk-QTDCFXPF.js.map +1 -0
- package/dist/chunk-VA7XC4FD.js +185 -0
- package/dist/chunk-VA7XC4FD.js.map +1 -0
- package/dist/chunk-WMYY5I3H.js +10818 -0
- package/dist/chunk-WMYY5I3H.js.map +1 -0
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +137 -0
- package/dist/daemon.js.map +1 -0
- package/dist/ed25519-OFFWPWRE.js +26 -0
- package/dist/ed25519-OFFWPWRE.js.map +1 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js +15 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js.map +1 -0
- package/dist/hmac-7WSXTWW4.js +11 -0
- package/dist/hmac-7WSXTWW4.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +80 -0
- package/dist/mcp.js.map +1 -0
- package/dist/sha512-LMOIUNFJ.js +33 -0
- package/dist/sha512-LMOIUNFJ.js.map +1 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js +138 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../client/src/ToonClient.ts","../../client/src/config.ts","../../client/src/errors.ts","../../client/src/keys/KeyDerivation.ts","../../client/src/utils/binary.ts","../../client/src/modes/http.ts","../../client/src/utils/retry.ts","../../client/src/adapters/HttpRuntimeClient.ts","../../client/src/btp/protocol.ts","../../client/src/btp/IsomorphicBtpClient.ts","../../client/src/adapters/BtpRuntimeClient.ts","../../client/src/channel/OnChainChannelClient.ts","../../client/src/channel/solana-payment-channel.ts","../../client/src/channel/mina-channel-open.ts","../../client/src/signing/evm-signer.ts","../../client/src/transport/index.ts","../../client/src/signing/solana-signer.ts","../../client/src/signing/mina-signer.ts","../../client/src/channel/mina-payment-channel.ts","../../client/src/channel/mina-deposit.ts","../../client/src/channel/ChannelManager.ts","../../client/src/channel/ChannelStore.ts","../../client/src/transport/hs-hostname.ts","../../client/src/adapters/HttpConnectorAdmin.ts","../../client/src/pet/filterPetDvmProviders.ts","../../client/src/pet/buildPetInteractionRequest.ts","../../client/src/pet/parsePetInteractionResult.ts","../../client/src/pet/parsePetInteractionEvent.ts","../../client/src/pet/buildPetListingEvent.ts","../../client/src/pet/parsePetListing.ts","../../client/src/pet/filterPetListings.ts","../../client/src/pet/buildPetPurchaseRequest.ts","../../client/src/blob-storage.ts","../../client/src/keys/KeyManager.ts","../../client/src/keys/PasskeyAuth.ts","../../client/src/keys/encoding.ts","../../client/src/keys/KeyVault.ts","../../client/src/keys/BackupService.ts","../../client/src/keys/keystore-node.ts","../../core/src/toon/encoder.ts","../../core/src/errors.ts","../../core/src/toon/decoder.ts","../../core/src/toon/validate.ts","../../core/src/toon/shallow-parse.ts","../../core/src/constants.ts","../../core/src/address/ilp-address-validation.ts","../../core/src/address/derive-child-address.ts","../../core/src/address/btp-prefix-exchange.ts","../../core/src/address/address-assignment.ts","../../core/src/address/address-registry.ts","../../core/src/address/prefix-validation.ts","../../core/src/chain/chain-id.ts","../../core/src/events/swap-pair-validation.ts","../../core/src/events/parsers.ts","../../core/src/events/builders.ts","../../core/src/events/seed-relay.ts","../../core/src/events/service-discovery.ts","../../core/src/events/attestation.ts","../../core/src/events/dvm.ts","../../core/src/events/workflow.ts","../../core/src/events/swarm.ts","../../core/src/events/prefix-claim.ts","../../core/src/bootstrap/AttestationVerifier.ts","../../core/src/events/attested-result-verifier.ts","../../core/src/events/arweave-storage.ts","../../core/src/events/reputation.ts","../../core/src/discovery/NostrPeerDiscovery.ts","../../core/src/discovery/genesis-peers.json","../../core/src/discovery/GenesisPeerLoader.ts","../../core/src/discovery/ArDrivePeerRegistry.ts","../../core/src/discovery/SocialPeerDiscovery.ts","../../core/src/discovery/seed-relay-discovery.ts","../../core/src/fee/calculate-route-amount.ts","../../core/src/fee/resolve-route-fees.ts","../../core/src/settlement/settlement.ts","../../core/src/settlement/hashes.ts","../../core/src/settlement/base58.ts","../../core/src/settlement/mina-key.ts","../../core/src/bootstrap/BootstrapService.ts","../../core/src/bootstrap/discovery-tracker.ts","../../core/src/bootstrap/ilp-client.ts","../../core/src/bootstrap/direct-ilp-client.ts","../../core/src/bootstrap/direct-connector-admin.ts","../../core/src/bootstrap/direct-channel-client.ts","../../core/src/bootstrap/http-connector-admin.ts","../../core/src/bootstrap/http-ilp-client.ts","../../core/src/bootstrap/http-channel-client.ts","../../core/src/bootstrap/AttestationBootstrap.ts","../../core/src/utils/reject-code.ts","../../core/src/compose.ts","../../core/src/chain/usdc.ts","../../core/src/chain/chain-config.ts","../../core/src/chain/network-profile.ts","../../core/src/x402/build-ilp-prepare.ts","../../core/src/identity/kms-identity.ts","../../core/src/build/nix-builder.ts","../../core/src/build/pcr-validator.ts","../../core/src/logger.ts","../../core/src/index.ts","../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/pbkdf2.ts","../../../node_modules/.pnpm/@scure+base@1.2.6/node_modules/@scure/base/index.ts","../../../node_modules/.pnpm/@scure+bip39@1.6.0/node_modules/@scure/bip39/esm/index.js","../../../node_modules/.pnpm/@scure+bip39@1.6.0/node_modules/@scure/bip39/esm/wordlists/english.js","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/utils.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/modular.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/curve.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/abstract/weierstrass.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/_shortw_utils.ts","../../../node_modules/.pnpm/@noble+curves@1.9.7/node_modules/@noble/curves/src/secp256k1.ts","../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/legacy.ts","../../../node_modules/.pnpm/@scure+bip32@1.7.0/node_modules/@scure/bip32/index.ts","../src/daemon/config.ts","../src/control-client.ts","../src/daemon/lifecycle.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 type { NetworkFamilyStatus } from '@toon-protocol/core';\nimport { validateConfig, applyDefaults, getNetworkStatus } 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 { SolanaSigner } from './signing/solana-signer.js';\nimport { MinaSigner } from './signing/mina-signer.js';\nimport { deriveFullIdentity } from './keys/KeyDerivation.js';\nimport {\n ChannelManager,\n type PeerNegotiation,\n} from './channel/ChannelManager.js';\nimport { JsonFileChannelStore } from './channel/ChannelStore.js';\nimport type { 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 * Teardown for a managed `anon` SOCKS5h proxy auto-started during start()\n * for a `.anyone` btpUrl. Present only when the SDK launched its own daemon;\n * `stop()` invokes it so the proxy does not outlive the client.\n */\n stopManagedProxy?: () => Promise<void>;\n}\n\n/**\n * ToonClient - High-level client for interacting with TOON network.\n *\n * This story implements HTTP mode only. Embedded mode will be added in a future epic.\n *\n * @example HTTP Mode\n * ```typescript\n * import { ToonClient } from '@toon-protocol/client';\n * import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\n * import { encodeEvent, decodeEvent } from '@toon-protocol/relay';\n *\n * const secretKey = generateSecretKey();\n * const pubkey = getPublicKey(secretKey);\n *\n * const client = new ToonClient({\n * connectorUrl: 'http://localhost:8080',\n * secretKey,\n * ilpInfo: {\n * pubkey,\n * ilpAddress: `g.toon.${pubkey.slice(0, 8)}`,\n * btpEndpoint: 'ws://localhost:3000',\n * },\n * toonEncoder: encodeEvent,\n * toonDecoder: decodeEvent,\n * });\n *\n * await client.start(); // Bootstrap peers, start monitoring\n *\n * // Publish to default destination (from config)\n * await client.publishEvent(signedEvent);\n *\n * // Publish to specific destination (multi-hop routing)\n * await client.publishEvent(signedEvent, { destination: 'g.toon.peer1' });\n *\n * await client.stop(); // Cleanup\n * ```\n */\nexport class ToonClient {\n private readonly config: ResolvedConfig;\n private state: ToonClientState | null = null;\n private readonly evmSigner?: EvmSigner;\n private solanaSigner?: SolanaSigner;\n /**\n * Ed25519 signing seed (32 bytes) derived from the mnemonic for the Solana\n * identity. Retained so `start()` can inject it into the on-chain channel\n * client's Solana config (same key as `solanaSigner`).\n */\n private solanaSeed?: Uint8Array;\n private minaSigner?: MinaSigner;\n /**\n * Mina private key (big-endian hex scalar, as `deriveFullIdentity` emits)\n * derived from the mnemonic. Retained so `start()` can inject it into the\n * on-chain channel client's Mina config (same key as `minaSigner`).\n */\n private minaPrivateKey?: string;\n private channelManager?: ChannelManager;\n private readonly peerNegotiations = new Map<string, PeerNegotiation>();\n\n /**\n * Creates a new ToonClient instance.\n *\n * @param config - Client configuration\n * @throws {ValidationError} If configuration is invalid\n */\n constructor(config: ToonClientConfig) {\n // Validate config (will reject embedded mode, require connectorUrl)\n validateConfig(config);\n\n // Apply defaults to optional fields (auto-generates secretKey if needed)\n this.config = applyDefaults(config);\n\n // Create EVM signer if private key provided\n if (this.config.evmPrivateKey) {\n this.evmSigner = new EvmSigner(this.config.evmPrivateKey);\n }\n }\n\n /**\n * Generates a new Nostr keypair.\n *\n * @returns Object with secretKey (Uint8Array) and pubkey (hex string)\n */\n static generateKeypair(): { secretKey: Uint8Array; pubkey: string } {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n }\n\n /**\n * Gets the Nostr public key derived from the secret key.\n * Works before start() is called.\n */\n getPublicKey(): string {\n return getPublicKey(this.config.secretKey);\n }\n\n /**\n * Per-chain settlement readiness for the configured `network` tier, mirroring\n * the townhouse node's status. Returns `undefined` when no named `network` is\n * set (or `network: 'custom'`), since there is no preset tier to report on.\n */\n getNetworkStatus(): NetworkFamilyStatus | undefined {\n return getNetworkStatus(this.config);\n }\n\n /**\n * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).\n */\n getEvmAddress(): string | undefined {\n return this.evmSigner?.address;\n }\n\n /**\n * Gets the Solana (base58) address, when the client was constructed from a\n * `mnemonic`. Available only AFTER `start()` (Solana keys are derived\n * asynchronously). Returns undefined otherwise.\n */\n getSolanaAddress(): string | undefined {\n return this.solanaSigner?.signerIdentifier;\n }\n\n /**\n * Gets the Mina (base58) address, when the client was constructed from a\n * `mnemonic` AND `mina-signer` is installed. Available only AFTER `start()`.\n * Returns undefined otherwise.\n */\n getMinaAddress(): string | undefined {\n return this.minaSigner?.signerIdentifier;\n }\n\n /**\n * Derive the Solana/Mina keys from the mnemonic and register their signers on\n * the ChannelManager. Mirrors how the EVM signer is wired, but for the\n * non-secp256k1 chains. Skips any chain whose optional dependency is missing.\n */\n private async registerMnemonicChainSigners(\n mnemonic: string,\n accountIndex = 0\n ): Promise<void> {\n if (!this.channelManager) return;\n const identity = await deriveFullIdentity(mnemonic, accountIndex);\n\n // Solana: @noble/curves Ed25519 expects a 32-byte seed; deriveFullIdentity\n // returns a 64-byte keypair (seed||pubkey).\n if (identity.solana.publicKey) {\n const seed = identity.solana.secretKey.slice(0, 32);\n this.solanaSeed = seed;\n this.solanaSigner = new SolanaSigner(seed, identity.solana.publicKey);\n this.channelManager.registerChainSigner('solana', this.solanaSigner);\n }\n\n // Mina: only present when mina-signer is installed (optional dep).\n if (identity.mina.publicKey) {\n this.minaPrivateKey = identity.mina.privateKey;\n // Pass the configured GraphQL URL so the signer can read the channel's\n // on-chain `depositTotal` and bind the conserved `balanceB = depositTotal\n // − balanceA` commitment that a funded zkApp requires (connector#133);\n // without it Mina claims use the legacy balanceB=0 form and a funded zkApp\n // rejects them (F06 - Invalid zk-SNARK proof on claim).\n this.minaSigner = new MinaSigner(\n identity.mina.privateKey,\n identity.mina.publicKey,\n this.config.minaChannel?.graphqlUrl\n ? { graphqlUrl: this.config.minaChannel.graphqlUrl }\n : undefined\n );\n this.channelManager.registerChainSigner('mina', this.minaSigner);\n }\n }\n\n /**\n * Starts the ToonClient.\n *\n * This will:\n * 1. Initialize HTTP mode components (runtime client, admin client, bootstrap, monitor)\n * 2. Bootstrap the network (discover peers, register, and open channels)\n * 3. Start monitoring relay for new peers (kind:10032 events)\n *\n * @returns Result with number of peers discovered and mode\n * @throws {ToonClientError} If client is already started\n * @throws {ToonClientError} If initialization fails\n */\n async start(): Promise<ToonStartResult> {\n if (this.state !== null) {\n throw new ToonClientError('Client already started', 'INVALID_STATE');\n }\n\n try {\n // Create channel manager FIRST (before bootstrap) so it can sign claims during settlement\n if (this.evmSigner) {\n const store = this.config.channelStorePath\n ? new JsonFileChannelStore(this.config.channelStorePath)\n : undefined;\n this.channelManager = new ChannelManager(this.evmSigner, store);\n\n // When constructed from a mnemonic, derive the non-secp256k1 keys\n // (Solana Ed25519, Mina Pallas) and register their signers so the\n // client can settle on those chains too. Derivation is async (dynamic\n // imports + optional deps), hence done here rather than in the\n // synchronous constructor. Gracefully skips a chain whose optional dep\n // is absent (e.g. mina-signer) — deriveFullIdentity leaves it empty.\n if (this.config.mnemonic) {\n await this.registerMnemonicChainSigners(\n this.config.mnemonic,\n this.config.mnemonicAccountIndex ?? 0\n );\n }\n }\n\n // Initialize HTTP mode components\n const initialization = await initializeHttpMode(this.config);\n\n const {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n btpClient,\n stopManagedProxy,\n } = initialization;\n\n // Wire claim signer to bootstrap service if we have channel manager\n if (this.channelManager) {\n const cm = this.channelManager;\n const nostrPubkey = this.getPublicKey();\n // Derive default chain context from config (first supported chain)\n const defaultChainCtx = this.getDefaultChainContext();\n bootstrapService.setClaimSigner(\n async (channelId: string, amount: bigint) => {\n // Track the channel if not already tracked\n if (!cm.isTracking(channelId)) {\n cm.trackChannel(channelId, defaultChainCtx);\n }\n // Sign balance proof and build full claim message with the\n // chain-appropriate signer (the channel is tracked above, so a\n // non-EVM channel yields its correct envelope, not an EVM claim).\n const proof = await cm.signBalanceProof(channelId, amount);\n const signer = cm.getSignerForChannel(channelId);\n return signer.buildClaimMessage(proof, nostrPubkey);\n }\n );\n }\n\n // Start bootstrap process (discover peers, register with settlement, announce)\n const bootstrapResults = await bootstrapService.bootstrap();\n\n // Store negotiation metadata from bootstrap results for lazy channel opening\n for (const result of bootstrapResults) {\n if (result.negotiatedChain && result.settlementAddress) {\n const chainType = result.negotiatedChain.split(':')[0] ?? 'evm';\n const parts = result.negotiatedChain.split(':');\n const chainId = parts.length >= 3 ? parseInt(parts[2] ?? '0', 10) : 0;\n const r = result as typeof result & {\n tokenAddress?: string;\n tokenNetwork?: string;\n };\n this.peerNegotiations.set(result.registeredPeerId, {\n chain: result.negotiatedChain,\n chainType,\n chainId: isNaN(chainId) ? 0 : chainId,\n settlementAddress: result.settlementAddress,\n tokenAddress: r.tokenAddress,\n tokenNetwork: r.tokenNetwork,\n });\n } else if (\n result.registeredPeerId &&\n !this.peerNegotiations.has(result.registeredPeerId)\n ) {\n // Lightweight client fallback: bootstrap discovered the peer but didn't\n // negotiate a chain (no connector admin to register with). Extract the\n // peer's settlement info from their kind:10032 event data and match\n // against our supported chains.\n const peerInfo = result.peerInfo as typeof result.peerInfo & {\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n };\n const peerChains = peerInfo.supportedChains ?? [];\n const ourChains = this.config.supportedChains ?? [];\n // Find the first chain both sides support\n const matchedChain =\n ourChains.find((c) => peerChains.includes(c)) ?? ourChains[0];\n if (matchedChain) {\n const peerAddr = peerInfo.settlementAddresses?.[matchedChain];\n const parts = matchedChain.split(':');\n const chainId =\n parts.length >= 3 ? parseInt(parts[2] ?? '0', 10) : 0;\n if (peerAddr) {\n this.peerNegotiations.set(result.registeredPeerId, {\n chain: matchedChain,\n chainType: parts[0] ?? 'evm',\n chainId: isNaN(chainId) ? 0 : chainId,\n settlementAddress: peerAddr,\n tokenAddress:\n peerInfo.preferredTokens?.[matchedChain] ??\n this.config.preferredTokens?.[matchedChain],\n tokenNetwork:\n peerInfo.tokenNetworks?.[matchedChain] ??\n this.config.tokenNetworks?.[matchedChain],\n });\n }\n }\n }\n // Track any pre-opened channels (backwards compat)\n if (\n this.channelManager &&\n result.channelId &&\n !this.channelManager.isTracking(result.channelId)\n ) {\n const chainCtx = this.getChainContext(result.negotiatedChain);\n this.channelManager.trackChannel(result.channelId, chainCtx);\n }\n }\n\n // Wire on-chain channel client into ChannelManager for lazy opens\n if (this.channelManager && initialization.onChainChannelClient) {\n this.channelManager.setChannelClient(\n initialization.onChainChannelClient\n );\n\n // Late-bind the Solana channel config: the program/RPC/token come from\n // config, the Ed25519 keypair from the mnemonic-derived Solana seed.\n // Requires both a Solana seed (mnemonic-derived) and explicit\n // solanaChannel config — otherwise the on-chain Solana opener has no\n // program/RPC and would throw at openChannel time.\n if (this.config.solanaChannel && this.solanaSeed) {\n initialization.onChainChannelClient.setSolanaConfig({\n rpcUrl: this.config.solanaChannel.rpcUrl,\n programId: this.config.solanaChannel.programId,\n tokenMint: this.config.solanaChannel.tokenMint,\n challengeDuration: this.config.solanaChannel.challengeDuration,\n deposit: this.config.solanaChannel.deposit,\n keypair: this.solanaSeed,\n });\n }\n\n // Late-bind the Mina channel config (parallel to Solana). The\n // graphqlUrl + zkAppAddress come from config; the Mina private key from\n // the mnemonic-derived Mina identity (same key as the registered Mina\n // signer). Requires both a Mina private key (mnemonic-derived, present\n // only when `mina-signer` is installed) and explicit minaChannel config.\n //\n // openMinaChannel now performs a REAL on-chain channel open\n // (initialize + optional deposit) on the deployed zkApp so the\n // connector's getChannelState reports `opened` and the claim verifies +\n // stores (parity with Solana). Full on-chain Mina SETTLE remains gated by\n // the connector-side settlement-executor (same blocker as Solana).\n if (this.config.minaChannel && this.minaPrivateKey) {\n initialization.onChainChannelClient.setMinaConfig({\n graphqlUrl: this.config.minaChannel.graphqlUrl,\n zkAppAddress: this.config.minaChannel.zkAppAddress,\n privateKey: this.minaPrivateKey,\n ...(this.config.minaChannel.challengeDuration !== undefined\n ? { challengeDuration: this.config.minaChannel.challengeDuration }\n : {}),\n ...(this.config.minaChannel.tokenId !== undefined\n ? { tokenId: this.config.minaChannel.tokenId }\n : {}),\n ...(this.config.minaChannel.deposit !== undefined\n ? { deposit: this.config.minaChannel.deposit }\n : {}),\n ...(this.config.minaChannel.networkId !== undefined\n ? { networkId: this.config.minaChannel.networkId }\n : {}),\n });\n }\n }\n\n // Store state\n this.state = {\n bootstrapService,\n discoveryTracker,\n runtimeClient,\n peersDiscovered: bootstrapResults.length,\n btpClient: btpClient ?? undefined,\n ...(stopManagedProxy ? { stopManagedProxy } : {}),\n };\n\n return {\n peersDiscovered: bootstrapResults.length,\n mode: 'http',\n };\n } catch (error) {\n throw new ToonClientError(\n 'Failed to start client',\n 'INITIALIZATION_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Publishes a Nostr event to the relay via ILP payment.\n *\n * The event must already be finalized (signed with id, pubkey, sig).\n *\n * @param event - Signed Nostr event to publish\n * @param options - Optional options including destination and signed balance proof claim\n * @returns Result with success status and event ID\n * @throws {ToonClientError} If client is not started\n * @throws {ToonClientError} If event publishing fails\n */\n async publishEvent(\n event: NostrEvent,\n options?: {\n destination?: string;\n claim?: SignedBalanceProof;\n ilpAmount?: bigint;\n }\n ): Promise<PublishEventResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n try {\n // Encode event to TOON format\n const toonData = this.config.toonEncoder(event);\n\n // Calculate payment amount: basePricePerByte * encoded size.\n // Callers may override via options.ilpAmount (e.g. 0n for free relays).\n const basePricePerByte = 10n;\n const amount =\n options?.ilpAmount !== undefined\n ? String(options.ilpAmount)\n : String(BigInt(toonData.length) * basePricePerByte);\n\n // 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 // Build the envelope with the chain-appropriate signer so a Solana/Mina\n // balance proof is not mis-wrapped as an EVM claim (see F06 root cause).\n claimMessage = this.buildClaimMessageForProof(options.claim);\n } else if (this.channelManager) {\n // NEW PATH: Auto-open channel + auto-sign claim (lazy channels)\n const peerId = this.resolvePeerId(destination);\n const negotiation = this.peerNegotiations.get(peerId);\n if (!negotiation) {\n throw new ToonClientError(\n `No negotiation metadata for peer \"${peerId}\" — was bootstrap completed?`,\n 'PEER_NOT_NEGOTIATED'\n );\n }\n const channelId = await this.channelManager.ensureChannel(\n peerId,\n negotiation\n );\n const proof = await this.channelManager.signBalanceProof(\n channelId,\n BigInt(amount)\n );\n const signer = this.channelManager.getSignerForChannel(channelId);\n claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());\n } else {\n throw new ToonClientError(\n 'No claim provided and no channel manager configured',\n 'MISSING_CLAIM'\n );\n }\n\n const response = await 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 * Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached\n * balance-proof claim. This is a lower-level surface than `publishEvent`:\n * it forwards the raw `IlpSendResult` so the sender (`streamSwap()`) can\n * decode FULFILL metadata itself.\n *\n * Claim resolution mirrors `publishEvent`:\n * (a) explicit `params.claim` -> use it,\n * (b) `channelManager` present -> auto-open + auto-sign for the peer\n * matching `destination`,\n * (c) neither -> throw MISSING_CLAIM.\n *\n * @throws {ToonClientError} INVALID_STATE / NO_BTP_CLIENT / MISSING_CLAIM\n */\n async sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n timeout?: number;\n claim?: SignedBalanceProof;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n if (!this.state.btpClient) {\n throw new ToonClientError(\n 'BTP client required for sending swap packets. Configure btpUrl.',\n 'NO_BTP_CLIENT'\n );\n }\n\n const claimMessage = await this.resolveClaimForDestination(\n params.destination,\n params.amount,\n params.claim\n );\n\n return this.state.btpClient.sendIlpPacketWithClaim(\n {\n destination: params.destination,\n amount: String(params.amount),\n data: toBase64(params.toonData),\n timeout: params.timeout ?? 30000,\n },\n claimMessage as unknown as Record<string, unknown>\n );\n }\n\n /**\n * Build a BTP claim message from a pre-signed balance proof using the\n * CHAIN-APPROPRIATE signer.\n *\n * The explicit-claim path (caller signs the balance proof, then passes\n * `{ claim }`) must wrap the proof with the signer matching the channel's\n * chain. Hardcoding `EvmSigner.buildClaimMessage` here produced an EVM\n * `BTPClaimMessage` for a Solana/Mina balance proof — no `blockchain`\n * discriminator and the base58 channel account placed in the EVM\n * `channelId` field — which the connector's inbound validator classifies\n * as EVM and rejects with F06 (`Invalid channelId format`).\n *\n * When the proof's `channelId` is tracked we use\n * `getSignerForChannel(channelId).buildClaimMessage`, which emits the\n * correct per-chain envelope (e.g. `blockchain:'solana'` + base58\n * `channelAccount`). When it is not tracked we fall back to the EVM signer\n * to preserve prior behavior for lightweight/EVM-only callers.\n *\n * EVM output is byte-identical to the previous hardcoded path (the EVM\n * adapter in `getSignerForChannel` delegates to the same\n * `EvmSigner.buildClaimMessage`).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- claim message is opaque forwarded type\n private buildClaimMessageForProof(claim: SignedBalanceProof): any {\n if (this.channelManager?.isTracking(claim.channelId)) {\n const signer = this.channelManager.getSignerForChannel(claim.channelId);\n return signer.buildClaimMessage(claim, this.getPublicKey());\n }\n return EvmSigner.buildClaimMessage(claim, this.getPublicKey());\n }\n\n /**\n * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.\n * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution\n * to call this helper. Kept duplicated for now to minimize regression risk.\n */\n private async resolveClaimForDestination(\n destination: string,\n amount: bigint,\n explicitClaim?: SignedBalanceProof\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- claim message is opaque forwarded type\n ): Promise<any> {\n if (explicitClaim) {\n return this.buildClaimMessageForProof(explicitClaim);\n }\n if (this.channelManager) {\n const peerId = this.resolvePeerId(destination);\n const negotiation = this.peerNegotiations.get(peerId);\n if (!negotiation) {\n throw new ToonClientError(\n `No negotiation metadata for peer \"${peerId}\" — was bootstrap completed?`,\n 'PEER_NOT_NEGOTIATED'\n );\n }\n const channelId = await this.channelManager.ensureChannel(\n peerId,\n negotiation\n );\n const proof = await this.channelManager.signBalanceProof(\n channelId,\n amount\n );\n const signer = this.channelManager.getSignerForChannel(channelId);\n return signer.buildClaimMessage(proof, this.getPublicKey());\n }\n throw new ToonClientError(\n 'No claim provided and no channel manager configured',\n 'MISSING_CLAIM'\n );\n }\n\n /**\n * Signs a balance proof for the given channel with the specified amount.\n * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.\n *\n * @param channelId - Payment channel identifier\n * @param amount - Additional amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws {ToonClientError} If no EVM signer configured or channel not tracked\n */\n async signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof> {\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No EVM signer configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n return this.channelManager.signBalanceProof(channelId, amount);\n }\n\n /**\n * Eagerly open (or return existing) payment channel for the given destination.\n *\n * Channels are normally opened lazily on the first `publishEvent()` /\n * `sendSwapPacket()` call. This method exposes the lazy-open path so\n * callers (and E2E tests) that need a tracked `channelId` BEFORE publishing\n * can force the open. Idempotent — returns the existing channel ID for the\n * peer if one is already open.\n *\n * @param destination - Optional ILP destination address. Defaults to\n * `config.destinationAddress`.\n * @returns The channel ID of the (now) open channel.\n * @throws {ToonClientError} If client not started, no channel manager\n * configured, or peer negotiation metadata missing.\n */\n async openChannel(destination?: string): Promise<string> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n if (!this.channelManager) {\n throw new ToonClientError(\n 'No channel manager configured. Provide evmPrivateKey in config.',\n 'NO_EVM_SIGNER'\n );\n }\n const dest = destination ?? this.config.destinationAddress;\n if (!dest) {\n throw new ToonClientError(\n 'No destination provided and no default destinationAddress configured.',\n 'NO_DESTINATION'\n );\n }\n const peerId = this.resolvePeerId(dest);\n const negotiation = this.peerNegotiations.get(peerId);\n if (!negotiation) {\n throw new ToonClientError(\n `No negotiation metadata for peer \"${peerId}\" — was bootstrap completed?`,\n 'PEER_NOT_NEGOTIATED'\n );\n }\n return this.channelManager.ensureChannel(peerId, negotiation);\n }\n\n /**\n * Gets list of tracked payment channel IDs.\n */\n getTrackedChannels(): string[] {\n return this.channelManager?.getTrackedChannels() ?? [];\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getChannelNonce(channelId: string): number {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getNonce(channelId);\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getChannelCumulativeAmount(channelId: string): bigint {\n if (!this.channelManager) throw new Error('ChannelManager not initialized');\n return this.channelManager.getCumulativeAmount(channelId);\n }\n\n /**\n * Resolves an ILP destination address to a peer ID.\n * Convention: destination \"g.toon.peer1\" → peerId \"peer1\" (last segment).\n * Falls back to first known peer if no match.\n */\n private resolvePeerId(destination: string): string {\n // Check if destination matches a known peer's ILP address pattern\n const segments = destination.split('.');\n const lastSegment = segments[segments.length - 1] ?? '';\n\n // Direct match against peerNegotiations keys\n if (lastSegment && this.peerNegotiations.has(lastSegment)) {\n return lastSegment;\n }\n\n // Try \"nostr-\" prefixed peer IDs (convention: nostr-{pubkey_prefix})\n for (const peerId of this.peerNegotiations.keys()) {\n if (\n destination.endsWith(`.${peerId}`) ||\n destination.endsWith(`.${peerId.replace('nostr-', '')}`)\n ) {\n return peerId;\n }\n }\n\n // Fallback: return first peer\n const firstPeerResult = this.peerNegotiations.keys().next();\n if (!firstPeerResult.done && firstPeerResult.value)\n return firstPeerResult.value;\n\n throw new ToonClientError(\n `Cannot resolve peer for destination: ${destination}`,\n 'PEER_NOT_FOUND'\n );\n }\n\n /**\n * Extracts chain context (chainId + tokenNetworkAddress) from a chain key like 'evm:base:421614'.\n */\n private getChainContext(\n negotiatedChain?: string\n ):\n | { chainId: number; tokenNetworkAddress: string; tokenAddress?: string }\n | undefined {\n if (!negotiatedChain) return undefined;\n const parts = negotiatedChain.split(':');\n const chainIdPart = parts.length >= 3 ? parts[2] : undefined;\n const numericChainId =\n chainIdPart !== undefined ? parseInt(chainIdPart, 10) : NaN;\n if (isNaN(numericChainId)) return undefined;\n const tokenNetworkAddress = this.config.tokenNetworks?.[negotiatedChain];\n if (!tokenNetworkAddress) return undefined;\n const tokenAddress = this.config.preferredTokens?.[negotiatedChain];\n return { chainId: numericChainId, tokenNetworkAddress, tokenAddress };\n }\n\n /**\n * Gets the default chain context from the first supported chain in config.\n */\n private getDefaultChainContext():\n | { chainId: number; tokenNetworkAddress: string; tokenAddress?: string }\n | undefined {\n const chains = this.config.supportedChains;\n if (!chains?.length) return undefined;\n return this.getChainContext(chains[0]);\n }\n\n /**\n * Sends an ILP payment, optionally with a balance proof claim via BTP.\n *\n * @param params - Payment parameters\n * @returns ILP send result\n * @throws {ToonClientError} If client is not started\n */\n async sendPayment(params: {\n destination: string;\n amount: string;\n data?: string;\n claim?: SignedBalanceProof;\n }): Promise<IlpSendResult> {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n const ilpParams = {\n destination: params.destination,\n amount: params.amount,\n data: params.data ?? '',\n };\n\n // Require claim + BTP — plain sendIlpPacket is only valid for\n // node-to-node forwarding (town.ts), not client-to-node.\n if (!params.claim) {\n throw new ToonClientError(\n 'Signed balance proof required. Call signBalanceProof() first.',\n 'MISSING_CLAIM'\n );\n }\n 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 = this.buildClaimMessageForProof(params.claim);\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 const stopManagedProxy = this.state.stopManagedProxy;\n try {\n // Disconnect BTP client if connected\n if (this.state.btpClient) {\n await this.state.btpClient.disconnect();\n }\n\n // Tear down a managed `anon` proxy this client auto-started (.anyone host\n // with no explicit proxy). Best-effort — a proxy stop failure must not\n // mask a clean disconnect. No-op when the SDK did not launch a proxy.\n if (stopManagedProxy) {\n await stopManagedProxy();\n }\n\n // Clear state\n this.state = null;\n } catch (error) {\n throw new ToonClientError(\n 'Failed to stop client',\n 'STOP_ERROR',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Returns true if the client is currently started.\n */\n isStarted(): boolean {\n return this.state !== null;\n }\n\n /**\n * Gets the number of peers discovered during bootstrap.\n *\n * @returns Number of peers discovered\n * @throws {ToonClientError} If client is not started\n */\n getPeersCount(): number {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.peersDiscovered;\n }\n\n /**\n * Gets the list of peers discovered by the relay monitor.\n *\n * @returns Array of discovered peer objects\n * @throws {ToonClientError} If client is not started\n */\n getDiscoveredPeers() {\n if (!this.state) {\n throw new ToonClientError(\n 'Client not started. Call start() first.',\n 'INVALID_STATE'\n );\n }\n\n return this.state.discoveryTracker.getDiscoveredPeers();\n }\n}\n","import { generateSecretKey } from 'nostr-tools/pure';\nimport {\n resolveClientNetwork,\n type NetworkFamilyStatus,\n} from '@toon-protocol/core';\nimport { ValidationError } from './errors.js';\nimport {\n validateMnemonic,\n deriveNostrKeyFromMnemonic,\n} from './keys/KeyDerivation.js';\nimport type { ToonClientConfig, ClientTransportConfig } from './types.js';\n\n/**\n * Settlement info produced by buildSettlementInfo().\n * Extends the core SettlementConfig shape with ilpAddress for client use.\n */\nexport interface ClientSettlementInfo {\n ilpAddress?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Applies named-network preset defaults to a client config.\n *\n * When `config.network` is set and != `'custom'`, the settlement-related\n * fields are defaulted from the shared core presets (`resolveClientNetwork`):\n * RPC/GraphQL URLs, supported chain identifiers, preferred tokens, EVM\n * TokenNetwork addresses, and the Solana/Mina channel params. Any explicit\n * per-chain field on `config` OVERRIDES the preset (explicit always wins).\n *\n * `'custom'` and the unset case both pass `config` through untouched, keeping\n * the fully-manual path and full backward compatibility.\n *\n * @returns A shallow copy with preset defaults merged in (never mutates input).\n */\nexport function applyNetworkPresets(\n config: ToonClientConfig\n): ToonClientConfig {\n const { network } = config;\n if (!network || network === 'custom') return config;\n\n const presets = resolveClientNetwork(network);\n\n // Merge a preset record under an explicit one (explicit keys win).\n const mergeRecord = (\n explicit: Record<string, string> | undefined,\n preset: Record<string, string>\n ): Record<string, string> => ({ ...preset, ...explicit });\n\n // supportedChains: union (preset first), preserving any explicit extras.\n const supportedChains = config.supportedChains\n ? Array.from(\n new Set([...presets.supportedChains, ...config.supportedChains])\n )\n : presets.supportedChains;\n\n return {\n ...config,\n supportedChains,\n chainRpcUrls: mergeRecord(config.chainRpcUrls, presets.chainRpcUrls),\n preferredTokens: mergeRecord(\n config.preferredTokens,\n presets.preferredTokens\n ),\n tokenNetworks: mergeRecord(config.tokenNetworks, presets.tokenNetworks),\n // settlementAddresses are identity-derived (per-client), so they have no\n // preset; pass any explicit value through unchanged.\n ...(config.settlementAddresses && {\n settlementAddresses: config.settlementAddresses,\n }),\n // Channel params: preset fills the deployed programId/zkApp + URLs unless\n // the caller supplied their own (explicit object wins wholesale).\n ...(presets.solanaChannel && {\n solanaChannel: config.solanaChannel ?? presets.solanaChannel,\n }),\n ...(presets.minaChannel && {\n minaChannel: config.minaChannel ?? presets.minaChannel,\n }),\n };\n}\n\n/**\n * Returns per-chain settlement readiness for the configured `network` tier,\n * mirroring the townhouse node's status. Returns `undefined` when `network` is\n * unset or `'custom'` (no preset tier to report on).\n */\nexport function getNetworkStatus(\n config: ToonClientConfig\n): NetworkFamilyStatus | undefined {\n const { network } = config;\n if (!network || network === 'custom') return undefined;\n return resolveClientNetwork(network).status;\n}\n\n/**\n * 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 // Validate mnemonic when provided. The mnemonic derives the Nostr/EVM +\n // Solana/Mina identity, so it cannot coexist with an explicit secretKey\n // (that would split the Nostr identity from the Solana/Mina identity). An\n // explicit evmPrivateKey override IS allowed (documented hardware-wallet case).\n if (config.mnemonic !== undefined) {\n if (config.secretKey !== undefined) {\n throw new ValidationError(\n 'Provide either `mnemonic` or `secretKey`, not both — the mnemonic ' +\n 'derives the Nostr key, so a separate secretKey would yield an ' +\n 'inconsistent cross-chain identity. (An `evmPrivateKey` override is allowed.)'\n );\n }\n if (\n typeof config.mnemonic !== 'string' ||\n !validateMnemonic(config.mnemonic)\n ) {\n throw new ValidationError('mnemonic must be a valid BIP-39 phrase');\n }\n }\n\n // Validate mnemonicAccountIndex when provided (must be a non-negative\n // integer within the BIP-32 non-hardened range, matching the SDK guard).\n if (config.mnemonicAccountIndex !== undefined) {\n const idx = config.mnemonicAccountIndex;\n if (!Number.isInteger(idx) || idx < 0 || idx > 0x7fffffff) {\n throw new ValidationError(\n 'mnemonicAccountIndex must be a non-negative integer (0 to 2147483647)'\n );\n }\n }\n\n if (!config.ilpInfo?.ilpAddress) {\n throw new ValidationError('ilpInfo.ilpAddress is required');\n }\n\n if (!config.toonEncoder || typeof config.toonEncoder !== 'function') {\n throw new ValidationError('toonEncoder function is required');\n }\n\n if (!config.toonDecoder || typeof config.toonDecoder !== 'function') {\n throw new ValidationError('toonDecoder function is required');\n }\n\n // Validate evmPrivateKey format when provided\n if (config.evmPrivateKey !== undefined) {\n if (config.evmPrivateKey instanceof Uint8Array) {\n if (config.evmPrivateKey.length !== 32) {\n throw new ValidationError('evmPrivateKey must be 32 bytes');\n }\n } else if (typeof config.evmPrivateKey === 'string') {\n const hex = config.evmPrivateKey.startsWith('0x')\n ? config.evmPrivateKey.slice(2)\n : config.evmPrivateKey;\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new ValidationError('evmPrivateKey must be a 32-byte hex string');\n }\n } else {\n throw new ValidationError(\n 'evmPrivateKey must be a hex string or Uint8Array'\n );\n }\n }\n\n // Validate btpUrl when provided\n if (config.btpUrl !== undefined) {\n try {\n const url = new URL(config.btpUrl);\n if (!url.protocol.startsWith('ws')) {\n throw new Error('Must be WS or WSS');\n }\n } catch (error) {\n throw new ValidationError(\n `Invalid btpUrl: must be a valid WebSocket URL (e.g., \"ws://localhost:3000\"). ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // Validate transport config\n if (config.transport) {\n if (config.transport.type === 'socks5') {\n if (!config.transport.socksProxy?.startsWith('socks5h://')) {\n throw new ValidationError(\n 'transport.socksProxy must use socks5h:// scheme to prevent DNS leaks. ' +\n 'The \"h\" suffix ensures .anyone hostnames are resolved by the proxy, not locally.'\n );\n }\n } else if (config.transport.type === 'gateway') {\n if (!config.transport.gatewayUrl) {\n throw new ValidationError(\n 'transport.gatewayUrl is required for gateway transport'\n );\n }\n } else if (config.transport.type !== 'direct') {\n throw new ValidationError(\n `Unknown transport type: \"${(config.transport as { type: string }).type}\"`\n );\n }\n }\n\n // Validate chainRpcUrls keys match supportedChains when both present\n if (config.chainRpcUrls && config.supportedChains) {\n for (const chain of Object.keys(config.chainRpcUrls)) {\n if (!config.supportedChains.includes(chain)) {\n throw new ValidationError(\n `chainRpcUrls key \"${chain}\" is not in supportedChains`\n );\n }\n }\n }\n}\n\n/**\n * The resolved config type after defaults are applied.\n * secretKey is guaranteed to be present (auto-generated if omitted).\n */\nexport type ResolvedConfig = Required<\n Omit<\n ToonClientConfig,\n | 'connector'\n | 'mnemonic'\n | 'mnemonicAccountIndex'\n | 'evmPrivateKey'\n | 'network'\n | 'supportedChains'\n | 'settlementAddresses'\n | 'preferredTokens'\n | 'tokenNetworks'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'btpPeerId'\n | 'chainRpcUrls'\n | 'initialDeposit'\n | 'settlementTimeout'\n | 'solanaChannel'\n | 'minaChannel'\n | 'channelStorePath'\n | 'knownPeers'\n | 'destinationAddress'\n | 'transport'\n | 'managedAnonProxy'\n | 'managedAnonSocksPort'\n >\n> & {\n connector?: unknown;\n /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */\n evmPrivateKey: string | Uint8Array;\n /**\n * BIP-39 phrase retained so `ToonClient.start()` can derive the Solana/Mina\n * keys asynchronously and register the corresponding signers. The Nostr/EVM\n * keys are already resolved synchronously into `secretKey`/`evmPrivateKey`.\n */\n mnemonic?: string;\n /**\n * BIP-44 account index for mnemonic-based derivation (defaults to 0).\n * Retained so `ToonClient.start()` derives the Solana/Mina signers at the\n * same index as the synchronously-resolved Nostr/EVM keys.\n */\n mnemonicAccountIndex?: number;\n /** Transport privacy config (optional — defaults to direct). */\n transport?: ClientTransportConfig;\n /** Named network tier, retained for `getNetworkStatus()`. */\n network?: ToonClientConfig['network'];\n /** Self-managed `anon` SOCKS5h proxy opt-out (default auto). */\n managedAnonProxy?: boolean;\n /** Loopback SOCKS port for the managed `anon` daemon (default 9050). */\n managedAnonSocksPort?: number;\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 solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n channelStorePath?: string;\n knownPeers?: {\n pubkey: string;\n relayUrl: string;\n btpEndpoint?: string;\n }[];\n destinationAddress: string;\n};\n\n/**\n * Applies default values to optional configuration fields.\n * Auto-generates a Nostr keypair when secretKey is omitted.\n * Derives btpUrl from connectorUrl when not provided.\n */\nexport function applyDefaults(rawConfig: ToonClientConfig): ResolvedConfig {\n // Fill settlement-related defaults from the named-network presets first\n // (explicit per-chain fields always win). No-op for unset/`custom`.\n const config = applyNetworkPresets(rawConfig);\n\n // Resolve the Nostr secret key. Precedence:\n // 1. explicit secretKey\n // 2. derived from mnemonic (Nostr/EVM only — Solana/Mina are derived\n // asynchronously in start(), which is why the mnemonic is retained)\n // 3. auto-generated ephemeral key\n const secretKey =\n config.secretKey ??\n (config.mnemonic\n ? deriveNostrKeyFromMnemonic(\n config.mnemonic,\n config.mnemonicAccountIndex ?? 0\n ).secretKey\n : generateSecretKey());\n\n // Derive 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 rawConfig: ToonClientConfig\n): ClientSettlementInfo | undefined {\n // Resolve named-network preset defaults so a `network`-only config still\n // produces settlement info (explicit fields win; no-op for unset/`custom`).\n const config = applyNetworkPresets(rawConfig);\n\n if (\n !config.supportedChains?.length &&\n !config.settlementAddresses &&\n !config.preferredTokens &&\n !config.tokenNetworks\n ) {\n return undefined;\n }\n\n return {\n ilpAddress: config.ilpInfo?.ilpAddress,\n supportedChains: config.supportedChains,\n settlementAddresses: config.settlementAddresses,\n preferredTokens: config.preferredTokens,\n tokenNetworks: config.tokenNetworks,\n };\n}\n","/**\n * Base error class for all TOON client errors.\n */\nexport class ToonClientError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n cause?: Error\n ) {\n super(message, { cause });\n this.name = 'ToonClientError';\n }\n}\n\n/**\n * Network error for connection failures (ECONNREFUSED, ETIMEDOUT).\n * These errors trigger retry logic with exponential backoff.\n */\nexport class NetworkError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Connector error for 5xx server errors.\n * These errors indicate the connector is unavailable or malfunctioning.\n */\nexport class ConnectorError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONNECTOR_ERROR', cause);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Validation error for invalid input parameters.\n * These errors are thrown before making any HTTP requests.\n */\nexport class ValidationError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'VALIDATION_ERROR', cause);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Unauthorized error for 401 responses from connector admin API.\n * Indicates missing or invalid authentication credentials.\n */\nexport class UnauthorizedError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'UNAUTHORIZED', cause);\n this.name = 'UnauthorizedError';\n }\n}\n\n/**\n * Peer not found error for 404 responses when removing a peer.\n * Indicates the specified peer ID does not exist in the connector.\n */\nexport class PeerNotFoundError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_NOT_FOUND', cause);\n this.name = 'PeerNotFoundError';\n }\n}\n\n/**\n * Peer already exists error for 409 responses when adding a peer.\n * Indicates a peer with the same ID already exists in the connector.\n */\nexport class PeerAlreadyExistsError extends ToonClientError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_ALREADY_EXISTS', cause);\n this.name = 'PeerAlreadyExistsError';\n }\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { toHex } from 'viem';\nimport {\n generateMnemonic as _genMnemonic,\n validateMnemonic as _validateMnemonic,\n mnemonicToSeedSync,\n} from '@scure/bip39';\nimport { wordlist as english } from '@scure/bip39/wordlists/english';\nimport { HDKey } from '@scure/bip32';\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport type { ToonIdentity } from './types.js';\n\n/**\n * Generate a new 12-word BIP-39 mnemonic.\n */\nexport function generateMnemonic(): string {\n return _genMnemonic(english, 128);\n}\n\n/**\n * Validate a BIP-39 mnemonic phrase.\n */\nexport function validateMnemonic(mnemonic: string): boolean {\n return _validateMnemonic(mnemonic, english);\n}\n\n/**\n * Maximum valid BIP-32 non-hardened child index (2^31 - 1).\n * Values at or above 2^31 are reserved for hardened derivation.\n */\nconst MAX_BIP32_INDEX = 0x7fffffff;\n\n/**\n * Validate a BIP-44 account index. Mirrors the SDK's `fromMnemonic` guard so\n * the client and SDK reject the same out-of-range indices.\n */\nfunction assertValidAccountIndex(accountIndex: number): void {\n if (\n !Number.isInteger(accountIndex) ||\n accountIndex < 0 ||\n accountIndex > MAX_BIP32_INDEX\n ) {\n throw new Error(\n `Invalid accountIndex: expected a non-negative integer (0 to ${MAX_BIP32_INDEX}), got ${String(accountIndex)}`\n );\n }\n}\n\n/**\n * Derive the Nostr secp256k1 key from mnemonic using NIP-06 path:\n * m/44'/1237'/0'/0/{accountIndex}. At accountIndex 0 this is the canonical\n * m/44'/1237'/0'/0/0, matching the SDK's `fromMnemonic`/`fromMnemonicFull`.\n */\nfunction deriveNostrKey(\n seed: Uint8Array,\n accountIndex = 0\n): {\n secretKey: Uint8Array;\n pubkey: string;\n} {\n const master = HDKey.fromMasterSeed(seed);\n const child = master.derive(`m/44'/1237'/0'/0/${accountIndex}`);\n if (!child.privateKey) {\n throw new Error('Failed to derive Nostr private key from seed');\n }\n const secretKey = new Uint8Array(child.privateKey);\n const pubkey = getPublicKey(secretKey);\n return { secretKey, pubkey };\n}\n\n/**\n * Derive the EVM address from the same secp256k1 key (shares curve with Nostr).\n */\nfunction deriveEvmIdentity(secretKey: Uint8Array): {\n privateKey: Uint8Array;\n address: string;\n} {\n const account = privateKeyToAccount(toHex(secretKey));\n return {\n privateKey: secretKey,\n address: account.address,\n };\n}\n\n/**\n * Derive Solana Ed25519 keypair using SLIP-0010 path:\n * m/44'/501'/{accountIndex}'/0' (all hardened). At accountIndex 0 this is the\n * canonical m/44'/501'/0'/0', matching the SDK's `deriveSolanaIdentity`.\n * Dynamically imports @noble/curves for Ed25519 operations.\n */\nasync function deriveSolanaKey(\n seed: Uint8Array,\n accountIndex = 0\n): Promise<{\n secretKey: Uint8Array;\n publicKey: string;\n}> {\n // SLIP-0010 Ed25519 derivation (hardened only)\n // Uses HMAC-SHA512 with \"ed25519 seed\" as key\n const { hmac } = await import('@noble/hashes/hmac');\n const { sha512 } = await import('@noble/hashes/sha512');\n const { ed25519 } = await import('@noble/curves/ed25519.js');\n\n // SLIP-0010 master key derivation for ed25519\n const encoder = new TextEncoder();\n let I = hmac(sha512, encoder.encode('ed25519 seed'), seed);\n let key = I.slice(0, 32);\n let chainCode = I.slice(32);\n\n // Derive path: m/44'/501'/{accountIndex}'/0' (all hardened)\n const indices = [\n 0x8000002c, // 44'\n 0x800001f5, // 501'\n (0x80000000 + accountIndex) >>> 0, // {accountIndex}'\n 0x80000000, // 0'\n ];\n\n for (const index of indices) {\n const data = new Uint8Array(37);\n data[0] = 0x00;\n data.set(key, 1);\n // Write index as big-endian uint32\n data[33] = (index >>> 24) & 0xff;\n data[34] = (index >>> 16) & 0xff;\n data[35] = (index >>> 8) & 0xff;\n data[36] = index & 0xff;\n\n I = hmac(sha512, chainCode, data);\n key = I.slice(0, 32);\n chainCode = I.slice(32);\n }\n\n const publicKeyBytes: Uint8Array = ed25519.getPublicKey(key);\n\n // Solana keypair = 32-byte private key + 32-byte public key = 64 bytes\n const keypair = new Uint8Array(64);\n keypair.set(key, 0);\n keypair.set(publicKeyBytes, 32);\n\n // Base58 encode the public key\n const publicKey = toBase58(publicKeyBytes);\n\n return { secretKey: keypair, publicKey };\n}\n\n/**\n * Derive Mina Pallas key using path: m/44'/12586'/{accountIndex}'/0/0. At\n * accountIndex 0 this is the canonical m/44'/12586'/0'/0/0, matching the SDK's\n * `deriveMinaIdentity`.\n * Dynamically imports mina-signer.\n */\nasync function deriveMinaKey(\n seed: Uint8Array,\n accountIndex = 0\n): Promise<{\n privateKey: string;\n publicKey: string;\n}> {\n const master = HDKey.fromMasterSeed(seed);\n // Mina coin type = 12586 (0x312A)\n const child = master.derive(`m/44'/12586'/${accountIndex}'/0/0`);\n if (!child.privateKey) {\n throw new Error('Failed to derive Mina private key from seed');\n }\n const keyBytes = new Uint8Array(child.privateKey);\n\n // Clamp the top 2 bits so the scalar is within the Pallas base-field order\n // (matches @toon-protocol/mill's `deriveMillKeys`). Without this, the raw\n // BIP-32 child scalar can exceed the field order and mina-signer rejects it.\n keyBytes[0] = (keyBytes[0] ?? 0) & 0x3f;\n\n // mina-signer needs the Mina base58check (`EK…`) private-key format, NOT a\n // raw hex scalar (raw hex fails with \"invalid checksum\"). Convert via the\n // shared @toon-protocol/core helper before deriving the public key.\n try {\n const MinaSignerLib = await import('mina-signer');\n const Client =\n 'default' in MinaSignerLib ? MinaSignerLib.default : MinaSignerLib;\n const client = new Client({ network: 'mainnet' });\n\n const hexKey = Array.from(keyBytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n const minaPrivateKey = hexToMinaBase58PrivateKey(hexKey);\n const publicKey = client.derivePublicKey(minaPrivateKey);\n return {\n // Store the clamped big-endian hex scalar; consumers (e.g. the client's\n // MinaSigner) re-convert to base58check via hexToMinaBase58PrivateKey.\n privateKey: hexKey,\n publicKey,\n };\n } catch {\n throw new Error(\n 'mina-signer is required for Mina key derivation. Install it as an optional dependency.'\n );\n }\n}\n\n/**\n * Synchronously derive ONLY the Nostr secp256k1 key (NIP-06) from a mnemonic.\n *\n * The EVM key shares this same secp256k1 key. Solana (Ed25519) and Mina\n * (Pallas) require async dynamic imports — use {@link deriveFullIdentity} for\n * those. This sync subset exists so `ToonClient`'s synchronous constructor can\n * resolve the Nostr/EVM identity from a `mnemonic` config field without an\n * async factory; the client derives Solana/Mina lazily in `start()`.\n */\nexport function deriveNostrKeyFromMnemonic(\n mnemonic: string,\n accountIndex = 0\n): {\n secretKey: Uint8Array;\n pubkey: string;\n} {\n assertValidAccountIndex(accountIndex);\n const seed = mnemonicToSeedSync(mnemonic);\n const result = deriveNostrKey(seed, accountIndex);\n seed.fill(0); // Zero seed after derivation (F7 fix)\n return result;\n}\n\n/**\n * Derive a full multi-chain ToonIdentity from a BIP-39 mnemonic.\n *\n * All four chains vary by `accountIndex` (default 0), matching the SDK's\n * {@link https://www.npmjs.com/package/@toon-protocol/sdk `fromMnemonicFull`}\n * path-per-index scheme so a non-zero index produces the SAME addresses as\n * `fromMnemonicFull(mnemonic, { accountIndex })`. Index 0 is unchanged from the\n * historical fixed paths (back-compat).\n *\n * Chains derived:\n * - Nostr (secp256k1): m/44'/1237'/0'/0/{accountIndex}\n * - EVM (secp256k1): same key as Nostr\n * - Solana (Ed25519): m/44'/501'/{accountIndex}'/0' (SLIP-0010)\n * - Mina (Pallas): m/44'/12586'/{accountIndex}'/0/0\n *\n * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).\n * @param accountIndex - BIP-44 account index (default 0).\n */\nexport async function deriveFullIdentity(\n mnemonic: string,\n accountIndex = 0\n): Promise<ToonIdentity> {\n assertValidAccountIndex(accountIndex);\n const seed = mnemonicToSeedSync(mnemonic);\n\n const nostr = deriveNostrKey(seed, accountIndex);\n const evm = deriveEvmIdentity(nostr.secretKey);\n\n // Solana and Mina can fail if optional deps are missing — derive independently\n let solana: ToonIdentity['solana'];\n try {\n solana = await deriveSolanaKey(seed, accountIndex);\n } catch {\n solana = { secretKey: new Uint8Array(64), publicKey: '' };\n }\n\n let mina: ToonIdentity['mina'];\n try {\n mina = await deriveMinaKey(seed, accountIndex);\n } catch {\n mina = { privateKey: '', publicKey: '' };\n }\n\n // Zero seed after derivation (F7 fix)\n seed.fill(0);\n\n return { nostr, evm, solana, mina };\n}\n\n/**\n * Derive a partial identity from an nsec (Nostr-only private key).\n * Nostr + EVM share the same secp256k1 key.\n * Solana and Mina get empty placeholders (not deterministically linked).\n */\nexport function deriveFromNsec(secretKey: Uint8Array): ToonIdentity {\n // Copy input to avoid caller mutation corrupting identity keys\n const keyCopy = new Uint8Array(secretKey);\n const pubkey = getPublicKey(keyCopy);\n const evm = deriveEvmIdentity(keyCopy);\n\n return {\n nostr: { secretKey: keyCopy, pubkey },\n evm,\n solana: { secretKey: new Uint8Array(64), publicKey: '' },\n mina: { privateKey: '', publicKey: '' },\n };\n}\n\n/**\n * Generate a random identity (no mnemonic — for testing or ephemeral use).\n */\nexport function generateRandomIdentity(): ToonIdentity {\n const secretKey = generateSecretKey();\n return deriveFromNsec(secretKey);\n}\n\n// --- Utility ---\n\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\nfunction toBase58(bytes: Uint8Array): string {\n let num = BigInt(0);\n for (const b of bytes) num = num * 256n + BigInt(b);\n let result = '';\n while (num > 0n) {\n result = BASE58_ALPHABET[Number(num % 58n)] + result;\n num = num / 58n;\n }\n for (const b of bytes) {\n if (b === 0) result = '1' + result;\n else break;\n }\n return result;\n}\n","/**\n * Isomorphic binary helpers — works in both Node.js and browser\n * without requiring Buffer polyfills.\n *\n * These replace Buffer usage throughout the client package so that\n * browser consumers (e.g. ditto) don't need the `buffer` npm polyfill.\n */\n\n/** Convert a Uint8Array to a base64 string (browser + Node compatible). */\nexport function toBase64(bytes: Uint8Array): string {\n // Node.js Buffer is a Uint8Array subclass and has toString('base64')\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(bytes)) {\n return (bytes as Buffer).toString('base64');\n }\n // Browser path: use btoa with binary string\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\n/** Convert a base64 string to a Uint8Array (browser + Node compatible). */\nexport function fromBase64(base64: string): Uint8Array {\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return new Uint8Array(Buffer.from(base64, 'base64'));\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/** Convert a Uint8Array to a hex string. */\nexport function toHex(bytes: Uint8Array): string {\n let hex = '';\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert a hex string to a Uint8Array. */\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.startsWith('0x') ? hex.slice(2) : hex;\n const bytes = new Uint8Array(clean.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/** Convert a UTF-8 string to Uint8Array. */\nexport function encodeUtf8(str: string): Uint8Array {\n return new TextEncoder().encode(str);\n}\n\n/** Convert a Uint8Array to a UTF-8 string. */\nexport function decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\n/** Check if a string is valid base64. */\nexport function isBase64(str: string): boolean {\n return /^[A-Za-z0-9+/]*={0,2}$/.test(str);\n}\n","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';\nimport { resolveTransport } from '../transport/index.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 // Resolve transport (probes SOCKS5 proxy or rewrites gateway URLs; for a\n // `.anyone` btpUrl with no explicit proxy, auto-starts a managed `anon`\n // SOCKS5h daemon). Fail-closed: throws if SOCKS5 proxy is configured but\n // unreachable.\n const transport = await resolveTransport(\n config.transport,\n config.btpUrl,\n config.connectorUrl,\n {\n ...(config.managedAnonProxy !== undefined\n ? { managedAnonProxy: config.managedAnonProxy }\n : {}),\n ...(config.managedAnonSocksPort !== undefined\n ? { managedAnonSocksPort: config.managedAnonSocksPort }\n : {}),\n }\n );\n\n // Apply gateway URL rewrites if present, otherwise use original URLs\n const effectiveBtpUrl = transport.btpUrl ?? config.btpUrl;\n const effectiveConnectorUrl = transport.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 (effectiveBtpUrl) {\n btpClient = new BtpRuntimeClient({\n btpUrl: effectiveBtpUrl,\n peerId: config.btpPeerId ?? `client`,\n authToken: config.btpAuthToken ?? '',\n createWebSocket: transport.createWebSocket,\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: effectiveConnectorUrl,\n timeout: config.queryTimeout,\n maxRetries: config.maxRetries,\n retryDelay: config.retryDelay,\n httpClient: transport.httpClient,\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 // Teardown handle for a managed `anon` proxy this init STARTED (undefined\n // for explicit-proxy/direct/gateway). ToonClient.stop() invokes it.\n stopManagedProxy: transport.stopManagedProxy,\n };\n}\n","/**\n * Configuration options for retry behavior with exponential backoff.\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Initial delay in milliseconds between retries (default: 1000) */\n retryDelay: number;\n /** Use exponential backoff for delays (default: true) */\n exponentialBackoff?: boolean;\n /** Maximum delay cap in milliseconds (default: 30000) */\n maxDelay?: number;\n /** Custom predicate to determine if an error should trigger a retry */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Executes an async operation with retry logic and exponential backoff.\n *\n * @param operation - The async function to execute\n * @param options - Retry configuration options\n * @returns The result of the successful operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await withRetry(\n * async () => fetchData(),\n * {\n * maxRetries: 3,\n * retryDelay: 1000,\n * shouldRetry: (err) => err.name === 'NetworkError'\n * }\n * );\n * ```\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n options: RetryOptions\n): Promise<T> {\n const {\n maxRetries,\n retryDelay,\n exponentialBackoff = true,\n maxDelay = 30000,\n shouldRetry,\n } = options;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry this error\n if (shouldRetry && !shouldRetry(lastError)) {\n throw lastError;\n }\n\n // If this was the last attempt, throw the error\n if (attempt === maxRetries) {\n throw lastError;\n }\n\n // Calculate delay with exponential backoff\n const currentDelay = exponentialBackoff\n ? Math.min(retryDelay * Math.pow(2, attempt), maxDelay)\n : retryDelay;\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, currentDelay));\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError ?? new Error('Unknown error during retry');\n}\n","import type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { NetworkError, ConnectorError, ValidationError } from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\nimport { isBase64 } from '../utils/binary.js';\n\n/**\n * Configuration options for HttpRuntimeClient.\n */\nexport interface HttpRuntimeClientConfig {\n /** Connector runtime API base URL (e.g., 'http://localhost:8080') */\n connectorUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client implementation (for testing) */\n httpClient?: typeof fetch;\n}\n\n/**\n * HTTP client for sending ILP packets to an external connector runtime API.\n *\n * Implements the IlpClient interface for use with TOON agents\n * that need to send ILP packets without embedding a full connector.\n *\n * Features:\n * - Request validation (destination, amount, data)\n * - Retry logic with exponential backoff for transient network failures\n * - Typed error handling (NetworkError, ConnectorError, ValidationError)\n * - Connection pooling and keep-alive (via Node.js fetch)\n *\n * @example\n * ```typescript\n * const client = new HttpRuntimeClient({\n * connectorUrl: 'http://localhost:8080'\n * });\n *\n * const result = await client.sendIlpPacket({\n * destination: 'g.toon.alice',\n * amount: '1000',\n * data: 'base64EncodedToonData==',\n * });\n *\n * if (result.accepted) {\n * console.log('Payment accepted');\n * } else {\n * console.error('Payment rejected:', result.code, result.message);\n * }\n * ```\n */\nexport class HttpRuntimeClient implements IlpClient {\n private readonly connectorUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpRuntimeClientConfig) {\n // Normalize connector URL (remove trailing slash)\n this.connectorUrl = config.connectorUrl.replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Send an ILP packet to the connector runtime API.\n *\n * @param params - ILP packet parameters\n * @returns ILP packet response with acceptance status\n * @throws {ValidationError} If request parameters are invalid\n * @throws {NetworkError} If network connection fails after retries\n * @throws {ConnectorError} If connector returns 5xx server error\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n // Validate request parameters\n this.validateRequest(params);\n\n // Wrap HTTP request with retry logic\n return withRetry(async () => this.sendHttpRequest(params), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Validate ILP packet request parameters.\n *\n * @throws {ValidationError} If any parameter is invalid\n */\n private validateRequest(params: {\n destination: string;\n amount: string;\n data: string;\n }): void {\n // Validate destination: non-empty, valid ILP address format\n if (!params.destination || params.destination.trim() === '') {\n throw new ValidationError('Destination cannot be empty');\n }\n if (!params.destination.startsWith('g.')) {\n throw new ValidationError(\n `Invalid ILP address format: \"${params.destination}\" (must start with \"g.\")`\n );\n }\n\n // Validate amount: non-empty, parseable as bigint, positive\n if (!params.amount || params.amount.trim() === '') {\n throw new ValidationError('Amount cannot be empty');\n }\n try {\n const amountBigInt = BigInt(params.amount);\n if (amountBigInt <= 0n) {\n throw new ValidationError(\n `Amount must be positive: \"${params.amount}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Amount must be a valid integer: \"${params.amount}\"`,\n error instanceof Error ? error : undefined\n );\n }\n\n // Validate data: non-empty, valid Base64 encoding\n if (!params.data || params.data.trim() === '') {\n throw new ValidationError('Data cannot be empty');\n }\n try {\n if (!isBase64(params.data)) {\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`\n );\n }\n } catch (error) {\n if (error instanceof ValidationError) throw error;\n throw new ValidationError(\n `Data must be valid Base64 encoding: \"${params.data}\"`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Send HTTP POST request to connector runtime API.\n *\n * @throws {NetworkError} On connection failures (ECONNREFUSED, ETIMEDOUT)\n * @throws {ConnectorError} On 5xx server errors\n * @returns IlpSendResult with acceptance status\n */\n private async sendHttpRequest(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n const requestTimeout = params.timeout ?? this.timeout;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeout);\n\n try {\n // NOTE: Using admin endpoint /admin/ilp/send since connector doesn't have public /ilp endpoint yet\n const response = await this.httpClient(\n `${this.connectorUrl}/admin/ilp/send`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n }),\n signal: controller.signal,\n }\n );\n\n clearTimeout(timeoutId);\n\n // Handle response by status code\n if (response.ok) {\n // 200 OK: Parse response as IlpSendResult\n const result = (await response.json()) as Record<string, unknown>;\n return {\n accepted: (result['accepted'] as boolean) ?? false,\n data: result['data'] as string | undefined,\n code: result['code'] as string | undefined,\n message: result['message'] as string | undefined,\n };\n } else if (response.status >= 400 && response.status < 500) {\n // 4xx: Client error - return as failed ILP response (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n return {\n accepted: false,\n code: `HTTP_${response.status}`,\n message:\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText,\n };\n } else if (response.status >= 500 && response.status < 600) {\n // 5xx: Server error - throw ConnectorError (no retry)\n const errorBody = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n throw new ConnectorError(\n `Connector server error (${response.status}): ${\n (errorBody['message'] as string) ??\n (errorBody['error'] as string) ??\n response.statusText\n }`\n );\n }\n\n // Unexpected status code (not 2xx, 4xx, or 5xx)\n throw new ConnectorError(\n `Unexpected HTTP status: ${response.status} ${response.statusText}`\n );\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle AbortController timeout\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request timeout after ${requestTimeout}ms`,\n error\n );\n }\n\n // Handle network errors (ECONNREFUSED, ETIMEDOUT, etc.)\n if (\n error instanceof TypeError &&\n (error.message.includes('fetch failed') ||\n error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('network'))\n ) {\n throw new NetworkError(\n `Network connection failed: ${error.message}`,\n error\n );\n }\n\n // Re-throw known error types\n if (\n error instanceof NetworkError ||\n error instanceof ConnectorError ||\n error instanceof ValidationError\n ) {\n throw error;\n }\n\n // Unknown error\n throw new ConnectorError(\n `Unexpected error during HTTP request: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}\n","/* eslint-disable @typescript-eslint/no-non-null-assertion -- bounds-checked binary parsing */\n/**\n * Isomorphic BTP + ILP binary protocol.\n * Uses Uint8Array and DataView — no Buffer, no Node.js dependencies.\n *\n * BTP (Bilateral Transfer Protocol) message format:\n * 1 byte — message type\n * 4 bytes — request ID (uint32 BE)\n * variable — payload (MESSAGE data or ERROR data)\n *\n * ILP (Interledger Protocol) OER-encoded packets:\n * PREPARE (type 12), FULFILL (type 13), REJECT (type 14)\n */\n\n// ─── Text codec (isomorphic) ────────────────────────────────────────────────\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\n// ─── BTP types ──────────────────────────────────────────────────────────────\n\nexport const BTPMessageType = {\n RESPONSE: 1,\n ERROR: 2,\n MESSAGE: 6,\n} as const;\n\nexport const ILPPacketType = {\n PREPARE: 12,\n FULFILL: 13,\n REJECT: 14,\n} as const;\n\nexport interface BTPProtocolData {\n protocolName: string;\n contentType: number;\n data: Uint8Array;\n}\n\nexport interface BTPMessageData {\n protocolData: BTPProtocolData[];\n ilpPacket?: Uint8Array;\n}\n\nexport interface BTPMessage {\n type: number;\n requestId: number;\n data: BTPMessageData | BTPErrorData;\n}\n\nexport interface BTPErrorData {\n code: string;\n name: string;\n triggeredAt: string;\n data: Uint8Array;\n}\n\nexport interface ILPPreparePacket {\n type: typeof ILPPacketType.PREPARE;\n amount: bigint;\n destination: string;\n executionCondition: Uint8Array;\n expiresAt: Date;\n data: Uint8Array;\n}\n\nexport interface ILPFulfillPacket {\n type: typeof ILPPacketType.FULFILL;\n data: Uint8Array;\n}\n\nexport interface ILPRejectPacket {\n type: typeof ILPPacketType.REJECT;\n code: string;\n message: string;\n data: Uint8Array;\n}\n\nexport type ILPResponsePacket = ILPFulfillPacket | ILPRejectPacket;\n\n// ─── Byte helpers ───────────────────────────────────────────────────────────\n\nfunction concat(...arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, a) => sum + a.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const a of arrays) {\n result.set(a, offset);\n offset += a.length;\n }\n return result;\n}\n\nfunction readUint8(buf: Uint8Array, offset: number): number {\n if (offset >= buf.length) throw new Error('Buffer underflow reading uint8');\n return buf[offset]!;\n}\n\nfunction readUint16BE(buf: Uint8Array, offset: number): number {\n if (offset + 2 > buf.length)\n throw new Error('Buffer underflow reading uint16');\n return (buf[offset]! << 8) | buf[offset + 1]!;\n}\n\nfunction readUint32BE(buf: Uint8Array, offset: number): number {\n if (offset + 4 > buf.length)\n throw new Error('Buffer underflow reading uint32');\n return (\n ((buf[offset]! << 24) |\n (buf[offset + 1]! << 16) |\n (buf[offset + 2]! << 8) |\n buf[offset + 3]!) >>>\n 0\n );\n}\n\nfunction writeUint8(value: number): Uint8Array {\n return new Uint8Array([value]);\n}\n\nfunction writeUint16BE(value: number): Uint8Array {\n return new Uint8Array([(value >> 8) & 0xff, value & 0xff]);\n}\n\nfunction writeUint32BE(value: number): Uint8Array {\n return new Uint8Array([\n (value >> 24) & 0xff,\n (value >> 16) & 0xff,\n (value >> 8) & 0xff,\n value & 0xff,\n ]);\n}\n\nfunction sliceUtf8(buf: Uint8Array, offset: number, length: number): string {\n return textDecoder.decode(buf.slice(offset, offset + length));\n}\n\n// ─── OER encoding (ILP wire format) ─────────────────────────────────────────\n\nfunction encodeVarUInt(value: bigint): Uint8Array {\n if (value >= 0n && value <= 127n) {\n return new Uint8Array([Number(value)]);\n }\n const bytes: number[] = [];\n let remaining = value;\n while (remaining > 0n) {\n bytes.unshift(Number(remaining & 0xffn));\n remaining = remaining >> 8n;\n }\n return new Uint8Array([0x80 | bytes.length, ...bytes]);\n}\n\nfunction decodeVarUInt(\n buf: Uint8Array,\n offset: number\n): { value: bigint; bytesRead: number } {\n const firstByte = readUint8(buf, offset);\n if (firstByte <= 127) {\n return { value: BigInt(firstByte), bytesRead: 1 };\n }\n const length = firstByte & 0x7f;\n if (offset + 1 + length > buf.length)\n throw new Error('VarUInt buffer underflow');\n let value = 0n;\n for (let i = 0; i < length; i++) {\n value = (value << 8n) | BigInt(buf[offset + 1 + i]!);\n }\n return { value, bytesRead: 1 + length };\n}\n\nfunction encodeVarOctetString(data: Uint8Array): Uint8Array {\n return concat(encodeVarUInt(BigInt(data.length)), data);\n}\n\nfunction decodeVarOctetString(\n buf: Uint8Array,\n offset: number\n): { value: Uint8Array; bytesRead: number } {\n const { value: length, bytesRead: lenBytes } = decodeVarUInt(buf, offset);\n const dataLen = Number(length);\n const start = offset + lenBytes;\n if (start + dataLen > buf.length)\n throw new Error('VarOctetString buffer underflow');\n return {\n value: buf.slice(start, start + dataLen),\n bytesRead: lenBytes + dataLen,\n };\n}\n\nfunction encodeGeneralizedTime(date: Date): Uint8Array {\n const y = date.getUTCFullYear().toString().padStart(4, '0');\n const mo = (date.getUTCMonth() + 1).toString().padStart(2, '0');\n const d = date.getUTCDate().toString().padStart(2, '0');\n const h = date.getUTCHours().toString().padStart(2, '0');\n const mi = date.getUTCMinutes().toString().padStart(2, '0');\n const s = date.getUTCSeconds().toString().padStart(2, '0');\n const ms = date.getUTCMilliseconds().toString().padStart(3, '0');\n return textEncoder.encode(`${y}${mo}${d}${h}${mi}${s}.${ms}Z`);\n}\n\n// ─── ILP packet serialization ───────────────────────────────────────────────\n\nexport function serializeIlpPrepare(packet: ILPPreparePacket): Uint8Array {\n const condition =\n packet.executionCondition.length === 32\n ? packet.executionCondition\n : new Uint8Array(32);\n return concat(\n writeUint8(ILPPacketType.PREPARE),\n encodeVarUInt(packet.amount),\n encodeGeneralizedTime(packet.expiresAt),\n condition,\n encodeVarOctetString(textEncoder.encode(packet.destination)),\n encodeVarOctetString(packet.data)\n );\n}\n\nexport function deserializeIlpPacket(buf: Uint8Array): ILPResponsePacket {\n if (buf.length === 0) throw new Error('Empty ILP packet');\n const type = buf[0]!;\n if (type === ILPPacketType.FULFILL) return deserializeIlpFulfill(buf);\n if (type === ILPPacketType.REJECT) return deserializeIlpReject(buf);\n throw new Error(`Unknown ILP packet type: ${type}`);\n}\n\nfunction deserializeIlpFulfill(buf: Uint8Array): ILPFulfillPacket {\n let offset = 1; // skip type byte\n // Skip 32-byte fulfillment (unused in TOON)\n offset += 32;\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.FULFILL, data };\n}\n\nfunction deserializeIlpReject(buf: Uint8Array): ILPRejectPacket {\n let offset = 1; // skip type byte\n // 3-byte error code\n const code = sliceUtf8(buf, offset, 3);\n offset += 3;\n // triggeredBy (skip)\n const { bytesRead: tbBytes } = decodeVarOctetString(buf, offset);\n offset += tbBytes;\n // message\n const { value: msgBuf, bytesRead: msgBytes } = decodeVarOctetString(\n buf,\n offset\n );\n offset += msgBytes;\n const message = textDecoder.decode(msgBuf);\n // data\n const { value: data } = decodeVarOctetString(buf, offset);\n return { type: ILPPacketType.REJECT, code, message, data };\n}\n\n// ─── BTP message serialization ──────────────────────────────────────────────\n\nexport function serializeBtpMessage(message: BTPMessage): Uint8Array {\n const parts: Uint8Array[] = [\n writeUint8(message.type),\n writeUint32BE(message.requestId),\n ];\n\n const data = message.data as BTPMessageData;\n const protocolData = data.protocolData ?? [];\n\n // Protocol data count\n parts.push(writeUint8(protocolData.length));\n\n // Each protocol data entry\n for (const pd of protocolData) {\n const nameBytes = textEncoder.encode(pd.protocolName);\n parts.push(writeUint8(nameBytes.length));\n parts.push(nameBytes);\n parts.push(writeUint16BE(pd.contentType));\n parts.push(writeUint32BE(pd.data.length));\n if (pd.data.length > 0) parts.push(pd.data);\n }\n\n // ILP packet\n const ilpPacket = data.ilpPacket ?? new Uint8Array(0);\n parts.push(writeUint32BE(ilpPacket.length));\n if (ilpPacket.length > 0) parts.push(ilpPacket);\n\n return concat(...parts);\n}\n\nexport function parseBtpMessage(buf: Uint8Array): BTPMessage {\n if (buf.length < 5) throw new Error('BTP message too short');\n let offset = 0;\n\n const type = readUint8(buf, offset);\n offset += 1;\n const requestId = readUint32BE(buf, offset);\n offset += 4;\n\n if (type === BTPMessageType.ERROR) {\n // code\n const codeLen = readUint8(buf, offset);\n offset += 1;\n const code = sliceUtf8(buf, offset, codeLen);\n offset += codeLen;\n // name\n const nameLen = readUint8(buf, offset);\n offset += 1;\n const name = sliceUtf8(buf, offset, nameLen);\n offset += nameLen;\n // triggeredAt\n const taLen = readUint8(buf, offset);\n offset += 1;\n const triggeredAt = sliceUtf8(buf, offset, taLen);\n offset += taLen;\n // data\n const dataLen = readUint32BE(buf, offset);\n offset += 4;\n const data = buf.slice(offset, offset + dataLen);\n return { type, requestId, data: { code, name, triggeredAt, data } };\n }\n\n // MESSAGE or RESPONSE\n const pdCount = readUint8(buf, offset);\n offset += 1;\n const protocolData: BTPProtocolData[] = [];\n for (let i = 0; i < pdCount; i++) {\n const nameLen = readUint8(buf, offset);\n offset += 1;\n const protocolName = sliceUtf8(buf, offset, nameLen);\n offset += nameLen;\n const contentType = readUint16BE(buf, offset);\n offset += 2;\n const dataLen = readUint32BE(buf, offset);\n offset += 4;\n const data = buf.slice(offset, offset + dataLen);\n offset += dataLen;\n protocolData.push({ protocolName, contentType, data });\n }\n\n let ilpPacket: Uint8Array | undefined;\n if (offset + 4 <= buf.length) {\n const ilpLen = readUint32BE(buf, offset);\n offset += 4;\n if (ilpLen > 0 && offset + ilpLen <= buf.length) {\n ilpPacket = buf.slice(offset, offset + ilpLen);\n }\n }\n\n return { type, requestId, data: { protocolData, ilpPacket } };\n}\n","/* eslint-disable @typescript-eslint/no-non-null-assertion -- ws is guaranteed non-null when _isConnected */\n/**\n * Isomorphic BTP client — works in both browser and Node.js.\n * Uses native WebSocket (browser) or globalThis.WebSocket (Node 21+).\n * No dependency on `ws`, `events`, or `Buffer`.\n *\n * Replaces the @toon-protocol/connector BTPClient for the client SDK.\n */\n\nimport {\n BTPMessageType,\n ILPPacketType,\n serializeBtpMessage,\n serializeIlpPrepare,\n parseBtpMessage,\n deserializeIlpPacket,\n type BTPProtocolData,\n type BTPMessageData,\n type BTPErrorData,\n type ILPPreparePacket,\n type ILPResponsePacket,\n} from './protocol.js';\n\nconst textEncoder = new TextEncoder();\n\nexport interface IsomorphicBtpClientConfig {\n url: string;\n peerId: string;\n authToken: string;\n sendTimeoutMs?: number;\n authTimeoutMs?: number;\n /** Custom WebSocket constructor (e.g., for SOCKS5 proxy support via `ws` package). */\n createWebSocket?: (url: string) => WebSocket;\n}\n\ninterface PendingRequest {\n resolve: (packet: ILPResponsePacket) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\nexport class BtpConnectionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BtpConnectionError';\n }\n}\n\nexport class BtpAuthError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BtpAuthError';\n }\n}\n\n/**\n * Lightweight BTP client that speaks the BTP binary protocol over WebSocket.\n * Handles: connect → authenticate → send ILP packets → receive responses.\n */\nexport class IsomorphicBtpClient {\n private ws: WebSocket | null = null;\n private _isConnected = false;\n private requestIdCounter = 0;\n private readonly pendingRequests = new Map<number, PendingRequest>();\n private readonly config: Required<\n Omit<IsomorphicBtpClientConfig, 'createWebSocket'>\n > &\n Pick<IsomorphicBtpClientConfig, 'createWebSocket'>;\n\n constructor(config: IsomorphicBtpClientConfig) {\n this.config = {\n sendTimeoutMs: 30_000,\n authTimeoutMs: 5_000,\n ...config,\n };\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n async connect(): Promise<void> {\n if (this._isConnected) return;\n\n return new Promise<void>((resolve, reject) => {\n try {\n this.ws = this.config.createWebSocket\n ? this.config.createWebSocket(this.config.url)\n : new WebSocket(this.config.url);\n this.ws.binaryType = 'arraybuffer';\n } catch (err) {\n reject(\n new BtpConnectionError(\n `Failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`\n )\n );\n return;\n }\n\n this.ws.onopen = async () => {\n try {\n await this.authenticate();\n this._isConnected = true;\n resolve();\n } catch (err) {\n this._isConnected = false;\n this.ws?.close();\n reject(err);\n }\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n this.handleMessage(event.data);\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.ws.onerror = (event: any) => {\n const underlying = event?.error ?? event?.message;\n const detail =\n underlying instanceof Error\n ? underlying.message\n : typeof underlying === 'string'\n ? underlying\n : null;\n reject(\n new BtpConnectionError(\n detail\n ? `WebSocket connection error: ${detail}`\n : 'WebSocket connection error'\n )\n );\n };\n\n this.ws.onclose = () => {\n this._isConnected = false;\n // Reject all pending requests\n for (const [id, pending] of this.pendingRequests) {\n clearTimeout(pending.timeoutId);\n pending.reject(new BtpConnectionError('Connection closed'));\n this.pendingRequests.delete(id);\n }\n };\n });\n }\n\n async disconnect(): Promise<void> {\n this._isConnected = false;\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n for (const [id, pending] of this.pendingRequests) {\n clearTimeout(pending.timeoutId);\n pending.reject(new BtpConnectionError('Disconnected'));\n this.pendingRequests.delete(id);\n }\n }\n\n /**\n * Send an ILP PREPARE packet, optionally with protocol data (e.g. payment channel claim).\n * Returns the ILP response (FULFILL or REJECT).\n */\n async sendPacket(\n packet: ILPPreparePacket,\n protocolData?: BTPProtocolData[]\n ): Promise<ILPResponsePacket> {\n if (!this._isConnected || !this.ws) {\n throw new BtpConnectionError('Not connected');\n }\n\n const serializedIlp = serializeIlpPrepare(packet);\n const requestId = this.nextRequestId();\n\n const btpMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: protocolData ?? [],\n ilpPacket: serializedIlp,\n },\n });\n\n this.ws.send(btpMessage);\n\n // Calculate timeout from packet expiry or default\n let timeoutMs = this.config.sendTimeoutMs;\n if (packet.expiresAt) {\n const remaining = packet.expiresAt.getTime() - Date.now();\n timeoutMs = Math.max(remaining - 500, 1000);\n }\n\n return new Promise<ILPResponsePacket>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new BtpConnectionError(`Packet send timeout (${timeoutMs}ms)`));\n }, timeoutMs);\n\n this.pendingRequests.set(requestId, { resolve, reject, timeoutId });\n });\n }\n\n /**\n * Send a fire-and-forget BTP MESSAGE carrying only protocol data (no ILP\n * packet). Used for out-of-band claim notifications that the connector's\n * ClaimReceiver consumes via `handleClaimMessage` — there is no RESPONSE\n * frame, so we resolve immediately after the WebSocket buffers the bytes.\n *\n * Mirrors `sendPacket` wire-format construction but uses an empty ILP\n * payload and does not enroll a pending request.\n */\n async sendProtocolData(\n protocolName: string,\n contentType: number,\n data: Uint8Array\n ): Promise<void> {\n if (!this._isConnected || !this.ws) {\n throw new BtpConnectionError('Not connected');\n }\n\n const requestId = this.nextRequestId();\n const btpMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: [{ protocolName, contentType, data }],\n ilpPacket: new Uint8Array(0),\n },\n });\n\n this.ws.send(btpMessage);\n }\n\n // ─── Private ────────────────────────────────────────────────────────────\n\n private async authenticate(): Promise<void> {\n if (!this.ws) throw new BtpAuthError('WebSocket not connected');\n\n const authData = JSON.stringify({\n peerId: this.config.peerId,\n secret: this.config.authToken,\n });\n\n const requestId = this.nextRequestId();\n const authMessage = serializeBtpMessage({\n type: BTPMessageType.MESSAGE,\n requestId,\n data: {\n protocolData: [\n {\n protocolName: 'auth',\n contentType: 0,\n data: textEncoder.encode(authData),\n },\n ],\n ilpPacket: new Uint8Array(0),\n },\n });\n\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new BtpAuthError('Authentication timeout'));\n }, this.config.authTimeoutMs);\n\n // Temporarily intercept messages for auth response\n const originalHandler = this.ws!.onmessage;\n this.ws!.onmessage = (event: MessageEvent) => {\n try {\n const data = this.toUint8Array(event.data);\n\n // Try JSON parse first (server may respond with JSON)\n try {\n const jsonStr = new TextDecoder().decode(data);\n if (jsonStr.startsWith('{')) {\n // JSON response — not a BTP binary auth response, ignore\n }\n } catch {\n /* not JSON */\n }\n\n // Parse as BTP binary\n const message = parseBtpMessage(data);\n if (message.requestId === requestId) {\n clearTimeout(timeout);\n this.ws!.onmessage = originalHandler;\n\n if (message.type === BTPMessageType.ERROR) {\n const errData = message.data as BTPErrorData;\n reject(\n new BtpAuthError(\n `Authentication failed: ${errData.code} msg=${errData.message ?? ''} trigger=${errData.triggeredBy ?? ''}`\n )\n );\n } else if (message.type === BTPMessageType.RESPONSE) {\n resolve();\n }\n }\n } catch (err) {\n clearTimeout(timeout);\n this.ws!.onmessage = originalHandler;\n reject(\n new BtpAuthError(err instanceof Error ? err.message : String(err))\n );\n }\n };\n\n this.ws!.send(authMessage);\n });\n }\n\n private handleMessage(raw: unknown): void {\n // Try JSON first (server can send JSON FULFILL/REJECT responses)\n try {\n const data = this.toUint8Array(raw);\n const jsonStr = new TextDecoder().decode(data);\n if (jsonStr.startsWith('{')) {\n const json = JSON.parse(jsonStr) as Record<string, unknown>;\n if (json['type'] === 'FULFILL' || json['type'] === 'REJECT') {\n const first = this.pendingRequests.entries().next();\n if (!first.done) {\n const [id, pending] = first.value;\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(id);\n\n if (json['type'] === 'FULFILL') {\n const responseData = json['data']\n ? this.base64ToUint8Array(json['data'] as string)\n : new Uint8Array(0);\n pending.resolve({\n type: ILPPacketType.FULFILL,\n data: responseData,\n });\n } else {\n pending.resolve({\n type: ILPPacketType.REJECT,\n code: (json['code'] as string) || 'F00',\n message: (json['message'] as string) || 'Unknown error',\n data: json['data']\n ? this.base64ToUint8Array(json['data'] as string)\n : new Uint8Array(0),\n });\n }\n }\n return;\n }\n }\n } catch {\n /* not JSON, try BTP binary */\n }\n\n // BTP binary response\n try {\n const data = this.toUint8Array(raw);\n const message = parseBtpMessage(data);\n\n if (\n message.type === BTPMessageType.RESPONSE ||\n message.type === BTPMessageType.ERROR\n ) {\n const pending = this.pendingRequests.get(message.requestId);\n if (!pending) return;\n\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(message.requestId);\n\n if (message.type === BTPMessageType.ERROR) {\n const errData = message.data as BTPErrorData;\n pending.reject(\n new BtpConnectionError(`BTP error: ${errData.code} ${errData.name}`)\n );\n return;\n }\n\n const msgData = message.data as BTPMessageData;\n if (msgData.ilpPacket && msgData.ilpPacket.length > 0) {\n const ilpResponse = deserializeIlpPacket(msgData.ilpPacket);\n pending.resolve(ilpResponse);\n }\n }\n } catch {\n // Unparseable message — ignore\n }\n }\n\n private toUint8Array(data: unknown): Uint8Array {\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (data instanceof Uint8Array) return data;\n if (typeof data === 'string') return textEncoder.encode(data);\n throw new Error(`Unexpected WebSocket data type: ${typeof data}`);\n }\n\n private base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n }\n\n private nextRequestId(): number {\n this.requestIdCounter = (this.requestIdCounter + 1) & 0xffffffff;\n return this.requestIdCounter;\n }\n}\n","import {\n IsomorphicBtpClient,\n BtpConnectionError,\n} from '../btp/IsomorphicBtpClient.js';\nimport { ILPPacketType, type BTPProtocolData } from '../btp/protocol.js';\nimport type { IlpClient, IlpSendResult } from '@toon-protocol/core';\nimport { withRetry } from '../utils/retry.js';\nimport { toBase64, fromBase64, encodeUtf8 } from '../utils/binary.js';\n\nexport interface BtpRuntimeClientConfig {\n btpUrl: string;\n peerId: string;\n authToken: string;\n /** Max reconnection attempts on send failure (default: 3) */\n maxRetries?: number;\n /** Delay between reconnection attempts in ms (default: 1000) */\n retryDelay?: number;\n /** Custom WebSocket constructor (for SOCKS5 proxy support). */\n createWebSocket?: (url: string) => WebSocket;\n}\n\n/**\n * Returns true if the error is a connection-level error worth retrying.\n * ILP application-level rejects (F02, T01, etc.) are NOT retried.\n */\nfunction isConnectionError(error: Error): boolean {\n const msg = error.message.toLowerCase();\n return (\n msg.includes('not connected') ||\n msg.includes('connection') ||\n msg.includes('websocket') ||\n msg.includes('econnrefused') ||\n msg.includes('econnreset') ||\n msg.includes('socket hang up') ||\n msg.includes('timeout')\n );\n}\n\n/**\n * BTP transport implementing IlpClient.\n * Uses IsomorphicBtpClient (browser-native, no Node.js dependencies).\n */\nexport class BtpRuntimeClient implements IlpClient {\n private btpClient: IsomorphicBtpClient | null = null;\n private readonly config: BtpRuntimeClientConfig;\n private _isConnected = false;\n\n constructor(config: BtpRuntimeClientConfig) {\n this.config = config;\n }\n\n /**\n * Connects to the BTP peer via WebSocket.\n */\n async connect(): Promise<void> {\n this.btpClient = new IsomorphicBtpClient({\n url: this.config.btpUrl,\n peerId: this.config.peerId,\n authToken: this.config.authToken,\n createWebSocket: this.config.createWebSocket,\n });\n\n await this.btpClient.connect();\n this._isConnected = true;\n }\n\n /**\n * Attempts to reconnect by creating a fresh client and connecting.\n */\n async reconnect(): Promise<void> {\n if (this.btpClient) {\n try {\n await this.btpClient.disconnect();\n } catch {\n // Ignore disconnect errors during reconnect\n }\n this.btpClient = null;\n this._isConnected = false;\n }\n\n await this.connect();\n }\n\n /**\n * Disconnects from the BTP peer.\n */\n async disconnect(): Promise<void> {\n if (this.btpClient) {\n await this.btpClient.disconnect();\n this._isConnected = false;\n this.btpClient = null;\n }\n }\n\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Sends an ILP packet via BTP with auto-reconnect on connection errors.\n * Satisfies IlpClient interface.\n */\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketOnce(params), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.\n * Auto-reconnects on connection errors.\n */\n async sendIlpPacketWithClaim(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: Record<string, unknown>\n ): Promise<IlpSendResult> {\n return withRetry(() => this._sendIlpPacketWithClaimOnce(params, claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n /**\n * Send a standalone `payment-channel-claim` BTP MESSAGE (no ILP packet\n * attached). The connector's ClaimReceiver consumes this fire-and-forget\n * to register cumulative claim state independently of the per-packet\n * forwarding path. Auto-reconnects on connection errors.\n */\n async sendClaimMessage(claim: Record<string, unknown>): Promise<void> {\n return withRetry(() => this._sendClaimMessageOnce(claim), {\n maxRetries: this.config.maxRetries ?? 3,\n retryDelay: this.config.retryDelay ?? 1000,\n shouldRetry: (error) => {\n if (!isConnectionError(error)) return false;\n this._isConnected = false;\n return true;\n },\n });\n }\n\n private async _sendClaimMessageOnce(\n claim: Record<string, unknown>\n ): Promise<void> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new BtpConnectionError('BTP client not connected');\n }\n\n await this.btpClient.sendProtocolData(\n 'payment-channel-claim',\n 1,\n encodeUtf8(JSON.stringify(claim))\n );\n }\n\n /**\n * Single-attempt ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketOnce(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed by reconnect() above\n const response = await this.btpClient!.sendPacket({\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n });\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n // Reject\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n /**\n * Single-attempt claim + ILP packet send. Reconnects if not connected.\n */\n private async _sendIlpPacketWithClaimOnce(\n params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n },\n claim: Record<string, unknown>\n ): Promise<IlpSendResult> {\n if (!this._isConnected) {\n await this.reconnect();\n }\n\n if (!this.btpClient) {\n throw new BtpConnectionError('BTP client not connected');\n }\n\n const protocolData: BTPProtocolData[] = [\n {\n protocolName: 'payment-channel-claim',\n contentType: 1,\n data: encodeUtf8(JSON.stringify(claim)),\n },\n ];\n\n const response = await this.btpClient.sendPacket(\n {\n type: 12 as const,\n amount: BigInt(params.amount),\n destination: params.destination,\n executionCondition: new Uint8Array(32),\n expiresAt: new Date(Date.now() + (params.timeout ?? 30000)),\n data: fromBase64(params.data),\n },\n protocolData\n );\n\n if (response.type === ILPPacketType.FULFILL) {\n return {\n accepted: true,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n\n return {\n accepted: false,\n code: response.code,\n message: response.message,\n data: response.data.length > 0 ? toBase64(response.data) : undefined,\n };\n }\n}\n","import {\n createPublicClient,\n createWalletClient,\n http,\n maxUint256,\n decodeEventLog,\n defineChain,\n type Hex,\n type TransactionReceipt,\n} from 'viem';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '@toon-protocol/core';\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { base58Encode } from '@toon-protocol/core';\nimport type { EvmSigner } from '../signing/evm-signer.js';\nimport {\n openSolanaChannel as openSolanaChannelOnChain,\n getChannelAccountState as getSolanaChannelAccountState,\n} from './solana-payment-channel.js';\nimport { openMinaChannelOnChain } from './mina-channel-open.js';\n\n// TokenNetwork ABI — only the functions we need\nconst TOKEN_NETWORK_ABI = [\n {\n name: 'openChannel',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'participant2', type: 'address' },\n { name: 'settlementTimeout', type: 'uint256' },\n ],\n outputs: [{ type: 'bytes32' }],\n },\n {\n name: 'setTotalDeposit',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'participant', type: 'address' },\n { name: 'totalDeposit', type: 'uint256' },\n ],\n outputs: [],\n },\n {\n name: 'channels',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ type: 'bytes32' }],\n outputs: [\n { name: 'settlementTimeout', type: 'uint256' },\n { name: 'state', type: 'uint8' },\n { name: 'closedAt', type: 'uint256' },\n { name: 'openedAt', type: 'uint256' },\n { name: 'participant1', type: 'address' },\n { name: 'participant2', type: 'address' },\n ],\n },\n {\n name: 'ChannelOpened',\n type: 'event',\n inputs: [\n { name: 'channelId', type: 'bytes32', indexed: true },\n { name: 'participant1', type: 'address', indexed: true },\n { name: 'participant2', type: 'address', indexed: true },\n { name: 'settlementTimeout', type: 'uint256', indexed: false },\n ],\n },\n] as const;\n\n// ERC20 ABI — only approve and allowance\nconst ERC20_ABI = [\n {\n name: 'approve',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'spender', type: 'address' },\n { name: 'amount', type: 'uint256' },\n ],\n outputs: [{ type: 'bool' }],\n },\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' },\n ],\n outputs: [{ type: 'uint256' }],\n },\n] as const;\n\n/** Maps on-chain state uint8 to ChannelState status */\nconst STATE_MAP: Record<number, ChannelState['status']> = {\n 0: 'settled',\n 1: 'open',\n 2: 'closed',\n 3: 'settled',\n};\n\nexport interface SolanaChannelConfig {\n rpcUrl: string;\n /**\n * Ed25519 keypair material. Accepts either a 32-byte seed or a 64-byte\n * `secretKey` (seed || pubkey, as produced by `deriveFullIdentity`). The first\n * 32 bytes are the signing seed; the public key is derived from it.\n */\n keypair: Uint8Array;\n programId: string;\n /**\n * SPL token mint (base58) for PDA derivation. Optional — the per-channel\n * negotiated token (`OpenChannelParams.token`) takes precedence when present.\n */\n tokenMint?: string;\n /**\n * Challenge-period duration in seconds for `initialize_channel`. Defaults to\n * `OpenChannelParams.settlementTimeout` or 86400.\n */\n challengeDuration?: number;\n /**\n * Optional deposit amount (base units, string) + the payer's funded SPL token\n * account (ATA, base58). When omitted, the channel is opened (initialized)\n * without an on-chain deposit — the connector accepts the claim on channel\n * `opened` status + participant membership; deposit is only consumed at\n * on-chain claim/settle time.\n */\n deposit?: { amount: string; payerTokenAccount: string };\n}\n\nexport interface MinaChannelConfig {\n graphqlUrl: string;\n privateKey: string;\n zkAppAddress: string;\n /**\n * Channel settlement timeout in slots for `initializeChannel`. Defaults to\n * `OpenChannelParams.settlementTimeout` or 86400.\n */\n challengeDuration?: number;\n /**\n * Mina token id field (decimal string) for `initializeChannel`. Default '1'\n * (native MINA). The connector reads this only as on-chain channel metadata.\n */\n tokenId?: string;\n /**\n * Optional on-chain deposit (base units, string) submitted after the channel\n * is initialized. When omitted, the channel is opened (OPEN state) without a\n * deposit — the connector accepts the claim on `opened` status; deposit is\n * only consumed at on-chain settle time.\n */\n deposit?: { amount: string };\n /** Mina network id for the account/Schnorr prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n}\n\nexport interface OnChainChannelClientConfig {\n evmSigner: EvmSigner;\n chainRpcUrls: Record<string, string>;\n solanaConfig?: SolanaChannelConfig;\n minaConfig?: MinaChannelConfig;\n}\n\n/**\n * Implements ConnectorChannelClient using viem for direct on-chain\n * interaction with TokenNetwork smart contract.\n *\n * Fully non-custodial — the client deposits its own funds on-chain.\n */\nexport class OnChainChannelClient implements ConnectorChannelClient {\n private readonly evmSigner: EvmSigner;\n private readonly chainRpcUrls: Record<string, string>;\n private solanaConfig?: SolanaChannelConfig;\n private minaConfig?: MinaChannelConfig;\n private readonly channelContext = new Map<\n string,\n { chain: string; tokenNetworkAddress: string }\n >();\n\n constructor(config: OnChainChannelClientConfig) {\n this.evmSigner = config.evmSigner;\n this.chainRpcUrls = config.chainRpcUrls;\n this.solanaConfig = config.solanaConfig;\n this.minaConfig = config.minaConfig;\n }\n\n /**\n * Late-binds the Solana channel config.\n *\n * `ToonClient.start()` derives the Solana Ed25519 keypair from the client's\n * mnemonic asynchronously (after this client is constructed), so the keypair\n * is injected here rather than at construction. Same keypair as the\n * registered Solana signer — guarantees the channel-open key and the\n * claim-signing key match.\n */\n setSolanaConfig(config: SolanaChannelConfig): void {\n this.solanaConfig = config;\n }\n\n /**\n * Late-binds the Mina channel config.\n *\n * Parallel to `setSolanaConfig`: `ToonClient.start()` derives the Mina private\n * key from the client's mnemonic asynchronously (after this client is\n * constructed), so the key is injected here rather than at construction. Same\n * key as the registered Mina signer.\n */\n setMinaConfig(config: MinaChannelConfig): void {\n this.minaConfig = config;\n }\n\n /**\n * Parse chain identifier to extract chainId.\n * Format: \"evm:{network}:{chainId}\" e.g., \"evm:anvil:31337\"\n */\n private parseChainId(chain: string): number {\n const parts = chain.split(':');\n if (parts.length < 3) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainIdStr = parts[2];\n if (!chainIdStr) {\n throw new Error(\n `Invalid chain format: \"${chain}\". Expected \"evm:{network}:{chainId}\".`\n );\n }\n const chainId = parseInt(chainIdStr, 10);\n if (isNaN(chainId)) {\n throw new Error(`Invalid chainId in chain \"${chain}\".`);\n }\n return chainId;\n }\n\n /**\n * Create viem clients for a given chain.\n */\n private createClients(chain: string) {\n const rpcUrl = this.chainRpcUrls[chain];\n if (!rpcUrl) {\n throw new Error(\n `No RPC URL configured for chain \"${chain}\". Available: ${Object.keys(this.chainRpcUrls).join(', ')}`\n );\n }\n\n const chainId = this.parseChainId(chain);\n\n const viemChain = defineChain({\n id: chainId,\n name: chain,\n nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },\n rpcUrls: { default: { http: [rpcUrl] } },\n });\n\n const publicClient = createPublicClient({\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n const walletClient = createWalletClient({\n account: this.evmSigner.account,\n transport: http(rpcUrl),\n chain: viemChain,\n });\n\n return { publicClient, walletClient };\n }\n\n /**\n * Opens a new payment channel on-chain.\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n const chainPrefix = params.chain.split(':')[0];\n\n // Dispatch to chain-specific opener\n if (chainPrefix === 'solana') return this.openSolanaChannel(params);\n if (chainPrefix === 'mina') return this.openMinaChannel(params);\n\n // EVM path (default)\n return this.openEvmChannel(params);\n }\n\n /**\n * Opens a REAL on-chain Solana payment channel.\n *\n * Derives the connector-parity channel PDA\n * (`[b\"channel\", min_pubkey, max_pubkey, token_mint]`), submits the\n * `initialize_channel` instruction (+ optional `deposit`) to the deployed\n * payment-channel program, and returns the base58 PDA as the channel id. That\n * PDA is what the claim carries as `channelAccount`, and the on-chain channel\n * is what the connector's `verifySolanaClaim` reads via\n * `provider.getChannelState` before accepting the claim.\n *\n * Mirrors `openEvmChannel`'s open(+deposit) structure. Idempotent: if the\n * channel account already exists on-chain, returns its PDA without\n * re-initializing.\n */\n private async openSolanaChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n if (!this.solanaConfig) {\n throw new Error(\n 'Solana channel config not provided — cannot open Solana channel'\n );\n }\n\n const cfg = this.solanaConfig;\n // First 32 bytes are the Ed25519 signing seed (config may pass a 64-byte\n // secretKey of seed||pubkey, or a bare 32-byte seed).\n const payerSeed = cfg.keypair.slice(0, 32);\n const payerPubkey = base58Encode(\n new Uint8Array(ed25519.getPublicKey(payerSeed))\n );\n\n // PDA mint: per-channel negotiated token takes precedence over config default.\n const tokenMint = params.token ?? cfg.tokenMint;\n if (!tokenMint) {\n throw new Error(\n 'Solana channel requires a token mint (OpenChannelParams.token or solanaConfig.tokenMint)'\n );\n }\n if (!params.peerAddress) {\n throw new Error(\n 'Solana channel requires peerAddress (apex settlement pubkey, base58)'\n );\n }\n\n const challengeDuration = BigInt(\n cfg.challengeDuration ?? params.settlementTimeout ?? 86400\n );\n\n const deposit = cfg.deposit\n ? {\n amount: BigInt(cfg.deposit.amount),\n payerTokenAccount: cfg.deposit.payerTokenAccount,\n }\n : undefined;\n\n const { channelPDA } = await openSolanaChannelOnChain({\n rpcUrl: cfg.rpcUrl,\n programId: cfg.programId,\n tokenMint,\n payerSeed,\n payerPubkey,\n peerPubkey: params.peerAddress,\n challengeDuration,\n deposit,\n });\n\n // Cache context (PDA is the channel id / channelAccount).\n this.channelContext.set(channelPDA, {\n chain: params.chain,\n tokenNetworkAddress: cfg.programId,\n });\n\n return { channelId: channelPDA, status: 'opening' };\n }\n\n /**\n * Opens a REAL on-chain Mina payment channel on the deployed `PaymentChannel`\n * zkApp.\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address). This client then calls\n * `initializeChannel` on that zkApp so its on-chain `channelState` becomes\n * `OPEN` — which is what the connector's `MinaPaymentChannelSDK.getChannelState`\n * reads to return status `'opened'` (claim verification otherwise fails with\n * `mina_claim_verification_failed`). The deployed zkApp address IS the channel\n * id: `MinaClaimMessage.zkAppAddress` is both the claim's channel identifier\n * AND the channel-hash preimage the off-chain proof binds to (see\n * `mina-payment-channel.ts`), so the channel-open id and the claim's channel id\n * are guaranteed identical.\n *\n * This is the Mina analog of `openSolanaChannel` (connector#105): the client\n * opens its own per-channel on-chain state (initialize + optional deposit). The\n * heavyweight o1js + `@toon-protocol/mina-zkapp` proof work is lazily imported\n * inside `openMinaChannelOnChain` so npm consumers who never open a Mina\n * channel don't pay the o1js cost.\n *\n * Idempotent: if the on-chain channel is already `OPEN`, the opener returns\n * without re-initializing.\n *\n * NOTE: full on-chain Mina SETTLE remains gated by the connector-side\n * settlement-executor (the same blocker that stops the Solana SETTLE); reaching\n * `opened` + a stored claim is parity with Solana.\n */\n private async openMinaChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n if (!this.minaConfig) {\n throw new Error(\n 'Mina channel config not provided — cannot open Mina channel'\n );\n }\n // The deployed zkApp address IS the channel id (claim `zkAppAddress`).\n const zkAppAddress = this.minaConfig.zkAppAddress;\n if (!zkAppAddress) {\n throw new Error(\n 'Mina channel requires a deployed zkAppAddress (minaConfig.zkAppAddress)'\n );\n }\n // The apex's Mina settlement B62 (participantB) is REQUIRED so the channel is\n // opened TWO-party. The off-chain claim is signed in participant form\n // (`Poseidon([client.x, apex.x, 0])`); without participantB the on-chain\n // channel records empty/duplicate participants and the connector's\n // participant-form verification fails on settle (`Invalid balance proof\n // signature`, `participants:[\"\",\"\"]`). Mirrors the Solana peerAddress guard.\n if (!params.peerAddress) {\n throw new Error(\n 'Mina channel requires peerAddress (apex Mina settlement B62) so the ' +\n 'on-chain channel is opened two-party — the participant-form claim ' +\n 'cannot settle against a single-party channel'\n );\n }\n\n const timeout = BigInt(\n this.minaConfig.challengeDuration ?? params.settlementTimeout ?? 86400\n );\n const deposit = this.minaConfig.deposit\n ? { amount: BigInt(this.minaConfig.deposit.amount) }\n : undefined;\n\n const openResult = await openMinaChannelOnChain({\n graphqlUrl: this.minaConfig.graphqlUrl,\n zkAppAddress,\n payerPrivateKey: this.minaConfig.privateKey,\n // params.peerAddress is the apex Mina settlement B62 pubkey (participantB).\n peerPublicKey: params.peerAddress,\n timeout,\n tokenId: this.minaConfig.tokenId,\n deposit,\n networkId: this.minaConfig.networkId,\n });\n\n // The deployed zkApp address IS the channel id (claim `zkAppAddress`).\n this.channelContext.set(zkAppAddress, {\n chain: params.chain,\n tokenNetworkAddress: zkAppAddress,\n });\n\n // Surface the CURRENT on-chain depositTotal so the Mina signer can bind\n // `balanceB = depositTotal − balanceA` (connector#133). Read at open time so\n // a re-deposited channel signs against the live value, not a stale config.\n return {\n channelId: zkAppAddress,\n status: 'opening',\n depositTotal: openResult.depositTotal,\n };\n }\n\n /**\n * Opens an EVM payment channel on-chain.\n *\n * 1. Approve token spend if needed\n * 2. Call TokenNetwork.openChannel()\n * 3. Extract channelId from ChannelOpened event\n * 4. Deposit initial funds if specified\n */\n private async openEvmChannel(\n params: OpenChannelParams\n ): Promise<OpenChannelResult> {\n const {\n chain,\n tokenNetwork,\n peerAddress,\n initialDeposit,\n settlementTimeout,\n } = params;\n\n if (!tokenNetwork) {\n throw new Error(\n 'tokenNetwork address is required for on-chain channel opening'\n );\n }\n\n const { publicClient, walletClient } = this.createClients(chain);\n const tokenNetworkAddr = tokenNetwork as Hex;\n const deposit = initialDeposit ? BigInt(initialDeposit) : 0n;\n\n // If deposit > 0, ensure token approval\n if (deposit > 0n && params.token) {\n const tokenAddr = params.token as Hex;\n const myAddress = this.evmSigner.address as Hex;\n\n const currentAllowance = await publicClient.readContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'allowance',\n args: [myAddress, tokenNetworkAddr],\n });\n\n if ((currentAllowance as bigint) < deposit) {\n const approveHash = await walletClient.writeContract({\n address: tokenAddr,\n abi: ERC20_ABI,\n functionName: 'approve',\n args: [tokenNetworkAddr, maxUint256],\n });\n await publicClient.waitForTransactionReceipt({ hash: approveHash });\n }\n }\n\n // Open channel\n const timeout = BigInt(settlementTimeout ?? 86400);\n const openHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'openChannel',\n args: [peerAddress as Hex, timeout],\n });\n\n const receipt: TransactionReceipt =\n await publicClient.waitForTransactionReceipt({ hash: openHash });\n\n // Extract channelId from ChannelOpened event\n let channelId: string | undefined;\n for (const log of receipt.logs) {\n try {\n const decoded = decodeEventLog({\n abi: TOKEN_NETWORK_ABI,\n data: log.data,\n topics: log.topics,\n });\n if (decoded.eventName === 'ChannelOpened') {\n channelId = (decoded.args as Record<string, unknown>)[\n 'channelId'\n ] as string;\n break;\n }\n } catch {\n // Not our event, skip\n }\n }\n\n if (!channelId) {\n throw new Error('Failed to extract channelId from ChannelOpened event');\n }\n\n // Cache context for getChannelState\n this.channelContext.set(channelId, {\n chain,\n tokenNetworkAddress: tokenNetwork,\n });\n\n // Deposit initial funds if specified\n if (deposit > 0n) {\n const depositHash = await walletClient.writeContract({\n address: tokenNetworkAddr,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'setTotalDeposit',\n args: [channelId as Hex, this.evmSigner.address as Hex, deposit],\n });\n await publicClient.waitForTransactionReceipt({ hash: depositHash });\n }\n\n return { channelId, status: 'opening' };\n }\n\n /**\n * Gets the current state of a payment channel from on-chain data.\n */\n async getChannelState(channelId: string): Promise<ChannelState> {\n const context = this.channelContext.get(channelId);\n if (!context) {\n throw new Error(\n `No context for channel \"${channelId}\". Channel must be opened via this client first.`\n );\n }\n\n // Mina channels are opened/deployed out-of-band; the connector performs the\n // authoritative on-chain `getChannelState(zkAppAddress)` check at claim\n // verification. Reading zkApp state client-side would require the o1js WASM\n // runtime, which the lightweight client intentionally avoids. Report `open`\n // for the configured deployed zkApp.\n if (context.chain.split(':')[0] === 'mina') {\n return { channelId, status: 'open', chain: context.chain };\n }\n\n // Solana channels read on-chain state from the PDA account, not an EVM contract.\n if (context.chain.split(':')[0] === 'solana' && this.solanaConfig) {\n const account = await getSolanaChannelAccountState(\n this.solanaConfig.rpcUrl,\n channelId\n );\n const status: ChannelState['status'] = !account.exists\n ? 'settled'\n : account.state === 'opened'\n ? 'open'\n : account.state === 'closed'\n ? 'closed'\n : 'settled';\n return { channelId, status, chain: context.chain };\n }\n\n const { publicClient } = this.createClients(context.chain);\n\n const result = await publicClient.readContract({\n address: context.tokenNetworkAddress as Hex,\n abi: TOKEN_NETWORK_ABI,\n functionName: 'channels',\n args: [channelId as Hex],\n });\n\n const [, state] = result as [\n bigint,\n number,\n bigint,\n bigint,\n string,\n string,\n ];\n const status = STATE_MAP[state] ?? 'settled';\n\n return {\n channelId,\n status,\n chain: context.chain,\n };\n }\n}\n","/**\n * Solana payment-channel primitives — connector-parity.\n *\n * Pure, dependency-light helpers that reproduce the EXACT on-chain contract the\n * connector's `SolanaPaymentChannelSDK` (`@toon-protocol/connector`\n * `settlement/solana-payment-channel-sdk.ts`) implements, so a client-issued\n * Solana payment-channel claim is accepted by connector 3.9.0's\n * `verifySolanaClaim` path:\n *\n * 1. PDA derivation — `[b\"channel\", min_pubkey, max_pubkey, token_mint]` sorted\n * lexicographically by raw 32-byte pubkey, derived against the program id.\n * This base58 PDA is the claim's `channelAccount` and the channel-state\n * account the connector reads via `provider.getChannelState`.\n * 2. The 48-byte balance-proof message the connector verifies the Ed25519\n * signature over: `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)`.\n * NOTE: this is NOT the swap-claim `balanceProofHashSolana` shape used by the\n * Mill ↔ sender wire / SDK `verifyEd25519Signature`; the connector's on-chain\n * payment-channel verifier (`solana-payment-channel-provider.verifyBalanceProof`)\n * verifies this raw 48-byte message, un-hashed.\n * 3. The `initialize_channel` (+ `deposit`) instructions, built and submitted\n * over raw Solana JSON-RPC (no `@solana/web3.js` / `@solana/kit` runtime\n * dependency — only `@noble/curves` + `@noble/hashes`, already client deps).\n *\n * Every byte layout / discriminator / account-meta order here is mirrored from\n * the connector SDK and the SDK reference E2E\n * (`packages/sdk/tests/e2e/docker-solana-settlement-e2e.test.ts`). Keep them in\n * lock-step; a mismatch makes the connector reject the claim (ON_CHAIN_VERIFICATION_FAILED\n * or INVALID_SIGNATURE).\n */\n\nimport { ed25519 } from '@noble/curves/ed25519.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { base58Encode, base58Decode } from '@toon-protocol/core';\n\n// ---------------------------------------------------------------------------\n// Constants (must match the Rust program + connector SDK exactly)\n// ---------------------------------------------------------------------------\n\n/** Well-known Solana program addresses (base58). */\nconst TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';\nconst SYSTEM_PROGRAM_ID = '11111111111111111111111111111111';\nconst RENT_SYSVAR_ID = 'SysvarRent111111111111111111111111111111111';\n\n/** Instruction discriminators — first byte of an 8-byte LE tag. */\nconst IX_INITIALIZE_CHANNEL = new Uint8Array([0x01, 0, 0, 0, 0, 0, 0, 0]);\nconst IX_DEPOSIT = new Uint8Array([0x02, 0, 0, 0, 0, 0, 0, 0]);\n\n/** On-chain channel-account discriminator: ASCII \"pchannel\". */\nconst CHANNEL_DISCRIMINATOR = new Uint8Array([\n 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,\n]);\n\n/** Channel-state account size in bytes. */\nconst CHANNEL_ACCOUNT_SIZE = 178;\n\n// ---------------------------------------------------------------------------\n// Byte helpers\n// ---------------------------------------------------------------------------\n\nconst MAX_U64 = (1n << 64n) - 1n;\n\nfunction writeU64LE(buf: Uint8Array, offset: number, value: bigint): void {\n if (value < 0n || value > MAX_U64) {\n throw new RangeError(`Value ${value} outside u64 range [0, 2^64-1]`);\n }\n for (let i = 0; i < 8; i++) {\n buf[offset + i] = Number((value >> BigInt(i * 8)) & 0xffn);\n }\n}\n\n/** Left-pad / trim a byte array to exactly 32 bytes. */\nfunction padTo32(bytes: Uint8Array): Uint8Array {\n if (bytes.length === 32) return bytes;\n if (bytes.length > 32) return bytes.slice(bytes.length - 32);\n const padded = new Uint8Array(32);\n padded.set(bytes, 32 - bytes.length);\n return padded;\n}\n\n/** Sort two 32-byte pubkeys lexicographically by raw bytes (matches Rust). */\nfunction sortPubkeys(a: Uint8Array, b: Uint8Array): [Uint8Array, Uint8Array] {\n for (let i = 0; i < 32; i++) {\n const ai = a[i] ?? 0;\n const bi = b[i] ?? 0;\n if (ai < bi) return [a, b];\n if (ai > bi) return [b, a];\n }\n return [a, b];\n}\n\n// ---------------------------------------------------------------------------\n// Ed25519 curve check + PDA derivation (matches Solana find_program_address)\n// ---------------------------------------------------------------------------\n\nfunction modPow(base: bigint, exp: bigint, mod: bigint): bigint {\n let result = 1n;\n base = ((base % mod) + mod) % mod;\n while (exp > 0n) {\n if (exp & 1n) result = (result * base) % mod;\n exp >>= 1n;\n base = (base * base) % mod;\n }\n return result;\n}\n\nfunction modInverse(a: bigint, m: bigint): bigint {\n return modPow(((a % m) + m) % m, m - 2n, m);\n}\n\n/** True if 32 bytes lie on the Ed25519 curve. A valid PDA must NOT be on-curve. */\nfunction isOnCurve(bytes: Uint8Array): boolean {\n const P = (1n << 255n) - 19n;\n const yBytes = new Uint8Array(32);\n yBytes.set(bytes);\n yBytes[31] = (yBytes[31] ?? 0) & 0x7f;\n\n let y = 0n;\n for (let i = 0; i < 32; i++) {\n y |= BigInt(yBytes[i] ?? 0) << BigInt(i * 8);\n }\n if (y >= P) return true;\n\n const y2 = (y * y) % P;\n const D = (P - ((121665n * modInverse(121666n, P)) % P) + P) % P;\n const numerator = (y2 - 1n + P) % P;\n const denominator = (D * y2 + 1n) % P;\n const x2 = (numerator * modInverse(denominator, P)) % P;\n if (x2 === 0n) return true;\n return modPow(x2, (P - 1n) / 2n, P) === 1n;\n}\n\nfunction findProgramAddress(\n seeds: Uint8Array[],\n programId: Uint8Array\n): { pda: Uint8Array; bump: number } {\n const PDA_MARKER = new TextEncoder().encode('ProgramDerivedAddress');\n for (let bump = 255; bump >= 0; bump--) {\n const allSeeds = [...seeds, new Uint8Array([bump])];\n let totalLen = programId.length + PDA_MARKER.length;\n for (const s of allSeeds) totalLen += s.length;\n\n const input = new Uint8Array(totalLen);\n let offset = 0;\n for (const s of allSeeds) {\n input.set(s, offset);\n offset += s.length;\n }\n input.set(programId, offset);\n offset += programId.length;\n input.set(PDA_MARKER, offset);\n\n const hash = sha256(input);\n if (!isOnCurve(hash)) return { pda: hash, bump };\n }\n throw new Error('Could not find a viable PDA bump seed');\n}\n\n/**\n * Derive the channel PDA — connector-parity.\n * Seeds: `[b\"channel\", min_pubkey, max_pubkey, token_mint]` (participants sorted).\n *\n * @returns base58 PDA + bump.\n */\nexport function deriveChannelPDA(\n participantA: string,\n participantB: string,\n tokenMint: string,\n programId: string\n): { pda: string; bump: number } {\n const a = padTo32(base58Decode(participantA));\n const b = padTo32(base58Decode(participantB));\n const mint = padTo32(base58Decode(tokenMint));\n const program = padTo32(base58Decode(programId));\n const [min, max] = sortPubkeys(a, b);\n const seeds = [new TextEncoder().encode('channel'), min, max, mint];\n const { pda, bump } = findProgramAddress(seeds, program);\n return { pda: base58Encode(pda), bump };\n}\n\n/**\n * Derive the vault PDA for a channel — connector-parity.\n * Seeds: `[b\"vault\", channel_pda]`.\n */\nexport function deriveVaultPDA(\n channelPDA: string,\n programId: string\n): { pda: string; bump: number } {\n const channel = padTo32(base58Decode(channelPDA));\n const program = padTo32(base58Decode(programId));\n const seeds = [new TextEncoder().encode('vault'), channel];\n const { pda, bump } = findProgramAddress(seeds, program);\n return { pda: base58Encode(pda), bump };\n}\n\n// ---------------------------------------------------------------------------\n// Balance-proof message + signing (connector-parity)\n// ---------------------------------------------------------------------------\n\n/**\n * Build the connector's canonical 48-byte balance-proof message:\n * `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)`.\n *\n * Mirrors `SolanaPaymentChannelSDK._buildBalanceProofMessage`. This is the EXACT\n * message the connector's `solana-payment-channel-provider.verifyBalanceProof`\n * reconstructs and Ed25519-verifies (un-hashed).\n */\nexport function buildBalanceProofMessage(\n channelPDA: string,\n nonce: bigint,\n transferredAmount: bigint\n): Uint8Array {\n const message = new Uint8Array(48);\n message.set(padTo32(base58Decode(channelPDA)), 0);\n writeU64LE(message, 32, nonce);\n writeU64LE(message, 40, transferredAmount);\n return message;\n}\n\n/** Sign the 48-byte balance-proof message with a 32-byte Ed25519 seed. */\nexport function signBalanceProofMessage(\n channelPDA: string,\n nonce: bigint,\n transferredAmount: bigint,\n seed: Uint8Array\n): Uint8Array {\n const message = buildBalanceProofMessage(\n channelPDA,\n nonce,\n transferredAmount\n );\n return ed25519.sign(message, seed);\n}\n\n// ---------------------------------------------------------------------------\n// On-chain channel open (initialize_channel + deposit) over raw JSON-RPC\n// ---------------------------------------------------------------------------\n\ninterface InstructionKey {\n pubkey: string;\n isSigner: boolean;\n isWritable: boolean;\n}\n\ninterface RawInstruction {\n programId: string;\n keys: InstructionKey[];\n data: Uint8Array;\n}\n\ninterface Signer {\n publicKey: Uint8Array;\n privateKey: Uint8Array;\n}\n\nlet rpcIdCounter = 1;\n\nasync function solanaRpc(\n rpcUrl: string,\n method: string,\n params: unknown[] = []\n): Promise<unknown> {\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method,\n params,\n id: rpcIdCounter++,\n }),\n signal: AbortSignal.timeout(30000),\n });\n const json = (await res.json()) as {\n result?: unknown;\n error?: { message: string; code: number };\n };\n if (json.error) {\n throw new Error(\n `Solana RPC error [${method}]: ${json.error.message} (code ${json.error.code})`\n );\n }\n return json.result;\n}\n\nasync function getLatestBlockhash(rpcUrl: string): Promise<string> {\n const result = (await solanaRpc(rpcUrl, 'getLatestBlockhash', [\n { commitment: 'confirmed' },\n ])) as { value: { blockhash: string } };\n return result.value.blockhash;\n}\n\ninterface AccountInfo {\n data: [string, string];\n owner: string;\n lamports: number;\n}\n\nasync function getAccountInfo(\n rpcUrl: string,\n pubkey: string\n): Promise<AccountInfo | null> {\n const result = (await solanaRpc(rpcUrl, 'getAccountInfo', [\n pubkey,\n { encoding: 'base64', commitment: 'confirmed' },\n ])) as { value: AccountInfo | null };\n return result.value;\n}\n\nasync function waitForConfirmation(\n rpcUrl: string,\n signature: string,\n timeoutMs = 30000\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const result = (await solanaRpc(rpcUrl, 'getSignatureStatuses', [\n [signature],\n ])) as {\n value: ({ confirmationStatus: string; err?: unknown } | null)[];\n };\n const status = result.value[0];\n if (\n status?.confirmationStatus === 'confirmed' ||\n status?.confirmationStatus === 'finalized'\n ) {\n if (status.err) {\n throw new Error(\n `Transaction ${signature} failed: ${JSON.stringify(status.err)}`\n );\n }\n return;\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n throw new Error(\n `Transaction ${signature} not confirmed within ${timeoutMs}ms`\n );\n}\n\nfunction compactU16Size(value: number): number {\n if (value > 0xffff) {\n throw new RangeError(`compact-u16 value ${value} exceeds u16 max (0xFFFF)`);\n }\n return value < 0x80 ? 1 : value < 0x4000 ? 2 : 3;\n}\n\nfunction writeCompactU16(\n buf: Uint8Array,\n offset: number,\n value: number\n): number {\n if (value < 0x80) {\n buf[offset++] = value;\n } else if (value < 0x4000) {\n buf[offset++] = (value & 0x7f) | 0x80;\n buf[offset++] = value >> 7;\n } else {\n buf[offset++] = (value & 0x7f) | 0x80;\n buf[offset++] = ((value >> 7) & 0x7f) | 0x80;\n buf[offset++] = value >> 14;\n }\n return offset;\n}\n\ninterface AccountEntry {\n pubkey: string;\n isSigner: boolean;\n isWritable: boolean;\n}\n\n/**\n * Build, sign, and send a Solana legacy transaction over raw JSON-RPC, then wait\n * for confirmation. Mirrors the SDK reference E2E's `buildAndSendTransaction`.\n */\nasync function buildAndSendTransaction(\n rpcUrl: string,\n feePayer: Signer,\n instructions: RawInstruction[],\n additionalSigners: Signer[] = []\n): Promise<string> {\n const blockhash = await getLatestBlockhash(rpcUrl);\n const feePayerPubkey = base58Encode(feePayer.publicKey);\n\n const accountMap = new Map<string, AccountEntry>();\n accountMap.set(feePayerPubkey, {\n pubkey: feePayerPubkey,\n isSigner: true,\n isWritable: true,\n });\n for (const ix of instructions) {\n for (const key of ix.keys) {\n const existing = accountMap.get(key.pubkey);\n if (existing) {\n existing.isSigner = existing.isSigner || key.isSigner;\n existing.isWritable = existing.isWritable || key.isWritable;\n } else {\n accountMap.set(key.pubkey, { ...key });\n }\n }\n if (!accountMap.has(ix.programId)) {\n accountMap.set(ix.programId, {\n pubkey: ix.programId,\n isSigner: false,\n isWritable: false,\n });\n }\n }\n\n const accounts = [...accountMap.values()].sort((a, b) => {\n if (a.pubkey === feePayerPubkey) return -1;\n if (b.pubkey === feePayerPubkey) return 1;\n const aScore = (a.isSigner ? 2 : 0) + (a.isWritable ? 1 : 0);\n const bScore = (b.isSigner ? 2 : 0) + (b.isWritable ? 1 : 0);\n return bScore - aScore;\n });\n\n const numSigners = accounts.filter((a) => a.isSigner).length;\n const numReadonlySigners = accounts.filter(\n (a) => a.isSigner && !a.isWritable\n ).length;\n const numReadonlyNonSigners = accounts.filter(\n (a) => !a.isSigner && !a.isWritable\n ).length;\n\n const accountIndexMap = new Map<string, number>();\n accounts.forEach((a, i) => accountIndexMap.set(a.pubkey, i));\n\n const compiled = instructions.map((ix) => ({\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- programId added to accountMap above\n programIdIndex: accountIndexMap.get(ix.programId)!,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every key added to accountMap above\n accountIndices: ix.keys.map((k) => accountIndexMap.get(k.pubkey)!),\n data: ix.data,\n }));\n\n const blockhashBytes = base58Decode(blockhash);\n\n let instructionSize = compactU16Size(compiled.length);\n for (const ix of compiled) {\n instructionSize += 1;\n instructionSize +=\n compactU16Size(ix.accountIndices.length) + ix.accountIndices.length;\n instructionSize += compactU16Size(ix.data.length) + ix.data.length;\n }\n\n const messageSize =\n 3 +\n compactU16Size(accounts.length) +\n 32 * accounts.length +\n 32 +\n instructionSize;\n const message = new Uint8Array(messageSize);\n let offset = 0;\n\n message[offset++] = numSigners;\n message[offset++] = numReadonlySigners;\n message[offset++] = numReadonlyNonSigners;\n\n offset = writeCompactU16(message, offset, accounts.length);\n for (const acct of accounts) {\n message.set(padTo32(base58Decode(acct.pubkey)), offset);\n offset += 32;\n }\n\n message.set(padTo32(blockhashBytes), offset);\n offset += 32;\n\n offset = writeCompactU16(message, offset, compiled.length);\n for (const ix of compiled) {\n message[offset++] = ix.programIdIndex;\n offset = writeCompactU16(message, offset, ix.accountIndices.length);\n for (const idx of ix.accountIndices) message[offset++] = idx;\n offset = writeCompactU16(message, offset, ix.data.length);\n message.set(ix.data, offset);\n offset += ix.data.length;\n }\n\n const finalMessage = message.slice(0, offset);\n\n const allSigners = [feePayer, ...additionalSigners];\n const signerPubkeys = accounts.filter((a) => a.isSigner).map((a) => a.pubkey);\n const signatures: Uint8Array[] = [];\n for (const signerPubkey of signerPubkeys) {\n const signer = allSigners.find(\n (s) => base58Encode(s.publicKey) === signerPubkey\n );\n if (!signer) throw new Error(`Missing signer for ${signerPubkey}`);\n signatures.push(ed25519.sign(finalMessage, signer.privateKey));\n }\n\n const txSize =\n compactU16Size(signatures.length) +\n signatures.length * 64 +\n finalMessage.length;\n const tx = new Uint8Array(txSize);\n let txOffset = 0;\n txOffset = writeCompactU16(tx, txOffset, signatures.length);\n for (const sig of signatures) {\n tx.set(sig, txOffset);\n txOffset += 64;\n }\n tx.set(finalMessage, txOffset);\n\n const txBase64 = Buffer.from(tx).toString('base64');\n const txSig = (await solanaRpc(rpcUrl, 'sendTransaction', [\n txBase64,\n {\n encoding: 'base64',\n skipPreflight: false,\n preflightCommitment: 'confirmed',\n },\n ])) as string;\n await waitForConfirmation(rpcUrl, txSig);\n return txSig;\n}\n\n/** Parsed status of an on-chain channel account. */\nexport interface SolanaChannelAccountState {\n exists: boolean;\n /** 'opened' | 'closed' | 'settled' when the account exists with valid data. */\n state?: 'opened' | 'closed' | 'settled';\n participantA?: string;\n participantB?: string;\n}\n\nconst STATE_MAP = ['opened', 'closed', 'settled'] as const;\n\n/** Fetch + minimally parse the on-chain channel account at a PDA. */\nexport async function getChannelAccountState(\n rpcUrl: string,\n channelPDA: string\n): Promise<SolanaChannelAccountState> {\n const info = await getAccountInfo(rpcUrl, channelPDA);\n if (!info) return { exists: false };\n const data = new Uint8Array(Buffer.from(info.data[0], 'base64'));\n if (data.length < CHANNEL_ACCOUNT_SIZE) return { exists: false };\n for (let i = 0; i < 8; i++) {\n if (data[i] !== CHANNEL_DISCRIMINATOR[i]) return { exists: false };\n }\n return {\n exists: true,\n state: STATE_MAP[data[160] ?? 0] ?? 'opened',\n participantA: base58Encode(data.slice(8, 40)),\n participantB: base58Encode(data.slice(40, 72)),\n };\n}\n\nexport interface OpenSolanaChannelParams {\n rpcUrl: string;\n programId: string;\n tokenMint: string;\n /** Client's 32-byte Ed25519 seed (participant A + fee payer). */\n payerSeed: Uint8Array;\n /** Client's base58 pubkey (participant A). */\n payerPubkey: string;\n /** Apex's base58 settlement pubkey (participant B). */\n peerPubkey: string;\n /** Challenge-period duration in seconds. */\n challengeDuration: bigint;\n /** Optional deposit amount + funded SPL token account (ATA) of the payer. */\n deposit?: { amount: bigint; payerTokenAccount: string };\n}\n\nexport interface OpenSolanaChannelResult {\n channelPDA: string;\n /** True if a fresh on-chain initialize_channel tx was submitted. */\n opened: boolean;\n initTxSignature?: string;\n depositTxSignature?: string;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Solana\n * payment channel at the connector-parity PDA. Idempotent: if the channel\n * account already exists on-chain, returns the PDA without re-initializing.\n *\n * The Ed25519 keypair derives both the participant-A identity and the fee\n * payer; the apex pubkey is participant B. The returned `channelPDA` (base58) is\n * the value carried in the claim's `channelAccount`.\n */\nexport async function openSolanaChannel(\n params: OpenSolanaChannelParams\n): Promise<OpenSolanaChannelResult> {\n const {\n rpcUrl,\n programId,\n tokenMint,\n payerSeed,\n payerPubkey,\n peerPubkey,\n challengeDuration,\n } = params;\n\n const { pda: channelPDA } = deriveChannelPDA(\n payerPubkey,\n peerPubkey,\n tokenMint,\n programId\n );\n\n // Idempotent: skip initialize if the channel account already exists.\n const existing = await getChannelAccountState(rpcUrl, channelPDA);\n if (existing.exists) {\n return { channelPDA, opened: false };\n }\n\n const payerPublicKey = padTo32(base58Decode(payerPubkey));\n const payer: Signer = { publicKey: payerPublicKey, privateKey: payerSeed };\n\n const { pda: vaultPDA } = deriveVaultPDA(channelPDA, programId);\n\n // initialize_channel: discriminator(8) + challenge_duration(8 LE)\n const initData = new Uint8Array(16);\n initData.set(IX_INITIALIZE_CHANNEL, 0);\n writeU64LE(initData, 8, challengeDuration);\n\n const initTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: true },\n { pubkey: payerPubkey, isSigner: false, isWritable: false }, // participant A\n { pubkey: peerPubkey, isSigner: false, isWritable: false }, // participant B\n { pubkey: tokenMint, isSigner: false, isWritable: false },\n { pubkey: channelPDA, isSigner: false, isWritable: true },\n { pubkey: vaultPDA, isSigner: false, isWritable: true },\n { pubkey: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },\n { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n { pubkey: RENT_SYSVAR_ID, isSigner: false, isWritable: false },\n ],\n data: initData,\n },\n ]);\n\n let depositTxSignature: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n // deposit: discriminator(8) + amount(8 LE)\n const depositData = new Uint8Array(16);\n depositData.set(IX_DEPOSIT, 0);\n writeU64LE(depositData, 8, params.deposit.amount);\n\n depositTxSignature = await buildAndSendTransaction(rpcUrl, payer, [\n {\n programId,\n keys: [\n { pubkey: payerPubkey, isSigner: true, isWritable: false },\n {\n pubkey: params.deposit.payerTokenAccount,\n isSigner: false,\n isWritable: true,\n },\n { pubkey: vaultPDA, isSigner: false, isWritable: true },\n { pubkey: channelPDA, isSigner: false, isWritable: true },\n { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n ],\n data: depositData,\n },\n ]);\n }\n\n return { channelPDA, opened: true, initTxSignature, depositTxSignature };\n}\n\n// Internal helpers exported for unit tests (parity assertions).\nexport const __testing = { padTo32, sortPubkeys, isOnCurve };\n","/**\n * On-chain Mina payment-channel open — connector-parity.\n *\n * Opens (initializes) — and optionally deposits into — a REAL on-chain Mina\n * payment channel on the deployed `PaymentChannel` zkApp, so the connector's\n * `MinaPaymentChannelSDK.getChannelState(zkAppAddress)` finds a channel whose\n * on-chain `channelState == OPEN` (status `'opened'`) and the Mina claim\n * verifies + stores. This is the Mina analog of `openSolanaChannel`\n * (`solana-payment-channel.ts`, connector#105): the client opens its own\n * per-channel on-chain state rather than relying on a pre-initialized channel.\n *\n * ## Why this is separate from `mina-payment-channel.ts`\n *\n * `mina-payment-channel.ts` builds the OFF-chain balance-proof claim with\n * `mina-signer` (no o1js — keeps the client lightweight). But INITIALIZING a\n * zkApp channel requires producing a zkApp method proof, which is heavyweight\n * o1js WASM circuit work. So this module lazily imports `o1js` +\n * `@toon-protocol/mina-zkapp` ONLY when an on-chain open is actually requested\n * (the e2e client / Node settlement path), mirroring the connector's own\n * `getO1js()` lazy-require. Both are OPTIONAL dependencies and are kept out of\n * the bundle via `tsup` `external` so npm consumers that never open a Mina\n * channel don't pay the o1js cost.\n *\n * ## Contract call (must match the connector's `MinaPaymentChannelSDK.openChannel`)\n *\n * `PaymentChannel.initializeChannel(participantA, participantB, nonce, timeout, tokenId)`\n * sets `channelState = OPEN` (1). The deployed zkApp address IS the channel id\n * (`MinaClaimMessage.zkAppAddress`), identical to the claim's channel-hash\n * preimage in `mina-payment-channel.ts`. `deposit(amount, depositor)` then\n * bumps `depositTotal` (only valid while `channelState == OPEN`).\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address); this module assumes the\n * account exists and only INITIALIZES the channel on it (idempotent — if the\n * channel is already `OPEN`, it returns without re-initializing).\n *\n * @module\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\n\n/** Result shape of o1js `fetchAccount` (the bits we read). */\ninterface FetchAccountResult {\n error?: unknown;\n account?: {\n zkapp?: { appState?: { toString(): string }[] };\n };\n}\n\n/** Minimal o1js surface this module uses (lazy-loaded). */\ninterface O1jsLike {\n Mina: any;\n PrivateKey: any;\n PublicKey: any;\n Field: any;\n AccountUpdate: any;\n fetchAccount: (args: { publicKey: any }) => Promise<FetchAccountResult>;\n}\n\nlet cachedO1js: O1jsLike | null = null;\nlet cachedPaymentChannel: any | null = null;\nlet compiledContract: any | null = null;\n\n/**\n * Test-only override for the o1js + contract loader. When set, `loadMinaRuntime`\n * returns this instead of doing the `createRequire` resolution — so unit tests\n * can inject fakes WITHOUT pulling the real o1js WASM runtime (vitest's\n * `vi.mock` cannot intercept the CJS `require` path the production loader uses).\n */\nlet runtimeOverride:\n | (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>)\n | null = null;\n\n/** Test hook: inject a fake o1js + PaymentChannel runtime. */\nexport function _setMinaRuntimeForTests(\n loader: (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>) | null\n): void {\n runtimeOverride = loader;\n}\n\n/**\n * Resolve `o1js` AND the `PaymentChannel` contract through ONE shared module\n * instance.\n *\n * ⚠️ o1js keeps its \"active Mina instance\" in a module-level closure\n * (`mina-instance.js`). `@toon-protocol/mina-zkapp` ships as CommonJS, so its\n * internal `import {Mina}` is emitted as `require('o1js')` → o1js's CJS build\n * (`dist/node/index.cjs`). A bare ESM `import('o1js')` from this module resolves\n * o1js's DIFFERENT ESM build (`dist/node/index.js`) — a SEPARATE module instance\n * with a SEPARATE `activeInstance` closure. Calling `setActiveInstance` on the\n * ESM instance while `PaymentChannel.initializeChannel` reads the CJS instance\n * throws `channelState.get() failed … Must call Mina.setActiveInstance first`\n * (observed in the local-HS Mina e2e on the FIRST publish). The connector's own\n * settlement path and `scripts/deploy-mina-zkapp.ts` both work around this by\n * requiring o1js through the same anchor the zkApp uses.\n *\n * Fix: anchor a `createRequire` at the `@toon-protocol/mina-zkapp` package and\n * `require('o1js')` + the contract from there, so both share the CJS o1js\n * instance and `setActiveInstance` is visible inside the contract method. Kept\n * lazy (and `external` in tsup) so the multi-hundred-MB WASM runtime is only\n * loaded when a Mina channel is actually opened.\n */\nasync function loadMinaRuntime(): Promise<{\n o1js: O1jsLike;\n PaymentChannel: any;\n}> {\n if (cachedO1js && cachedPaymentChannel) {\n return { o1js: cachedO1js, PaymentChannel: cachedPaymentChannel };\n }\n if (runtimeOverride) {\n const injected = await runtimeOverride();\n cachedO1js = injected.o1js;\n cachedPaymentChannel = injected.PaymentChannel;\n return injected;\n }\n const { createRequire } = await import('node:module');\n const nodePath = await import('node:path');\n // Anchor resolution at this module so the consumer's node_modules graph (where\n // both o1js and @toon-protocol/mina-zkapp are installed) resolves them, then\n // re-anchor at the mina-zkapp package so its CJS `require('o1js')` and ours are\n // the SAME physical module instance (shared active-instance closure).\n const requireHere = createRequire(import.meta.url);\n const mzkPkgPath = requireHere.resolve(\n '@toon-protocol/mina-zkapp/package.json'\n );\n const requireFromMzk = createRequire(mzkPkgPath);\n // o1js resolved from the mina-zkapp anchor → the SAME (CJS) instance the\n // contract's `require('o1js')` uses.\n const o1js = requireFromMzk('o1js') as O1jsLike;\n // ⚠️ A pnpm workspace package has NO self-referential symlink, so\n // `requireFromMzk('@toon-protocol/mina-zkapp')` fails with MODULE_NOT_FOUND.\n // Load the contract by PATH from the package's own `main` entry instead (the\n // same approach scripts/deploy-mina-zkapp.ts uses). This works in both the\n // workspace (no self-symlink) and the flat consumer node_modules layouts.\n const mzkPkgJson: { main?: string } = requireFromMzk(mzkPkgPath);\n const mzkDir = nodePath.dirname(mzkPkgPath);\n const mzkEntry = nodePath.join(mzkDir, mzkPkgJson.main ?? 'dist/index.js');\n const mzk: any = requireFromMzk(mzkEntry);\n const PaymentChannel = mzk.PaymentChannel ?? mzk.default?.PaymentChannel;\n if (!PaymentChannel) {\n throw new Error(\n '@toon-protocol/mina-zkapp does not export PaymentChannel — cannot open a Mina channel'\n );\n }\n cachedO1js = o1js;\n cachedPaymentChannel = PaymentChannel;\n return { o1js, PaymentChannel };\n}\n\n/** Lazily resolve `o1js` (shared CJS instance with the contract). */\nasync function getO1js(): Promise<O1jsLike> {\n return (await loadMinaRuntime()).o1js;\n}\n\n/**\n * Lazily resolve + compile the `PaymentChannel` contract. Compilation is the\n * expensive o1js step; cache the compiled artifact so repeated opens in the\n * same process don't recompile.\n */\nasync function getCompiledPaymentChannel(): Promise<any> {\n const { PaymentChannel } = await loadMinaRuntime();\n if (!compiledContract) {\n await PaymentChannel.compile();\n compiledContract = PaymentChannel;\n }\n return compiledContract;\n}\n\n/** Test hook: reset the cached o1js + compiled-contract state. */\nexport function _resetMinaChannelOpenCache(): void {\n cachedO1js = null;\n cachedPaymentChannel = null;\n compiledContract = null;\n}\n\n/** CHANNEL_STATE.OPEN from `@toon-protocol/mina-zkapp` constants. */\nconst MINA_CHANNEL_STATE_OPEN = 1n;\n/** CHANNEL_STATE.UNINITIALIZED. */\nconst MINA_CHANNEL_STATE_UNINITIALIZED = 0n;\n\nexport interface OpenMinaChannelParams {\n /** Mina GraphQL endpoint of the network the zkApp is deployed on. */\n graphqlUrl: string;\n /** Deployed payment-channel zkApp B62 address (the channel id). */\n zkAppAddress: string;\n /**\n * Fee-payer / participantA `EK…` base58 private key — the client's Mina key\n * (same key the off-chain claim is signed with). Pays the Mina tx fee and\n * authorizes the `initializeChannel` (+ `deposit`) transaction.\n */\n payerPrivateKey: string;\n /**\n * participantB B62 public key — the apex's Mina settlement address. When\n * omitted, the payer is used for both participants (single-party dev channel).\n */\n peerPublicKey?: string;\n /** Channel settlement timeout in slots. Default 86400. */\n timeout?: bigint;\n /** Mina token id field (decimal string). Default '1' (native MINA). */\n tokenId?: string;\n /** Optional on-chain deposit amount (base units) after initialization. */\n deposit?: { amount: bigint };\n /** Per-call network id for the Schnorr/account prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n /**\n * Transaction fee in nanomina for the `initializeChannel` + `deposit` zkApp\n * method calls. Lightnet/devnet REJECTS fee-less zkApp commands with\n * \"Insufficient fee\", so a non-zero fee is REQUIRED. Default 100_000_000\n * (0.1 MINA), matching scripts/deploy-mina-zkapp.ts.\n */\n feeNanomina?: bigint;\n}\n\nexport interface OpenMinaChannelResult {\n /** The zkApp address (channel id) — echoed for parity with the Solana opener. */\n zkAppAddress: string;\n /** True when a fresh `initializeChannel` tx was submitted this call. */\n opened: boolean;\n /** `initializeChannel` tx hash (absent when the channel was already OPEN). */\n initTxHash?: string;\n /** `deposit` tx hash, when a deposit was requested + submitted. */\n depositTxHash?: string;\n /** On-chain channelState after the call (0=UNINIT,1=OPEN,2=CLOSING,3=SETTLED). */\n channelState: number;\n /**\n * On-chain `depositTotal` (base units), read from the zkApp appState after the\n * open/deposit settled. The Mina balance-proof signer needs this so it can bind\n * `balanceB = depositTotal − balanceA` (toon-protocol/connector#133). A channel\n * can be re-deposited, so this is the CURRENT on-chain value, not a config one.\n */\n depositTotal: bigint;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Mina\n * payment channel on the already-deployed `PaymentChannel` zkApp.\n *\n * Idempotent: if the on-chain `channelState` is already `OPEN`, returns without\n * re-initializing (mirrors `openSolanaChannel`'s \"channel already exists\" path).\n * Throws if the zkApp account does not exist on-chain.\n */\nexport async function openMinaChannelOnChain(\n params: OpenMinaChannelParams\n): Promise<OpenMinaChannelResult> {\n const { Mina, PrivateKey, PublicKey, Field, AccountUpdate, fetchAccount } =\n await getO1js();\n\n // Use the plain-string `Mina.Network(graphqlUrl)` form (matching the\n // connector's MinaPaymentChannelSDK._setNetwork). The object form\n // (`{ networkId, mina }`) behaves inconsistently across o1js versions and left\n // the active-instance ledger unable to resolve fetched accounts\n // (`channelState.get()` → \"we can't find this zkapp account\"). The Schnorr\n // network prefix is governed by the off-chain signer (`networkId`), not this\n // on-chain endpoint binding.\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n // zkApp method txs MUST carry a fee on lightnet/devnet (\"Insufficient fee\"\n // otherwise). 0.1 MINA matches scripts/deploy-mina-zkapp.ts.\n const txFee = params.feeNanomina ?? 100_000_000n;\n\n // The client's mnemonic-derived Mina key is a big-endian hex scalar (the form\n // `deriveFullIdentity()` emits); o1js `PrivateKey.fromBase58` needs the Mina\n // `EK…` base58check form. Convert (idempotent — passes an already-`EK…` key\n // through unchanged), matching what the off-chain MinaSigner does.\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);\n const payerPublicKey = payerPrivateKey.toPublicKey();\n const zkAppPublicKey = PublicKey.fromBase58(params.zkAppAddress);\n\n // Read channelState (appState index 3) straight from the `fetchAccount`\n // result rather than `zkApp.channelState.get()`. `.get()` outside a\n // transaction is fragile here (it can throw \"Must call Mina.setActiveInstance\n // first\" / \"can't find this zkapp account\" even right after a successful\n // fetch); the network-fetched appState array is the reliable source. A\n // missing account is a hard error — the zkApp must be deployed out-of-band.\n const readChannelState = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n // PaymentChannel state field order: [channelHash, balanceCommitment,\n // nonceField, channelState, depositTotal, ...] → channelState is index 3.\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[3]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n // Read the on-chain `depositTotal` (appState index 4). The signer must bind\n // `balanceB = depositTotal − balanceA` against this CURRENT on-chain value\n // (connector#133); a channel can be re-deposited, so a stale config value\n // would fail the signatureA verification on settle. A missing account is a\n // hard error (same as readChannelState).\n const readDepositTotal = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[4]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n const currentState = await readChannelState();\n await fetchAccount({ publicKey: payerPublicKey });\n let opened = false;\n let initTxHash: string | undefined;\n let zkApp: any;\n const getZkApp = async () => {\n if (!zkApp) {\n const PaymentChannel = await getCompiledPaymentChannel();\n zkApp = new PaymentChannel(zkAppPublicKey);\n }\n return zkApp;\n };\n\n if (currentState === MINA_CHANNEL_STATE_UNINITIALIZED) {\n const channel = await getZkApp();\n const participantA = payerPublicKey;\n const participantB = params.peerPublicKey\n ? PublicKey.fromBase58(params.peerPublicKey)\n : payerPublicKey;\n const nonce = Field(0);\n const timeoutField = Field((params.timeout ?? 86400n).toString());\n const tokenIdField = Field(params.tokenId ?? '1');\n\n // `initializeChannel` reads `this.channelState.getAndRequireEquals()` as a\n // precondition, which needs the zkApp account in o1js's active-instance\n // cache. Re-fetch BOTH the zkApp and the fee-payer immediately before\n // building the transaction so the precondition read resolves (a stale or\n // missing cache surfaces as \"channelState.get() failed / Must call\n // setActiveInstance first\" — even though the network IS set).\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n\n const initTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.initializeChannel(\n participantA,\n participantB,\n nonce,\n timeoutField,\n tokenIdField\n );\n }\n );\n await initTx.prove();\n const sentInit = await initTx.sign([payerPrivateKey]).send();\n initTxHash = sentInit.hash ?? undefined;\n opened = true;\n // ALWAYS wait for the init tx to be INCLUDED in a block (and re-fetch the\n // account) before returning — NOT only when a deposit follows.\n //\n // Why this matters (issue #158): the two-party `channelHash =\n // Poseidon([client.x, apex.x, 0])` is only written to the zkApp's on-chain\n // state once `initializeChannel` is included in a block. If we fire-and-forget\n // the init tx, the publish proceeds immediately and the connector reads the\n // STILL-BARE zkApp (channelState=0, channelHash empty → `participants:[\"\",\"\"]`)\n // before the init lands, so its participant-form balance-proof reconstruction\n // mismatches → `mina_claim_verification_failed: \"Invalid balance proof\n // signature\"`. The EVM (`waitForTransactionReceipt`) and Solana\n // (`waitForConfirmation`) openers both confirm their open tx before returning;\n // Mina must do the same for parity. `.wait()` blocks until inclusion (lightnet\n // block time can be a few minutes).\n await sentInit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n } else if (currentState !== MINA_CHANNEL_STATE_OPEN) {\n // CLOSING (2) or SETTLED (3): cannot (re)open. Surface clearly.\n throw new Error(\n `Mina channel ${params.zkAppAddress} is in state ${currentState} (not UNINITIALIZED/OPEN) — cannot open`\n );\n }\n\n // Optional deposit (only valid while OPEN — which it now is).\n let depositTxHash: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n const channel = await getZkApp();\n // Re-fetch so the deposit tx sees the post-init state.\n await fetchAccount({ publicKey: zkAppPublicKey });\n const amountField = Field(params.deposit.amount.toString());\n const depositTx = await Mina.transaction(\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.deposit(amountField, payerPublicKey);\n }\n );\n await depositTx.prove();\n const sentDeposit = await depositTx.sign([payerPrivateKey]).send();\n depositTxHash = sentDeposit.hash ?? undefined;\n // ALWAYS wait for the deposit tx to be INCLUDED before returning — same\n // confirmation discipline as initializeChannel above (issue #158). The\n // connector's claimFromChannel runs a #126 balance-conservation gate that\n // reads the on-chain `depositTotal`; if we fire-and-forget the deposit, the\n // publish + claim race ahead and the connector reads depositTotal=0 (deposit\n // not yet in a block) → `PROOF_GENERATION_FAILED: Claim violates balance\n // conservation` and the settle aborts non-retryably. Blocking on inclusion\n // (and re-fetching) guarantees the funded depositTotal is on-chain before any\n // claim settles against it.\n await sentDeposit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n }\n\n // Read the resulting state from the network-fetched appState (best-effort —\n // may still reflect the pre-confirmation value on a slow node; the connector\n // re-reads at verification time). If we just opened, optimistically report\n // OPEN even if the node hasn't surfaced the new state yet.\n let finalState: number;\n try {\n finalState = Number(await readChannelState());\n } catch {\n finalState = opened\n ? Number(MINA_CHANNEL_STATE_OPEN)\n : Number(currentState);\n }\n if (opened && finalState === Number(MINA_CHANNEL_STATE_UNINITIALIZED)) {\n finalState = Number(MINA_CHANNEL_STATE_OPEN);\n }\n\n // Touch AccountUpdate so the (lazy) import is retained even if a future\n // refactor stops referencing it directly above; harmless no-op.\n void AccountUpdate;\n\n // Read the resulting on-chain depositTotal (post init+deposit confirmation, so\n // a fresh re-deposit is reflected). Best-effort: fall back to 0n if the read\n // throws on a slow node — the connector re-reads at verification time.\n let depositTotal: bigint;\n try {\n depositTotal = await readDepositTotal();\n } catch {\n depositTotal = 0n;\n }\n\n return {\n zkAppAddress: params.zkAppAddress,\n opened,\n initTxHash,\n depositTxHash,\n channelState: finalState,\n depositTotal,\n };\n}\n","import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport { type Hex, toHex } from 'viem';\nimport type { BalanceProofParams, SignedBalanceProof } from '../types.js';\n// Types re-exported for convenience\nexport type { ClaimMessage } from './types.js';\n\n/**\n * EVM claim message for BTP protocol data.\n * Matches @toon-protocol/connector's EVMClaimMessage interface.\n *\n * The connector's validateClaimMessage() requires envelope fields\n * (version, messageId, timestamp) plus EVM claim fields, and optionally\n * chainId + tokenNetworkAddress for self-describing signature verification.\n */\nexport interface EVMClaimMessage {\n version: '1.0';\n blockchain: 'evm';\n messageId: string;\n timestamp: string;\n senderId: string;\n channelId: string;\n nonce: number;\n transferredAmount: string;\n lockedAmount: string;\n locksRoot: string;\n signature: string;\n signerAddress: string;\n /** Chain ID for self-describing EIP-712 verification */\n chainId: number;\n /** TokenNetwork address for self-describing EIP-712 verification */\n tokenNetworkAddress: string;\n /** ERC-20 token address for self-describing claim verification */\n tokenAddress?: string;\n}\n\n/**\n * EIP-712 domain for TokenNetwork balance proofs.\n * Must match connector's eip712-helper.js getDomainSeparator().\n */\nfunction getBalanceProofDomain(chainId: number, tokenNetworkAddress: string) {\n return {\n name: 'TokenNetwork' as const,\n version: '1' as const,\n chainId,\n verifyingContract: tokenNetworkAddress as Hex,\n };\n}\n\n/**\n * EIP-712 types for balance proofs.\n * Must match connector's eip712-helper.js getBalanceProofTypes().\n */\nconst BALANCE_PROOF_TYPES = {\n BalanceProof: [\n { name: 'channelId', type: 'bytes32' },\n { name: 'nonce', type: 'uint256' },\n { name: 'transferredAmount', type: 'uint256' },\n { name: 'lockedAmount', type: 'uint256' },\n { name: 'locksRoot', type: 'bytes32' },\n ],\n} as const;\n\n/**\n * EVM signer for EIP-712 balance proofs and on-chain transactions.\n *\n * Encapsulates the private key — no getPrivateKey() method is exposed.\n */\nexport class EvmSigner {\n readonly chainType = 'evm' as const;\n private readonly _account: PrivateKeyAccount;\n\n /**\n * @param privateKey - EVM private key as hex string (with or without 0x prefix) or Uint8Array\n */\n constructor(privateKey: string | Uint8Array) {\n let hexKey: Hex;\n if (privateKey instanceof Uint8Array) {\n hexKey = toHex(privateKey);\n } else {\n hexKey = (\n privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`\n ) as Hex;\n }\n this._account = privateKeyToAccount(hexKey);\n }\n\n /** Derived 0x EVM address */\n get address(): string {\n return this._account.address;\n }\n\n /** ChainSigner identifier — EVM address */\n get signerIdentifier(): string {\n return this._account.address;\n }\n\n /** Viem PrivateKeyAccount — usable with walletClient for on-chain transactions */\n get account(): PrivateKeyAccount {\n return this._account;\n }\n\n /**\n * Signs a balance proof using EIP-712 typed data.\n *\n * @param params - Balance proof parameters plus chain context\n * @returns Signed balance proof with signature\n */\n async signBalanceProof(\n params: BalanceProofParams & {\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n }\n ): Promise<SignedBalanceProof> {\n const domain = getBalanceProofDomain(\n params.chainId,\n params.tokenNetworkAddress\n );\n\n const signature = await this._account.signTypedData({\n domain,\n types: BALANCE_PROOF_TYPES,\n primaryType: 'BalanceProof',\n message: {\n channelId: params.channelId as Hex,\n nonce: BigInt(params.nonce),\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot as Hex,\n },\n });\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature,\n signerAddress: this._account.address,\n chainId: params.chainId,\n tokenNetworkAddress: params.tokenNetworkAddress,\n ...(params.tokenAddress && { tokenAddress: params.tokenAddress }),\n };\n }\n\n /**\n * Builds an EVMClaimMessage from a signed balance proof.\n * Static so it can be called without an EvmSigner instance.\n *\n * @param proof - Signed balance proof (includes chainId and tokenNetworkAddress)\n * @param senderId - Nostr pubkey or identifier of the sender\n * @returns EVMClaimMessage compatible with BTP_CLAIM_PROTOCOL\n */\n static buildClaimMessage(\n proof: SignedBalanceProof,\n senderId: string\n ): EVMClaimMessage {\n return {\n version: '1.0',\n blockchain: 'evm',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n channelId: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n lockedAmount: proof.lockedAmount.toString(),\n locksRoot: proof.locksRoot,\n signature: proof.signature,\n signerAddress: proof.signerAddress,\n chainId: proof.chainId,\n tokenNetworkAddress: proof.tokenNetworkAddress,\n ...(proof.tokenAddress && { tokenAddress: proof.tokenAddress }),\n };\n }\n}\n","/**\n * Transport resolution — resolves ClientTransportConfig into concrete\n * factories and URL rewrites for use by BTP and HTTP clients.\n *\n * Called during `initializeHttpMode()` before any connections are created.\n */\n\nimport type { ClientTransportConfig } from '../types.js';\n\n/**\n * Resolved transport artifacts ready for client wiring.\n */\nexport interface ResolvedTransport {\n /** Custom WebSocket factory for SOCKS5 mode (Node.js only). */\n createWebSocket?: (url: string) => WebSocket;\n /** Custom fetch implementation for SOCKS5 mode (Node.js only). */\n httpClient?: typeof fetch;\n /** Rewritten BTP URL for gateway mode. */\n btpUrl?: string;\n /** Rewritten connector URL for gateway mode. */\n connectorUrl?: string;\n /**\n * Teardown handle for a proxy this resolver STARTED (managed `anon` daemon).\n * Present only when `resolveTransport` auto-launched a managed proxy for a\n * `.anyone` host. `ToonClient.stop()` invokes it. Undefined for all\n * explicit-proxy / direct / gateway paths (the caller owns their own proxy).\n */\n stopManagedProxy?: () => Promise<void>;\n}\n\n/**\n * Returns true when `url`'s host ends in the `.anyone` TLD (ATOR hidden\n * service). Tolerant of `ws://`/`wss://` and a missing scheme.\n */\nfunction isAnyoneHost(url: string | undefined): boolean {\n if (!url) return false;\n try {\n const withScheme = /:\\/\\//.test(url) ? url : `ws://${url}`;\n const host = new URL(withScheme).hostname.toLowerCase();\n return host.endsWith('.anyone');\n } catch {\n return false;\n }\n}\n\n/**\n * Managed-proxy auto-start knob. `managedAnonProxy` defaults to `true`\n * (auto-start for `.anyone` hosts when no explicit proxy is configured).\n */\nexport interface ManagedProxyResolveOptions {\n /**\n * Opt-out switch. When explicitly `false`, the managed `anon` proxy is never\n * auto-started even for a `.anyone` host (the caller must supply their own\n * `transport.socksProxy` or `ANYONE_PROXY_URLS`). Default: auto (true).\n */\n managedAnonProxy?: boolean;\n /** Override the SOCKS port the managed daemon binds. Default 9050. */\n managedAnonSocksPort?: number;\n}\n\n/**\n * Resolves a transport config into concrete connection artifacts.\n *\n * - `direct` (or undefined): returns empty object (use defaults) — UNLESS the\n * btpUrl host ends in `.anyone` and no explicit proxy is configured, in which\n * case a managed `anon` SOCKS5h proxy is auto-started (zero-setup ATOR).\n * - `socks5`: dynamically imports Node.js SOCKS5 helpers, probes proxy\n * reachability (fail-closed), returns WebSocket factory + fetch wrapper.\n * - `gateway`: rewrites URLs to route through the gateway.\n *\n * @throws If SOCKS5 proxy is unreachable or URL is invalid.\n */\nexport async function resolveTransport(\n transport: ClientTransportConfig | undefined,\n originalBtpUrl?: string,\n originalConnectorUrl?: string,\n managedProxyOptions?: ManagedProxyResolveOptions\n): Promise<ResolvedTransport> {\n // Auto-managed ATOR: a `.anyone` btpUrl with no explicit proxy configured.\n // Triggers only when the caller did NOT supply an explicit socks5/gateway\n // transport, did NOT opt out (managedAnonProxy !== false), and the\n // ANYONE_PROXY_URLS escape hatch is unset. Browser bundles never reach the\n // node-only `anon-proxy` module — it is dynamically imported here, on the\n // Node-only `.anyone` path.\n const hasExplicitProxy =\n !!transport &&\n (transport.type === 'socks5' || transport.type === 'gateway');\n const envProxy = process.env['ANYONE_PROXY_URLS'];\n if (\n !hasExplicitProxy &&\n managedProxyOptions?.managedAnonProxy !== false &&\n !envProxy &&\n isAnyoneHost(originalBtpUrl)\n ) {\n const { startManagedAnonProxy } = await import('./anon-proxy.js');\n const { createSocks5WebSocketFactory, createSocks5Fetch } =\n await import('./socks5.js');\n\n const proxy = await startManagedAnonProxy({\n ...(managedProxyOptions?.managedAnonSocksPort !== undefined\n ? { socksPort: managedProxyOptions.managedAnonSocksPort }\n : {}),\n });\n\n try {\n return {\n createWebSocket: createSocks5WebSocketFactory(proxy.socksProxy),\n httpClient: createSocks5Fetch(proxy.socksProxy),\n stopManagedProxy: proxy.stop,\n };\n } catch (err) {\n // Wiring failed after the daemon started — tear it down so we don't leak.\n await proxy.stop();\n throw err;\n }\n }\n\n if (!transport || transport.type === 'direct') {\n return {};\n }\n\n if (transport.type === 'socks5') {\n const {\n createSocks5WebSocketFactory,\n createSocks5Fetch,\n probeSocks5Proxy,\n } = await import('./socks5.js');\n\n // Fail-closed: abort if proxy is unreachable\n await probeSocks5Proxy(transport.socksProxy);\n\n return {\n createWebSocket: createSocks5WebSocketFactory(transport.socksProxy),\n httpClient: createSocks5Fetch(transport.socksProxy),\n };\n }\n\n if (transport.type === 'gateway') {\n const { rewriteUrlsForGateway } = await import('./gateway.js');\n const rewritten = rewriteUrlsForGateway(\n transport.gatewayUrl,\n originalBtpUrl,\n originalConnectorUrl\n );\n return {\n btpUrl: rewritten.btpUrl,\n connectorUrl: rewritten.connectorUrl,\n };\n }\n\n // Exhaustiveness guard\n throw new Error(\n `Unknown transport type: \"${(transport as { type: string }).type}\"`\n );\n}\n","import { ed25519 } from '@noble/curves/ed25519.js';\nimport { base58Encode } from '@toon-protocol/core';\nimport type { SignedBalanceProof } from '../types.js';\nimport type {\n ChainSigner,\n ChainMetadata,\n ClaimMessage,\n SolanaClaimMessage,\n} from './types.js';\nimport { toHex as bytesToHex } from '../utils/binary.js';\nimport { buildBalanceProofMessage } from '../channel/solana-payment-channel.js';\n\n/**\n * Solana signer for the connector payment-channel claim path.\n *\n * Signs the connector's on-chain payment-channel balance-proof message — the\n * raw 48-byte `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)` (see\n * `@toon-protocol/connector` `SolanaPaymentChannelSDK._buildBalanceProofMessage`\n * + `solana-payment-channel-provider.verifyBalanceProof`). The produced 64-byte\n * Ed25519 signature verifies on the connector's `verifySolanaClaim` path, which\n * is what makes a client-issued Solana payment-channel claim (paying the apex\n * to write) acceptable on connector 3.9.0.\n *\n * NOTE: this is a DIFFERENT message from the Mill ↔ sender swap-claim wire\n * contract (`balanceProofHashSolana`, SDK `verifyEd25519Signature`). The client\n * here is paying a payment-channel claim to the apex, not issuing a swap claim,\n * so it must sign the connector's on-chain payment-channel message. `channelId`\n * MUST be the base58 channel PDA (produced by `OnChainChannelClient.openChannel`).\n */\nexport class SolanaSigner implements ChainSigner {\n readonly chainType = 'solana' as const;\n /** 32-byte Ed25519 seed. */\n private readonly privateKey: Uint8Array;\n private pubkeyBase58Cache?: string;\n\n /**\n * @param privateKey - 32-byte Ed25519 seed (e.g. `identity.solana.secretKey.slice(0, 32)`).\n * @param publicKeyBase58 - Optional base58 public key (e.g. `identity.solana.publicKey`).\n * When omitted it is derived lazily from `privateKey`.\n */\n constructor(privateKey: Uint8Array, publicKeyBase58?: string) {\n if (privateKey.length !== 32) {\n throw new Error(\n `SolanaSigner requires a 32-byte Ed25519 seed, got ${privateKey.length} bytes`\n );\n }\n this.privateKey = privateKey;\n this.pubkeyBase58Cache = publicKeyBase58;\n }\n\n private ensurePublicKey(): string {\n if (this.pubkeyBase58Cache) return this.pubkeyBase58Cache;\n const pk = ed25519.getPublicKey(this.privateKey);\n this.pubkeyBase58Cache = base58Encode(new Uint8Array(pk));\n return this.pubkeyBase58Cache;\n }\n\n get signerIdentifier(): string {\n return this.pubkeyBase58Cache ?? 'uninitialized';\n }\n\n async signBalanceProof(params: {\n channelId: string;\n nonce: number;\n transferredAmount: bigint;\n lockedAmount: bigint;\n locksRoot: string;\n recipient: string;\n metadata: ChainMetadata;\n }): Promise<SignedBalanceProof> {\n if (params.metadata.chainType !== 'solana') {\n throw new Error(\n `SolanaSigner cannot sign for chain type: ${params.metadata.chainType}`\n );\n }\n\n const base58 = this.ensurePublicKey();\n\n // Connector on-chain payment-channel balance-proof message:\n // channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)\n // `channelId` is the base58 channel PDA (from OnChainChannelClient.openChannel).\n // cumulativeAmount == transferredAmount. No recipient term — the connector's\n // verifyBalanceProof reconstructs exactly these three fields.\n const message = buildBalanceProofMessage(\n params.channelId,\n BigInt(params.nonce),\n params.transferredAmount\n );\n\n const signature = ed25519.sign(message, this.privateKey);\n const signatureHex = '0x' + bytesToHex(new Uint8Array(signature));\n\n return {\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n signature: signatureHex,\n signerAddress: base58,\n chainId: 0,\n tokenNetworkAddress: params.metadata.programId,\n recipient: params.recipient,\n };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\n // The connector verifies a base64 Ed25519 signature; the signed proof carries\n // a 0x-prefixed 64-byte hex signature, so convert hex -> bytes -> base64.\n const sigHex = proof.signature.startsWith('0x')\n ? proof.signature.slice(2)\n : proof.signature;\n const sigBytes = Uint8Array.from(\n sigHex.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? []\n );\n const signatureBase64 = Buffer.from(sigBytes).toString('base64');\n\n const claim: SolanaClaimMessage = {\n version: '1.0',\n blockchain: 'solana',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n // channelId IS the base58 channel PDA -> connector's channelAccount.\n channelAccount: proof.channelId,\n nonce: proof.nonce,\n transferredAmount: proof.transferredAmount.toString(),\n signature: signatureBase64,\n signerPublicKey: this.pubkeyBase58Cache ?? proof.signerAddress,\n programId: proof.tokenNetworkAddress,\n };\n return claim;\n }\n}\n","import { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type {\n ChainSigner,\n ChainMetadata,\n ClaimMessage,\n MinaClaimMessage,\n} from './types.js';\nimport {\n buildMinaPaymentChannelProof,\n loadMinaPaymentChannelBindings,\n} from '../channel/mina-payment-channel.js';\nimport { readMinaDepositTotal } from '../channel/mina-deposit.js';\n\n/** Reads a channel zkApp's on-chain `depositTotal` (base units). */\nexport type MinaDepositReader = (zkAppAddress: string) => Promise<bigint>;\n\n/** Optional `MinaSigner` wiring for on-chain `depositTotal` resolution. */\nexport interface MinaSignerOptions {\n /**\n * Mina GraphQL URL used to read the channel's on-chain `depositTotal` when a\n * caller doesn't supply it to `signBalanceProof`. Enables conserved\n * `balanceB = depositTotal − balanceA` claims (settleable on funded zkApps).\n */\n graphqlUrl?: string;\n /** Inject a deposit reader (tests / custom transport). Overrides `graphqlUrl`. */\n depositReader?: MinaDepositReader;\n}\n\n/** Default Mina token id when the metadata omits one. */\nconst DEFAULT_MINA_TOKEN_ID = 'MINA';\n\n/** Mina network id carried in the claim (matches the connector devnet prefix). */\nconst MINA_CLAIM_NETWORK = 'devnet' as const;\n\n/**\n * Pallas base-field-safe salt derived from `(zkAppAddress, nonce)`.\n *\n * The commitment binds an arbitrary salt; we derive it deterministically so the\n * same (channel, nonce) reproduces the same proof, and take the first 240 bits\n * of `sha256` to stay safely inside the Pallas field (< 2^254). Non-zero by\n * construction (connector `validateMinaClaim` requires a non-empty `salt`).\n */\nfunction deriveMinaSalt(zkAppAddress: string, nonce: number): bigint {\n const digestHex = bytesToHex(\n sha256(new TextEncoder().encode(`mina-pc-salt:${zkAppAddress}:${nonce}`))\n );\n const salt = BigInt('0x' + digestHex.slice(0, 60));\n return salt === 0n ? 1n : salt;\n}\n\n/**\n * Mina (Pallas) signer for the connector payment-channel claim path.\n *\n * Produces the connector 3.9.0 `MinaClaimMessage` contract — `{ zkAppAddress,\n * tokenId, balanceCommitment, proof (base64), salt, nonce }` — by reproducing\n * `MinaPaymentChannelSDK.signBalanceProof` exactly (via\n * {@link buildMinaPaymentChannelProof}):\n *\n * commitment = Poseidon([Field(balanceA), Field(0), Field(salt)])\n * channelHashField = Poseidon([participantA.x, participantB.x, 0]) (see below)\n * proof = base64(JSON{ commitment, signature: { r, s }, nonce, signerPublicKey })\n *\n * with the Schnorr signature computed over `[commitment, Field(nonce),\n * channelHashField]` using the Mina `'devnet'` network id (matching o1js's\n * hardcoded `Signature.create` prefix). Verified field-by-field against the\n * connector's o1js `Signature.fromJSON({r,s}).verify` (see the package tests).\n *\n * `channelHashField` is the ON-CHAIN participant form\n * (`Poseidon([client.x, apex.x, 0])`, participantA=client, participantB=apex)\n * whenever the apex's Mina pubkey is known (the negotiated `recipient`), so the\n * claim can SETTLE on-chain via the zkApp's `claimFromChannel` (which only\n * verifies the participant form). When the apex pubkey is unavailable the signer\n * falls back to the legacy zkApp-x form (`Poseidon([zkApp.x])`); the connector's\n * off-chain `verifyBalanceProof` accepts EITHER, so off-chain store/FULFILL works\n * in both cases — only on-chain settle requires the participant form.\n *\n * NOTE: this is a DIFFERENT message + format from the Mill ↔ sender swap-claim\n * wire contract (`balanceProofFieldsMina` in `@toon-protocol/core`, verified by\n * the SDK's `verifyMinaSignature`). The client here pays a payment-channel claim\n * to the apex, so it signs the connector's on-chain payment-channel scheme; the\n * swap-format hash is left untouched (mirrors the Solana #105 separation).\n *\n * `channelId` MUST be the deployed payment-channel zkApp B62 address (the same\n * address the apex's Mina provider resolves on-chain via `getChannelState`),\n * which is what `OnChainChannelClient.openMinaChannel` returns.\n *\n * `mina-signer` is an OPTIONAL dependency: its crypto (Poseidon, Pallas Schnorr,\n * the base58 signature codec) is loaded dynamically so the client builds and runs\n * for non-Mina users without it installed, and WITHOUT pulling the o1js WASM\n * circuit runtime.\n */\nexport class MinaSigner implements ChainSigner {\n readonly chainType = 'mina' as const;\n /** Big-endian hex scalar (or already-`EK…` base58) Mina private key. */\n private readonly privateKey: string;\n private publicKeyBase58?: string;\n private readonly depositReader?: MinaDepositReader;\n /** Per-zkApp `depositTotal` cache (deposits are rare; the connector re-reads). */\n private readonly depositCache = new Map<string, bigint>();\n\n /**\n * @param privateKey - Mina private key as big-endian hex scalar (the form\n * `deriveFullIdentity()` emits, `identity.mina.privateKey`) or an `EK…`\n * base58 key. Converted to the base58check form mina-signer requires.\n * @param publicKeyBase58 - Optional base58 public key (e.g.\n * `identity.mina.publicKey`). When omitted it is derived during signing.\n * @param options - Optional on-chain `depositTotal` resolution (graphqlUrl or\n * an injected reader) so claims conserve balances on funded zkApps.\n */\n constructor(\n privateKey: string,\n publicKeyBase58?: string,\n options?: MinaSignerOptions\n ) {\n this.privateKey = privateKey;\n this.publicKeyBase58 = publicKeyBase58;\n if (options?.depositReader) {\n this.depositReader = options.depositReader;\n } else if (options?.graphqlUrl) {\n const url = options.graphqlUrl;\n this.depositReader = (zkAppAddress) =>\n readMinaDepositTotal(url, zkAppAddress);\n }\n }\n\n /**\n * Resolve the channel's on-chain `depositTotal`, caching per zkApp. Returns\n * `undefined` when no reader is configured or the read fails — callers then\n * fall back to the legacy `balanceB = 0` commitment.\n */\n private async resolveDepositTotal(\n zkAppAddress: string\n ): Promise<bigint | undefined> {\n if (this.depositCache.has(zkAppAddress)) {\n return this.depositCache.get(zkAppAddress);\n }\n if (!this.depositReader) return undefined;\n try {\n const depositTotal = await this.depositReader(zkAppAddress);\n this.depositCache.set(zkAppAddress, depositTotal);\n return depositTotal;\n } catch {\n return undefined;\n }\n }\n\n get signerIdentifier(): string {\n return this.publicKeyBase58 ?? 'uninitialized';\n }\n\n /** Derive this signer's B62 public key from its (base58) private key. */\n private async deriveOwnPublicKey(\n minaPrivateKeyBase58: string\n ): Promise<string> {\n const { Client } = await loadMinaPaymentChannelBindings();\n return new Client({ network: MINA_CLAIM_NETWORK }).derivePublicKey(\n minaPrivateKeyBase58\n );\n }\n\n async signBalanceProof(params: {\n channelId: string;\n nonce: number;\n transferredAmount: bigint;\n lockedAmount: bigint;\n locksRoot: string;\n recipient: string;\n metadata: ChainMetadata;\n /**\n * On-chain channel `depositTotal`. When provided (>0), the signed commitment\n * binds `balanceB = depositTotal − balanceA` (the funder's remaining\n * balance), matching the connector's claimFromChannel reconstruction\n * (toon-protocol/connector#133) and the on-chain circuit's\n * `balanceA + balanceB == depositTotal` invariant. Omitted/0 keeps the\n * legacy `balanceB = 0` form (off-chain-store-only, non-settleable).\n */\n depositTotal?: bigint;\n }): Promise<SignedBalanceProof> {\n if (params.metadata.chainType !== 'mina') {\n throw new Error(\n `MinaSigner cannot sign for chain type: ${params.metadata.chainType}`\n );\n }\n\n // The zkApp address IS the channel id: it is the channel-hash preimage the\n // connector binds the proof to and the on-chain account it reads. Prefer the\n // negotiated channelId, fall back to the metadata zkAppAddress.\n const zkAppAddress = params.channelId || params.metadata.zkAppAddress;\n if (!zkAppAddress) {\n throw new Error(\n 'MinaSigner requires a zkAppAddress (channel id) to sign a balance proof'\n );\n }\n\n const minaPrivateKey = hexToMinaBase58PrivateKey(this.privateKey);\n const tokenId = params.metadata.tokenId ?? DEFAULT_MINA_TOKEN_ID;\n const salt = deriveMinaSalt(zkAppAddress, params.nonce);\n\n // Derive the client's own Mina pubkey now (needed as participantA for the\n // on-chain channelHash). `buildMinaPaymentChannelProof` derives it too, but\n // we need it here to pass the participant pair.\n const clientPubKey =\n this.publicKeyBase58 ?? (await this.deriveOwnPublicKey(minaPrivateKey));\n this.publicKeyBase58 = clientPubKey;\n\n // The apex's Mina settlement pubkey (B62) is the channel counterparty\n // (participantB). It flows through as the negotiated recipient. When present,\n // sign over the on-chain participant-form channelHash so the claim can SETTLE\n // on-chain (the zkApp's claimFromChannel verifies sigA over\n // Poseidon([client.x, apex.x, 0])). Order matches the open:\n // participantA = client (payer), participantB = apex (peer).\n const apexPubKey =\n params.recipient && /^B62[a-zA-Z0-9]{40,60}$/.test(params.recipient)\n ? params.recipient\n : undefined;\n\n // Conserved counterparty balance for the signed commitment. The on-chain\n // PaymentChannel.claimFromChannel circuit verifies signatureA over\n // Poseidon([balanceA, balanceB, salt]) AND asserts balanceA + balanceB ==\n // depositTotal. The connector reconstructs balanceB = depositTotal − balanceA\n // from the public on-chain depositTotal (toon-protocol/connector#133), so the\n // CLIENT must sign over that same balanceB or signatureA fails verification\n // (\"participant A signature verification failed\") at proof generation. When\n // depositTotal is unknown (legacy/off-chain-only), fall back to balanceB = 0.\n // Prefer a caller-supplied depositTotal; otherwise self-resolve it from\n // chain (when this signer was configured with a graphqlUrl / reader) so the\n // claim conserves balances on a FUNDED zkApp. Falls back to balanceB = 0\n // (legacy, off-chain-store-only) when neither is available.\n const depositTotal =\n params.depositTotal ?? (await this.resolveDepositTotal(zkAppAddress));\n let balanceB = 0n;\n if (depositTotal != null && depositTotal > 0n) {\n if (params.transferredAmount > depositTotal) {\n throw new Error(\n `Mina claim balanceA (${params.transferredAmount}) exceeds on-chain ` +\n `depositTotal (${depositTotal}) — cannot conserve balances`\n );\n }\n balanceB = depositTotal - params.transferredAmount;\n }\n\n const built = await buildMinaPaymentChannelProof({\n zkAppAddress,\n minaPrivateKeyBase58: minaPrivateKey,\n signerPublicKey: clientPubKey,\n // Recipient-credit (unidirectional): party A carries the cumulative amount;\n // party B carries the funder's remaining balance (depositTotal − balanceA)\n // so the signed commitment conserves and the on-chain claimFromChannel\n // signatureA check passes. `signatureB` remains apex-co-signed downstream.\n balanceA: params.transferredAmount,\n balanceB,\n salt,\n nonce: BigInt(params.nonce),\n // Participant-form channelHash (on-chain-settleable) when the apex pubkey\n // is known; otherwise the legacy zkApp-x form (off-chain-store only).\n ...(apexPubKey\n ? { participantA: clientPubKey, participantB: apexPubKey }\n : {}),\n });\n this.publicKeyBase58 = built.signerPublicKey;\n\n return {\n channelId: zkAppAddress,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n // `signature` is unused on the Mina wire (the proof carries the Schnorr\n // signature); keep the base64 proof here too for symmetry / debugging.\n signature: built.proof,\n signerAddress: built.signerPublicKey,\n chainId: 0,\n tokenNetworkAddress: zkAppAddress,\n recipient: params.recipient,\n mina: {\n balanceCommitment: built.balanceCommitment,\n proof: built.proof,\n salt: built.salt,\n tokenId,\n },\n };\n }\n\n buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage {\n if (!proof.mina) {\n throw new Error(\n 'MinaSigner.buildClaimMessage requires a Mina-signed proof (missing `mina` fields)'\n );\n }\n const claim: MinaClaimMessage = {\n version: '1.0',\n blockchain: 'mina',\n messageId: crypto.randomUUID(),\n timestamp: new Date().toISOString().replace(/\\.\\d{3}Z$/, '.000Z'),\n senderId,\n zkAppAddress: proof.channelId,\n tokenId: proof.mina.tokenId,\n balanceCommitment: proof.mina.balanceCommitment,\n nonce: proof.nonce,\n proof: proof.mina.proof,\n salt: proof.mina.salt,\n transferredAmount: proof.transferredAmount.toString(),\n // Surface the signer's Mina pubkey top-level (it is also embedded in the\n // base64 `proof`). The connector's SettlementExecutor reads\n // `latestClaim.signerPublicKey` to resolve participant keys for the\n // on-chain claimFromChannel on an inbound/externally-opened channel;\n // without it the Mina SDK throws ACCOUNT_NOT_FOUND. `signerAddress`\n // carries the B62 base58 pubkey for Mina proofs (see MinaSigner.sign*).\n signerPublicKey: proof.signerAddress,\n network: MINA_CLAIM_NETWORK,\n };\n return claim;\n }\n}\n","/**\n * Mina payment-channel primitives — connector-parity.\n *\n * Pure, dependency-light helpers that reproduce the EXACT off-chain\n * balance-proof contract the connector's `MinaPaymentChannelSDK`\n * (`@toon-protocol/connector` `settlement/mina-payment-channel-sdk.ts`)\n * implements, so a client-issued Mina payment-channel claim is structurally\n * accepted by connector 3.9.0's `validateMinaClaim` PREPARE gate and (once the\n * connector's proof-encoding bug is fixed, see below) verified by its\n * `verifyMinaClaim` / `verifyBalanceProof` settlement path.\n *\n * ## The connector's payment-channel scheme (authoritative, from 3.9.0 dist)\n *\n * `MinaPaymentChannelSDK.signBalanceProof(channel, balA, balB, salt, nonce)`:\n *\n * commitment = Poseidon.hash([Field(balA), Field(balB), Field(salt)])\n * channelHashField = Poseidon.hash([PublicKey.fromBase58(zkAppAddress).x])\n * message = [commitment, Field(nonce), channelHashField]\n * signature = Signature.create(privKey, message) // Schnorr, Pallas\n * proof (string) = JSON.stringify({\n * commitment: commitment.toString(),\n * signature: { r, s }, // o1js JSON form\n * nonce: nonce.toString(),\n * })\n *\n * `MinaPaymentChannelSDK.verifyBalanceProof(channel, balanceCommitment, proof, nonce)`\n * re-derives `channelHashField` from `channel`, parses `proof` as the JSON above,\n * checks `proof.commitment === balanceCommitment` and `BigInt(proof.nonce) === nonce`,\n * then `Signature.fromJSON({r,s}).verify(signerPubKey, [commitment, Field(nonce),\n * channelHashField])`. The signer prefix is HARDCODED to `'devnet'` inside o1js\n * `Signature.create` / `.verify`, so the off-chain signature MUST be produced\n * with the Mina `'devnet'` network id (NOT `'mainnet'`).\n *\n * This is DISTINCT from the Mill ↔ sender swap-claim wire contract\n * (`balanceProofFieldsMina` in `@toon-protocol/core` — a Schnorr signature over\n * `[minaHashToField(channelId), amount, nonce, minaHashToField(recipient)]`).\n * That format is unchanged; this module is the separate payment-channel path\n * (mirrors the Solana #105 precedent: a payment-channel-specific message\n * distinct from the swap-format hash).\n *\n * ## Why mina-signer (not o1js)\n *\n * Poseidon, the Pallas Schnorr signature, the base58 signature codec and the\n * Pallas `PublicKey` field decode are all shipped INSIDE `mina-signer` (the same\n * implementations o1js re-exports). We reach them via a file-URL deep import off\n * the resolved `mina-signer` package root, so the client reproduces the\n * connector's scheme byte-for-byte WITHOUT pulling the multi-hundred-MB o1js\n * WASM circuit runtime. `mina-signer` is an OPTIONAL dependency.\n *\n * ## ⚠️ Connector 3.9.0 proof-encoding bug (documented, NOT worked around here)\n *\n * `validateMinaClaim` requires `claim.proof` to match `/^[A-Za-z0-9+/]+=*$/`\n * (base64), but `verifyBalanceProof` (and the connector's OWN claim producer\n * `per-packet-claim-service`) treat `proof` as RAW JSON (`JSON.parse(proof)` with\n * no base64 decode — and the connector emits the raw `JSON.stringify(...)` above).\n * Raw JSON fails the base64 regex, so a raw-JSON proof is REJECTED at the PREPARE\n * gate (no FULFILL). To pass the PREPARE gate (the FULFILL-deciding path) the\n * client MUST base64-encode the proof JSON. The connector's settlement-side\n * `JSON.parse` then fails on the base64 string — but that is post-FULFILL and is\n * the connector's own inconsistency (its producer and consumer disagree with its\n * validator). For non-EVM dynamic hidden-service peers settlement is gated by\n * connector#88 (`No chain configured for peer`) regardless, so this is a\n * documented connector follow-up, not a client defect. See\n * {@link buildMinaPaymentChannelProof} `proofEncoding`.\n *\n * @module\n */\n\n/** Result of building a connector-parity Mina payment-channel proof. */\nexport interface MinaPaymentChannelProof {\n /** `Poseidon([balanceA, balanceB, salt]).toString()` — the claim's `balanceCommitment`. */\n balanceCommitment: string;\n /**\n * The claim's `proof` field: base64-encoded JSON\n * `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. Base64 is\n * REQUIRED to pass connector 3.9.0 `validateMinaClaim` (see module note).\n */\n proof: string;\n /** Decimal salt string — the claim's `salt` field. */\n salt: string;\n /** Base58 signer public key — echoed for convenience. */\n signerPublicKey: string;\n}\n\ninterface MinaSignerClientLike {\n signFields(fields: bigint[], privateKey: string): { signature: string };\n derivePublicKey(privateKey: string): string;\n}\ntype MinaSignerClientCtor = new (opts: {\n network: string;\n}) => MinaSignerClientLike;\n\ninterface PoseidonLike {\n hash(input: bigint[]): bigint;\n}\ninterface SignatureCodecLike {\n fromBase58(base58: string): { r: bigint; s: bigint };\n}\ninterface PublicKeyCodecLike {\n fromBase58(base58: string): { x: bigint; y: bigint };\n}\n\ninterface MinaCryptoBindings {\n Client: MinaSignerClientCtor;\n Poseidon: PoseidonLike;\n Signature: SignatureCodecLike;\n PublicKey: PublicKeyCodecLike;\n}\n\nlet cachedBindings: MinaCryptoBindings | null = null;\n\n/**\n * Resolve the `mina-signer` package and load both its public `Client` and the\n * internal Poseidon / Signature / PublicKey codecs via file-URL deep import.\n *\n * `mina-signer`'s `package.json` `exports` map does NOT expose its\n * `bindings/crypto/poseidon.js` or `mina-signer/src/{signature,curve-bigint}.js`\n * subpaths, so a bare-specifier deep import (`import('mina-signer/.../poseidon.js')`)\n * is blocked (`ERR_PACKAGE_PATH_NOT_EXPORTED`). Node only applies the exports\n * gate to bare specifiers, NOT to `file://` URLs — so we resolve the main entry\n * to a URL and navigate to the sibling internal files. These are the same\n * implementations o1js ships; reaching them here gives byte-for-byte parity with\n * the connector's o1js verify without the o1js WASM runtime.\n */\nexport async function loadMinaPaymentChannelBindings(): Promise<MinaCryptoBindings> {\n if (cachedBindings) return cachedBindings;\n\n const specifier = 'mina-signer';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const lib: any = await import(/* @vite-ignore */ specifier);\n const Client: MinaSignerClientCtor = 'default' in lib ? lib.default : lib;\n\n // Resolve the main entry to a file URL, then navigate to the internal modules.\n // mina-signer main: <root>/dist/node/mina-signer/mina-signer.js\n //\n // import.meta.resolve is unavailable under some module transforms (notably\n // vitest's SSR, which replaces import.meta and drops `.resolve` → throws\n // \"__vite_ssr_import_meta__.resolve is not a function\"). Fall back to Node's\n // createRequire().resolve and convert to a file URL. Both the ESM and CJS\n // entries live in dist/node/mina-signer/, so the relative navigation below is\n // identical either way.\n const resolveFn = (import.meta as { resolve?: (specifier: string) => string })\n .resolve;\n let mainUrl: string;\n if (typeof resolveFn === 'function') {\n mainUrl = resolveFn(specifier);\n } else {\n const { createRequire } = await import('node:module');\n const { pathToFileURL } = await import('node:url');\n mainUrl = pathToFileURL(\n createRequire(import.meta.url).resolve(specifier)\n ).href;\n }\n const minaSignerDir = new URL('./', mainUrl); // .../dist/node/mina-signer/\n const poseidonUrl = new URL('../bindings/crypto/poseidon.js', minaSignerDir)\n .href;\n const signatureUrl = new URL('./src/signature.js', minaSignerDir).href;\n const curveUrl = new URL('./src/curve-bigint.js', minaSignerDir).href;\n\n const [poseidonMod, signatureMod, curveMod] = await Promise.all([\n import(/* @vite-ignore */ poseidonUrl),\n import(/* @vite-ignore */ signatureUrl),\n import(/* @vite-ignore */ curveUrl),\n ]);\n\n cachedBindings = {\n Client,\n Poseidon: poseidonMod.Poseidon as PoseidonLike,\n Signature: signatureMod.Signature as SignatureCodecLike,\n PublicKey: curveMod.PublicKey as PublicKeyCodecLike,\n };\n return cachedBindings;\n}\n\n/** Reset the cached bindings (test hook). */\nexport function _resetMinaBindingsCache(): void {\n cachedBindings = null;\n}\n\n/**\n * Compute the connector's Mina balance commitment:\n * `Poseidon.hash([Field(balanceA), Field(balanceB), Field(salt)])`.\n */\nexport function minaBalanceCommitment(\n poseidon: PoseidonLike,\n balanceA: bigint,\n balanceB: bigint,\n salt: bigint\n): bigint {\n return poseidon.hash([balanceA, balanceB, salt]);\n}\n\n/**\n * Compute the connector's LEGACY channel-hash field, bound into the signed\n * message: `Poseidon.hash([PublicKey.fromBase58(zkAppAddress).x])`.\n *\n * This is the off-chain-only form. The connector's `verifyBalanceProof` accepts\n * a signature over EITHER this OR the participant form (see\n * {@link minaParticipantChannelHashField}), but the on-chain\n * `PaymentChannel.claimFromChannel` only accepts the PARTICIPANT form — so for a\n * claim that must SETTLE on-chain, sign the participant form instead.\n */\nexport function minaChannelHashField(\n poseidon: PoseidonLike,\n publicKeyCodec: PublicKeyCodecLike,\n zkAppAddress: string\n): bigint {\n const zkAppPubKey = publicKeyCodec.fromBase58(zkAppAddress);\n return poseidon.hash([zkAppPubKey.x]);\n}\n\n/**\n * Compute the ON-CHAIN channel-hash field the zkApp stores and verifies:\n * `Poseidon.hash([participantA.x, participantB.x, channelNonce])`.\n *\n * This MUST byte-for-byte reproduce what `PaymentChannel.initializeChannel`\n * wrote (`Poseidon([participantA.x, participantB.x, nonce])`, see\n * `packages/mina-zkapp/src/PaymentChannel.ts`) so that:\n * 1. the on-chain `claimFromChannel` signature check\n * (`signatureA.verify(participantA, [commitment, nonce, storedChannelHash])`)\n * passes, and\n * 2. the connector's off-chain `verifyBalanceProof` accepts it via its\n * on-chain-channelHash message branch.\n *\n * Participant ORDER must match how the channel was opened. The client opens with\n * `participantA = client (payer)`, `participantB = apex (peer)`\n * (`OnChainChannelClient.openMinaChannel` → `openMinaChannelOnChain`), so the\n * client signs `Poseidon([client.x, apex.x, 0])`. The connector tries both\n * orderings when reconstructing, so it resolves the matching one regardless.\n */\nexport function minaParticipantChannelHashField(\n poseidon: PoseidonLike,\n publicKeyCodec: PublicKeyCodecLike,\n participantA_B62: string,\n participantB_B62: string,\n channelNonce: bigint\n): bigint {\n const a = publicKeyCodec.fromBase58(participantA_B62);\n const b = publicKeyCodec.fromBase58(participantB_B62);\n return poseidon.hash([a.x, b.x, channelNonce]);\n}\n\n/**\n * Build a connector-parity Mina payment-channel balance proof.\n *\n * Reproduces `MinaPaymentChannelSDK.signBalanceProof` exactly: computes the\n * Poseidon balance commitment, the Poseidon channel-hash field, signs the\n * `[commitment, Field(nonce), channelHashField]` Pallas Schnorr message with the\n * `'devnet'` network id (matching o1js's hardcoded prefix), decodes the base58\n * signature to `{ r, s }` (o1js JSON form), and serializes\n * `{ commitment, signature: { r, s }, nonce, signerPublicKey }`.\n *\n * @param params.zkAppAddress Deployed payment-channel zkApp B62 address — the\n * claim's `zkAppAddress` AND the channel-hash preimage. MUST be the SAME\n * address the apex's Mina provider resolves on-chain.\n * @param params.minaPrivateKeyBase58 Signer's Mina `EK…` base58 private key.\n * @param params.balanceA Cumulative amount credited to the recipient (apex).\n * @param params.balanceB Counterparty balance (0 for the unidirectional\n * recipient-credit direction; `balanceB`/`signatureB` are OPTIONAL at\n * connector validation, so single-party suffices).\n * @param params.salt Commitment salt (bigint).\n * @param params.nonce Monotonic claim nonce.\n * @param params.proofEncoding `'base64'` (default) base64-encodes the proof JSON\n * so it passes connector 3.9.0 `validateMinaClaim`'s base64 regex (REQUIRED for\n * FULFILL). `'json'` emits the raw JSON the connector's `verifyBalanceProof`\n * actually parses (rejected at the PREPARE gate today; provided for forward\n * compatibility / tests once the connector's regex is fixed). See module note.\n */\nexport async function buildMinaPaymentChannelProof(params: {\n zkAppAddress: string;\n minaPrivateKeyBase58: string;\n signerPublicKey?: string;\n balanceA: bigint;\n balanceB: bigint;\n salt: bigint;\n nonce: bigint;\n proofEncoding?: 'base64' | 'json';\n /**\n * Participant pubkeys (B62) of the on-chain channel. When BOTH are supplied,\n * the proof is signed over the ON-CHAIN participant-form channelHash\n * (`Poseidon([participantA.x, participantB.x, channelNonce])`) instead of the\n * legacy zkApp-x form, so the resulting claim can settle on-chain via the\n * zkApp's `claimFromChannel` (which only verifies the participant form). The\n * order MUST match how the channel was opened (participantA = client/payer,\n * participantB = apex/peer). The connector accepts EITHER form off-chain, so\n * this is strictly an enabler for the on-chain settle path.\n */\n participantA?: string;\n participantB?: string;\n /** Channel nonce baked into the on-chain channelHash (default 0). */\n channelNonce?: bigint;\n}): Promise<MinaPaymentChannelProof> {\n const { Client, Poseidon, Signature, PublicKey } =\n await loadMinaPaymentChannelBindings();\n\n // o1js `Signature.create`/`.verify` hardcode the 'devnet' prefix; the off-chain\n // signature must be produced with the matching mina-signer network id.\n const client = new Client({ network: 'devnet' });\n const signerPublicKey =\n params.signerPublicKey ??\n client.derivePublicKey(params.minaPrivateKeyBase58);\n\n const commitment = minaBalanceCommitment(\n Poseidon,\n params.balanceA,\n params.balanceB,\n params.salt\n );\n // Sign over the on-chain participant-form channelHash when both participants\n // are known (enables on-chain settle); otherwise fall back to the legacy\n // zkApp-x form (off-chain-store-only). Both are accepted by the connector's\n // verifyBalanceProof.\n const channelHashField =\n params.participantA && params.participantB\n ? minaParticipantChannelHashField(\n Poseidon,\n PublicKey,\n params.participantA,\n params.participantB,\n params.channelNonce ?? 0n\n )\n : minaChannelHashField(Poseidon, PublicKey, params.zkAppAddress);\n\n const message = [commitment, params.nonce, channelHashField];\n const signed = client.signFields(message, params.minaPrivateKeyBase58);\n // mina-signer `signFields` emits a base58 signature string; the connector\n // expects the o1js JSON `{ r, s }` decimal form, reachable by decoding the\n // base58 sig with the shared Pallas codec.\n const { r, s } = Signature.fromBase58(signed.signature);\n\n const proofObject = {\n commitment: commitment.toString(),\n signature: { r: r.toString(), s: s.toString() },\n nonce: params.nonce.toString(),\n signerPublicKey,\n };\n const proofJson = JSON.stringify(proofObject);\n const encoding = params.proofEncoding ?? 'base64';\n const proof =\n encoding === 'base64'\n ? Buffer.from(proofJson, 'utf8').toString('base64')\n : proofJson;\n\n return {\n balanceCommitment: commitment.toString(),\n proof,\n salt: params.salt.toString(),\n signerPublicKey,\n };\n}\n","/**\n * Read a Mina payment-channel zkApp's on-chain `depositTotal` via a plain\n * GraphQL query (no o1js / WASM). Used by {@link MinaSigner} to bind the\n * conserved `balanceB = depositTotal − balanceA` commitment that a FUNDED zkApp\n * requires (connector#133); without it the connector's `claimFromChannel`\n * verification rejects the claim with `F06 - Invalid zk-SNARK proof on claim`.\n *\n * The `PaymentChannel` zkApp app-state field order is\n * `[channelHash, balanceCommitment, nonceField, channelState, depositTotal, …]`\n * (see `mina-channel-open.ts`), so `depositTotal` is `zkappState[4]`.\n */\n\n/** GraphQL response shape (only the fields we read). */\ninterface AccountStateResponse {\n data?: { account?: { zkappState?: string[] | null } | null } | null;\n errors?: { message: string }[];\n}\n\nconst DEPOSIT_TOTAL_STATE_INDEX = 4;\n\n/**\n * Query `account(publicKey).zkappState` and return the channel's `depositTotal`\n * (base units). Throws when the account/state is unavailable so callers can fall\n * back to the legacy `balanceB = 0` behavior.\n *\n * @param fetchImpl - injectable for tests; defaults to global `fetch`.\n */\nexport async function readMinaDepositTotal(\n graphqlUrl: string,\n zkAppAddress: string,\n fetchImpl: typeof fetch = fetch\n): Promise<bigint> {\n const query = 'query($pk:String!){account(publicKey:$pk){zkappState}}';\n const res = await fetchImpl(graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ query, variables: { pk: zkAppAddress } }),\n });\n if (!res.ok) {\n throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);\n }\n const json = (await res.json()) as AccountStateResponse;\n if (json.errors && json.errors.length > 0) {\n throw new Error(\n `Mina GraphQL error: ${json.errors[0]?.message ?? 'unknown'}`\n );\n }\n const state = json.data?.account?.zkappState;\n if (!state || state.length <= DEPOSIT_TOTAL_STATE_INDEX) {\n throw new Error(\n `Mina zkApp ${zkAppAddress} has no readable zkappState (account not found or not a zkApp)`\n );\n }\n return BigInt(state[DEPOSIT_TOTAL_STATE_INDEX] as string);\n}\n","import { EvmSigner } from '../signing/evm-signer.js';\nimport type { ChainSigner, ChainMetadata } from '../signing/types.js';\nimport type { SignedBalanceProof } from '../types.js';\nimport type { ChannelStore } from './ChannelStore.js';\nimport type { ConnectorChannelClient } from '@toon-protocol/core';\n\ninterface ChannelTracking {\n nonce: number;\n cumulativeAmount: bigint;\n chainType: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n /**\n * Counterparty settlement address on this channel's chain. Required to sign\n * Solana/Mina balance proofs (folded into the canonical message); unused for\n * the EVM EIP-712 path.\n */\n recipient?: string;\n /**\n * On-chain channel `depositTotal` (base units), captured at channel-open time\n * (#220). Threaded into the Mina signer so it binds `balanceB = depositTotal −\n * balanceA` (toon-protocol/connector#133); the Solana signer ignores it and\n * the EVM path never reads it. When unset (e.g. a channel RESUMED via\n * `trackChannel` rather than opened, or an idempotent re-open that didn't\n * surface it), the Mina signer self-resolves it from chain via its GraphQL URL\n * (#223).\n */\n depositTotal?: bigint;\n}\n\nexport interface ChannelManagerConfig {\n initialDeposit?: string;\n settlementTimeout?: number;\n}\n\nexport interface PeerNegotiation {\n chain: string;\n chainType: string;\n chainId: number | string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n initialDeposit?: string;\n settlementTimeout?: number;\n}\n\n/**\n * Local nonce tracking, multi-chain signing, and lazy channel opening.\n *\n * Supports multiple ChainSigner implementations (EVM, Solana, Mina).\n * The ensureChannel() method provides idempotent lazy channel opening.\n */\nexport class ChannelManager {\n private readonly channels = new Map<string, ChannelTracking>();\n private readonly chainSigners = new Map<string, ChainSigner>();\n private readonly peerChannels = new Map<string, string>();\n private readonly pendingOpens = new Map<string, Promise<string>>();\n private readonly store?: ChannelStore;\n private readonly defaultInitialDeposit: string;\n private readonly defaultSettlementTimeout: number;\n private channelClient?: ConnectorChannelClient;\n\n // Legacy: keep EvmSigner reference for backwards compatibility\n private readonly evmSigner?: EvmSigner;\n\n constructor(\n evmSigner?: EvmSigner,\n store?: ChannelStore,\n config?: ChannelManagerConfig\n ) {\n this.evmSigner = evmSigner;\n this.store = store;\n this.defaultInitialDeposit = config?.initialDeposit ?? '100000';\n this.defaultSettlementTimeout = config?.settlementTimeout ?? 86400;\n }\n\n /**\n * Register a chain-specific signer.\n */\n registerChainSigner(chainType: string, signer: ChainSigner): void {\n this.chainSigners.set(chainType, signer);\n }\n\n /**\n * Set the on-chain channel client for lazy channel opening.\n */\n setChannelClient(client: ConnectorChannelClient): void {\n this.channelClient = client;\n }\n\n /**\n * Get the signer for a tracked channel's chain type.\n * For EVM, returns an adapter wrapping the EvmSigner.\n */\n getSignerForChannel(channelId: string): ChainSigner {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n\n // Check non-EVM signers first\n const signer = this.chainSigners.get(tracking.chainType);\n if (signer) return signer;\n\n // EVM: wrap EvmSigner as ChainSigner adapter\n if (tracking.chainType === 'evm' && this.evmSigner) {\n const evmSigner = this.evmSigner;\n return {\n chainType: 'evm' as const,\n signerIdentifier: evmSigner.address,\n async signBalanceProof(params) {\n if (params.metadata.chainType !== 'evm')\n throw new Error('Expected EVM metadata');\n return evmSigner.signBalanceProof({\n channelId: params.channelId,\n nonce: params.nonce,\n transferredAmount: params.transferredAmount,\n lockedAmount: params.lockedAmount,\n locksRoot: params.locksRoot,\n chainId: params.metadata.chainId,\n tokenNetworkAddress: params.metadata.tokenNetworkAddress,\n tokenAddress: params.metadata.tokenAddress,\n });\n },\n buildClaimMessage(proof, senderId) {\n return EvmSigner.buildClaimMessage(proof, senderId);\n },\n };\n }\n\n throw new Error(\n `No signer registered for chain type: ${tracking.chainType}`\n );\n }\n\n /**\n * Lazily open a channel for a peer. Idempotent — returns existing channel\n * if already open. Deduplicates concurrent opens for the same peer.\n */\n async ensureChannel(\n peerId: string,\n negotiation: PeerNegotiation\n ): Promise<string> {\n // Return existing channel\n const existing = this.peerChannels.get(peerId);\n if (existing) return existing;\n\n // Deduplicate concurrent opens\n const pending = this.pendingOpens.get(peerId);\n if (pending) return pending;\n\n if (!this.channelClient) {\n throw new Error(\n 'No channel client configured — cannot open payment channel'\n );\n }\n\n const openPromise = (async () => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- channelClient checked in constructor\n const result = await this.channelClient!.openChannel({\n peerId,\n chain: negotiation.chain,\n token: negotiation.tokenAddress,\n tokenNetwork: negotiation.tokenNetwork,\n peerAddress: negotiation.settlementAddress,\n initialDeposit:\n negotiation.initialDeposit ?? this.defaultInitialDeposit,\n settlementTimeout:\n negotiation.settlementTimeout ?? this.defaultSettlementTimeout,\n });\n\n this.trackChannel(result.channelId, {\n chainType: negotiation.chainType,\n chainId:\n typeof negotiation.chainId === 'number' ? negotiation.chainId : 0,\n tokenNetworkAddress: negotiation.tokenNetwork ?? '',\n tokenAddress: negotiation.tokenAddress,\n recipient: negotiation.settlementAddress,\n // On-chain depositTotal (Mina only) — needed so the Mina signer binds\n // balanceB = depositTotal − balanceA (connector#133).\n depositTotal: result.depositTotal,\n });\n this.peerChannels.set(peerId, result.channelId);\n return result.channelId;\n } finally {\n this.pendingOpens.delete(peerId);\n }\n })();\n\n this.pendingOpens.set(peerId, openPromise);\n return openPromise;\n }\n\n /**\n * Get channel ID for a peer (if any).\n */\n getChannelForPeer(peerId: string): string | undefined {\n return this.peerChannels.get(peerId);\n }\n\n /**\n * Start tracking a channel.\n * Called after bootstrap returns a channelId.\n *\n * @param channelId - Payment channel identifier\n * @param chainContext - Chain context for signing (chainType + chainId + tokenNetworkAddress)\n * @param initialNonce - Starting nonce (default: 0)\n * @param initialAmount - Starting cumulative amount (default: 0n)\n */\n trackChannel(\n channelId: string,\n chainContext?: {\n chainType?: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n recipient?: string;\n depositTotal?: bigint;\n },\n initialNonce = 0,\n initialAmount = 0n\n ): void {\n const cId = chainContext?.chainId ?? 31337;\n const tnAddr =\n chainContext?.tokenNetworkAddress ??\n '0x0000000000000000000000000000000000000000';\n\n // If store has persisted state for this channel, resume from it\n if (this.store) {\n const persisted = this.store.load(channelId);\n if (persisted) {\n this.channels.set(channelId, {\n nonce: persisted.nonce,\n cumulativeAmount: persisted.cumulativeAmount,\n chainType: chainContext?.chainType ?? 'evm',\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n recipient: chainContext?.recipient,\n depositTotal: chainContext?.depositTotal,\n });\n return;\n }\n }\n\n this.channels.set(channelId, {\n nonce: initialNonce,\n cumulativeAmount: initialAmount,\n chainType: chainContext?.chainType ?? 'evm',\n chainId: cId,\n tokenNetworkAddress: tnAddr,\n tokenAddress: chainContext?.tokenAddress,\n recipient: chainContext?.recipient,\n depositTotal: chainContext?.depositTotal,\n });\n }\n\n /**\n * Signs a balance proof for the given channel.\n * Auto-increments nonce and adds to cumulative amount.\n * Routes to the correct ChainSigner based on the channel's chain type.\n *\n * @param channelId - Payment channel identifier\n * @param additionalAmount - Amount to add to cumulative transferred amount\n * @returns Signed balance proof\n * @throws Error if channel is not being tracked\n */\n async signBalanceProof(\n channelId: string,\n additionalAmount: bigint\n ): Promise<SignedBalanceProof> {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(\n `Channel \"${channelId}\" is not being tracked. Call trackChannel() first.`\n );\n }\n\n tracking.nonce += 1;\n tracking.cumulativeAmount += additionalAmount;\n\n // Persist updated state\n if (this.store) {\n this.store.save(channelId, {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount,\n });\n }\n\n // Route to appropriate signer for non-EVM chains\n const signer = this.chainSigners.get(tracking.chainType);\n if (signer && tracking.chainType !== 'evm') {\n if (!tracking.recipient) {\n throw new Error(\n `Channel \"${channelId}\" (${tracking.chainType}) has no recipient settlement address; ` +\n 'cannot sign a Solana/Mina balance proof. Ensure the peer negotiation supplied a settlementAddress.'\n );\n }\n const metadata = this.buildMetadata(tracking);\n return signer.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n recipient: tracking.recipient,\n metadata,\n // On-chain depositTotal captured at open time (#220) — the Mina signer\n // binds balanceB = depositTotal − balanceA (connector#133); the Solana\n // signer ignores it. When undefined (resume / idempotent re-open) the\n // Mina signer self-resolves it from chain (#223).\n depositTotal: tracking.depositTotal,\n });\n }\n\n // EVM path (backwards compatible — uses EvmSigner directly)\n if (!this.evmSigner) {\n throw new Error('No EVM signer configured for EVM channel signing.');\n }\n return this.evmSigner.signBalanceProof({\n channelId,\n nonce: tracking.nonce,\n transferredAmount: tracking.cumulativeAmount,\n lockedAmount: 0n,\n locksRoot:\n '0x0000000000000000000000000000000000000000000000000000000000000000',\n chainId: tracking.chainId,\n tokenNetworkAddress: tracking.tokenNetworkAddress,\n tokenAddress: tracking.tokenAddress,\n });\n }\n\n private buildMetadata(tracking: ChannelTracking): ChainMetadata {\n switch (tracking.chainType) {\n case 'solana':\n return { chainType: 'solana', programId: tracking.tokenNetworkAddress };\n case 'mina':\n return {\n chainType: 'mina',\n zkAppAddress: tracking.tokenNetworkAddress,\n };\n default:\n return {\n chainType: 'evm',\n chainId: tracking.chainId,\n tokenNetworkAddress: tracking.tokenNetworkAddress,\n tokenAddress: tracking.tokenAddress,\n };\n }\n }\n\n /**\n * Gets the current nonce for a tracked channel.\n */\n getNonce(channelId: string): number {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.nonce;\n }\n\n /**\n * Gets the cumulative transferred amount for a tracked channel.\n */\n getCumulativeAmount(channelId: string): bigint {\n const tracking = this.channels.get(channelId);\n if (!tracking) {\n throw new Error(`Channel \"${channelId}\" is not being tracked.`);\n }\n return tracking.cumulativeAmount;\n }\n\n /**\n * Gets all tracked channel IDs.\n */\n getTrackedChannels(): string[] {\n return Array.from(this.channels.keys());\n }\n\n /**\n * Returns true if the channel is being tracked.\n */\n isTracking(channelId: string): boolean {\n return this.channels.has(channelId);\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\n\nexport interface ChannelStoreEntry {\n nonce: number;\n cumulativeAmount: bigint;\n}\n\n/**\n * Persistence interface for payment channel nonce/amount state.\n */\nexport interface ChannelStore {\n save(channelId: string, tracking: ChannelStoreEntry): void;\n load(channelId: string): ChannelStoreEntry | undefined;\n list(): string[];\n delete(channelId: string): void;\n}\n\ninterface JsonEntry {\n nonce: number;\n /** Stored as string to preserve bigint precision */\n cumulativeAmount: string;\n}\n\n/**\n * JSON file-backed ChannelStore.\n * Uses synchronous I/O to match ChannelManager's sync API surface.\n */\nexport class JsonFileChannelStore implements ChannelStore {\n private readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n save(channelId: string, tracking: ChannelStoreEntry): void {\n const data = this.readFile();\n data[channelId] = {\n nonce: tracking.nonce,\n cumulativeAmount: tracking.cumulativeAmount.toString(),\n };\n this.writeFile(data);\n }\n\n load(channelId: string): ChannelStoreEntry | undefined {\n const data = this.readFile();\n const entry = data[channelId];\n if (!entry) return undefined;\n return {\n nonce: entry.nonce,\n cumulativeAmount: BigInt(entry.cumulativeAmount),\n };\n }\n\n list(): string[] {\n return Object.keys(this.readFile());\n }\n\n delete(channelId: string): void {\n const data = this.readFile();\n const { [channelId]: _, ...rest } = data;\n this.writeFile(rest);\n }\n\n private readFile(): Record<string, JsonEntry> {\n if (!existsSync(this.filePath)) {\n return {};\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as Record<string, JsonEntry>;\n }\n\n private writeFile(data: Record<string, JsonEntry>): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n}\n","/**\n * Hidden-service hostname validation for the anyone-protocol / ATOR network.\n *\n * The `anon` binary routes hidden-service hostnames under the **`.anyone`** TLD\n * ONLY. A `<host>.anon` name is NOT recognized as a hidden service — anon treats\n * it as a clearnet name and tries to exit-resolve it, which fails\n * (`resolve failed` / `HostUnreachable`). Only `<host>.anyone` triggers anon's\n * `parse_extended_hostname: Anyone dns address lookup` and is validated.\n *\n * Historically the client and the toon-client pod accepted BOTH `.anon` and\n * `.anyone`, so a `.anon` address was silently accepted and then failed deep in\n * the transport with an opaque error. This module makes `.anyone` the single\n * accepted/routable HS TLD and rejects `.anon` up front with an actionable\n * message (see issue #201).\n *\n * This is a pure, browser-safe helper (no Node built-ins) so it can be imported\n * from any transport path.\n */\n\n/**\n * A `<host>.anyone` hidden-service hostname. The label is base32 (`a-z2-7`),\n * matching the on-wire onion-style address alphabet anon uses.\n */\nexport const HS_HOSTNAME_REGEX = /^[a-z2-7]+\\.anyone$/;\n\n/** Max length of an HS hostname (defensive bound against pathological input). */\nexport const HS_HOSTNAME_MAX_LENGTH = 80;\n\n/**\n * Returns true iff `s` is a routable `.anyone` hidden-service hostname.\n * Does NOT accept the legacy `.anon` TLD (see {@link assertRoutableHsHostname}).\n */\nexport function isRoutableHsHostname(s: unknown): s is string {\n return (\n typeof s === 'string' &&\n s.length <= HS_HOSTNAME_MAX_LENGTH &&\n HS_HOSTNAME_REGEX.test(s)\n );\n}\n\n/**\n * Validates that `hostname` is a routable `.anyone` hidden-service address.\n *\n * - `<host>.anyone` → returns the hostname unchanged.\n * - `<host>.anon` → throws with an actionable message pointing at `.anyone`\n * (anon does NOT route `.anon`; it would silently fail in the transport).\n * - anything else → throws a generic format error.\n *\n * @throws {Error} if the hostname is not a routable `.anyone` HS address.\n */\nexport function assertRoutableHsHostname(hostname: unknown): string {\n if (typeof hostname === 'string' && /\\.anon$/.test(hostname)) {\n throw new Error(\n `\"${hostname}\" is not a routable hidden-service address; use the .anyone TLD ` +\n `(e.g. \"${hostname.replace(/\\.anon$/, '.anyone')}\"). ` +\n 'The anon daemon only resolves hidden services under .anyone — a .anon ' +\n 'name is treated as a clearnet address and fails (HostUnreachable).'\n );\n }\n if (!isRoutableHsHostname(hostname)) {\n throw new Error(\n `Invalid hidden-service hostname: ${JSON.stringify(hostname)}. ` +\n `Expected a base32 .anyone address matching ${HS_HOSTNAME_REGEX}.`\n );\n }\n return hostname;\n}\n","import type { ConnectorAdminClient } from '@toon-protocol/core';\nimport {\n ValidationError,\n NetworkError,\n ConnectorError,\n UnauthorizedError,\n PeerNotFoundError,\n PeerAlreadyExistsError,\n} from '../errors.js';\nimport { withRetry } from '../utils/retry.js';\n\n/**\n * Configuration for HttpConnectorAdmin.\n */\nexport interface HttpConnectorAdminConfig {\n /** Admin API base URL (e.g., 'http://localhost:8081') */\n adminUrl: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum retry attempts for network failures (default: 3) */\n maxRetries?: number;\n /** Initial retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** HTTP client for testing (default: global fetch) */\n httpClient?: typeof fetch;\n}\n\n/**\n * Result of a bulk peer operation.\n */\nexport interface PeerOperationResult {\n /** Peer ID that was operated on */\n peerId: string;\n /** Whether the operation succeeded */\n success: boolean;\n /** Error that occurred (if failed) */\n error?: Error;\n}\n\n/**\n * HTTP-based connector admin client for managing ILP peers via REST API.\n *\n * Implements the ConnectorAdminClient interface using HTTP requests to the\n * connector's admin API (typically port 8081).\n *\n * @example\n * ```typescript\n * // Embedded mode (DirectConnectorAdmin)\n * const adminClient = new DirectConnectorAdmin(connectorNode);\n *\n * // HTTP mode (HttpConnectorAdmin)\n * const adminClient = new HttpConnectorAdmin({\n * adminUrl: 'http://localhost:8081'\n * });\n *\n * // Add peer\n * await adminClient.addPeer({\n * id: 'nostr-abc123',\n * url: 'btp+ws://alice.example.com:3000',\n * authToken: 'secret-token',\n * routes: [{ prefix: 'g.toon.alice' }]\n * });\n *\n * // Remove peer\n * await adminClient.removePeer('nostr-abc123');\n * ```\n *\n * @throws {ValidationError} Input validation failed (before HTTP request)\n * @throws {NetworkError} Connection failed (ECONNREFUSED, ETIMEDOUT)\n * @throws {UnauthorizedError} Admin API returned 401 (missing/invalid auth)\n * @throws {PeerAlreadyExistsError} Admin API returned 409 (duplicate peer)\n * @throws {PeerNotFoundError} Admin API returned 404 (peer not found)\n * @throws {ConnectorError} Admin API returned 5xx (server error)\n */\nexport class HttpConnectorAdmin implements ConnectorAdminClient {\n private readonly adminUrl: string;\n private readonly timeout: number;\n private readonly retryConfig: { maxRetries: number; retryDelay: number };\n private readonly httpClient: typeof fetch;\n\n constructor(config: HttpConnectorAdminConfig) {\n this.adminUrl = config.adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.timeout = config.timeout ?? 30000;\n this.retryConfig = {\n maxRetries: config.maxRetries ?? 3,\n retryDelay: config.retryDelay ?? 1000,\n };\n this.httpClient = config.httpClient ?? fetch;\n }\n\n /**\n * Add a peer to the connector via the admin API.\n *\n * Validates peer config parameters and sends HTTP POST to /admin/peers.\n *\n * @param config - Peer configuration\n * @param config.id - Unique peer identifier (non-empty string)\n * @param config.url - BTP WebSocket URL (must start with 'btp+ws://' or 'btp+wss://')\n * @param config.authToken - Authentication token (non-empty string)\n * @param config.routes - Optional routing table entries\n * @param config.settlement - Optional settlement configuration\n *\n * @throws {ValidationError} Invalid peer config (missing id, invalid url, etc.)\n * @throws {PeerAlreadyExistsError} Peer with same ID already exists (409 Conflict)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async addPeer(config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }): Promise<void> {\n // Validate required fields\n if (\n !config.id ||\n typeof config.id !== 'string' ||\n config.id.trim() === ''\n ) {\n throw new ValidationError('Peer id must be a non-empty string');\n }\n\n if (\n !config.url ||\n typeof config.url !== 'string' ||\n config.url.trim() === ''\n ) {\n throw new ValidationError('Peer url must be a non-empty string');\n }\n\n // Validate BTP URL format (accept both ws:// and btp+ws:// formats)\n const hasWsPrefix =\n config.url.startsWith('ws://') || config.url.startsWith('wss://');\n const hasBtpPrefix =\n config.url.startsWith('btp+ws://') || config.url.startsWith('btp+wss://');\n\n if (!hasWsPrefix && !hasBtpPrefix) {\n throw new ValidationError(\n `Invalid BTP URL format: \"${config.url}\". Must start with 'ws://', 'wss://', 'btp+ws://', or 'btp+wss://'`\n );\n }\n\n // authToken can be empty string for BTP (doesn't require authentication)\n if (\n config.authToken === undefined ||\n config.authToken === null ||\n typeof config.authToken !== 'string'\n ) {\n throw new ValidationError(\n 'Peer authToken must be a string (can be empty for no auth)'\n );\n }\n\n // Validate routes (if provided)\n if (config.routes !== undefined) {\n if (!Array.isArray(config.routes)) {\n throw new ValidationError('Peer routes must be an array');\n }\n\n for (const route of config.routes) {\n if (\n !route.prefix ||\n typeof route.prefix !== 'string' ||\n route.prefix.trim() === ''\n ) {\n throw new ValidationError('Route prefix must be a non-empty string');\n }\n if (\n route.priority !== undefined &&\n typeof route.priority !== 'number'\n ) {\n throw new ValidationError('Route priority must be a number');\n }\n }\n }\n\n // Validate settlement (if provided)\n if (config.settlement !== undefined) {\n if (typeof config.settlement !== 'object' || config.settlement === null) {\n throw new ValidationError('Peer settlement must be an object');\n }\n\n if (\n !config.settlement.preference ||\n typeof config.settlement.preference !== 'string'\n ) {\n throw new ValidationError(\n 'Settlement preference must be a non-empty string'\n );\n }\n }\n\n // Send HTTP POST request with retry logic\n const url = `${this.adminUrl}/admin/peers`;\n\n await withRetry(async () => this.sendAddPeerRequest(url, config), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors (ECONNREFUSED, ETIMEDOUT)\n // Do not retry on validation errors, 4xx, or 5xx errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Remove a peer from the connector via the admin API.\n *\n * Sends HTTP DELETE to /admin/peers/:id.\n *\n * @param peerId - Unique peer identifier to remove (non-empty string)\n *\n * @throws {ValidationError} Invalid peerId (empty string)\n * @throws {PeerNotFoundError} Peer does not exist (404 Not Found)\n * @throws {UnauthorizedError} Admin API authentication failed (401)\n * @throws {NetworkError} Connection to admin API failed\n * @throws {ConnectorError} Admin API server error (5xx)\n */\n async removePeer(peerId: string): Promise<void> {\n // Validate peerId\n if (!peerId || typeof peerId !== 'string' || peerId.trim() === '') {\n throw new ValidationError('peerId must be a non-empty string');\n }\n\n // Send HTTP DELETE request with retry logic\n const url = `${this.adminUrl}/admin/peers/${encodeURIComponent(peerId)}`;\n\n await withRetry(async () => this.sendRemovePeerRequest(url, peerId), {\n maxRetries: this.retryConfig.maxRetries,\n retryDelay: this.retryConfig.retryDelay,\n exponentialBackoff: true,\n shouldRetry: (error) => {\n // Only retry on network errors\n return error instanceof NetworkError;\n },\n });\n }\n\n /**\n * Add multiple peers in parallel for efficient bootstrapping.\n *\n * Uses Promise.allSettled() to execute peer additions concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param configs - Array of peer configurations to add\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.addPeers([\n * { id: 'peer1', url: 'btp+ws://...', authToken: 'token1' },\n * { id: 'peer2', url: 'btp+ws://...', authToken: 'token2' },\n * ]);\n *\n * results.forEach(result => {\n * if (result.success) {\n * console.log(`Added peer: ${result.peerId}`);\n * } else {\n * console.error(`Failed to add ${result.peerId}:`, result.error);\n * }\n * });\n * ```\n */\n async addPeers(\n configs: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }[]\n ): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n configs.map((config) => this.addPeer(config))\n );\n\n return results.map((result, index) => {\n const config = configs[index];\n return {\n peerId: config ? config.id : 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Remove multiple peers in parallel.\n *\n * Uses Promise.allSettled() to execute peer removals concurrently,\n * returning results for each operation regardless of individual failures.\n *\n * @param peerIds - Array of peer IDs to remove\n * @returns Array of results indicating success/failure for each peer\n *\n * @example\n * ```typescript\n * const results = await admin.removePeers(['peer1', 'peer2', 'peer3']);\n *\n * const succeeded = results.filter(r => r.success).length;\n * console.log(`Removed ${succeeded}/${results.length} peers`);\n * ```\n */\n async removePeers(peerIds: string[]): Promise<PeerOperationResult[]> {\n const results = await Promise.allSettled(\n peerIds.map((peerId) => this.removePeer(peerId))\n );\n\n return results.map((result, index) => {\n const peerId = peerIds[index];\n return {\n peerId: peerId ?? 'unknown',\n success: result.status === 'fulfilled',\n error: result.status === 'rejected' ? result.reason : undefined,\n };\n });\n }\n\n /**\n * Send HTTP POST request to add a peer.\n * Separated for retry logic wrapping.\n */\n private async sendAddPeerRequest(\n url: string,\n config: {\n id: string;\n url: string;\n authToken: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: {\n preference: string;\n evmAddress?: string;\n tokenAddress?: string;\n tokenNetworkAddress?: string;\n chainId?: number;\n channelId?: string;\n initialDeposit?: string;\n };\n }\n ): Promise<void> {\n // Normalize URL for connector API (expects ws:// or wss://)\n // Strip btp+ prefix if present, or use as-is if already plain ws://\n let connectorUrl = config.url;\n if (connectorUrl.startsWith('btp+')) {\n connectorUrl = connectorUrl.replace(/^btp\\+/, '');\n }\n\n try {\n const response = await this.httpClient(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...config,\n url: connectorUrl,\n }),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (201 Created)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `POST ${url}`, config.id);\n } catch (error) {\n this.handleNetworkError(error, url, 'addPeer');\n }\n }\n\n /**\n * Send HTTP DELETE request to remove a peer.\n * Separated for retry logic wrapping.\n */\n private async sendRemovePeerRequest(\n url: string,\n peerId: string\n ): Promise<void> {\n try {\n const response = await this.httpClient(url, {\n method: 'DELETE',\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (response.ok) {\n return; // Success (204 No Content)\n }\n\n // Handle error responses\n await this.handleErrorResponse(response, `DELETE ${url}`, peerId);\n } catch (error) {\n this.handleNetworkError(error, url, 'removePeer');\n }\n }\n\n /**\n * Handle network errors from HTTP requests.\n *\n * Converts connection failures, timeouts, and unknown errors to NetworkError.\n * Re-throws existing ToonClientError instances.\n *\n * @param error - Error thrown by HTTP client\n * @param url - Request URL (for error messages)\n * @param operation - Operation name (for error messages)\n * @throws {NetworkError} Network connection or timeout error\n */\n private handleNetworkError(\n error: unknown,\n url: string,\n operation: string\n ): never {\n // Timeout errors (AbortSignal)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new NetworkError(\n `Request to ${url} timed out after ${this.timeout}ms`,\n error\n );\n }\n\n // Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failures)\n if (\n error instanceof Error &&\n (error.message.includes('ECONNREFUSED') ||\n error.message.includes('ETIMEDOUT') ||\n error.message.includes('ENOTFOUND'))\n ) {\n throw new NetworkError(\n `Failed to connect to connector admin API at ${url}: ${error.message}`,\n error\n );\n }\n\n // Re-throw if already a ToonClientError\n if (\n error instanceof ValidationError ||\n error instanceof PeerAlreadyExistsError ||\n error instanceof PeerNotFoundError ||\n error instanceof UnauthorizedError ||\n error instanceof ConnectorError\n ) {\n throw error;\n }\n\n // Unknown error - wrap in NetworkError\n throw new NetworkError(\n `Unexpected error during ${operation}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Handle HTTP error responses from the admin API.\n *\n * Converts HTTP status codes to appropriate error types.\n *\n * @param response - HTTP response from admin API\n * @param endpoint - Endpoint being called (for error messages)\n * @param peerId - Peer ID (for error messages)\n * @throws {UnauthorizedError} 401 Unauthorized\n * @throws {PeerNotFoundError} 404 Not Found\n * @throws {PeerAlreadyExistsError} 409 Conflict\n * @throws {ConnectorError} 5xx Server Error\n */\n private async handleErrorResponse(\n response: Response,\n endpoint: string,\n peerId: string\n ): Promise<never> {\n const status = response.status;\n const statusText = response.statusText;\n\n // Try to extract error message from response body\n let errorMessage = '';\n try {\n const body = await response.text();\n if (body) {\n errorMessage = ` - ${body}`;\n }\n } catch {\n // Ignore body parsing errors\n }\n\n switch (status) {\n case 401:\n throw new UnauthorizedError(\n `Admin API authentication failed for ${endpoint}: ${statusText}${errorMessage}`\n );\n\n case 404:\n throw new PeerNotFoundError(\n `Peer not found: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n case 409:\n throw new PeerAlreadyExistsError(\n `Peer already exists: \"${peerId}\" (${endpoint}): ${statusText}${errorMessage}`\n );\n\n default:\n if (status >= 500) {\n throw new ConnectorError(\n `Connector admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n\n // Other 4xx errors (400, 403, etc.)\n throw new ConnectorError(\n `Admin API error (${endpoint}): ${status} ${statusText}${errorMessage}`\n );\n }\n }\n}\n","/**\n * Pet DVM Provider Discovery\n *\n * Filters Kind 10035 service discovery events to find providers that\n * support Pet DVM interactions (Kind 5900).\n *\n * @module pet/filterPetDvmProviders\n */\n\nimport { parseServiceDiscovery } from '@toon-protocol/core';\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetDvmProvider } from './types.js';\n\n/**\n * Minimal Nostr event shape needed for filtering.\n * Using a local interface to avoid importing nostr-tools types.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Filter Kind 10035 service discovery events to find pet DVM providers.\n *\n * Accepts raw NostrEvent[] and internally parses content via parseServiceDiscovery.\n * Filters events where skill.kinds includes 5900 (PET_INTERACTION_REQUEST_KIND).\n * Returns provider metadata sorted by price ascending (cheapest first).\n *\n * Handles missing/malformed skill descriptors gracefully (returns empty array, no throw).\n *\n * @param events - Array of raw Nostr events (kind:10035)\n * @returns Array of PetDvmProvider metadata, sorted by price ascending\n */\nexport function filterPetDvmProviders(\n events: NostrEventLike[]\n): PetDvmProvider[] {\n const providers: PetDvmProvider[] = [];\n\n for (const event of events) {\n let parsed;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parsed = parseServiceDiscovery(event as any);\n } catch {\n continue;\n }\n\n if (!parsed) continue;\n\n const skill = parsed.skill;\n if (!skill) continue;\n\n // Check if this provider supports Kind 5900\n if (!skill.kinds.includes(PET_INTERACTION_REQUEST_KIND)) continue;\n\n const pricing = skill.pricing[String(PET_INTERACTION_REQUEST_KIND)] ?? '0';\n\n providers.push({\n ilpAddress: parsed.ilpAddress,\n pricing,\n pubkey: event.pubkey,\n features: skill.features,\n });\n }\n\n // Sort by price ascending (cheapest first)\n providers.sort((a, b) => {\n const priceA = Number(a.pricing) || 0;\n const priceB = Number(b.pricing) || 0;\n return priceA - priceB;\n });\n\n return providers;\n}\n","/**\n * Pet Interaction Request Builder (Kind 5900)\n *\n * Builds unsigned Kind 5900 Nostr events for pet DVM interaction requests.\n * Compatible with nostr-tools/pure finalizeEvent for signing.\n *\n * @module pet/buildPetInteractionRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport { ValidationError } from '../errors.js';\nimport type {\n PetInteractionRequestParams,\n UnsignedNostrEvent,\n} from './types.js';\n\n/** Maximum valid action type index (0-10 inclusive, ACTION_COUNT = 11) */\nconst MAX_ACTION_TYPE = 10;\n\n/**\n * Build an unsigned Kind 5900 pet interaction request event.\n *\n * All tag values are stringified per Nostr protocol convention.\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed interaction parameters\n * @returns Unsigned Nostr event ready for signing\n * @throws ValidationError for invalid input\n */\nexport function buildPetInteractionRequest(\n params: PetInteractionRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, actionType, itemId, tokenCost, isSleeping } = params;\n\n // Validate blobbiId\n if (!blobbiId || blobbiId.trim() === '') {\n throw new ValidationError('blobbiId must be a non-empty string');\n }\n\n // Validate actionType (0 <= actionType <= 10)\n if (\n !Number.isInteger(actionType) ||\n actionType < 0 ||\n actionType > MAX_ACTION_TYPE\n ) {\n throw new ValidationError(\n `actionType must be an integer between 0 and ${MAX_ACTION_TYPE}, got ${actionType}`\n );\n }\n\n // Validate itemId (>= 0)\n if (!Number.isInteger(itemId) || itemId < 0) {\n throw new ValidationError(\n `itemId must be a non-negative integer, got ${itemId}`\n );\n }\n\n // Validate tokenCost (>= 0)\n if (!Number.isFinite(tokenCost) || tokenCost < 0) {\n throw new ValidationError(\n `tokenCost must be a non-negative number, got ${tokenCost}`\n );\n }\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['action', String(actionType)],\n ['item', String(itemId)],\n ['cost', String(tokenCost)],\n ['sleeping', String(isSleeping)],\n ],\n content: '',\n };\n}\n","/**\n * Pet Interaction Result Parser (Kind 6900)\n *\n * Decodes base64-encoded JSON from IlpSendResult.data field.\n * Uses browser-safe atob() -- NOT Node.js Buffer -- for ditto React SPA compatibility.\n *\n * @module pet/parsePetInteractionResult\n */\n\nimport type { PetInteractionResultData, StatValues } from './types.js';\n\n/** Required stat field names */\nconst STAT_FIELDS: readonly (keyof StatValues)[] = [\n 'hunger',\n 'happiness',\n 'health',\n 'hygiene',\n 'energy',\n];\n\n/** Regex for validating 64-char hex string (BLAKE3 brain hash) */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/**\n * Validate that an object has all required StatValues fields as numbers.\n */\nfunction isValidStats(obj: unknown): obj is StatValues {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return STAT_FIELDS.every(\n (field) =>\n typeof record[field] === 'number' && Number.isFinite(record[field])\n );\n}\n\n/**\n * Parse base64-encoded JSON result data from a Kind 6900 DVM response.\n *\n * Uses atob() for browser compatibility (ditto React SPA).\n * Returns null for malformed/missing data (no throw).\n *\n * Validates:\n * - brainHash is 64-char hex\n * - stats has all 5 fields\n * - cycle >= 0\n * - stage 0-2\n *\n * @param data - Base64-encoded JSON string from IlpSendResult.data\n * @returns Parsed PetInteractionResultData or null if invalid\n */\nexport function parsePetInteractionResult(\n data: string\n): PetInteractionResultData | null {\n if (!data) return null;\n\n let json: string;\n try {\n // Use atob() for browser compatibility; works in Node 16+ too\n json = atob(data);\n } catch {\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return null;\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const record = parsed as Record<string, unknown>;\n\n // Validate stats\n if (!isValidStats(record['stats'])) return null;\n\n // Validate stage (0-2)\n const stage = record['stage'];\n if (\n typeof stage !== 'number' ||\n !Number.isInteger(stage) ||\n stage < 0 ||\n stage > 2\n ) {\n return null;\n }\n\n // Validate cycle (>= 0)\n const cycle = record['cycle'];\n if (typeof cycle !== 'number' || !Number.isInteger(cycle) || cycle < 0) {\n return null;\n }\n\n // Validate lastInteraction\n const lastInteraction = record['lastInteraction'];\n if (\n typeof lastInteraction !== 'number' ||\n !Number.isFinite(lastInteraction)\n ) {\n return null;\n }\n\n // Validate brainHash (64-char hex)\n const brainHash = record['brainHash'];\n if (typeof brainHash !== 'string' || !HEX_64_RE.test(brainHash)) {\n return null;\n }\n\n // Validate cooldownTimestamps (array of numbers)\n const cooldownTimestamps = record['cooldownTimestamps'];\n if (!Array.isArray(cooldownTimestamps)) return null;\n if (\n !cooldownTimestamps.every(\n (t): t is number => typeof t === 'number' && Number.isFinite(t)\n )\n ) {\n return null;\n }\n\n // Construct clean stat object to prevent prototype pollution from JSON.parse\n const validatedStats = record['stats'] as StatValues;\n const stats: StatValues = {\n hunger: validatedStats.hunger,\n happiness: validatedStats.happiness,\n health: validatedStats.health,\n hygiene: validatedStats.hygiene,\n energy: validatedStats.energy,\n };\n\n return {\n stats,\n stage,\n cycle,\n lastInteraction,\n brainHash,\n cooldownTimestamps: [...cooldownTimestamps],\n };\n}\n","/**\n * Pet Interaction Event Parser (Kind 14919)\n *\n * Parses Kind 14919 optimistic/proven pet interaction events.\n * Detects proof status from presence of 'proof' + 'mina_tx' tags.\n *\n * @module pet/parsePetInteractionEvent\n */\n\nimport type {\n PetInteractionEventData,\n InteractionResultContent,\n ProofStatus,\n StatValues,\n} from './types.js';\n\n/**\n * Minimal Nostr event shape needed for parsing.\n */\ninterface NostrEventLike {\n kind: number;\n pubkey: string;\n content: string;\n tags: string[][];\n id: string;\n sig: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Check that an object has the expected stat fields as numbers.\n */\nfunction isStatLike(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return false;\n const r = obj as Record<string, unknown>;\n return (\n typeof r['hunger'] === 'number' &&\n Number.isFinite(r['hunger']) &&\n typeof r['happiness'] === 'number' &&\n Number.isFinite(r['happiness']) &&\n typeof r['health'] === 'number' &&\n Number.isFinite(r['health']) &&\n typeof r['hygiene'] === 'number' &&\n Number.isFinite(r['hygiene']) &&\n typeof r['energy'] === 'number' &&\n Number.isFinite(r['energy'])\n );\n}\n\n/**\n * Construct a clean StatValues object from a validated stat-like object.\n * Prevents prototype pollution by extracting only known fields.\n */\nfunction cleanStats(obj: Record<string, unknown>): StatValues {\n return {\n hunger: obj['hunger'] as number,\n happiness: obj['happiness'] as number,\n health: obj['health'] as number,\n hygiene: obj['hygiene'] as number,\n energy: obj['energy'] as number,\n };\n}\n\n/**\n * Attempt to parse content JSON into InteractionResultContent.\n * Returns null if content is malformed.\n */\nfunction parseContent(content: string): InteractionResultContent | null {\n try {\n const parsed = JSON.parse(content);\n if (typeof parsed !== 'object' || parsed === null) return null;\n // Structural check -- must have stat objects with correct numeric fields\n if (\n !isStatLike(parsed.priorStats) ||\n !isStatLike(parsed.decayedStats) ||\n !isStatLike(parsed.finalStats)\n ) {\n return null;\n }\n if (\n typeof parsed.cycle !== 'number' ||\n typeof parsed.stage !== 'number' ||\n typeof parsed.tokenCost !== 'number'\n ) {\n return null;\n }\n // Construct clean object to prevent prototype pollution from JSON.parse\n return {\n priorStats: cleanStats(parsed.priorStats),\n decayedStats: cleanStats(parsed.decayedStats),\n finalStats: cleanStats(parsed.finalStats),\n cycle: parsed.cycle,\n stage: parsed.stage,\n tokenCost: parsed.tokenCost,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a Kind 14919 pet interaction event.\n *\n * Extracts all tag values and detects proof status:\n * - 'optimistic': no 'proof' tag\n * - 'proven': has 'proof' + 'mina_tx' tags\n *\n * Returns null if required tags are missing.\n *\n * @param event - A Nostr event (Kind 14919)\n * @returns Parsed PetInteractionEventData or null if malformed\n */\nexport function parsePetInteractionEvent(\n event: NostrEventLike\n): PetInteractionEventData | null {\n const tags = event.tags;\n\n // Extract required tags\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId) return null;\n\n const actionStr = getTagValue(tags, 'action');\n if (!actionStr) return null;\n const actionType = Number(actionStr);\n if (!Number.isFinite(actionType)) return null;\n\n const itemStr = getTagValue(tags, 'item');\n if (!itemStr) return null;\n const itemId = Number(itemStr);\n if (!Number.isFinite(itemId)) return null;\n\n const costStr = getTagValue(tags, 'cost');\n if (!costStr) return null;\n const tokenCost = Number(costStr);\n if (!Number.isFinite(tokenCost)) return null;\n\n const cycleStr = getTagValue(tags, 'cycle');\n if (!cycleStr) return null;\n const cycle = Number(cycleStr);\n if (!Number.isFinite(cycle)) return null;\n\n const stageStr = getTagValue(tags, 'stage');\n if (!stageStr) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n const brainHash = getTagValue(tags, 'brain_hash');\n if (!brainHash) return null;\n\n // Detect proof status\n const proof = getTagValue(tags, 'proof');\n const minaTx = getTagValue(tags, 'mina_tx');\n const proofStatus: ProofStatus = proof && minaTx ? 'proven' : 'optimistic';\n\n // Parse content\n const content = parseContent(event.content);\n\n const result: PetInteractionEventData = {\n blobbiId,\n actionType,\n itemId,\n tokenCost,\n cycle,\n stage,\n brainHash,\n proofStatus,\n content,\n };\n\n if (proof) result.proof = proof;\n if (minaTx) result.minaTx = minaTx;\n\n return result;\n}\n","/**\n * Pet Listing Event Builder (Kind 30402)\n *\n * Builds unsigned Kind 30402 (NIP-99 classified listing) Nostr events\n * for pet-for-sale marketplace listings. Every listing includes a\n * verified biography attachment (lifecycleHash + totalSpent) so buyers\n * can verify the listing against on-chain PetZkApp state.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetListingEvent\n */\n\nimport type { PetListingParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Kind 30402: NIP-99 parameterized replaceable classified listing.\n * Not yet defined in @toon-protocol/core (pet marketplace is new in Story 11-14).\n * Defined locally until core exports this constant.\n */\nconst PET_LISTING_KIND = 30402;\n\n/** Human-readable stage names for listing summaries */\nconst STAGE_NAMES: Record<number, string> = {\n 0: 'Egg',\n 1: 'Baby',\n 2: 'Adult',\n};\n\n/**\n * Build an unsigned Kind 30402 pet-for-sale classified listing event.\n *\n * The listing uses the NIP-99 classified listing format with TOON-specific\n * extension tags for verified biography (lifecycleHash, totalSpent).\n * The `d` tag is set to `blobbiId` for stable parameterized replaceability —\n * republishing with the same `d` tag updates the listing on relays.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed listing parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetListingEvent(\n params: PetListingParams\n): UnsignedNostrEvent {\n const {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n } = params;\n\n const stageName = STAGE_NAMES[stage] ?? 'Unknown';\n const summary = `${stageName} pet for sale — ${totalSpent} PET tokens spent (verified biography)`;\n\n return {\n kind: PET_LISTING_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', blobbiId],\n ['title', `Pet ${blobbiId} for sale`],\n ['price', String(askPriceUsdc), 'USDC', ''],\n ['summary', summary],\n ['t', 'pet'],\n ['t', 'toon-pet'],\n ['lifecycle_hash', lifecycleHash],\n ['total_spent', totalSpent],\n ['stage', String(stage)],\n ['expiration', String(expiresAt)],\n ['relay', relayUrl],\n ['p', sellerPubkey],\n ],\n content: JSON.stringify(stats),\n };\n}\n","/**\n * Pet Listing Parser (Kind 30402)\n *\n * Parses Kind 30402 (NIP-99 classified listing) Nostr events into\n * typed PetListing objects. Returns null for invalid or malformed events.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/parsePetListing\n */\n\nimport type { PetListing, StatValues } from './types.js';\n\n/** Regex for 64-char lowercase hex strings */\nconst HEX_64_RE = /^[0-9a-f]{64}$/i;\n\n/** Minimal Nostr event shape required for parsing */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Extract the first value for a given tag name from a tags array.\n * Uses noUncheckedIndexedAccess-safe bracket notation.\n */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/**\n * Default StatValues used when listing content is unparseable.\n * Note: values are 0 (outside the normal [1,100] game range) — this is\n * intentional as a sentinel for \"stats unknown / content malformed\".\n * Consumers should check for all-zero stats and display accordingly.\n */\nconst DEFAULT_STATS: StatValues = {\n hunger: 0,\n happiness: 0,\n health: 0,\n hygiene: 0,\n energy: 0,\n};\n\n/**\n * Attempt to parse content JSON into StatValues.\n * Returns DEFAULT_STATS if content is missing or malformed.\n */\nfunction parseStats(content: string): StatValues {\n try {\n const parsed = JSON.parse(content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return DEFAULT_STATS;\n const r = parsed as Record<string, unknown>;\n if (\n typeof r['hunger'] === 'number' &&\n typeof r['happiness'] === 'number' &&\n typeof r['health'] === 'number' &&\n typeof r['hygiene'] === 'number' &&\n typeof r['energy'] === 'number'\n ) {\n return {\n hunger: r['hunger'],\n happiness: r['happiness'],\n health: r['health'],\n hygiene: r['hygiene'],\n energy: r['energy'],\n };\n }\n return DEFAULT_STATS;\n } catch {\n return DEFAULT_STATS;\n }\n}\n\n/**\n * Parse a Kind 30402 pet classified listing event into a PetListing.\n *\n * Validation rules:\n * - event.kind must be 30402\n * - 'd' tag must be present and non-empty\n * - 'price' tag must be present with a valid positive numeric first element\n * - 'lifecycle_hash' tag must be a 64-char hex string\n * - 'total_spent' tag must be a valid non-negative numeric string\n * - 'stage' tag must be present\n *\n * Stats are parsed from content JSON; unparseable content falls back to DEFAULT_STATS.\n *\n * @param event - A Nostr event (expected Kind 30402)\n * @returns Parsed PetListing or null if invalid\n */\nexport function parsePetListing(event: NostrEventLike): PetListing | null {\n // Kind check\n if (event.kind !== 30402) return null;\n\n const { tags } = event;\n\n // Required: 'd' tag (blobbiId)\n const blobbiId = getTagValue(tags, 'd');\n if (!blobbiId || blobbiId.trim() === '') return null;\n\n // Required: 'price' tag — must have at least 2 elements, first must be positive numeric\n let askPriceUsdc = 0;\n let foundPrice = false;\n for (const tag of tags) {\n if (tag[0] === 'price') {\n const priceStr = tag[1];\n if (priceStr === undefined) return null;\n const parsed = Number(priceStr);\n if (!Number.isFinite(parsed) || parsed <= 0) return null;\n askPriceUsdc = parsed;\n foundPrice = true;\n break;\n }\n }\n if (!foundPrice) return null;\n\n // Required: 'lifecycle_hash' tag — must be 64-char hex\n const lifecycleHash = getTagValue(tags, 'lifecycle_hash');\n if (!lifecycleHash) return null;\n if (!HEX_64_RE.test(lifecycleHash)) return null;\n\n // Required: 'total_spent' tag — must be a valid non-negative numeric string\n const totalSpent = getTagValue(tags, 'total_spent');\n if (totalSpent === undefined || totalSpent === '') return null;\n const totalSpentNum = Number(totalSpent);\n if (!Number.isFinite(totalSpentNum) || totalSpentNum < 0) return null;\n\n // Required: 'stage' tag\n const stageStr = getTagValue(tags, 'stage');\n if (stageStr === undefined) return null;\n const stage = Number(stageStr);\n if (!Number.isFinite(stage)) return null;\n\n // Optional tags\n const sellerPubkey = getTagValue(tags, 'p') ?? '';\n const relayUrl = getTagValue(tags, 'relay') ?? '';\n const expiresAtStr = getTagValue(tags, 'expiration');\n const expiresAt = expiresAtStr !== undefined ? Number(expiresAtStr) : 0;\n\n // Parse stats from content (null-safe)\n const stats = parseStats(event.content);\n\n return {\n blobbiId,\n askPriceUsdc,\n lifecycleHash,\n totalSpent,\n stage,\n stats,\n sellerPubkey,\n relayUrl,\n expiresAt,\n eventId: event.id,\n createdAt: event.created_at,\n };\n}\n","/**\n * Pet Listing Discovery Filter\n *\n * Filters and sorts Kind 30402 pet marketplace listing events into\n * typed PetListing objects. Handles expiry, stage, price, biography\n * value, and seller filtering. Results sorted by totalSpent descending\n * (highest biography value first) to surface the most battle-hardened pets.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/filterPetListings\n */\n\nimport { parsePetListing } from './parsePetListing.js';\nimport type { PetListing, PetListingFilterOptions } from './types.js';\n\n/** Minimal Nostr event shape accepted by the filter */\ninterface NostrEventLike {\n id: string;\n kind: number;\n pubkey: string;\n tags: string[][];\n content: string;\n created_at: number;\n}\n\n/**\n * Compare two numeric strings. Returns negative if a < b, 0 if equal, positive if a > b.\n * Handles arbitrarily large integers (bigint comparison).\n */\nfunction compareNumericStrings(a: string, b: string): number {\n // Fast path for equal strings\n if (a === b) return 0;\n // Use BigInt for correct comparison of large token amounts\n try {\n const bigA = BigInt(a);\n const bigB = BigInt(b);\n if (bigA < bigB) return -1;\n if (bigA > bigB) return 1;\n return 0;\n } catch {\n // Fallback to float comparison for non-integer numerics\n const fa = Number(a);\n const fb = Number(b);\n // Guard against NaN — treat NaN as less than any valid number\n if (!Number.isFinite(fa) && !Number.isFinite(fb)) return 0;\n if (!Number.isFinite(fa)) return -1;\n if (!Number.isFinite(fb)) return 1;\n return fa - fb;\n }\n}\n\n/**\n * Filter and sort Kind 30402 pet marketplace listing events.\n *\n * Parsing is done via parsePetListing — invalid events are silently dropped.\n * Expired listings (expiration tag < current unix time) are excluded.\n * Options allow additional filtering by stage, price, biography value, and seller.\n * Results are sorted by totalSpent descending (highest biography value first).\n *\n * @param events - Array of raw Nostr events to filter\n * @param options - Optional filter criteria\n * @returns Filtered and sorted array of PetListing objects\n */\nexport function filterPetListings(\n events: NostrEventLike[],\n options?: PetListingFilterOptions\n): PetListing[] {\n const now = Math.floor(Date.now() / 1000);\n const listings: PetListing[] = [];\n\n for (const event of events) {\n // Parse and validate the listing\n const listing = parsePetListing(event);\n if (listing === null) continue;\n\n // Expiry filter — only exclude if expiresAt is set (> 0) AND has passed\n // listing.expiresAt is already parsed from the expiration tag by parsePetListing\n // (0 means no expiration tag was present — treat as never-expires)\n if (listing.expiresAt > 0 && listing.expiresAt < now) continue;\n\n // Stage filter\n if (options?.minStage !== undefined && listing.stage < options.minStage) {\n continue;\n }\n\n // Price filter\n if (\n options?.maxAskPriceUsdc !== undefined &&\n listing.askPriceUsdc > options.maxAskPriceUsdc\n ) {\n continue;\n }\n\n // Biography value filter (totalSpent numeric string comparison)\n if (options?.minTotalSpent !== undefined) {\n if (\n compareNumericStrings(listing.totalSpent, options.minTotalSpent) < 0\n ) {\n continue;\n }\n }\n\n // Seller pubkey filter\n if (\n options?.sellerPubkey !== undefined &&\n listing.sellerPubkey !== options.sellerPubkey\n ) {\n continue;\n }\n\n listings.push(listing);\n }\n\n // Sort by totalSpent descending (highest biography value first)\n listings.sort((a, b) => compareNumericStrings(b.totalSpent, a.totalSpent));\n\n return listings;\n}\n","/**\n * Pet Purchase Request Builder (Kind 5900, action type 9)\n *\n * Builds unsigned Kind 5900 Nostr events for pet transfer-ownership\n * purchase requests. Action type 9 is a reserved slot in the pet DVM\n * protocol — this event signals purchase intent and routes ILP payment\n * to the seller. The actual Mina on-chain ownership transfer (PetZkApp\n * .transferOperator) is handled by downstream stories.\n *\n * Browser-compatible — no Node.js-only imports.\n *\n * @module pet/buildPetPurchaseRequest\n */\n\nimport { PET_INTERACTION_REQUEST_KIND } from '@toon-protocol/core';\nimport type { PetPurchaseRequestParams, UnsignedNostrEvent } from './types.js';\n\n/**\n * Action type 9 — transfer-ownership reserved slot.\n * Not currently handled server-side; defines the client-side protocol shape\n * for downstream Mina ownership transfer implementation.\n */\nconst TRANSFER_OWNERSHIP_ACTION = 9;\n\n/**\n * Build an unsigned Kind 5900 pet purchase request event.\n *\n * Reuses the existing pet interaction event kind (5900) with action type 9\n * (transfer-ownership). The `listing` tag references the kind:30402 listing\n * event being purchased. The `p` tag routes ILP payment to the seller.\n *\n * The returned event is compatible with nostr-tools `finalizeEvent`.\n *\n * @param params - Typed purchase request parameters\n * @returns Unsigned Nostr event ready for signing and publishing\n */\nexport function buildPetPurchaseRequest(\n params: PetPurchaseRequestParams\n): UnsignedNostrEvent {\n const { blobbiId, listingEventId, buyerPubkey, tokenCost, sellerPubkey } =\n params;\n\n return {\n kind: PET_INTERACTION_REQUEST_KIND,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['action', String(TRANSFER_OWNERSHIP_ACTION)],\n ['i', blobbiId],\n ['listing', listingEventId],\n ['buyer', buyerPubkey],\n ['p', sellerPubkey],\n ['cost', String(tokenCost)],\n ],\n content: '',\n };\n}\n","/**\n * Client-side helper for kind:5094 Arweave blob storage DVM requests.\n *\n * This composes the three steps a caller previously had to wire by hand:\n * 1. Build the kind:5094 event via `buildBlobStorageRequest()` (@toon-protocol/core).\n * 2. Publish it to the DVM destination over ILP via `ToonClient.publishEvent()`\n * (reusing the existing claim / channel plumbing).\n * 3. Decode the FULFILL `data` field into the Arweave transaction ID.\n *\n * ## FULFILL data contract\n *\n * For a successful single-packet (non-chunked) blob upload, the DVM provider\n * returns the Arweave transaction ID as a **UTF-8 string, base64-encoded** in\n * the ILP FULFILL `data` field (the connector validates that FULFILL data is\n * base64). An Arweave tx ID is a 43-character base64url string (32 raw bytes).\n *\n * So the decode is:\n * `txId = utf8(base64decode(result.data))`\n *\n * See `packages/sdk/src/arweave/arweave-dvm-handler.ts` for the server side and\n * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts` for the reference\n * end-to-end flow this helper mirrors.\n *\n * Chunked uploads (multi-packet, via `uploadId` / `chunkIndex` / `totalChunks`\n * params) are intentionally out of scope for this single-packet helper — the\n * provider returns `ack:<n>` for intermediate chunks and the tx ID only on the\n * final chunk. Callers needing chunking should drive `publishEvent()` directly\n * (see the chunked case in the reference E2E test). Tracked as a follow-up.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { buildBlobStorageRequest } from '@toon-protocol/core';\nimport { fromBase64, decodeUtf8 } from './utils/binary.js';\nimport type { ToonClient } from './ToonClient.js';\nimport type { SignedBalanceProof } from './types.js';\n\n/** Arweave tx IDs are base64url-encoded 32-byte values (43 chars, no padding). */\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\n/**\n * Parameters for {@link requestBlobStorage}.\n */\nexport interface RequestBlobStorageParams {\n /** The raw blob data to store on Arweave. */\n blobData: Uint8Array;\n\n /**\n * MIME type of the blob. Defaults to `'application/octet-stream'`\n * (matching `buildBlobStorageRequest`).\n */\n contentType?: string;\n\n /**\n * Bid amount in USDC micro-units, as a string (declared in the event's\n * `bid` tag). Defaults to the stringified `ilpAmount` when omitted.\n *\n * At least one of `bid` / `ilpAmount` should be provided so the declared\n * bid and the paid ILP amount agree.\n */\n bid?: string;\n\n /**\n * ILP destination address of the DVM that performs the upload\n * (e.g. `'g.toon.peer1'`). Falls back to the client's configured\n * `destinationAddress` when omitted.\n */\n destination?: string;\n\n /**\n * Pre-signed balance proof claim for this packet. When omitted, the\n * client's channel manager auto-opens a channel and auto-signs a claim\n * (same lazy-channel behavior as `publishEvent`).\n */\n claim?: SignedBalanceProof;\n\n /**\n * Explicit ILP payment amount (bigint micro-units). When omitted,\n * `publishEvent` computes it from the encoded event size. When `bid`\n * is omitted, this value is stringified to populate the event's bid tag.\n */\n ilpAmount?: bigint;\n}\n\n/**\n * Typed result of {@link requestBlobStorage}.\n */\nexport interface RequestBlobStorageResult {\n /** Whether the upload succeeded and a tx ID was decoded. */\n success: boolean;\n\n /** The Arweave transaction ID (43-char base64url) when `success` is true. */\n txId?: string;\n\n /** The id of the kind:5094 event that was published. */\n eventId?: string;\n\n /** Error message when `success` is false. */\n error?: string;\n}\n\n/**\n * Requests permanent Arweave blob storage from a DVM in a single ILP packet.\n *\n * Mirrors the single-packet flow in\n * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts`: builds a signed\n * kind:5094 event, publishes it through the supplied {@link ToonClient}\n * (reusing its claim/channel plumbing), and decodes the FULFILL `data` field\n * into an Arweave transaction ID.\n *\n * @param client - A started `ToonClient` (call `client.start()` first).\n * @param secretKey - The Nostr secret key used to sign the kind:5094 event.\n * @param params - Blob, content type, bid, destination, and payment options.\n * @returns `{ success, txId?, eventId?, error? }`.\n */\nexport async function requestBlobStorage(\n client: ToonClient,\n secretKey: Uint8Array,\n params: RequestBlobStorageParams\n): Promise<RequestBlobStorageResult> {\n const bid =\n params.bid ??\n (params.ilpAmount !== undefined ? String(params.ilpAmount) : undefined);\n\n if (bid === undefined || bid === '') {\n return {\n success: false,\n error: 'requestBlobStorage requires a bid (or ilpAmount to derive it)',\n };\n }\n\n // buildBlobStorageRequest expects a Node Buffer (it calls .toString('base64')).\n // ToonClient surfaces Uint8Array for browser-compat, so normalize here.\n const blobBuffer = Buffer.from(\n params.blobData.buffer,\n params.blobData.byteOffset,\n params.blobData.byteLength\n );\n\n let event: NostrEvent;\n try {\n event = buildBlobStorageRequest(\n {\n blobData: blobBuffer,\n contentType: params.contentType,\n bid,\n },\n secretKey\n );\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n const result = await client.publishEvent(event, {\n destination: params.destination,\n claim: params.claim,\n ilpAmount: params.ilpAmount,\n });\n\n if (!result.success) {\n return {\n success: false,\n eventId: result.eventId ?? event.id,\n error: result.error ?? 'Blob storage request rejected',\n };\n }\n\n if (!result.data) {\n return {\n success: false,\n eventId: event.id,\n error: 'FULFILL contained no data; expected base64-encoded Arweave tx ID',\n };\n }\n\n // FULFILL data contract: base64(utf8(arweaveTxId)).\n const txId = decodeUtf8(fromBase64(result.data));\n\n if (!ARWEAVE_TX_ID_REGEX.test(txId)) {\n return {\n success: false,\n eventId: event.id,\n error: `Decoded FULFILL data is not a valid Arweave tx ID: \"${txId}\"`,\n };\n }\n\n return {\n success: true,\n txId,\n eventId: event.id,\n };\n}\n","import { finalizeEvent } from 'nostr-tools/pure';\nimport { nip19 } from 'nostr-tools';\nimport { EvmSigner } from '../signing/evm-signer.js';\nimport { SolanaSigner } from '../signing/solana-signer.js';\nimport { MinaSigner } from '../signing/mina-signer.js';\nimport type {\n KeyManagerConfig,\n PasskeyInfo,\n ToonIdentity,\n VaultData,\n} from './types.js';\nimport {\n deriveFullIdentity,\n deriveFromNsec,\n generateMnemonic,\n validateMnemonic,\n} from './KeyDerivation.js';\nimport {\n registerPasskey,\n assertPasskey,\n hashCredentialId,\n isPrfSupported,\n} from './PasskeyAuth.js';\nimport {\n createVault,\n deriveKek,\n deriveKekFromPassword,\n unlockVault,\n addKekToVault,\n removeKekFromVault,\n addRecoveryCodeToVault,\n unlockVaultWithRecoveryCode,\n generateRecoveryCode as generateRecoveryCodeRaw,\n} from './KeyVault.js';\nimport {\n buildBackupEvent,\n publishBackupToRelays,\n fetchBackupFromRelays,\n} from './BackupService.js';\nimport { fromBase64, hexToBytes, bytesToHex } from './encoding.js';\n\n/**\n * KeyManager orchestrates the full key lifecycle:\n * generate, derive, store, backup, recover — gated by WebAuthn Passkeys.\n *\n * Usage:\n * const km = new KeyManager({ relayUrls: ['wss://relay.example'] });\n * const identity = await km.create();\n * // or: const identity = await km.recover();\n *\n * const client = new ToonClient({\n * secretKey: identity.nostr.secretKey,\n * // ...\n * });\n *\n * Security note: JavaScript strings (like mnemonics) are immutable and cannot be\n * zeroed from memory. Uint8Array key material is zeroed on lock(), but mnemonic\n * strings persist until garbage collected. This is a known JS platform limitation.\n */\nexport class KeyManager {\n private readonly config: Required<KeyManagerConfig>;\n private identity: ToonIdentity | null = null;\n private vault: VaultData | null = null;\n private activeCredentialIdHash: string | null = null;\n\n constructor(config: KeyManagerConfig) {\n if (!config.relayUrls || config.relayUrls.length === 0) {\n throw new Error('KeyManager requires at least one relay URL');\n }\n\n this.config = {\n relayUrls: config.relayUrls,\n rpId:\n config.rpId ??\n (typeof window !== 'undefined'\n ? window.location.hostname\n : 'localhost'),\n rpName: config.rpName ?? 'TOON Protocol',\n storageKey: config.storageKey ?? 'toon:keys',\n };\n }\n\n // --- Account Lifecycle ---\n\n /**\n * Create a new account: generate mnemonic, create Passkey, encrypt, backup.\n */\n async create(): Promise<ToonIdentity> {\n const mnemonic = generateMnemonic();\n const identity = await deriveFullIdentity(mnemonic);\n\n // Generate a unique PRF salt for this credential\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n\n // Convert pubkey hex to bytes for userHandle\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n // Register Passkey with PRF\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n // Derive KEK from PRF output\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n // Create encrypted vault\n this.vault = await createVault(mnemonic, kek, credIdHash, prfSalt);\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n // Persist to IndexedDB\n await this.saveToLocalStorage();\n\n // Backup to relay (best-effort)\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal — local vault is still available\n });\n\n return identity;\n }\n\n /**\n * Recover an account using a synced Passkey.\n * The Nostr pubkey is extracted from the Passkey's userHandle.\n *\n * Flow: single assertion → userHandle → fetch backup → derive KEK → unlock.\n * If the local vault is available (has the PRF salt), we use a single assertion\n * with the correct salt. Otherwise, we need the backup from relays first, which\n * requires a discovery assertion to get the pubkey.\n */\n async recover(): Promise<ToonIdentity> {\n // Check local vault first — if available, we can do a single-assertion unlock\n const localVault = await this.loadFromLocalStorage();\n if (localVault) {\n return this.unlockWithVault(localVault);\n }\n\n // No local vault — need to discover pubkey from Passkey userHandle,\n // fetch backup from relays, then do a second assertion with the correct salt.\n // This is the cross-device recovery path (two assertions unavoidable).\n const discovery = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: crypto.getRandomValues(new Uint8Array(32)), // Dummy salt for discovery\n });\n\n if (!discovery.userHandle || discovery.userHandle.length === 0) {\n throw new Error(\n 'Passkey did not return a userHandle. Cannot determine Nostr pubkey for recovery.'\n );\n }\n\n // Extract Nostr pubkey from userHandle\n const pubkey = bytesToHex(discovery.userHandle);\n\n // Fetch backup from relays\n const vault = await fetchBackupFromRelays(pubkey, this.config.relayUrls);\n if (!vault) {\n throw new Error(\n 'No backup found on configured relays for this identity. ' +\n 'Try importing with a mnemonic or nsec instead.'\n );\n }\n\n // Find the matching wrapped key entry for this credential\n const credIdHash = await hashCredentialId(discovery.credentialId);\n const entry = vault.wrappedKeys.find((e) => e.id === credIdHash);\n if (!entry) {\n throw new Error(\n 'This Passkey is not registered with the backup. ' +\n 'Try a different Passkey or use a recovery code.'\n );\n }\n\n // Re-assert with the correct PRF salt from the backup\n const saltBytes = fromBase64(entry.salt);\n const reassertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: saltBytes,\n allowCredentials: [discovery.credentialId],\n });\n\n // Derive KEK and unlock vault\n const kek = await deriveKek(reassertion.prfOutput);\n const mnemonic = await unlockVault(vault, kek, credIdHash);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n // Cache locally for future single-assertion unlocks\n await this.saveToLocalStorage();\n\n return identity;\n }\n\n /**\n * Import an existing BIP-39 mnemonic. Creates a Passkey and backup.\n */\n async importMnemonic(mnemonic: string): Promise<ToonIdentity> {\n if (!validateMnemonic(mnemonic)) {\n throw new Error('Invalid BIP-39 mnemonic phrase');\n }\n\n const identity = await deriveFullIdentity(mnemonic);\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n this.vault = await createVault(mnemonic, kek, credIdHash, prfSalt);\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n\n return identity;\n }\n\n /**\n * Import from an nsec (Nostr-only key).\n * Nostr + EVM are derived; Solana + Mina get fresh keys (not deterministically linked).\n */\n async importNsec(nsec: string): Promise<ToonIdentity> {\n const decoded = nip19.decode(nsec);\n if (decoded.type !== 'nsec') {\n throw new Error('Invalid nsec string');\n }\n const secretKey = decoded.data;\n const identity = deriveFromNsec(secretKey);\n\n if (isPrfSupported()) {\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(identity.nostr.pubkey);\n\n try {\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const kek = await deriveKek(registration.prfOutput);\n const credIdHash = await hashCredentialId(registration.credentialId);\n\n // For nsec import without mnemonic, we store the hex-encoded secret key\n const hexKey = bytesToHex(secretKey);\n this.vault = await createVault(hexKey, kek, credIdHash, prfSalt);\n this.activeCredentialIdHash = credIdHash;\n\n await this.saveToLocalStorage();\n } catch {\n // PRF may be reported as supported but fail at registration time.\n // Proceed without vault — identity is still usable.\n }\n }\n\n this.identity = identity;\n return identity;\n }\n\n // --- Passkey Management ---\n\n /**\n * Register an additional Passkey for this identity.\n */\n async addPasskey(): Promise<void> {\n if (!this.identity || !this.vault || !this.activeCredentialIdHash) {\n throw new Error('No active identity — call create() or recover() first');\n }\n\n const prfSalt = crypto.getRandomValues(new Uint8Array(32));\n const userIdBytes = hexToBytes(this.identity.nostr.pubkey);\n\n const registration = await registerPasskey({\n rpId: this.config.rpId,\n rpName: this.config.rpName,\n userId: userIdBytes,\n userName: `TOON ${this.identity.nostr.pubkey.slice(0, 8)}`,\n prfSalt,\n });\n\n const newKek = await deriveKek(registration.prfOutput);\n const newCredIdHash = await hashCredentialId(registration.credentialId);\n\n // Re-assert the current Passkey to get KEK for unwrapping\n const currentEntry = this.vault.wrappedKeys.find(\n (e) => e.id === this.activeCredentialIdHash\n );\n if (!currentEntry) {\n throw new Error('Active credential not found in vault');\n }\n\n const currentSaltBytes = fromBase64(currentEntry.salt);\n const currentAssertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: currentSaltBytes,\n });\n const currentKek = await deriveKek(currentAssertion.prfOutput);\n\n this.vault = await addKekToVault(\n this.vault,\n currentKek,\n this.activeCredentialIdHash,\n newKek,\n newCredIdHash,\n prfSalt\n );\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n }\n\n /**\n * List registered Passkey credentials.\n */\n listPasskeys(): PasskeyInfo[] {\n if (!this.vault) return [];\n return this.vault.wrappedKeys.map((entry) => ({\n credentialIdHash: entry.id,\n createdAt: entry.created_at,\n }));\n }\n\n /**\n * Remove a Passkey from the vault. Cannot remove the last one.\n */\n async removePasskey(credentialIdHash: string): Promise<void> {\n if (!this.vault) {\n throw new Error('No active vault');\n }\n\n this.vault = removeKekFromVault(this.vault, credentialIdHash);\n\n // If we removed the active credential, switch to another\n if (this.activeCredentialIdHash === credentialIdHash) {\n const remaining = this.vault.wrappedKeys[0];\n this.activeCredentialIdHash = remaining ? remaining.id : null;\n }\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n }\n\n // --- Recovery ---\n\n /**\n * Generate a printable recovery code and add it to the vault.\n * The PBKDF2 salt is persisted alongside the wrapped DEK so the code\n * can be verified later without the original salt.\n *\n * @returns The recovery code — user must store it securely.\n */\n async generateRecoveryCode(): Promise<string> {\n if (!this.vault || !this.activeCredentialIdHash) {\n throw new Error('No active vault');\n }\n\n const code = generateRecoveryCodeRaw();\n\n // Generate and persist PBKDF2 salt for recovery code\n const salt = crypto.getRandomValues(new Uint8Array(16));\n const recoveryKek = await deriveKekFromPassword(code, salt);\n\n // Get current KEK to unwrap DEK\n const currentEntry = this.vault.wrappedKeys.find(\n (e) => e.id === this.activeCredentialIdHash\n );\n if (!currentEntry) {\n throw new Error('Active credential not found in vault');\n }\n\n const currentSaltBytes = fromBase64(currentEntry.salt);\n const currentAssertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: currentSaltBytes,\n });\n const currentKek = await deriveKek(currentAssertion.prfOutput);\n\n this.vault = await addRecoveryCodeToVault(\n this.vault,\n currentKek,\n this.activeCredentialIdHash,\n recoveryKek,\n salt\n );\n\n await this.saveToLocalStorage();\n await this.backupToRelay().catch(() => {\n // Backup failure is non-fatal\n });\n\n return code;\n }\n\n /**\n * Recover identity using a recovery code.\n * The PBKDF2 salt is read from the persisted vault data.\n */\n async recoverWithCode(code: string): Promise<ToonIdentity> {\n const vault = await this.loadFromLocalStorage();\n\n if (!vault) {\n throw new Error(\n 'No local vault found. Recovery code requires the encrypted vault. ' +\n 'If you have a Passkey, use recover() to fetch from relays first.'\n );\n }\n\n if (!vault.recoveryCodeWrappedDek || !vault.recoveryCodeSalt) {\n throw new Error('No recovery code configured for this vault');\n }\n\n // Use the persisted PBKDF2 salt to reproduce the exact KEK\n const salt = fromBase64(vault.recoveryCodeSalt);\n const recoveryKek = await deriveKekFromPassword(code, salt);\n\n const mnemonic = await unlockVaultWithRecoveryCode(vault, recoveryKek);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n\n return identity;\n }\n\n // --- Key Access ---\n\n /**\n * Get the current identity, or null if not unlocked.\n */\n getIdentity(): ToonIdentity | null {\n return this.identity;\n }\n\n /**\n * Get the Nostr secret key. Throws if not unlocked.\n */\n getNostrSecretKey(): Uint8Array {\n if (!this.identity) throw new Error('Identity not unlocked');\n return this.identity.nostr.secretKey;\n }\n\n /**\n * Get an EvmSigner instance. Throws if not unlocked.\n */\n getEvmSigner(): EvmSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n return new EvmSigner(this.identity.evm.privateKey);\n }\n\n /**\n * Get a SolanaSigner instance. Throws if not unlocked or Solana not derived.\n */\n getSolanaSigner(): SolanaSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n if (!this.identity.solana.publicKey) {\n throw new Error(\n 'Solana keys not available — was this imported from nsec?'\n );\n }\n return new SolanaSigner(this.identity.solana.secretKey);\n }\n\n /**\n * Get a MinaSigner instance. Throws if not unlocked or Mina not derived.\n */\n getMinaSigner(): MinaSigner {\n if (!this.identity) throw new Error('Identity not unlocked');\n if (!this.identity.mina.publicKey) {\n throw new Error('Mina keys not available — was this imported from nsec?');\n }\n return new MinaSigner(this.identity.mina.privateKey);\n }\n\n // --- Backup ---\n\n /**\n * Publish the current vault to configured relays as a kind:30078 event.\n */\n async backupToRelay(): Promise<void> {\n if (!this.identity || !this.vault) {\n throw new Error('No active identity or vault to backup');\n }\n\n const eventTemplate = buildBackupEvent(\n this.vault,\n this.identity.nostr.secretKey\n );\n\n // Sign with nostr-tools\n const signedEvent = finalizeEvent(\n eventTemplate,\n this.identity.nostr.secretKey\n );\n\n await publishBackupToRelays(signedEvent, this.config.relayUrls);\n }\n\n // --- Lock/Unlock ---\n\n /**\n * Clear keys from memory. The vault remains in IndexedDB.\n * Note: JavaScript strings (mnemonics) cannot be zeroed — only Uint8Array keys are cleared.\n */\n lock(): void {\n if (this.identity) {\n // Zero out sensitive Uint8Array key material\n this.identity.nostr.secretKey.fill(0);\n // evm.privateKey may be the same reference as nostr.secretKey — fill is idempotent\n this.identity.evm.privateKey.fill(0);\n this.identity.solana.secretKey.fill(0);\n }\n this.identity = null;\n }\n\n /**\n * Re-assert Passkey to decrypt local vault and restore identity.\n * Uses the local vault's stored PRF salt for a single biometric prompt.\n */\n async unlock(): Promise<ToonIdentity> {\n const vault = await this.loadFromLocalStorage();\n if (!vault) {\n throw new Error('No local vault found — use create() or recover()');\n }\n\n return this.unlockWithVault(vault);\n }\n\n // --- Private helpers ---\n\n /**\n * Unlock a vault with a single Passkey assertion using stored PRF salts.\n * If the vault has only one credential, uses allowCredentials to constrain.\n */\n private async unlockWithVault(vault: VaultData): Promise<ToonIdentity> {\n // We don't have the raw credential IDs (only hashes), so we can't constrain\n // allowCredentials. The user picks a credential, then we verify it matches.\n const firstEntry = vault.wrappedKeys[0];\n if (!firstEntry) {\n throw new Error('Vault has no registered credentials');\n }\n\n const assertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: fromBase64(firstEntry.salt),\n });\n\n const credIdHash = await hashCredentialId(assertion.credentialId);\n const matchingEntry = vault.wrappedKeys.find((e) => e.id === credIdHash);\n\n if (!matchingEntry) {\n throw new Error('This Passkey is not registered with the local vault');\n }\n\n // If the user picked the first credential, we already have the correct PRF output.\n // If they picked a different one, we need to re-assert with the correct salt.\n let prfOutput = assertion.prfOutput;\n if (matchingEntry.id !== firstEntry.id) {\n const correctSalt = fromBase64(matchingEntry.salt);\n const reassertion = await assertPasskey({\n rpId: this.config.rpId,\n prfSalt: correctSalt,\n allowCredentials: [assertion.credentialId],\n });\n prfOutput = reassertion.prfOutput;\n }\n\n const kek = await deriveKek(prfOutput);\n const mnemonic = await unlockVault(vault, kek, credIdHash);\n const identity = await deriveFullIdentity(mnemonic);\n\n this.vault = vault;\n this.identity = identity;\n this.activeCredentialIdHash = credIdHash;\n\n return identity;\n }\n\n // --- IndexedDB Persistence ---\n\n private async saveToLocalStorage(): Promise<void> {\n if (!this.vault) return;\n if (typeof indexedDB === 'undefined') return;\n\n const dbName = this.config.storageKey;\n const db = await openDb(dbName);\n const tx = db.transaction('vault', 'readwrite');\n const store = tx.objectStore('vault');\n store.put(JSON.stringify(this.vault), 'current');\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n db.close();\n }\n\n private async loadFromLocalStorage(): Promise<VaultData | null> {\n if (typeof indexedDB === 'undefined') return null;\n\n const dbName = this.config.storageKey;\n try {\n const db = await openDb(dbName);\n const tx = db.transaction('vault', 'readonly');\n const store = tx.objectStore('vault');\n const request = store.get('current');\n const result = await new Promise<string | undefined>(\n (resolve, reject) => {\n request.onsuccess = () =>\n resolve(request.result as string | undefined);\n request.onerror = () => reject(request.error);\n }\n );\n db.close();\n if (!result) return null;\n return JSON.parse(result) as VaultData;\n } catch {\n return null;\n }\n }\n}\n\n// --- Helpers ---\n\nfunction openDb(name: string): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(name, 1);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains('vault')) {\n db.createObjectStore('vault');\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n","import type {\n PasskeyAssertionResult,\n PasskeyRegistrationResult,\n} from './types.js';\n\n/**\n * WebAuthn Passkey authentication with PRF extension for key encryption.\n *\n * Handles credential registration (create) and assertion (get) with the\n * prf extension to derive a deterministic secret from the authenticator.\n * When PRF is unavailable, callers should fall back to password-based KEK.\n */\n\n/**\n * Register a new Passkey credential with PRF extension.\n *\n * @param rpId - Relying party identifier (e.g., \"example.com\")\n * @param rpName - Human-readable relying party name\n * @param userId - Raw user ID bytes (Nostr pubkey, 32 bytes)\n * @param userName - Display name for the credential\n * @param prfSalt - Random salt for the PRF evaluation\n * @returns Registration result with PRF output and credential ID\n */\nexport async function registerPasskey(params: {\n rpId: string;\n rpName: string;\n userId: Uint8Array;\n userName: string;\n prfSalt: Uint8Array;\n}): Promise<PasskeyRegistrationResult> {\n const { rpId, rpName, userId, userName, prfSalt } = params;\n\n const publicKeyOptions: PublicKeyCredentialCreationOptions = {\n rp: { id: rpId, name: rpName },\n user: {\n id: userId as unknown as BufferSource,\n name: userName,\n displayName: userName,\n },\n challenge: crypto.getRandomValues(\n new Uint8Array(32)\n ) as unknown as BufferSource,\n pubKeyCredParams: [\n { alg: -7, type: 'public-key' }, // ES256\n { alg: -257, type: 'public-key' }, // RS256\n ],\n authenticatorSelection: {\n residentKey: 'required',\n userVerification: 'required',\n },\n extensions: {\n prf: {\n eval: {\n first: prfSalt as unknown as BufferSource,\n },\n },\n } as AuthenticationExtensionsClientInputs,\n };\n\n const credential = (await navigator.credentials.create({\n publicKey: publicKeyOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey registration was cancelled or failed');\n }\n\n const response = credential.response as AuthenticatorAttestationResponse;\n const extensionResults = credential.getClientExtensionResults();\n\n const prfResults = (extensionResults as Record<string, unknown>)['prf'] as\n | { enabled?: boolean; results?: { first: ArrayBuffer } }\n | undefined;\n\n if (!prfResults?.results?.first) {\n throw new Error(\n 'PRF extension not supported by this authenticator. ' +\n 'Passkey was created but cannot be used for key encryption. ' +\n 'Use password-based encryption as fallback.'\n );\n }\n\n const credentialId = new Uint8Array(credential.rawId);\n\n // Verify attestation response is valid\n if (!response.attestationObject) {\n throw new Error('Invalid attestation response');\n }\n\n return {\n prfOutput: prfResults.results.first,\n credentialId,\n };\n}\n\n/**\n * Assert an existing Passkey credential with PRF extension.\n *\n * @param rpId - Relying party identifier\n * @param prfSalt - The same salt used during registration for this credential\n * @param allowCredentials - Optional list of credential IDs to filter\n * @returns Assertion result with PRF output, credential ID, and userHandle\n */\nexport async function assertPasskey(params: {\n rpId: string;\n prfSalt: Uint8Array;\n allowCredentials?: Uint8Array[];\n}): Promise<PasskeyAssertionResult> {\n const { rpId, prfSalt, allowCredentials } = params;\n\n const publicKeyOptions = {\n rpId,\n challenge: crypto.getRandomValues(\n new Uint8Array(32)\n ) as unknown as BufferSource,\n userVerification: 'required' as const,\n ...(allowCredentials && {\n allowCredentials: allowCredentials.map((id) => ({\n id: id as unknown as BufferSource,\n type: 'public-key' as const,\n })),\n }),\n extensions: {\n prf: {\n eval: {\n first: prfSalt as unknown as BufferSource,\n },\n },\n } as AuthenticationExtensionsClientInputs,\n } as PublicKeyCredentialRequestOptions;\n\n const credential = (await navigator.credentials.get({\n publicKey: publicKeyOptions,\n })) as PublicKeyCredential | null;\n\n if (!credential) {\n throw new Error('Passkey assertion was cancelled or failed');\n }\n\n const response = credential.response as AuthenticatorAssertionResponse;\n const extensionResults = credential.getClientExtensionResults();\n\n const prfResults = (extensionResults as Record<string, unknown>)['prf'] as\n | { results?: { first: ArrayBuffer } }\n | undefined;\n\n if (!prfResults?.results?.first) {\n throw new Error(\n 'PRF extension did not return a result. ' +\n 'The authenticator may not support PRF.'\n );\n }\n\n return {\n prfOutput: prfResults.results.first,\n credentialId: new Uint8Array(credential.rawId),\n userHandle: response.userHandle\n ? new Uint8Array(response.userHandle)\n : null,\n };\n}\n\n/**\n * Check whether the current browser supports WebAuthn with PRF extension.\n * Returns false in Node.js or browsers without PublicKeyCredential.\n */\nexport function isPrfSupported(): boolean {\n if (typeof window === 'undefined') return false;\n if (typeof navigator === 'undefined') return false;\n if (!navigator.credentials) return false;\n if (typeof PublicKeyCredential === 'undefined') return false;\n // PRF support cannot be feature-detected without actually creating a credential,\n // so we return true if WebAuthn is available and let registration handle the error.\n return true;\n}\n\n/**\n * Compute SHA-256 hash of a credential ID for use as a lookup key.\n */\nexport async function hashCredentialId(\n credentialId: Uint8Array\n): Promise<string> {\n const arrayBuffer = credentialId.buffer.slice(\n credentialId.byteOffset,\n credentialId.byteOffset + credentialId.byteLength\n ) as ArrayBuffer;\n const hash = await crypto.subtle.digest('SHA-256', arrayBuffer);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Browser-safe Base64 encoding/decoding utilities.\n * Accepts Uint8Array directly to avoid TypedArray .buffer offset issues.\n */\n\nexport function toBase64(data: Uint8Array | ArrayBuffer): string {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n let binary = '';\n for (const b of bytes) binary += String.fromCharCode(b);\n return btoa(binary);\n}\n\nexport function fromBase64(b64: string): Uint8Array {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n }\n return bytes;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","import type { VaultData, WrappedKeyEntry } from './types.js';\nimport { toBase64, fromBase64 } from './encoding.js';\n\n/**\n * Envelope encryption for mnemonic storage.\n *\n * Pattern: DEK encrypts mnemonic, KEK wraps DEK.\n * Multiple KEKs (Passkeys, recovery codes) can each wrap the same DEK independently.\n *\n * - DEK: random 256-bit AES key (Data Encryption Key)\n * - KEK: derived from Passkey PRF or recovery code (Key Encryption Key)\n * - Mnemonic encrypted with AES-256-GCM using DEK\n * - DEK wrapped with AES-KW using KEK\n */\n\n// --- DEK operations ---\n\n/**\n * Generate a random 256-bit Data Encryption Key.\n */\nexport async function generateDek(): Promise<CryptoKey> {\n return crypto.subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Encrypt a mnemonic string with a DEK using AES-256-GCM.\n * @returns Base64-encoded ciphertext and IV\n */\nexport async function encryptMnemonic(\n dek: CryptoKey,\n mnemonic: string\n): Promise<{ encryptedMnemonic: string; iv: string }> {\n const encoder = new TextEncoder();\n const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit GCM nonce\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n dek,\n encoder.encode(mnemonic)\n );\n return {\n encryptedMnemonic: toBase64(ciphertext),\n iv: toBase64(iv),\n };\n}\n\n/**\n * Decrypt a mnemonic from its encrypted form using a DEK.\n */\nexport async function decryptMnemonic(\n dek: CryptoKey,\n encryptedMnemonic: string,\n iv: string\n): Promise<string> {\n const decoder = new TextDecoder();\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: fromBase64(iv) },\n dek,\n fromBase64(encryptedMnemonic)\n );\n return decoder.decode(plaintext);\n}\n\n// --- KEK operations ---\n\n/**\n * Derive a KEK from a Passkey PRF output using HKDF.\n *\n * Flow: PRF(CredRandom, salt) → HKDF-SHA-256 → 256-bit AES-KW key\n */\nexport async function deriveKek(prfOutput: ArrayBuffer): Promise<CryptoKey> {\n // Import PRF output as HKDF key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n prfOutput,\n 'HKDF',\n false,\n ['deriveKey']\n );\n\n const encoder = new TextEncoder();\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: new Uint8Array(0), // PRF salt was already applied at the WebAuthn level\n info: encoder.encode('toon:kek'),\n },\n keyMaterial,\n { name: 'AES-KW', length: 256 },\n false, // not extractable\n ['wrapKey', 'unwrapKey']\n );\n}\n\n/**\n * Derive a KEK from a password string using PBKDF2.\n * Used as fallback when PRF is not available, or for recovery codes.\n */\nexport async function deriveKekFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n hash: 'SHA-256',\n salt,\n iterations: 600_000, // OWASP 2023 recommendation for SHA-256\n },\n keyMaterial,\n { name: 'AES-KW', length: 256 },\n false,\n ['wrapKey', 'unwrapKey']\n );\n}\n\n// --- Wrap/Unwrap DEK ---\n\n/**\n * Wrap (encrypt) a DEK with a KEK using AES-KW.\n * @returns Base64-encoded wrapped DEK\n */\nexport async function wrapDek(kek: CryptoKey, dek: CryptoKey): Promise<string> {\n const wrapped = await crypto.subtle.wrapKey('raw', dek, kek, 'AES-KW');\n return toBase64(new Uint8Array(wrapped));\n}\n\n/**\n * Unwrap (decrypt) a DEK from its wrapped form using a KEK.\n * @returns The unwrapped DEK as a CryptoKey\n */\nexport async function unwrapDek(\n kek: CryptoKey,\n wrappedDek: string\n): Promise<CryptoKey> {\n return crypto.subtle.unwrapKey(\n 'raw',\n fromBase64(wrappedDek),\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: 256 },\n true, // extractable — needed for re-wrapping when adding new KEKs\n ['encrypt', 'decrypt']\n );\n}\n\n// --- High-level vault operations ---\n\n/**\n * Create a new vault: generate DEK, encrypt mnemonic, wrap DEK with initial KEK.\n */\nexport async function createVault(\n mnemonic: string,\n kek: CryptoKey,\n credentialIdHash: string,\n prfSalt: Uint8Array\n): Promise<VaultData> {\n const dek = await generateDek();\n const { encryptedMnemonic, iv } = await encryptMnemonic(dek, mnemonic);\n const wrappedDek = await wrapDek(kek, dek);\n\n const entry: WrappedKeyEntry = {\n id: credentialIdHash,\n wrapped_dek: wrappedDek,\n salt: toBase64(prfSalt),\n created_at: Math.floor(Date.now() / 1000),\n };\n\n return {\n encryptedMnemonic,\n iv,\n wrappedKeys: [entry],\n };\n}\n\n/**\n * Unlock a vault: unwrap DEK with KEK, decrypt mnemonic.\n */\nexport async function unlockVault(\n vault: VaultData,\n kek: CryptoKey,\n credentialIdHash: string\n): Promise<string> {\n const entry = vault.wrappedKeys.find((e) => e.id === credentialIdHash);\n if (!entry) {\n throw new Error(`No wrapped key found for credential ${credentialIdHash}`);\n }\n\n const dek = await unwrapDek(kek, entry.wrapped_dek);\n return decryptMnemonic(dek, vault.encryptedMnemonic, vault.iv);\n}\n\n/**\n * Add a new KEK (from a new Passkey or recovery code) to an existing vault.\n * Requires an existing KEK to unwrap the DEK first.\n */\nexport async function addKekToVault(\n vault: VaultData,\n existingKek: CryptoKey,\n existingCredentialIdHash: string,\n newKek: CryptoKey,\n newCredentialIdHash: string,\n newPrfSalt: Uint8Array\n): Promise<VaultData> {\n // Unwrap DEK with existing KEK\n const existingEntry = vault.wrappedKeys.find(\n (e) => e.id === existingCredentialIdHash\n );\n if (!existingEntry) {\n throw new Error(\n `No wrapped key found for credential ${existingCredentialIdHash}`\n );\n }\n const dek = await unwrapDek(existingKek, existingEntry.wrapped_dek);\n\n // Wrap DEK with new KEK\n const newWrappedDek = await wrapDek(newKek, dek);\n\n const newEntry: WrappedKeyEntry = {\n id: newCredentialIdHash,\n wrapped_dek: newWrappedDek,\n salt: toBase64(newPrfSalt),\n created_at: Math.floor(Date.now() / 1000),\n };\n\n return {\n ...vault,\n wrappedKeys: [...vault.wrappedKeys, newEntry],\n };\n}\n\n/**\n * Remove a KEK from a vault. Always requires at least one passkey KEK to remain.\n */\nexport function removeKekFromVault(\n vault: VaultData,\n credentialIdHash: string\n): VaultData {\n const remaining = vault.wrappedKeys.filter((e) => e.id !== credentialIdHash);\n\n if (remaining.length === 0) {\n throw new Error(\n 'Cannot remove the last passkey — at least one passkey must remain for vault access'\n );\n }\n\n return {\n ...vault,\n wrappedKeys: remaining,\n };\n}\n\n/**\n * Add a recovery code KEK to the vault.\n * The PBKDF2 salt is persisted in the vault so recoverWithCode can reproduce the KEK.\n */\nexport async function addRecoveryCodeToVault(\n vault: VaultData,\n existingKek: CryptoKey,\n existingCredentialIdHash: string,\n recoveryKek: CryptoKey,\n recoverySalt: Uint8Array\n): Promise<VaultData> {\n const existingEntry = vault.wrappedKeys.find(\n (e) => e.id === existingCredentialIdHash\n );\n if (!existingEntry) {\n throw new Error(\n `No wrapped key found for credential ${existingCredentialIdHash}`\n );\n }\n const dek = await unwrapDek(existingKek, existingEntry.wrapped_dek);\n const recoveryWrappedDek = await wrapDek(recoveryKek, dek);\n\n return {\n ...vault,\n recoveryCodeWrappedDek: recoveryWrappedDek,\n recoveryCodeSalt: toBase64(recoverySalt),\n };\n}\n\n/**\n * Unlock a vault using a recovery code.\n */\nexport async function unlockVaultWithRecoveryCode(\n vault: VaultData,\n recoveryKek: CryptoKey\n): Promise<string> {\n if (!vault.recoveryCodeWrappedDek) {\n throw new Error('No recovery code is configured for this vault');\n }\n\n const dek = await unwrapDek(recoveryKek, vault.recoveryCodeWrappedDek);\n return decryptMnemonic(dek, vault.encryptedMnemonic, vault.iv);\n}\n\n/**\n * Generate a human-readable recovery code (24 random hex chars with dashes).\n * Format: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (easy to write down).\n */\nexport function generateRecoveryCode(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(12));\n const hex = Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n const groups = hex.match(/.{4}/g) ?? [];\n return groups.join('-');\n}\n","import { getPublicKey } from 'nostr-tools/pure';\nimport type { BackupPayload, VaultData } from './types.js';\n\n/**\n * Relay-based encrypted backup service using kind:30078 (NIP-78 application-specific data).\n *\n * The backup event is a replaceable event keyed by `d: \"toon:identity-backup\"`.\n * Content is an encrypted payload — the relay cannot read the mnemonic.\n */\n\nconst BACKUP_KIND = 30078;\nconst BACKUP_D_TAG = 'toon:identity-backup';\nconst BACKUP_VERSION = '1';\n\n/**\n * Build a kind:30078 Nostr event for identity backup.\n * The event must be signed by the caller before publishing.\n *\n * @param vault - The encrypted vault data\n * @param secretKey - Nostr secret key for signing\n * @param chains - Comma-separated list of supported chains\n * @returns Unsigned Nostr event template\n */\nexport function buildBackupEvent(\n vault: VaultData,\n secretKey: Uint8Array,\n chains = 'nostr,evm,solana,mina'\n): {\n kind: number;\n pubkey: string;\n created_at: number;\n tags: string[][];\n content: string;\n} {\n const pubkey = getPublicKey(secretKey);\n\n const payload: BackupPayload = {\n encrypted_mnemonic: vault.encryptedMnemonic,\n wrapped_keys: vault.wrappedKeys,\n iv: vault.iv,\n ...(vault.recoveryCodeWrappedDek && {\n recovery_code_wrapped_dek: vault.recoveryCodeWrappedDek,\n }),\n ...(vault.recoveryCodeSalt && {\n recovery_code_salt: vault.recoveryCodeSalt,\n }),\n };\n\n return {\n kind: BACKUP_KIND,\n pubkey,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n ['d', BACKUP_D_TAG],\n ['v', BACKUP_VERSION],\n ['chains', chains],\n ],\n content: JSON.stringify(payload),\n };\n}\n\n/**\n * Build a relay filter to fetch the identity backup for a given pubkey.\n */\nexport function buildBackupFilter(pubkey: string): {\n kinds: number[];\n authors: string[];\n '#d': string[];\n} {\n return {\n kinds: [BACKUP_KIND],\n authors: [pubkey],\n '#d': [BACKUP_D_TAG],\n };\n}\n\n/**\n * Parse a backup event's content into VaultData.\n * Validates the structure before returning.\n */\nexport function parseBackupPayload(content: string): VaultData {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n throw new Error('Invalid backup event content: not valid JSON');\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('Invalid backup event content: not an object');\n }\n\n const payload = parsed as Record<string, unknown>;\n\n if (typeof payload['encrypted_mnemonic'] !== 'string') {\n throw new Error('Invalid backup: missing encrypted_mnemonic');\n }\n if (typeof payload['iv'] !== 'string') {\n throw new Error('Invalid backup: missing iv');\n }\n if (!Array.isArray(payload['wrapped_keys'])) {\n throw new Error('Invalid backup: missing wrapped_keys array');\n }\n\n // Validate each wrapped key entry\n for (const entry of payload['wrapped_keys']) {\n if (typeof entry !== 'object' || entry === null) {\n throw new Error('Invalid backup: wrapped_keys entry is not an object');\n }\n const e = entry as Record<string, unknown>;\n if (typeof e['id'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing id');\n }\n if (typeof e['wrapped_dek'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing wrapped_dek');\n }\n if (typeof e['salt'] !== 'string') {\n throw new Error('Invalid backup: wrapped key missing salt');\n }\n if (typeof e['created_at'] !== 'number') {\n throw new Error(\n 'Invalid backup: wrapped key missing or invalid created_at'\n );\n }\n }\n\n return {\n encryptedMnemonic: payload['encrypted_mnemonic'] as string,\n iv: payload['iv'] as string,\n wrappedKeys: payload['wrapped_keys'] as VaultData['wrappedKeys'],\n ...(typeof payload['recovery_code_wrapped_dek'] === 'string' && {\n recoveryCodeWrappedDek: payload['recovery_code_wrapped_dek'],\n }),\n ...(typeof payload['recovery_code_salt'] === 'string' && {\n recoveryCodeSalt: payload['recovery_code_salt'],\n }),\n };\n}\n\n/**\n * Publish a backup event to one or more relays.\n *\n * Uses the SimplePool from nostr-tools for relay communication.\n * The event must be signed before calling this function.\n */\nexport async function publishBackupToRelays(\n signedEvent: {\n id: string;\n kind: number;\n pubkey: string;\n created_at: number;\n tags: string[][];\n content: string;\n sig: string;\n },\n relayUrls: string[]\n): Promise<void> {\n // Dynamic import to avoid pulling nostr-tools/pool into non-browser bundles\n const { SimplePool } = await import('nostr-tools/pool');\n const pool = new SimplePool();\n\n try {\n await Promise.allSettled(\n relayUrls.map((url) => pool.publish([url], signedEvent))\n );\n } finally {\n pool.close(relayUrls);\n }\n}\n\n/**\n * Fetch a backup event from relays for a given pubkey.\n * Returns the most recent backup event, or null if none found.\n */\nexport async function fetchBackupFromRelays(\n pubkey: string,\n relayUrls: string[]\n): Promise<VaultData | null> {\n const { SimplePool } = await import('nostr-tools/pool');\n const pool = new SimplePool();\n\n try {\n const filter = buildBackupFilter(pubkey);\n const events = await pool.querySync(relayUrls, filter);\n\n if (!events || events.length === 0) {\n return null;\n }\n\n // Sort by created_at descending, take most recent\n events.sort(\n (a: { created_at: number }, b: { created_at: number }) =>\n b.created_at - a.created_at\n );\n\n const latest = events[0];\n if (!latest) return null;\n return parseBackupPayload(latest.content);\n } finally {\n pool.close(relayUrls);\n }\n}\n","/**\n * Node-only encrypted mnemonic keystore for @toon-protocol/client.\n *\n * Mirrors the Townhouse node wallet crypto (`packages/townhouse/src/wallet/\n * crypto.ts`): a BIP-39 mnemonic is encrypted at rest with scrypt (KDF) +\n * AES-256-GCM (authenticated encryption), serialized as JSON, and written to\n * disk with mode 0o600. Decryption requires the operator password; a wrong\n * password fails the GCM auth-tag verification and throws.\n *\n * This is the Node-side counterpart to the browser Passkey/IndexedDB\n * `KeyManager`/`KeyVault` flow — it does NOT touch those. It is guarded against\n * browser bundling: every entry point throws if `node:crypto`/`node:fs` are not\n * available (e.g. when accidentally imported in a browser bundle).\n *\n * @module\n */\n\nimport {\n scryptSync,\n createCipheriv,\n createDecipheriv,\n randomBytes,\n} from 'node:crypto';\nimport { writeFileSync, readFileSync } from 'node:fs';\nimport {\n generateMnemonic as genMnemonic,\n validateMnemonic as isValidMnemonic,\n} from './KeyDerivation.js';\n\n/** scrypt parameters — N=2^17 (~0.5-1s on modern hardware), r=8, p=1. */\nconst SCRYPT_N = 2 ** 17;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SCRYPT_KEY_LEN = 32;\n/** maxmem for scrypt: N * r * 128 * 2 (with headroom for Node.js overhead). */\nconst SCRYPT_MAXMEM = SCRYPT_N * SCRYPT_R * 256 + 32 * 1024 * 1024;\n\n/** Salt length in bytes. */\nconst SALT_LEN = 32;\n/** AES-GCM IV length in bytes. */\nconst IV_LEN = 12;\n/** AES-GCM authentication tag length in bytes (128-bit). */\nconst AUTH_TAG_LEN = 16;\n\n/**\n * Encrypted keystore file format (JSON, all binary fields base64-encoded).\n * Wire-compatible with Townhouse's `EncryptedWallet`.\n */\nexport interface EncryptedKeystore {\n /** scrypt salt (base64). */\n salt: string;\n /** AES-GCM initialization vector (base64). */\n iv: string;\n /** AES-256-GCM ciphertext (base64). */\n ciphertext: string;\n /** AES-GCM authentication tag (base64). */\n tag: string;\n /** Envelope version for forward-compat (currently 1). */\n version?: number;\n}\n\n/**\n * Throws if this module is running outside Node.js. The scrypt/AES-256-GCM\n * primitives and the 0o600 file write are Node-only — there is no browser\n * equivalent of `node:crypto`'s `scryptSync` or POSIX file modes, so we fail\n * loudly rather than silently bundling broken code into a browser build.\n */\nfunction assertNode(): void {\n // `process.versions.node` is present in Node and absent in browsers.\n const versions = (\n globalThis as { process?: { versions?: { node?: string } } }\n ).process?.versions;\n if (!versions?.node) {\n throw new Error(\n 'keystore-node is Node.js-only and cannot run in a browser. ' +\n 'Use the Passkey/IndexedDB KeyManager for browser key storage.'\n );\n }\n}\n\n/**\n * Encrypt a mnemonic with a password using scrypt + AES-256-GCM.\n * Returns the JSON-serializable encrypted envelope (does NOT write to disk).\n */\nexport function encryptMnemonic(\n mnemonic: string,\n password: string\n): EncryptedKeystore {\n assertNode();\n if (typeof mnemonic !== 'string' || mnemonic.length === 0) {\n throw new Error('encryptMnemonic: mnemonic must be a non-empty string');\n }\n if (typeof password !== 'string' || password.length === 0) {\n throw new Error('encryptMnemonic: password must be a non-empty string');\n }\n\n const salt = randomBytes(SALT_LEN);\n const iv = randomBytes(IV_LEN);\n const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {\n N: SCRYPT_N,\n r: SCRYPT_R,\n p: SCRYPT_P,\n maxmem: SCRYPT_MAXMEM,\n });\n\n try {\n const cipher = createCipheriv('aes-256-gcm', key, iv, {\n authTagLength: AUTH_TAG_LEN,\n });\n const ciphertext = Buffer.concat([\n cipher.update(mnemonic, 'utf8'),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n\n return {\n salt: salt.toString('base64'),\n iv: iv.toString('base64'),\n ciphertext: ciphertext.toString('base64'),\n tag: tag.toString('base64'),\n version: 1,\n };\n } finally {\n key.fill(0);\n }\n}\n\n/**\n * Decrypt an encrypted keystore envelope with a password.\n * Throws on a wrong password (GCM auth-tag verification failure) or corruption.\n */\nexport function decryptMnemonic(\n encrypted: EncryptedKeystore,\n password: string\n): string {\n assertNode();\n if (typeof password !== 'string' || password.length === 0) {\n throw new Error('decryptMnemonic: password must be a non-empty string');\n }\n if (\n !encrypted ||\n typeof encrypted.salt !== 'string' ||\n typeof encrypted.iv !== 'string' ||\n typeof encrypted.ciphertext !== 'string' ||\n typeof encrypted.tag !== 'string'\n ) {\n throw new Error('decryptMnemonic: malformed keystore envelope');\n }\n\n const salt = Buffer.from(encrypted.salt, 'base64');\n const iv = Buffer.from(encrypted.iv, 'base64');\n const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');\n const tag = Buffer.from(encrypted.tag, 'base64');\n\n const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {\n N: SCRYPT_N,\n r: SCRYPT_R,\n p: SCRYPT_P,\n maxmem: SCRYPT_MAXMEM,\n });\n\n try {\n const decipher = createDecipheriv('aes-256-gcm', key, iv, {\n authTagLength: AUTH_TAG_LEN,\n });\n decipher.setAuthTag(tag);\n try {\n const plaintext = Buffer.concat([\n decipher.update(ciphertext),\n decipher.final(),\n ]);\n return plaintext.toString('utf8');\n } catch {\n throw new Error(\n 'Decryption failed: wrong password or corrupted keystore file'\n );\n }\n } finally {\n key.fill(0);\n }\n}\n\n/**\n * Generate a fresh 12-word BIP-39 mnemonic, encrypt it under `password`, and\n * write the encrypted keystore to `path` with mode 0o600.\n *\n * Returns the mnemonic (for one-time display/backup) alongside the encrypted\n * envelope. The caller is responsible for displaying the mnemonic securely and\n * NOT persisting it in plaintext.\n */\nexport function generateKeystore(\n path: string,\n password: string\n): { mnemonic: string; keystore: EncryptedKeystore } {\n assertNode();\n const mnemonic = genMnemonic();\n const keystore = encryptMnemonic(mnemonic, password);\n writeKeystoreFile(path, keystore);\n return { mnemonic, keystore };\n}\n\n/**\n * Import an existing BIP-39 mnemonic (12 or 24 words), encrypt it under\n * `password`, and write the encrypted keystore to `path` with mode 0o600.\n *\n * Throws if the mnemonic is not a valid BIP-39 phrase (wrong checksum/word\n * count) before any file is written.\n */\nexport function importKeystore(\n path: string,\n mnemonic: string,\n password: string\n): EncryptedKeystore {\n assertNode();\n if (!isValidMnemonic(mnemonic)) {\n throw new Error(\n 'Invalid BIP-39 mnemonic: checksum or word-list validation failed'\n );\n }\n const keystore = encryptMnemonic(mnemonic, password);\n writeKeystoreFile(path, keystore);\n return keystore;\n}\n\n/**\n * Load and decrypt a keystore file at `path` with `password`, returning the\n * plaintext mnemonic. Throws on a wrong password or corruption.\n */\nexport function loadKeystore(path: string, password: string): string {\n assertNode();\n const raw = readFileSync(path, 'utf8');\n let parsed: EncryptedKeystore;\n try {\n parsed = JSON.parse(raw) as EncryptedKeystore;\n } catch {\n throw new Error(`Keystore file at ${path} is not valid JSON`);\n }\n return decryptMnemonic(parsed, password);\n}\n\n/**\n * Serialize and write an encrypted keystore to disk with mode 0o600\n * (owner read/write only), mirroring the Townhouse wallet file permissions.\n */\nexport function writeKeystoreFile(\n path: string,\n keystore: EncryptedKeystore\n): void {\n assertNode();\n writeFileSync(path, JSON.stringify(keystore, null, 2), {\n encoding: 'utf8',\n mode: 0o600,\n });\n}\n","import { encode } from '@toon-format/toon';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { ToonError } from '../errors.js';\n\n/**\n * Error thrown when TOON encoding fails.\n */\nexport class ToonEncodeError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'TOON_ENCODE_ERROR', cause);\n this.name = 'ToonEncodeError';\n }\n}\n\n/**\n * Encode a NostrEvent to TOON format as a Uint8Array.\n *\n * Used for embedding Nostr events in ILP packets where compact encoding\n * reduces bytes and cost.\n *\n * @param event - The NostrEvent to encode\n * @returns Uint8Array containing the TOON-encoded event\n * @throws ToonEncodeError if encoding fails\n */\nexport function encodeEventToToon(event: NostrEvent): Uint8Array {\n try {\n const toonString = encode(event);\n return new TextEncoder().encode(toonString);\n } catch (error) {\n throw new ToonEncodeError(\n `Failed to encode event to TOON: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Encode a NostrEvent to TOON format as a string.\n *\n * Used for embedding TOON-encoded events in outbound WebSocket messages\n * where the NIP-01 framing remains JSON but the event payload is TOON.\n *\n * @param event - The NostrEvent to encode\n * @returns TOON-encoded string representation of the event\n * @throws ToonEncodeError if encoding fails\n */\nexport function encodeEventToToonString(event: NostrEvent): string {\n try {\n return encode(event);\n } catch (error) {\n throw new ToonEncodeError(\n `Failed to encode event to TOON: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n","/**\n * Custom error classes for @toon-protocol/core.\n */\n\n/**\n * Base error class for all toon errors.\n * Provides a consistent error interface with error codes and cause chaining.\n */\nexport class ToonError extends Error {\n public readonly code: string;\n\n constructor(message: string, code: string, cause?: Error) {\n super(message, { cause });\n this.name = 'ToonError';\n this.code = code;\n }\n}\n\n/**\n * Error thrown when parsing a Nostr event fails.\n * Used for malformed events, wrong kind, invalid JSON, or missing required fields.\n */\nexport class InvalidEventError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'INVALID_EVENT', cause);\n this.name = 'InvalidEventError';\n }\n}\n\n/**\n * Error thrown when peer discovery fails.\n * Used for invalid pubkeys or relay failures.\n */\nexport class PeerDiscoveryError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'PEER_DISCOVERY_FAILED', cause);\n this.name = 'PeerDiscoveryError';\n }\n}\n","import { decode } from '@toon-format/toon';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { ToonError } from '../errors.js';\nimport { isValidHex } from './validate.js';\n\n/**\n * Error thrown when TOON decoding or validation fails.\n */\nexport class ToonDecodeError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'TOON_DECODE_ERROR', cause);\n this.name = 'ToonDecodeError';\n }\n}\n\n/**\n * Validate that a decoded object is a valid NostrEvent.\n */\nfunction validateNostrEvent(obj: unknown): asserts obj is NostrEvent {\n if (typeof obj !== 'object' || obj === null) {\n throw new ToonDecodeError('Decoded value is not an object');\n }\n\n const event = obj as Record<string, unknown>;\n\n // Validate id (64-char hex)\n if (!isValidHex(event['id'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event id: must be a 64-character hex string'\n );\n }\n\n // Validate pubkey (64-char hex)\n if (!isValidHex(event['pubkey'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event pubkey: must be a 64-character hex string'\n );\n }\n\n // Validate kind (number)\n if (typeof event['kind'] !== 'number' || !Number.isInteger(event['kind'])) {\n throw new ToonDecodeError('Invalid event kind: must be an integer');\n }\n\n // Validate content (string)\n if (typeof event['content'] !== 'string') {\n throw new ToonDecodeError('Invalid event content: must be a string');\n }\n\n // Validate tags (array of string arrays)\n const tags = event['tags'];\n if (!Array.isArray(tags)) {\n throw new ToonDecodeError('Invalid event tags: must be an array');\n }\n for (let i = 0; i < tags.length; i++) {\n const tag = tags[i];\n if (!Array.isArray(tag)) {\n throw new ToonDecodeError(`Invalid event tags[${i}]: must be an array`);\n }\n for (let j = 0; j < tag.length; j++) {\n if (typeof tag[j] !== 'string') {\n throw new ToonDecodeError(\n `Invalid event tags[${i}][${j}]: must be a string`\n );\n }\n }\n }\n\n // Validate created_at (number)\n if (\n typeof event['created_at'] !== 'number' ||\n !Number.isInteger(event['created_at'])\n ) {\n throw new ToonDecodeError('Invalid event created_at: must be an integer');\n }\n\n // Validate sig (128-char hex)\n if (!isValidHex(event['sig'], 128)) {\n throw new ToonDecodeError(\n 'Invalid event sig: must be a 128-character hex string'\n );\n }\n}\n\n/**\n * Decode a TOON-encoded Uint8Array back to a NostrEvent.\n *\n * Used for extracting Nostr events from ILP packets.\n *\n * @param data - The TOON-encoded Uint8Array\n * @returns The decoded NostrEvent\n * @throws ToonDecodeError if decoding or validation fails\n */\nexport function decodeEventFromToon(data: Uint8Array): NostrEvent {\n try {\n const toonString = new TextDecoder().decode(data);\n const decoded = decode(toonString);\n validateNostrEvent(decoded);\n return decoded;\n } catch (error) {\n if (error instanceof ToonDecodeError) {\n throw error;\n }\n throw new ToonDecodeError(\n `Failed to decode TOON data: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n","/**\n * Shared validation utilities for the TOON codec.\n */\n\n/**\n * Validate that a value is a valid hex string of expected length.\n *\n * @param value - The value to check\n * @param length - The expected string length (number of hex characters)\n * @returns True if value is a hex string of the expected length\n */\nexport function isValidHex(value: unknown, length: number): value is string {\n if (typeof value !== 'string') return false;\n if (value.length !== length) return false;\n return /^[0-9a-f]+$/i.test(value);\n}\n","import { decode } from '@toon-format/toon';\nimport { ToonDecodeError } from './decoder.js';\nimport { isValidHex } from './validate.js';\n\n/**\n * Routing metadata extracted from TOON-encoded bytes without full NostrEvent validation.\n *\n * The shallow parser extracts only the 4 routing fields needed for signature verification\n * and event routing, skipping expensive validation of content, tags, and created_at.\n */\nexport interface ToonRoutingMeta {\n kind: number;\n pubkey: string;\n id: string;\n sig: string;\n rawBytes: Uint8Array;\n}\n\n/**\n * Shallow-parse TOON-encoded bytes to extract routing metadata.\n *\n * This function decodes the TOON data and extracts only the 4 routing fields\n * (kind, pubkey, id, sig) without performing full NostrEvent validation.\n * It is cheaper than a full decode + validate cycle.\n *\n * The `rawBytes` field preserves the original input bytes for downstream\n * use (e.g., Schnorr signature verification against the serialized payload).\n *\n * @param data - The TOON-encoded Uint8Array\n * @returns Routing metadata with the original raw bytes\n * @throws ToonDecodeError if the data cannot be parsed or required routing fields are missing/invalid\n */\nexport function shallowParseToon(data: Uint8Array): ToonRoutingMeta {\n let decoded: unknown;\n try {\n const toonString = new TextDecoder().decode(data);\n decoded = decode(toonString);\n } catch (error) {\n throw new ToonDecodeError(\n `Failed to parse TOON data: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (typeof decoded !== 'object' || decoded === null) {\n throw new ToonDecodeError('Decoded TOON value is not an object');\n }\n\n const obj = decoded as Record<string, unknown>;\n\n // Validate kind (number, integer)\n if (typeof obj['kind'] !== 'number' || !Number.isInteger(obj['kind'])) {\n throw new ToonDecodeError(\n 'Missing or invalid routing field: kind must be an integer'\n );\n }\n\n // Validate pubkey (64-char hex)\n if (!isValidHex(obj['pubkey'], 64)) {\n throw new ToonDecodeError(\n 'Missing or invalid routing field: pubkey must be a 64-character hex string'\n );\n }\n\n // Validate id (64-char hex)\n if (!isValidHex(obj['id'], 64)) {\n throw new ToonDecodeError(\n 'Missing or invalid routing field: id must be a 64-character hex string'\n );\n }\n\n // Validate sig (128-char hex)\n if (!isValidHex(obj['sig'], 128)) {\n throw new ToonDecodeError(\n 'Missing or invalid routing field: sig must be a 128-character hex string'\n );\n }\n\n return {\n kind: obj['kind'] as number,\n pubkey: obj['pubkey'] as string,\n id: obj['id'] as string,\n sig: obj['sig'] as string,\n rawBytes: data,\n };\n}\n","/**\n * Nostr event kind constants for ILP-related events.\n *\n * These follow the NIP convention for replaceable (10000-19999) event kinds.\n */\n\n/**\n * ILP Peer Info (kind 10032)\n * Replaceable event containing connector's ILP address, BTP endpoint, and settlement info.\n */\nexport const ILP_PEER_INFO_KIND = 10032;\n\n/**\n * Service Discovery (kind 10035)\n * Replaceable event advertising a node's capabilities, pricing, and endpoints.\n * Published to the local relay and optionally to peers so that clients and\n * agents can programmatically discover available services.\n * NIP-16 replaceable: relays store only the latest event per pubkey + kind.\n */\nexport const SERVICE_DISCOVERY_KIND = 10035;\n\n/**\n * Seed Relay List (kind 10036)\n * Replaceable event containing a list of relay nodes that serve as bootstrap\n * entry points for new network participants. Published to public Nostr relays.\n * NIP-16 replaceable: relays store only the latest event per pubkey + kind.\n */\nexport const SEED_RELAY_LIST_KIND = 10036;\n\n/**\n * TEE Attestation (kind 10033)\n * NIP-16 replaceable event containing TEE attestation data: PCR values,\n * enclave image hash, and attestation documents from the TEE platform.\n * Published by nodes running in a Trusted Execution Environment (e.g.,\n * Marlin Oyster CVM / AWS Nitro Enclaves). Relays store only the latest\n * event per pubkey + kind. No `d` tag needed -- NIP-16 replaces by\n * pubkey + kind alone (unlike NIP-33 parameterized replaceable events).\n */\nexport const TEE_ATTESTATION_KIND = 10033;\n\n// ---------------------------------------------------------------------------\n// NIP-90 DVM (Data Vending Machine) Event Kinds\n// ---------------------------------------------------------------------------\n\n/**\n * Base kind for NIP-90 DVM job requests (kind range 5000-5999).\n * Job requests are regular (non-replaceable) events. Each specific DVM task\n * type is assigned a unique kind within this range (e.g., 5100 for text\n * generation). Providers listen for requests in their supported kind range.\n */\nexport const JOB_REQUEST_KIND_BASE = 5000;\n\n/**\n * Base kind for NIP-90 DVM job results (kind range 6000-6999).\n * Result kind = request kind + 1000 (e.g., Kind 5100 request -> Kind 6100\n * result). Result events reference the original request via an `e` tag and\n * include the compute cost in an `amount` tag.\n */\nexport const JOB_RESULT_KIND_BASE = 6000;\n\n/**\n * NIP-90 DVM job feedback (kind 7000).\n * A single kind used for all feedback messages (processing, error, success,\n * partial). Feedback events reference the original request via an `e` tag\n * and carry a `status` tag indicating the current job state.\n */\nexport const JOB_FEEDBACK_KIND = 7000;\n\n/**\n * Text Generation DVM kind (kind 5100).\n * Reference DVM kind for the TOON protocol. Used for general-purpose\n * text generation tasks (e.g., LLM inference, summarization, Q&A).\n */\nexport const TEXT_GENERATION_KIND = 5100;\n\n/**\n * Image Generation DVM kind (kind 5200).\n * Used for image generation tasks (e.g., text-to-image, image editing).\n * Optional provider support -- not all nodes are required to handle this kind.\n */\nexport const IMAGE_GENERATION_KIND = 5200;\n\n/**\n * Text-to-Speech DVM kind (kind 5300).\n * Used for text-to-speech conversion tasks.\n * Optional provider support -- not all nodes are required to handle this kind.\n */\nexport const TEXT_TO_SPEECH_KIND = 5300;\n\n/**\n * Translation DVM kind (kind 5302).\n * Used for language translation tasks.\n * Optional provider support -- not all nodes are required to handle this kind.\n */\nexport const TRANSLATION_KIND = 5302;\n\n/**\n * Workflow Chain definition (kind 10040).\n * Replaceable event defining a multi-step DVM pipeline where each step's\n * output feeds into the next step's input. Uses a unique `d` tag per\n * workflow instance for NIP-33 parameterized replaceable semantics.\n * In the TOON-specific replaceable range (10032-10099).\n */\nexport const WORKFLOW_CHAIN_KIND = 10040;\n\n/**\n * Job Review (kind 31117)\n * NIP-33 parameterized replaceable event for post-job reviews.\n * `d` tag = job request event ID enforces one review per job per reviewer.\n * Rating tag contains integer 1-5. Role tag indicates 'customer' or 'provider'.\n */\nexport const JOB_REVIEW_KIND = 31117;\n\n/**\n * Web of Trust declaration (kind 30382)\n * NIP-33 parameterized replaceable event endorsing a provider pubkey.\n * `d` tag = target provider pubkey enforces one WoT declaration per\n * declarer per target. Used for reputation scoring sybil defense.\n */\nexport const WEB_OF_TRUST_KIND = 30382;\n\n/**\n * Prefix Claim (kind 10034)\n * Replaceable event requesting a prefix claim from an upstream node.\n * The content field contains a PrefixClaimContent JSON payload with the\n * requested prefix string. Payment is carried in the ILP PREPARE packet.\n */\nexport const PREFIX_CLAIM_KIND = 10034;\n\n/**\n * Prefix Grant (kind 10037)\n * Replaceable event confirming a prefix claim was accepted by the upstream node.\n * Published by the upstream node after validating payment and prefix availability.\n * Contains the granted prefix, claimer's pubkey, and derived ILP address.\n */\nexport const PREFIX_GRANT_KIND = 10037;\n\n// ---------------------------------------------------------------------------\n// ILP Address Hierarchy Constants\n// ---------------------------------------------------------------------------\n\n/**\n * ILP root prefix for the TOON network.\n * `g.` is the ILP global allocation prefix (standard ILP convention).\n * `toon` is the TOON network identifier.\n * The genesis node uses this directly -- it does not derive its address from a pubkey.\n * All other nodes derive addresses as children of their upstream peer's prefix.\n */\nexport const ILP_ROOT_PREFIX = 'g.toon';\n\n// ---------------------------------------------------------------------------\n// Arweave / Blob Storage DVM Event Kinds\n// ---------------------------------------------------------------------------\n\n/**\n * Blob Storage DVM kind (kind 5094).\n * Used for permanent blob storage requests (e.g., Arweave uploads).\n * The blob data is base64-encoded in the `i` tag with type `blob`.\n * Payment is carried in the ILP PREPARE packet (prepaid model).\n */\nexport const BLOB_STORAGE_REQUEST_KIND = 5094;\n\n/**\n * Blob Storage Result DVM kind (kind 6094).\n * Reserved for informational result events. In the prepaid model,\n * the Arweave tx ID is returned in the ILP FULFILL data field,\n * NOT as a kind:6094 event.\n */\nexport const BLOB_STORAGE_RESULT_KIND = 6094;\n\n// ---------------------------------------------------------------------------\n// Pet DVM Event Kinds (Epic 11)\n// ---------------------------------------------------------------------------\n\n/**\n * Pet Interaction Request DVM kind (kind 5900).\n * Used for pet interaction requests (feed, play, clean, etc.).\n * Kind 5900 chosen to leave room for future DVM kinds in 5000-5899 range.\n * Payment is carried in the ILP PREPARE packet (prepaid model).\n */\nexport const PET_INTERACTION_REQUEST_KIND = 5900;\n\n/**\n * Pet Interaction Result DVM kind (kind 6900).\n * Result kind = request kind + 1000 per NIP-90 convention.\n * In the prepaid model, the new pet state is returned in the ILP FULFILL\n * data field as base64-encoded JSON.\n */\nexport const PET_INTERACTION_RESULT_KIND = 6900;\n\n/**\n * Pet Interaction Event kind (kind 14919).\n * Optimistic interaction event published to relays after processing.\n * Contains action details, stat changes, and brain hash.\n * No proof or mina_tx tags -- those are added by the proof settlement\n * pipeline after ZK proof generation.\n */\nexport const PET_INTERACTION_EVENT_KIND = 14919;\n","/**\n * Shared ILP address validation utilities.\n *\n * Centralizes ILP address structure validation used by both\n * `derive-child-address.ts` and `btp-prefix-exchange.ts`.\n *\n * @module\n */\n\nimport { ToonError } from '../errors.js';\n\n/**\n * Valid ILP address segment pattern: lowercase alphanumeric + hyphen.\n * Each dot-separated segment must match this pattern and be non-empty.\n */\nconst ILP_SEGMENT_PATTERN = /^[a-z0-9-]+$/;\n\n/**\n * Maximum allowed ILP address length (practical limit).\n * Rejects oversized input before splitting and regex processing.\n */\nconst MAX_ILP_ADDRESS_LENGTH = 1023;\n\n/**\n * Returns `true` if the string is a structurally valid ILP address\n * (dot-separated, non-empty segments, valid characters only).\n */\nexport function isValidIlpAddressStructure(address: string): boolean {\n if (!address) return false;\n if (address.length > MAX_ILP_ADDRESS_LENGTH) return false;\n const segments = address.split('.');\n for (const segment of segments) {\n if (segment.length === 0) return false;\n if (!ILP_SEGMENT_PATTERN.test(segment)) return false;\n }\n return true;\n}\n\n/**\n * Validates that a string is a valid ILP address. Throws a `ToonError`\n * with code `ADDRESS_INVALID_PREFIX` on any structural violation.\n *\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if the address\n * contains empty segments or invalid characters.\n */\nexport function validateIlpAddress(address: string): void {\n if (address.length > MAX_ILP_ADDRESS_LENGTH) {\n throw new ToonError(\n `Invalid ILP address: exceeds maximum length of ${MAX_ILP_ADDRESS_LENGTH}`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n const segments = address.split('.');\n for (const segment of segments) {\n if (segment.length === 0) {\n throw new ToonError(\n `Invalid ILP address: empty segment in \"${address}\"`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n if (!ILP_SEGMENT_PATTERN.test(segment)) {\n throw new ToonError(\n `Invalid ILP address: segment \"${segment}\" contains invalid characters`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n }\n}\n","/**\n * Deterministic ILP address derivation from Nostr pubkeys.\n *\n * Derives a child ILP address by appending the first 8 hex characters\n * of a Nostr pubkey as a new segment to a parent ILP address prefix.\n *\n * @module\n */\n\nimport { ToonError } from '../errors.js';\nimport { validateIlpAddress } from './ilp-address-validation.js';\n\n/**\n * Hex-only pattern (case-insensitive for input validation).\n */\nconst HEX_PATTERN = /^[0-9a-fA-F]+$/;\n\n/**\n * Number of hex characters to extract from the pubkey for the child segment.\n * 8 hex chars = 4,294,967,296 possible values (birthday collision > 1% only at ~9,292 peers).\n */\nconst PUBKEY_TRUNCATION_LENGTH = 8;\n\n/**\n * Maximum allowed pubkey length. Nostr pubkeys are 64 hex characters\n * (32 bytes). Allow some headroom but reject excessively long strings\n * to prevent unnecessary regex processing (defense-in-depth).\n */\nconst MAX_PUBKEY_LENGTH = 128;\n\n/**\n * Maximum ILP address length (practical limit).\n */\nconst MAX_ILP_ADDRESS_LENGTH = 1023;\n\n/**\n * Derives a child ILP address by appending the first 8 hex characters of a\n * Nostr pubkey as a new segment to a parent ILP address prefix.\n *\n * @param parentPrefix - The parent ILP address prefix (e.g., `g.toon` or `g.toon.ef567890`)\n * @param childPubkey - The child's Nostr pubkey (hex string, at least 8 characters)\n * @returns The derived child ILP address (e.g., `g.toon.abcd1234`)\n *\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if `parentPrefix` is empty or\n * contains invalid ILP address characters.\n * @throws {ToonError} With code `ADDRESS_INVALID_PUBKEY` if `childPubkey` is shorter than\n * 8 hex characters or contains non-hex characters.\n *\n * @example\n * ```ts\n * deriveChildAddress('g.toon', 'abcd1234abcd1234...') // => 'g.toon.abcd1234'\n * deriveChildAddress('g.toon.ef567890', '11aabb22...') // => 'g.toon.ef567890.11aabb22'\n * ```\n */\nexport function deriveChildAddress(\n parentPrefix: string,\n childPubkey: string\n): string {\n // Validate parent prefix\n if (!parentPrefix) {\n throw new ToonError(\n 'Parent prefix must not be empty',\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n validateIlpAddress(parentPrefix);\n\n // Validate pubkey: must be at least 8 hex characters (check length before content)\n if (childPubkey.length < PUBKEY_TRUNCATION_LENGTH) {\n throw new ToonError(\n `Invalid pubkey: must be at least ${PUBKEY_TRUNCATION_LENGTH} hex characters, got ${childPubkey.length}`,\n 'ADDRESS_INVALID_PUBKEY'\n );\n }\n\n // Validate pubkey: reject excessively long strings before regex (defense-in-depth)\n if (childPubkey.length > MAX_PUBKEY_LENGTH) {\n throw new ToonError(\n `Invalid pubkey: exceeds maximum length of ${MAX_PUBKEY_LENGTH} characters`,\n 'ADDRESS_INVALID_PUBKEY'\n );\n }\n\n // Validate pubkey: must be hex-only\n if (!HEX_PATTERN.test(childPubkey)) {\n throw new ToonError(\n `Invalid pubkey: contains non-hex characters`,\n 'ADDRESS_INVALID_PUBKEY'\n );\n }\n\n // Derive child segment: first 8 hex chars, lowercased\n const childSegment = childPubkey\n .slice(0, PUBKEY_TRUNCATION_LENGTH)\n .toLowerCase();\n\n // Construct the derived address\n const derivedAddress = `${parentPrefix}.${childSegment}`;\n\n // Validate total address length\n if (derivedAddress.length > MAX_ILP_ADDRESS_LENGTH) {\n throw new ToonError(\n `Derived ILP address exceeds maximum length of ${MAX_ILP_ADDRESS_LENGTH}`,\n 'ADDRESS_TOO_LONG'\n );\n }\n\n return derivedAddress;\n}\n","/**\n * BTP handshake prefix exchange utilities.\n *\n * Provides extraction, building, and validation functions for the prefix\n * data exchanged during BTP handshake. This module defines the structural\n * type for prefix extension data but does NOT modify the BTP wire protocol\n * (which lives in @toon-protocol/connector).\n *\n * @module\n */\n\nimport { ToonError } from '../errors.js';\nimport { isValidIlpAddressStructure } from './ilp-address-validation.js';\n\n/**\n * Maximum allowed ILP address length (practical limit, matches derive-child-address.ts).\n * Applied early in extractPrefixFromHandshake to reject oversized untrusted input\n * before string splitting and regex processing (defense-in-depth).\n */\nconst MAX_PREFIX_LENGTH = 1023;\n\n/**\n * Shape of prefix data in BTP handshake messages.\n * Upstream peers include this in their handshake response so that\n * connecting nodes can derive their own ILP address.\n */\nexport interface BtpHandshakeExtension {\n prefix: string;\n}\n\n/**\n * Extracts and validates the prefix field from BTP handshake response data.\n *\n * Fail-closed behavior: throws if the prefix is absent, empty, or invalid.\n * This is the critical safety contract -- nodes MUST NOT fall back to\n * hardcoded addresses when the upstream peer omits the prefix.\n *\n * @param handshakeData - The handshake response data object\n * @returns The validated prefix string\n *\n * @throws {ToonError} With code `ADDRESS_MISSING_PREFIX` if the `prefix` field\n * is absent or empty.\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if the prefix fails\n * ILP address validation.\n */\nexport function extractPrefixFromHandshake(\n handshakeData: Record<string, unknown>\n): string {\n const prefix = handshakeData['prefix'];\n\n if (prefix === undefined || prefix === null || prefix === '') {\n throw new ToonError(\n 'BTP handshake response missing required prefix field',\n 'ADDRESS_MISSING_PREFIX'\n );\n }\n\n if (typeof prefix !== 'string') {\n throw new ToonError(\n 'BTP handshake response missing required prefix field',\n 'ADDRESS_MISSING_PREFIX'\n );\n }\n\n // Reject oversized input before string splitting / regex (defense-in-depth)\n if (prefix.length > MAX_PREFIX_LENGTH) {\n throw new ToonError(\n `BTP handshake prefix exceeds maximum length of ${MAX_PREFIX_LENGTH}`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n\n if (!isValidIlpAddressStructure(prefix)) {\n throw new ToonError(\n `BTP handshake prefix is not a valid ILP address: \"${prefix}\"`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n\n return prefix;\n}\n\n/**\n * Constructs the prefix extension data that upstream peers include\n * in their handshake response.\n *\n * Validates the address before building the handshake data to ensure\n * upstream peers never send structurally invalid prefixes.\n *\n * @param ownIlpAddress - The upstream peer's own ILP address\n * @returns The handshake extension data containing the prefix\n *\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if the address\n * is not a valid ILP address.\n */\nexport function buildPrefixHandshakeData(\n ownIlpAddress: string\n): BtpHandshakeExtension {\n if (ownIlpAddress.length > MAX_PREFIX_LENGTH) {\n throw new ToonError(\n `Cannot build handshake data: address exceeds maximum length of ${MAX_PREFIX_LENGTH}`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n if (!isValidIlpAddressStructure(ownIlpAddress)) {\n throw new ToonError(\n `Cannot build handshake data: \"${ownIlpAddress}\" is not a valid ILP address`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n return { prefix: ownIlpAddress };\n}\n\n/**\n * Cross-validates the handshake prefix against the upstream peer's\n * kind:10032 advertised address.\n *\n * When both values are available and they do not match, throws a\n * ToonError indicating potential prefix spoofing. When the advertised\n * prefix is undefined (kind:10032 not yet discovered), validation is\n * deferred (no-op).\n *\n * @param handshakePrefix - The prefix received during BTP handshake\n * @param advertisedPrefix - The upstream peer's kind:10032 advertised address (optional)\n *\n * @throws {ToonError} With code `ADDRESS_PREFIX_MISMATCH` if both values\n * are available and do not match.\n */\nexport function validatePrefixConsistency(\n handshakePrefix: string,\n advertisedPrefix?: string\n): void {\n if (advertisedPrefix === undefined) {\n return; // Deferred validation -- kind:10032 not yet discovered\n }\n\n if (handshakePrefix !== advertisedPrefix) {\n throw new ToonError(\n `BTP handshake prefix \"${handshakePrefix}\" does not match upstream kind:10032 advertised address \"${advertisedPrefix}\"`,\n 'ADDRESS_PREFIX_MISMATCH'\n );\n }\n}\n\n/**\n * Checks whether a derived address collides with any known peer addresses.\n *\n * Safety net for the 8-char truncation collision case (exceedingly unlikely\n * at < 9,292 peers, but the check exists per E7-R001).\n *\n * @param derivedAddress - The newly derived ILP address\n * @param knownPeerAddresses - List of existing peer addresses to check against\n *\n * @throws {ToonError} With code `ADDRESS_COLLISION` if the derived address\n * already exists in the known peer list.\n */\nexport function checkAddressCollision(\n derivedAddress: string,\n knownPeerAddresses: string[]\n): void {\n if (knownPeerAddresses.includes(derivedAddress)) {\n throw new ToonError(\n `Derived ILP address \"${derivedAddress}\" collides with an existing peer address`,\n 'ADDRESS_COLLISION'\n );\n }\n}\n","/**\n * Address assignment orchestration layer.\n *\n * Combines BTP prefix extraction with deterministic address derivation\n * to assign ILP addresses during the handshake process.\n *\n * @module\n */\n\nimport { ILP_ROOT_PREFIX } from '../constants.js';\nimport { deriveChildAddress } from './derive-child-address.js';\nimport { extractPrefixFromHandshake } from './btp-prefix-exchange.js';\n\n/**\n * Orchestrates address assignment from BTP handshake data:\n * 1. Extracts and validates the prefix from handshake data\n * 2. Derives the child address using `deriveChildAddress(prefix, ownPubkey)`\n *\n * @param handshakeData - The BTP handshake response data object\n * @param ownPubkey - The connecting node's Nostr pubkey (hex string)\n * @returns The derived ILP address\n *\n * @throws {ToonError} With code `ADDRESS_MISSING_PREFIX` if the handshake\n * data lacks a prefix field (fail-closed behavior).\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if the prefix is\n * not a valid ILP address.\n * @throws {ToonError} With code `ADDRESS_INVALID_PUBKEY` if the pubkey is\n * shorter than 8 hex characters or contains non-hex characters.\n */\nexport function assignAddressFromHandshake(\n handshakeData: Record<string, unknown>,\n ownPubkey: string\n): string {\n const prefix = extractPrefixFromHandshake(handshakeData);\n return deriveChildAddress(prefix, ownPubkey);\n}\n\n/**\n * Determines whether a node configuration represents a genesis node.\n *\n * Genesis nodes use `ILP_ROOT_PREFIX` (`g.toon`) directly without\n * derivation -- they are the root of the address hierarchy and do\n * not need a handshake to learn their prefix.\n *\n * @param config - Node configuration with optional ilpAddress\n * @returns `true` if the node's configured address equals `ILP_ROOT_PREFIX`\n */\nexport function isGenesisNode(config: { ilpAddress?: string }): boolean {\n return config.ilpAddress === ILP_ROOT_PREFIX;\n}\n","/**\n * AddressRegistry tracks upstream prefix -> derived ILP address mappings.\n *\n * Used by multi-peered nodes to manage address lifecycle: when an upstream\n * peer connects, the node derives and registers an address; when the peer\n * disconnects, the address is removed and kind:10032 republished.\n *\n * Addresses are returned in insertion order so that the primary address\n * (first inserted) is stable across lifecycle events.\n *\n * @module\n */\n\n/**\n * Tracks the mapping from upstream ILP prefix to derived ILP address.\n *\n * Uses a `Map<string, string>` internally, which preserves insertion order\n * per the ECMAScript specification.\n */\nexport class AddressRegistry {\n private readonly prefixToAddress = new Map<string, string>();\n\n /**\n * Registers a new upstream prefix -> derived address mapping.\n *\n * @param upstreamPrefix - The upstream peer's ILP address prefix\n * @param derivedAddress - The derived ILP address for this node under that prefix\n */\n addAddress(upstreamPrefix: string, derivedAddress: string): void {\n this.prefixToAddress.set(upstreamPrefix, derivedAddress);\n }\n\n /**\n * Removes the mapping for the given upstream prefix.\n *\n * @param upstreamPrefix - The upstream prefix to remove\n * @returns The removed derived address, or `undefined` if the prefix was not found\n */\n removeAddress(upstreamPrefix: string): string | undefined {\n const address = this.prefixToAddress.get(upstreamPrefix);\n if (address !== undefined) {\n this.prefixToAddress.delete(upstreamPrefix);\n }\n return address;\n }\n\n /**\n * Returns all derived addresses in insertion order.\n */\n getAddresses(): string[] {\n return [...this.prefixToAddress.values()];\n }\n\n /**\n * Returns `true` if the given upstream prefix is registered.\n */\n hasPrefix(upstreamPrefix: string): boolean {\n return this.prefixToAddress.has(upstreamPrefix);\n }\n\n /**\n * Returns the number of registered addresses.\n */\n get size(): number {\n return this.prefixToAddress.size;\n }\n\n /**\n * Returns the primary (first inserted) address.\n *\n * @returns The first address, or `undefined` if the registry is empty\n */\n getPrimaryAddress(): string | undefined {\n const first = this.prefixToAddress.values().next();\n return first.done ? undefined : first.value;\n }\n}\n","/**\n * Prefix validation utility for the TOON prefix claim marketplace.\n *\n * Validates requested prefix strings against naming rules:\n * lowercase alphanumeric only, length constraints, no reserved words.\n */\n\n/** Reserved words that cannot be used as prefixes. */\nconst RESERVED_WORDS = new Set(['toon', 'ilp', 'local', 'peer', 'test']);\n\n/** Prefix pattern: lowercase alphanumeric only. */\nconst PREFIX_PATTERN = /^[a-z0-9]+$/;\n\n/** Minimum prefix length. */\nconst MIN_LENGTH = 2;\n\n/** Maximum prefix length. */\nconst MAX_LENGTH = 16;\n\n/**\n * Result of prefix validation.\n */\nexport interface PrefixValidationResult {\n /** Whether the prefix is valid. */\n valid: boolean;\n /** Reason for rejection (only present when valid is false). */\n reason?: string;\n}\n\n/**\n * Validates a prefix string for use in the prefix claim marketplace.\n *\n * Rules:\n * - Lowercase alphanumeric only (`[a-z0-9]`)\n * - Minimum 2 characters\n * - Maximum 16 characters\n * - No reserved words (`toon`, `ilp`, `local`, `peer`, `test`)\n *\n * @param prefix - The prefix string to validate\n * @returns Validation result with valid flag and optional reason\n */\nexport function validatePrefix(prefix: string): PrefixValidationResult {\n if (typeof prefix !== 'string' || prefix.length === 0) {\n return { valid: false, reason: 'Prefix must be a non-empty string' };\n }\n\n if (prefix.length < MIN_LENGTH) {\n return {\n valid: false,\n reason: `Prefix must have a minimum of ${MIN_LENGTH} characters, got ${prefix.length}`,\n };\n }\n\n if (prefix.length > MAX_LENGTH) {\n return {\n valid: false,\n reason: `Prefix must have a maximum of ${MAX_LENGTH} characters, got ${prefix.length}`,\n };\n }\n\n if (!PREFIX_PATTERN.test(prefix)) {\n return {\n valid: false,\n reason:\n 'Prefix must contain only lowercase alphanumeric characters [a-z0-9]',\n };\n }\n\n if (RESERVED_WORDS.has(prefix)) {\n return {\n valid: false,\n reason: `Prefix \"${prefix}\" is a reserved word`,\n };\n }\n\n return { valid: true };\n}\n","/**\n * Chain identifier validation.\n *\n * Shared helper used by `parseIlpPeerInfo` and `swap-pair-validation` to avoid\n * a circular import between `events/parsers.ts` and `events/swap-pair-validation.ts`\n * (Story 12.1 Task 2.2 fallback).\n */\n\n/**\n * Validates a chain identifier string.\n * Valid format: {blockchain}:{network} or {blockchain}:{network}:{chainId}\n * Minimum 2 segments, maximum 3, separated by `:`. All segments must be non-empty.\n *\n * @param chainId - The chain identifier to validate\n * @returns true if the chain identifier is valid\n */\nexport function validateChainId(chainId: string): boolean {\n if (!chainId) return false;\n const segments = chainId.split(':');\n if (segments.length < 2 || segments.length > 3) return false;\n return segments.every((s) => s.length > 0);\n}\n","/**\n * Shared validation logic for `SwapPair` structures used by kind:10032\n * `IlpPeerInfo` events (Epic 12 — Token Swap Primitive, Story 12.1).\n *\n * The same rules are enforced in two places: the builder (before serializing\n * outgoing events) and the parser (when ingesting events from the wire).\n * Keeping both paths in this module guarantees they cannot diverge.\n *\n * @see Story 12.1 AC-5 for the full rule set.\n */\n\nimport { ToonError, InvalidEventError } from '../errors.js';\nimport { validateChainId } from '../chain/chain-id.js';\nimport type { SwapPair } from '../types.js';\n\n/** Result of validating an unknown value as a `SwapPair`. */\nexport type SwapPairValidationResult =\n | { valid: true }\n | { valid: false; reason: string; field: string };\n\n/**\n * Non-negative decimal rate: no leading zeros (except `0`), no exponent, no trailing dot.\n * Rate `\"0\"` is explicitly valid (means \"not currently quoting this pair\").\n *\n * Both regexes are linear (no alternation over overlapping classes, no nested\n * quantifiers), so they are not vulnerable to catastrophic backtracking (ReDoS).\n */\nconst RATE_REGEX = /^(0|[1-9]\\d*)(\\.\\d+)?$/;\n\n/** Non-negative integer (micro-unit) amount. */\nconst AMOUNT_REGEX = /^\\d+$/;\n\n/**\n * Hard cap on numeric string length for `rate`, `minAmount`, and `maxAmount`.\n *\n * `BigInt(str)` is super-linear in the length of `str` (V8 implements it in\n * roughly O(n²)), so an attacker could publish a `kind:10032` event with a\n * multi-megabyte numeric string and force parsers to burn CPU on a single\n * `BigInt()` call during the `minAmount <= maxAmount` cross-check. 80 digits\n * accommodates any realistic token micro-unit amount (2^256 ≈ 78 decimal\n * digits, which is the theoretical ceiling for EVM uint256 balances) with\n * headroom, while keeping `BigInt` conversion effectively constant-time.\n */\nconst MAX_NUMERIC_STRING_LENGTH = 80;\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0;\n}\n\nfunction validateAsset(\n asset: unknown,\n side: 'from' | 'to'\n): SwapPairValidationResult {\n if (!isObject(asset)) {\n return {\n valid: false,\n reason: `${side} must be an object`,\n field: side,\n };\n }\n if (!isNonEmptyString(asset.assetCode)) {\n return {\n valid: false,\n reason: `${side}.assetCode must be a non-empty string`,\n field: `${side}.assetCode`,\n };\n }\n if (!isNonNegativeInteger(asset.assetScale)) {\n return {\n valid: false,\n reason: `${side}.assetScale must be a non-negative integer`,\n field: `${side}.assetScale`,\n };\n }\n if (typeof asset.chain !== 'string' || !validateChainId(asset.chain)) {\n return {\n valid: false,\n reason: `${side}.chain must be a valid chain identifier (e.g., \"evm:base:8453\")`,\n field: `${side}.chain`,\n };\n }\n return { valid: true };\n}\n\n/**\n * Validates an unknown value as a `SwapPair` per AC-5 rules.\n * Pure function — never throws. Returns a discriminated union so callers\n * can emit context-appropriate errors (build-time vs parse-time).\n */\nexport function isValidSwapPair(pair: unknown): SwapPairValidationResult {\n if (!isObject(pair)) {\n return { valid: false, reason: 'pair must be an object', field: '' };\n }\n\n const fromResult = validateAsset(pair.from, 'from');\n if (!fromResult.valid) return fromResult;\n\n const toResult = validateAsset(pair.to, 'to');\n if (!toResult.valid) return toResult;\n\n if (\n typeof pair.rate !== 'string' ||\n pair.rate.length > MAX_NUMERIC_STRING_LENGTH ||\n !RATE_REGEX.test(pair.rate)\n ) {\n return {\n valid: false,\n reason: `rate must be a non-negative decimal string (no leading zeros, no exponent, max ${MAX_NUMERIC_STRING_LENGTH} chars)`,\n field: 'rate',\n };\n }\n\n if (pair.minAmount !== undefined) {\n if (\n typeof pair.minAmount !== 'string' ||\n pair.minAmount.length > MAX_NUMERIC_STRING_LENGTH ||\n !AMOUNT_REGEX.test(pair.minAmount)\n ) {\n return {\n valid: false,\n reason: `minAmount must be a non-negative integer string (max ${MAX_NUMERIC_STRING_LENGTH} digits)`,\n field: 'minAmount',\n };\n }\n }\n\n if (pair.maxAmount !== undefined) {\n if (\n typeof pair.maxAmount !== 'string' ||\n pair.maxAmount.length > MAX_NUMERIC_STRING_LENGTH ||\n !AMOUNT_REGEX.test(pair.maxAmount)\n ) {\n return {\n valid: false,\n reason: `maxAmount must be a non-negative integer string (max ${MAX_NUMERIC_STRING_LENGTH} digits)`,\n field: 'maxAmount',\n };\n }\n }\n\n if (pair.minAmount !== undefined && pair.maxAmount !== undefined) {\n // Use BigInt — amounts may exceed Number.MAX_SAFE_INTEGER (Epic 11 retro guard).\n if (BigInt(pair.minAmount as string) > BigInt(pair.maxAmount as string)) {\n return {\n valid: false,\n reason: 'minAmount must not exceed maxAmount',\n field: 'minAmount/maxAmount',\n };\n }\n }\n\n return { valid: true };\n}\n\nfunction formatMessage(\n index: number,\n result: Extract<SwapPairValidationResult, { valid: false }>\n): string {\n return `swapPairs[${index}]: ${result.reason} (field: ${result.field})`;\n}\n\n/**\n * Build-time asserter. Throws `ToonError('INVALID_SWAP_PAIR')` on failure.\n * Used by `buildIlpPeerInfoEvent` to prevent publishing malformed events.\n */\nexport function assertSwapPairForBuild(\n pair: unknown,\n index: number\n): asserts pair is SwapPair {\n const result = isValidSwapPair(pair);\n if (!result.valid) {\n throw new ToonError(formatMessage(index, result), 'INVALID_SWAP_PAIR');\n }\n}\n\n/**\n * Parse-time asserter. Throws `InvalidEventError` on failure.\n * Used by `parseIlpPeerInfo` when ingesting events from the wire.\n */\nexport function assertSwapPairForParse(\n pair: unknown,\n index: number\n): asserts pair is SwapPair {\n const result = isValidSwapPair(pair);\n if (!result.valid) {\n throw new InvalidEventError(formatMessage(index, result));\n }\n}\n","/**\n * Parsers for ILP-related Nostr events.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { ILP_PEER_INFO_KIND } from '../constants.js';\nimport { InvalidEventError } from '../errors.js';\nimport { isValidIlpAddressStructure } from '../address/ilp-address-validation.js';\nimport { validateChainId } from '../chain/chain-id.js';\nimport { assertSwapPairForParse } from './swap-pair-validation.js';\nimport type { IlpPeerInfo, SwapPair } from '../types.js';\n\n// Re-export `validateChainId` for backward compatibility with consumers that\n// imported it from this module prior to Story 12.1 (e.g., `index.ts`).\nexport { validateChainId };\n\n/**\n * Type guard: non-null plain object (arrays explicitly excluded).\n *\n * Rejecting arrays matters for fields like `settlementAddresses`,\n * `preferredTokens`, `tokenNetworks`, and `prefixPricing` — a malicious or\n * malformed event that set any of these to `[]` would otherwise slip through\n * because `typeof [] === 'object'` and `Object.entries([])` is empty. Mirrors\n * the same-named helper in `swap-pair-validation.ts`.\n */\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parses a kind:10032 Nostr event into an IlpPeerInfo object.\n *\n * @param event - The Nostr event to parse\n * @returns The parsed IlpPeerInfo object\n * @throws {InvalidEventError} if the event is malformed or missing required fields.\n * This includes: malformed JSON, invalid required fields, invalid `feePerByte`,\n * invalid `prefixPricing`, invalid `ilpAddresses`, `swapPairs` not being an array,\n * or any `SwapPair` element failing structural validation (Story 12.1 AC-5).\n */\nexport function parseIlpPeerInfo(event: NostrEvent): IlpPeerInfo {\n if (event.kind !== ILP_PEER_INFO_KIND) {\n throw new InvalidEventError(\n `Expected event kind ${ILP_PEER_INFO_KIND}, got ${event.kind}`\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.content);\n } catch (err) {\n throw new InvalidEventError(\n 'Failed to parse event content as JSON',\n err instanceof Error ? err : undefined\n );\n }\n\n if (!isObject(parsed)) {\n throw new InvalidEventError('Event content must be a JSON object');\n }\n\n const {\n ilpAddress,\n btpEndpoint,\n blsHttpEndpoint,\n settlementEngine,\n assetCode,\n assetScale,\n ilpAddresses: rawIlpAddresses,\n } = parsed;\n\n if (typeof ilpAddress !== 'string' || ilpAddress.length === 0) {\n throw new InvalidEventError(\n 'Missing or invalid required field: ilpAddress'\n );\n }\n\n // btpEndpoint is optional (Story 50.3): an ILP-addressed peer reached via its\n // `ilpAddress` through a connector — e.g. an embedded/HS-mode Mill — has no\n // standalone BTP endpoint and advertises an empty string. `buildIlpPeerInfoEvent`\n // already accepts `''`; the parser must too, or the peer's own kind:10032\n // cannot be read back. Only a present-but-non-string value is invalid.\n if (btpEndpoint !== undefined && typeof btpEndpoint !== 'string') {\n throw new InvalidEventError('Invalid field: btpEndpoint must be a string');\n }\n\n if (typeof assetCode !== 'string' || assetCode.length === 0) {\n throw new InvalidEventError('Missing or invalid required field: assetCode');\n }\n\n if (typeof assetScale !== 'number' || !Number.isInteger(assetScale)) {\n throw new InvalidEventError(\n 'Missing or invalid required field: assetScale'\n );\n }\n\n if (settlementEngine !== undefined && typeof settlementEngine !== 'string') {\n throw new InvalidEventError(\n 'Invalid optional field: settlementEngine must be a string'\n );\n }\n\n // Parse new settlement fields\n const {\n supportedChains,\n settlementAddresses,\n preferredTokens,\n tokenNetworks,\n } = parsed;\n\n // supportedChains validation\n if (supportedChains !== undefined) {\n if (!Array.isArray(supportedChains)) {\n throw new InvalidEventError('supportedChains must be an array');\n }\n if (supportedChains.length === 0) {\n throw new InvalidEventError(\n 'supportedChains must be a non-empty array when provided'\n );\n }\n for (const chainId of supportedChains) {\n if (typeof chainId !== 'string' || !validateChainId(chainId)) {\n throw new InvalidEventError(\n `Invalid chain identifier: ${String(chainId)}`\n );\n }\n }\n }\n\n // settlementAddresses validation\n if (settlementAddresses !== undefined) {\n if (!isObject(settlementAddresses)) {\n throw new InvalidEventError('settlementAddresses must be an object');\n }\n for (const [key, value] of Object.entries(settlementAddresses)) {\n if (!validateChainId(key)) {\n throw new InvalidEventError(\n `Invalid chain identifier in settlementAddresses: ${key}`\n );\n }\n if (typeof value !== 'string' || value.length === 0) {\n throw new InvalidEventError(\n 'settlementAddresses values must be non-empty strings'\n );\n }\n }\n // Cross-field validation: settlementAddresses keys must be in supportedChains\n if (Array.isArray(supportedChains)) {\n const chainSet = new Set(supportedChains as string[]);\n for (const key of Object.keys(settlementAddresses)) {\n if (!chainSet.has(key)) {\n throw new InvalidEventError(\n `settlementAddresses key '${key}' is not in supportedChains`\n );\n }\n }\n }\n }\n\n // preferredTokens validation\n if (preferredTokens !== undefined) {\n if (!isObject(preferredTokens)) {\n throw new InvalidEventError('preferredTokens must be an object');\n }\n }\n\n // tokenNetworks validation\n if (tokenNetworks !== undefined) {\n if (!isObject(tokenNetworks)) {\n throw new InvalidEventError('tokenNetworks must be an object');\n }\n }\n\n // feePerByte validation (Story 7.4)\n const { feePerByte: rawFeePerByte } = parsed;\n let feePerByte: string;\n if (rawFeePerByte === undefined) {\n feePerByte = '0';\n } else if (\n typeof rawFeePerByte !== 'string' ||\n !/^\\d+$/.test(rawFeePerByte)\n ) {\n throw new InvalidEventError(\n `Invalid feePerByte: \"${String(rawFeePerByte)}\" must be a non-negative integer string`\n );\n } else {\n feePerByte = rawFeePerByte;\n }\n\n // prefixPricing validation (Story 7.6)\n const { prefixPricing: rawPrefixPricing } = parsed;\n let prefixPricing: { basePrice: string } | undefined;\n if (rawPrefixPricing !== undefined) {\n if (!isObject(rawPrefixPricing)) {\n throw new InvalidEventError('prefixPricing must be an object');\n }\n const { basePrice } = rawPrefixPricing;\n if (typeof basePrice !== 'string' || !/^\\d+$/.test(basePrice)) {\n throw new InvalidEventError(\n `Invalid prefixPricing.basePrice: \"${String(basePrice)}\" must be a non-negative integer string`\n );\n }\n prefixPricing = { basePrice };\n }\n\n // swapPairs validation (Story 12.1)\n const { swapPairs: rawSwapPairs } = parsed;\n let swapPairs: SwapPair[] | undefined;\n if (rawSwapPairs !== undefined) {\n if (!Array.isArray(rawSwapPairs)) {\n throw new InvalidEventError('swapPairs must be an array');\n }\n rawSwapPairs.forEach((pair, index) => {\n assertSwapPairForParse(pair, index);\n });\n swapPairs = rawSwapPairs as SwapPair[];\n }\n\n // ilpAddresses validation (Story 7.3)\n let ilpAddresses: string[];\n if (rawIlpAddresses !== undefined) {\n if (!Array.isArray(rawIlpAddresses)) {\n throw new InvalidEventError('ilpAddresses must be an array');\n }\n for (const addr of rawIlpAddresses) {\n if (typeof addr !== 'string' || addr.length === 0) {\n throw new InvalidEventError(\n 'ilpAddresses elements must be non-empty strings'\n );\n }\n if (!isValidIlpAddressStructure(addr)) {\n throw new InvalidEventError(\n `Invalid ILP address in ilpAddresses: \"${addr}\"`\n );\n }\n }\n ilpAddresses = rawIlpAddresses as string[];\n } else {\n // Backward-compatible default: wrap the singular ilpAddress in an array\n ilpAddresses = [ilpAddress as string];\n }\n\n return {\n ilpAddress,\n btpEndpoint: typeof btpEndpoint === 'string' ? btpEndpoint : '',\n ...(blsHttpEndpoint !== undefined &&\n typeof blsHttpEndpoint === 'string' && { blsHttpEndpoint }),\n assetCode,\n assetScale,\n ...(settlementEngine !== undefined && { settlementEngine }),\n supportedChains:\n supportedChains !== undefined ? (supportedChains as string[]) : [],\n settlementAddresses:\n settlementAddresses !== undefined\n ? (settlementAddresses as Record<string, string>)\n : {},\n ...(preferredTokens !== undefined && {\n preferredTokens: preferredTokens as Record<string, string>,\n }),\n ...(tokenNetworks !== undefined && {\n tokenNetworks: tokenNetworks as Record<string, string>,\n }),\n ilpAddresses,\n feePerByte,\n ...(prefixPricing !== undefined && { prefixPricing }),\n ...(swapPairs !== undefined && { swapPairs }),\n };\n}\n","/**\n * Builders for ILP-related Nostr events.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { ILP_PEER_INFO_KIND } from '../constants.js';\nimport { ToonError } from '../errors.js';\nimport { isValidIlpAddressStructure } from '../address/ilp-address-validation.js';\nimport { assertSwapPairForBuild } from './swap-pair-validation.js';\nimport type { IlpPeerInfo } from '../types.js';\n\n/**\n * Builds and signs a kind:10032 Nostr event from IlpPeerInfo data.\n *\n * When `ilpAddresses` is present, validates that the array is non-empty and\n * that all elements are structurally valid ILP addresses. Normalizes\n * `ilpAddress` (singular) to equal `ilpAddresses[0]` for backward compatibility.\n *\n * @param info - The ILP peer info to serialize into the event\n * @param secretKey - The secret key to sign the event with\n * @returns A signed Nostr event\n *\n * @throws {ToonError} With code `INVALID_FEE` if `feePerByte` is not a non-negative integer string\n * @throws {ToonError} With code `ADDRESS_EMPTY_ADDRESSES` if `ilpAddresses` is an empty array\n * @throws {ToonError} With code `ADDRESS_INVALID_PREFIX` if any element of `ilpAddresses` is invalid\n * @throws {ToonError} With code `INVALID_SWAP_PAIR` if any element of `swapPairs` is structurally invalid\n */\nexport function buildIlpPeerInfoEvent(\n info: IlpPeerInfo,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate feePerByte if provided\n if (info.feePerByte !== undefined) {\n if (typeof info.feePerByte !== 'string' || !/^\\d+$/.test(info.feePerByte)) {\n throw new ToonError(\n `Invalid feePerByte: \"${String(info.feePerByte)}\" must be a non-negative integer string`,\n 'INVALID_FEE'\n );\n }\n }\n\n let effectiveInfo = info;\n\n if (info.ilpAddresses !== undefined) {\n const addresses = info.ilpAddresses;\n if (addresses.length === 0) {\n throw new ToonError(\n 'ilpAddresses must be a non-empty array: a node must have at least one address',\n 'ADDRESS_EMPTY_ADDRESSES'\n );\n }\n\n for (const addr of addresses) {\n if (!isValidIlpAddressStructure(addr)) {\n throw new ToonError(\n `Invalid ILP address in ilpAddresses: \"${addr}\"`,\n 'ADDRESS_INVALID_PREFIX'\n );\n }\n }\n\n // Normalize ilpAddress to ilpAddresses[0] for backward compatibility\n // Safe: length > 0 guaranteed by the check above\n const primaryAddress = addresses[0] as string;\n effectiveInfo = {\n ...info,\n ilpAddress: primaryAddress,\n };\n }\n\n // Validate swapPairs (Story 12.1). Empty array is legal (swap peer with no\n // currently active pairs); undefined means \"no swap support\" and is omitted\n // from the serialized JSON via JSON.stringify's default undefined handling.\n // Defensive runtime check: the TypeScript signature forbids non-array values,\n // but a JS caller (or any untyped boundary) could still pass garbage — reject\n // that with a typed `ToonError` rather than a generic `TypeError` from\n // `.forEach` on a non-array.\n if (info.swapPairs !== undefined) {\n if (!Array.isArray(info.swapPairs)) {\n throw new ToonError(\n 'swapPairs must be an array when provided',\n 'INVALID_SWAP_PAIR'\n );\n }\n info.swapPairs.forEach((pair, index) => {\n assertSwapPairForBuild(pair, index);\n });\n }\n\n return finalizeEvent(\n {\n kind: ILP_PEER_INFO_KIND,\n content: JSON.stringify(effectiveInfo),\n tags: [],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n","/**\n * Event builder and parser for kind:10036 Seed Relay List events.\n *\n * Kind 10036 is a NIP-16 replaceable event (kind 10000-19999) published to\n * public Nostr relays. Relays store only the latest event per `pubkey + kind`.\n * The `d` tag with value `toon-seed-list` is included as a content marker\n * for filtering.\n *\n * Seed relay list events advertise relay nodes that can serve as bootstrap\n * entry points for new network participants.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { SEED_RELAY_LIST_KIND } from '../constants.js';\n\n// ---------- Types ----------\n\n/** Content payload for a kind:10036 Seed Relay List event. */\nexport interface SeedRelayEntry {\n // nosemgrep: javascript.lang.security.detect-insecure-websocket.detect-insecure-websocket -- JSDoc documents that dev mode uses ws:// (intentional)\n /** WebSocket URL of the relay (wss:// for production, ws:// for dev). */\n url: string;\n /** Nostr pubkey of the relay operator (64-char lowercase hex). */\n pubkey: string;\n /** Optional metadata. */\n metadata?: {\n region?: string;\n version?: string;\n services?: string[];\n };\n}\n\n// ---------- Validation Helpers ----------\n\n/** Regex for valid 64-char lowercase hex pubkey. */\nconst PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n\n/**\n * Validates that a URL has a WebSocket scheme prefix.\n */\nfunction isValidWsUrl(url: unknown): url is string {\n return (\n typeof url === 'string' &&\n // nosemgrep: javascript.lang.security.detect-insecure-websocket.detect-insecure-websocket -- validation check, not a connection\n (url.startsWith('ws://') || url.startsWith('wss://'))\n );\n}\n\n/**\n * Validates that a pubkey is a 64-char lowercase hex string.\n */\nfunction isValidPubkey(pubkey: unknown): pubkey is string {\n return typeof pubkey === 'string' && PUBKEY_REGEX.test(pubkey);\n}\n\n// ---------- Builder ----------\n\n/**\n * Builds a kind:10036 Seed Relay List event (NIP-16 replaceable).\n * Uses 'd' tag with value 'toon-seed-list' for replaceable event pattern.\n *\n * @param secretKey - The secret key to sign the event with.\n * @param entries - The seed relay entries to include.\n * @returns A signed Nostr event.\n */\nexport function buildSeedRelayListEvent(\n secretKey: Uint8Array,\n entries: SeedRelayEntry[]\n): NostrEvent {\n return finalizeEvent(\n {\n kind: SEED_RELAY_LIST_KIND,\n content: JSON.stringify(entries),\n tags: [['d', 'toon-seed-list']],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parser ----------\n\n/**\n * Parses a kind:10036 event content into SeedRelayEntry[].\n * Validates URLs (WebSocket scheme prefix) and pubkeys (64-char hex).\n * Malformed entries are silently skipped (graceful degradation).\n *\n * @param event - The Nostr event to parse.\n * @returns An array of valid SeedRelayEntry objects.\n */\nexport function parseSeedRelayList(event: NostrEvent): SeedRelayEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.content);\n } catch {\n return [];\n }\n\n if (!Array.isArray(parsed)) {\n return [];\n }\n\n const results: SeedRelayEntry[] = [];\n\n for (const item of parsed) {\n // Skip non-object entries\n if (typeof item !== 'object' || item === null) {\n continue;\n }\n\n const record = item as Record<string, unknown>;\n const url = record['url'];\n const pubkey = record['pubkey'];\n\n // Validate required fields\n if (!isValidWsUrl(url)) {\n continue;\n }\n if (!isValidPubkey(pubkey)) {\n continue;\n }\n\n const entry: SeedRelayEntry = { url, pubkey };\n\n // Preserve metadata if present\n const metadata = record['metadata'];\n if (typeof metadata === 'object' && metadata !== null) {\n const meta = metadata as Record<string, unknown>;\n const entryMeta: SeedRelayEntry['metadata'] = {};\n if (typeof meta['region'] === 'string') {\n entryMeta.region = meta['region'];\n }\n if (typeof meta['version'] === 'string') {\n entryMeta.version = meta['version'];\n }\n if (Array.isArray(meta['services'])) {\n entryMeta.services = (meta['services'] as unknown[]).filter(\n (s): s is string => typeof s === 'string'\n );\n }\n if (Object.keys(entryMeta).length > 0) {\n entry.metadata = entryMeta;\n }\n }\n\n results.push(entry);\n }\n\n return results;\n}\n","/**\n * Event builder and parser for kind:10035 Service Discovery events.\n *\n * Kind 10035 is a NIP-16 replaceable event (kind 10000-19999) published to\n * the local relay and optionally to peers. Relays store only the latest event\n * per `pubkey + kind`. The `d` tag with value `toon-service-discovery` is\n * included as a content marker for filtering.\n *\n * Service discovery events advertise a node's capabilities, pricing, and\n * endpoints so that clients and AI agents can programmatically discover\n * available services.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { SERVICE_DISCOVERY_KIND } from '../constants.js';\nimport type { ReputationScore } from './reputation.js';\n\n// Re-export the constant for convenient co-located imports\nexport { SERVICE_DISCOVERY_KIND };\n\n// ---------- Types ----------\n\n/**\n * Structured skill descriptor for DVM service discovery.\n *\n * Embedded in kind:10035 events to advertise DVM capabilities.\n * Enables programmatic agent-to-agent service discovery: agents\n * can read `inputSchema` to construct valid Kind 5xxx job requests\n * without prior knowledge of the provider's capabilities.\n *\n * The `attestation` field is a placeholder for Epic 6 TEE integration\n * (Story 6.3: TEE-attested DVM results).\n */\nexport interface SkillDescriptor {\n /** Service identifier (e.g., 'toon-dvm'). */\n name: string;\n /** Schema version (e.g., '1.0'). */\n version: string;\n /** Supported DVM Kind 5xxx numbers (e.g., [5100, 5200]). */\n kinds: number[];\n /** Capability list (e.g., ['text-generation', 'streaming']). */\n features: string[];\n /** JSON Schema draft-07 object describing job request parameters. */\n inputSchema: Record<string, unknown>;\n /** Kind number (as string) -> USDC micro-units cost (as string). */\n pricing: Record<string, string>;\n /** Available AI models (e.g., ['gpt-4', 'claude-3']). */\n models?: string[];\n /** Placeholder for Epic 6 TEE attestation integration. */\n attestation?: Record<string, unknown>;\n /** Composite reputation score with individual signal values (Story 6.4). */\n reputation?: ReputationScore;\n}\n\n/** Content payload for a kind:10035 Service Discovery event. */\nexport interface ServiceDiscoveryContent {\n /** Service type identifier (e.g., 'relay', 'rig'). */\n serviceType: string;\n /** ILP address of the node's connector. */\n ilpAddress: string;\n /** Pricing configuration. */\n pricing: {\n /** Base price per byte in smallest token unit. */\n basePricePerByte: number;\n /** Payment token symbol (e.g., 'USDC'). */\n currency: string;\n };\n /**\n * x402 endpoint configuration.\n * Omitted entirely when x402 is disabled (not set to `{ enabled: false }`).\n */\n x402?: {\n /** Whether x402 is enabled. */\n enabled: boolean;\n /** HTTP endpoint path (e.g., '/publish'). */\n endpoint?: string;\n };\n /** Nostr event kinds this node accepts for storage. */\n supportedKinds: number[];\n /** Capabilities advertised by this node (e.g., ['relay', 'x402']). */\n capabilities: string[];\n /** Chain preset name (e.g., 'anvil', 'arbitrum-one'). */\n chain: string;\n /** Node software version. */\n version: string;\n /**\n * Optional DVM skill descriptor.\n * Present when the node has DVM handlers registered; omitted otherwise\n * (backward compatible with pre-DVM kind:10035 events).\n */\n skill?: SkillDescriptor;\n}\n\n// ---------- Builder ----------\n\n/**\n * Builds a kind:10035 Service Discovery event (NIP-16 replaceable).\n * Kind 10035 is in the 10000-19999 replaceable range (NIP-16).\n * Relays store only the latest event per pubkey + kind.\n * Includes 'd' tag with value 'toon-service-discovery' as a content marker.\n *\n * @param content - The service discovery payload.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n */\nexport function buildServiceDiscoveryEvent(\n content: ServiceDiscoveryContent,\n secretKey: Uint8Array\n): NostrEvent {\n return finalizeEvent(\n {\n kind: SERVICE_DISCOVERY_KIND,\n content: JSON.stringify(content),\n tags: [['d', 'toon-service-discovery']],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parser ----------\n\n/**\n * Parses a kind:10035 event content into ServiceDiscoveryContent.\n * Validates required fields. Returns null for malformed content.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed content, or null if invalid.\n */\nexport function parseServiceDiscovery(\n event: NostrEvent\n): ServiceDiscoveryContent | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.content);\n } catch {\n return null;\n }\n\n // Must be a non-null object (not an array)\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const record = parsed as Record<string, unknown>;\n\n // Validate required string fields\n const serviceType = record['serviceType'];\n if (typeof serviceType !== 'string') return null;\n\n const ilpAddress = record['ilpAddress'];\n if (typeof ilpAddress !== 'string') return null;\n\n const chain = record['chain'];\n if (typeof chain !== 'string') return null;\n\n const version = record['version'];\n if (typeof version !== 'string') return null;\n\n // Validate pricing object (must be a plain object, not an array)\n const pricing = record['pricing'];\n if (typeof pricing !== 'object' || pricing === null || Array.isArray(pricing))\n return null;\n const pricingRecord = pricing as Record<string, unknown>;\n\n const basePricePerByte = pricingRecord['basePricePerByte'];\n if (typeof basePricePerByte !== 'number') return null;\n if (!isFinite(basePricePerByte) || basePricePerByte < 0) return null;\n\n const currency = pricingRecord['currency'];\n if (typeof currency !== 'string') return null;\n\n // Validate supportedKinds array\n const supportedKinds = record['supportedKinds'];\n if (!Array.isArray(supportedKinds)) return null;\n // Ensure all elements are finite non-negative integers (valid Nostr event kinds)\n if (\n !supportedKinds.every(\n (k): k is number => typeof k === 'number' && Number.isInteger(k) && k >= 0\n )\n ) {\n return null;\n }\n\n // Validate capabilities array\n const capabilities = record['capabilities'];\n if (!Array.isArray(capabilities)) return null;\n if (!capabilities.every((c): c is string => typeof c === 'string')) {\n return null;\n }\n\n // Build result\n const result: ServiceDiscoveryContent = {\n serviceType,\n ilpAddress,\n pricing: { basePricePerByte, currency },\n supportedKinds,\n capabilities,\n chain,\n version,\n };\n\n // Validate optional x402 field\n const x402 = record['x402'];\n if (x402 !== undefined) {\n if (typeof x402 !== 'object' || x402 === null || Array.isArray(x402))\n return null;\n const x402Record = x402 as Record<string, unknown>;\n\n const enabled = x402Record['enabled'];\n if (typeof enabled !== 'boolean') return null;\n\n const x402Result: ServiceDiscoveryContent['x402'] = { enabled };\n\n const endpoint = x402Record['endpoint'];\n if (endpoint !== undefined) {\n if (typeof endpoint !== 'string') return null;\n x402Result.endpoint = endpoint;\n }\n\n result.x402 = x402Result;\n }\n\n // Validate optional skill field (Story 5.4: DVM skill descriptor)\n const skill = record['skill'];\n if (skill !== undefined) {\n if (typeof skill !== 'object' || skill === null || Array.isArray(skill))\n return null;\n const skillRecord = skill as Record<string, unknown>;\n\n // Validate required string fields\n const skillName = skillRecord['name'];\n if (typeof skillName !== 'string') return null;\n\n const skillVersion = skillRecord['version'];\n if (typeof skillVersion !== 'string') return null;\n\n // Validate kinds array (non-negative integers)\n const kinds = skillRecord['kinds'];\n if (!Array.isArray(kinds)) return null;\n if (\n !kinds.every(\n (k): k is number =>\n typeof k === 'number' && Number.isInteger(k) && k >= 0\n )\n ) {\n return null;\n }\n\n // Validate features array (strings)\n const features = skillRecord['features'];\n if (!Array.isArray(features)) return null;\n if (!features.every((f): f is string => typeof f === 'string')) {\n return null;\n }\n\n // Validate inputSchema (must be a non-null object)\n const inputSchema = skillRecord['inputSchema'];\n if (\n typeof inputSchema !== 'object' ||\n inputSchema === null ||\n Array.isArray(inputSchema)\n )\n return null;\n\n // Validate pricing (must be a non-null object with string keys and string values)\n // Named `skillPricing` to avoid shadowing the top-level `pricing` variable (line 159).\n const skillPricing = skillRecord['pricing'];\n if (\n typeof skillPricing !== 'object' ||\n skillPricing === null ||\n Array.isArray(skillPricing)\n )\n return null;\n const pricingEntries = Object.entries(\n skillPricing as Record<string, unknown>\n );\n if (!pricingEntries.every(([, v]) => typeof v === 'string')) {\n return null;\n }\n\n // Build skill descriptor\n const skillResult: SkillDescriptor = {\n name: skillName,\n version: skillVersion,\n kinds,\n features,\n inputSchema: inputSchema as Record<string, unknown>,\n pricing: skillPricing as Record<string, string>,\n };\n\n // Validate optional models array (strings)\n const models = skillRecord['models'];\n if (models !== undefined) {\n if (!Array.isArray(models)) return null;\n if (!models.every((m): m is string => typeof m === 'string')) {\n return null;\n }\n skillResult.models = models;\n }\n\n // Validate optional attestation (must be a non-null object when present)\n const attestation = skillRecord['attestation'];\n if (attestation !== undefined) {\n if (\n typeof attestation !== 'object' ||\n attestation === null ||\n Array.isArray(attestation)\n )\n return null;\n skillResult.attestation = attestation as Record<string, unknown>;\n }\n\n // Validate optional reputation field (Story 6.4: Reputation scoring)\n const reputation = skillRecord['reputation'];\n if (reputation !== undefined) {\n if (\n typeof reputation !== 'object' ||\n reputation === null ||\n Array.isArray(reputation)\n )\n return null;\n const repRecord = reputation as Record<string, unknown>;\n\n const repScore = repRecord['score'];\n if (typeof repScore !== 'number' || !isFinite(repScore)) return null;\n\n const signals = repRecord['signals'];\n if (\n typeof signals !== 'object' ||\n signals === null ||\n Array.isArray(signals)\n )\n return null;\n const sigRecord = signals as Record<string, unknown>;\n\n const trustedBy = sigRecord['trustedBy'];\n if (typeof trustedBy !== 'number' || !isFinite(trustedBy)) return null;\n\n const channelVolumeUsdc = sigRecord['channelVolumeUsdc'];\n if (typeof channelVolumeUsdc !== 'number' || !isFinite(channelVolumeUsdc))\n return null;\n\n const jobsCompleted = sigRecord['jobsCompleted'];\n if (typeof jobsCompleted !== 'number' || !isFinite(jobsCompleted))\n return null;\n\n const avgRating = sigRecord['avgRating'];\n if (typeof avgRating !== 'number' || !isFinite(avgRating)) return null;\n\n skillResult.reputation = {\n score: repScore,\n signals: { trustedBy, channelVolumeUsdc, jobsCompleted, avgRating },\n };\n }\n\n result.skill = skillResult;\n }\n\n return result;\n}\n","/**\n * Event builder and parser for kind:10033 TEE Attestation events.\n *\n * Kind 10033 is a NIP-16 replaceable event (kind 10000-19999) published by\n * nodes running in a Trusted Execution Environment (TEE). Relays store only\n * the latest event per `pubkey + kind`. No `d` tag is needed -- NIP-16\n * replaces by pubkey + kind alone (unlike NIP-33 parameterized replaceable\n * events in the 30000-39999 range).\n *\n * TEE attestation events contain PCR measurements, enclave type, and the\n * base64-encoded attestation document from the TEE platform (e.g., AWS Nitro\n * Enclaves via Marlin Oyster CVM).\n *\n * Content format (Pattern 14):\n * ```json\n * {\n * \"enclave\": \"marlin-oyster\",\n * \"pcr0\": \"<sha384-hex-96-chars>\",\n * \"pcr1\": \"<sha384-hex-96-chars>\",\n * \"pcr2\": \"<sha384-hex-96-chars>\",\n * \"attestationDoc\": \"<base64-encoded>\",\n * \"version\": \"1.0.0\"\n * }\n * ```\n *\n * Tags: ['relay', url], ['chain', chainId], ['expiry', unixTimestamp]\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { TEE_ATTESTATION_KIND } from '../constants.js';\nimport type { TeeAttestation } from '../types.js';\n\n// Re-export for convenient co-located imports\nexport { TEE_ATTESTATION_KIND };\nexport type { TeeAttestation };\n\n// ---------- Types ----------\n\n/** Options for building a kind:10033 attestation event. */\nexport interface AttestationEventOptions {\n /** WebSocket URL where this relay can be reached. */\n relay: string;\n /** Chain identifier (e.g., '42161' for Arbitrum One). */\n chain: string;\n /** Unix timestamp when this attestation expires. */\n expiry: number;\n}\n\n/** Parsed result from a kind:10033 attestation event. */\nexport interface ParsedAttestation {\n /** The attestation content fields. */\n attestation: TeeAttestation;\n /** WebSocket relay URL from the 'relay' tag. */\n relay: string;\n /** Chain identifier from the 'chain' tag. */\n chain: string;\n /** Expiry unix timestamp from the 'expiry' tag. */\n expiry: number;\n}\n\n// ---------- Validation Helpers ----------\n\n/** Regex for 96-char lowercase hex (SHA-384). */\nconst PCR_REGEX = /^[0-9a-f]{96}$/;\n\n/** Regex for valid base64 (standard alphabet, 0-2 padding chars). */\nconst BASE64_REGEX = /^[A-Za-z0-9+/]+={0,2}$/;\n\n// ---------- Builder ----------\n\n/**\n * Builds a kind:10033 TEE Attestation event (NIP-16 replaceable).\n *\n * Content is `JSON.stringify(attestation)` per enforcement guideline 11.\n * Tags include relay, chain, and expiry. No `d` tag is included -- NIP-16\n * replaces by pubkey + kind alone.\n *\n * @param attestation - The TEE attestation content payload.\n * @param secretKey - The secret key to sign the event with.\n * @param options - Event options (relay URL, chain ID, expiry timestamp).\n * @returns A signed Nostr event.\n */\nexport function buildAttestationEvent(\n attestation: TeeAttestation,\n secretKey: Uint8Array,\n options: AttestationEventOptions\n): NostrEvent {\n return finalizeEvent(\n {\n kind: TEE_ATTESTATION_KIND,\n content: JSON.stringify(attestation),\n tags: [\n ['relay', options.relay],\n ['chain', options.chain],\n ['expiry', String(options.expiry)],\n ],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parser ----------\n\n/**\n * Parses a kind:10033 event into a ParsedAttestation.\n *\n * Validates required content fields (enclave, pcr0-2, attestationDoc, version)\n * and required tags (relay, chain, expiry). Returns `null` for malformed or\n * missing data.\n *\n * When `options.verify` is `true`, performs strict validation:\n * - PCR values must be 96-char lowercase hex (SHA-384)\n * - attestationDoc must be non-empty valid base64\n * Throws on invalid data when verify=true (adversarial input gate).\n *\n * @param event - The Nostr event to parse.\n * @param options - Optional parse options. `verify: true` enables strict validation.\n * @returns The parsed attestation, or null if invalid.\n */\nexport function parseAttestation(\n event: NostrEvent,\n options?: { verify?: boolean }\n): ParsedAttestation | null {\n // Parse JSON content\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.content);\n } catch {\n return null;\n }\n\n // Must be a non-null object (not an array)\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const record = parsed as Record<string, unknown>;\n\n // Validate required string fields\n const enclave = record['enclave'];\n if (typeof enclave !== 'string') return null;\n\n const pcr0 = record['pcr0'];\n if (typeof pcr0 !== 'string') return null;\n\n const pcr1 = record['pcr1'];\n if (typeof pcr1 !== 'string') return null;\n\n const pcr2 = record['pcr2'];\n if (typeof pcr2 !== 'string') return null;\n\n const attestationDoc = record['attestationDoc'];\n if (typeof attestationDoc !== 'string') return null;\n\n const version = record['version'];\n if (typeof version !== 'string') return null;\n\n // Strict validation when verify=true\n if (options?.verify) {\n // Validate PCR format (96-char lowercase hex)\n if (!PCR_REGEX.test(pcr0)) {\n throw new Error('Invalid PCR0 format: expected 96-char lowercase hex');\n }\n if (!PCR_REGEX.test(pcr1)) {\n throw new Error('Invalid PCR1 format: expected 96-char lowercase hex');\n }\n if (!PCR_REGEX.test(pcr2)) {\n throw new Error('Invalid PCR2 format: expected 96-char lowercase hex');\n }\n\n // Validate attestationDoc is non-empty valid base64\n if (attestationDoc.length === 0) {\n throw new Error('Attestation document is empty');\n }\n if (!BASE64_REGEX.test(attestationDoc)) {\n throw new Error('Attestation document is not valid base64');\n }\n }\n\n // Extract required tags\n const relayTag = event.tags.find((t: string[]) => t[0] === 'relay');\n const chainTag = event.tags.find((t: string[]) => t[0] === 'chain');\n const expiryTag = event.tags.find((t: string[]) => t[0] === 'expiry');\n\n if (!relayTag?.[1] || !chainTag?.[1] || !expiryTag?.[1]) {\n return null;\n }\n\n const expiry = parseInt(expiryTag[1], 10);\n if (isNaN(expiry)) {\n return null;\n }\n\n return {\n attestation: {\n enclave,\n pcr0,\n pcr1,\n pcr2,\n attestationDoc,\n version,\n },\n relay: relayTag[1],\n chain: chainTag[1],\n expiry,\n };\n}\n","/**\n * Event builders and parsers for NIP-90 DVM (Data Vending Machine) events.\n *\n * NIP-90 defines three event categories for the DVM protocol:\n * - Kind 5000-5999: Job requests (each task type has a unique kind)\n * - Kind 6000-6999: Job results (result kind = request kind + 1000)\n * - Kind 7000: Job feedback (single kind for all status updates)\n *\n * TOON adopts NIP-90 kinds for cross-network interoperability with the\n * broader Nostr DVM ecosystem. The `bid` and `amount` tags extend NIP-90\n * with a third element ('usdc') for explicit currency declaration. NIP-90\n * uses satoshis; TOON uses USDC micro-units (6 decimals).\n *\n * DVM events are standard Nostr events with specific kinds. They flow\n * through the same SDK processing pipeline as all other events: shallow\n * parse -> verify -> price -> dispatch. No special-casing required.\n *\n * Tag reference:\n *\n * Kind 5xxx (Job Request):\n * Required: ['i', data, type, relay?, marker?], ['bid', amount, 'usdc'], ['output', mimeType]\n * Optional: ['p', providerPubkey], ['param', key, value], ['relays', url1, ...]\n *\n * Kind 6xxx (Job Result):\n * Required: ['e', requestEventId], ['p', customerPubkey], ['amount', cost, 'usdc']\n * Content: result data\n *\n * Kind 7000 (Job Feedback):\n * Required: ['e', requestEventId], ['p', customerPubkey], ['status', statusValue]\n * Content: optional status details\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport {\n JOB_REQUEST_KIND_BASE,\n JOB_RESULT_KIND_BASE,\n JOB_FEEDBACK_KIND,\n TEXT_GENERATION_KIND,\n IMAGE_GENERATION_KIND,\n TEXT_TO_SPEECH_KIND,\n TRANSLATION_KIND,\n} from '../constants.js';\nimport { ToonError } from '../errors.js';\n\n// Re-export constants for convenient co-located imports\nexport {\n JOB_REQUEST_KIND_BASE,\n JOB_RESULT_KIND_BASE,\n JOB_FEEDBACK_KIND,\n TEXT_GENERATION_KIND,\n IMAGE_GENERATION_KIND,\n TEXT_TO_SPEECH_KIND,\n TRANSLATION_KIND,\n};\n\n// ---------- Validation Helpers ----------\n\n/** Regex for 64-char lowercase hex string. */\nconst HEX_64_REGEX = /^[0-9a-f]{64}$/;\n\n/** Valid DVM job status values per NIP-90. */\nconst VALID_STATUSES = new Set<string>([\n 'processing',\n 'error',\n 'success',\n 'partial',\n]);\n\n// ---------- Types ----------\n\n/**\n * DVM job status values per NIP-90.\n * - `'processing'`: Job is currently being processed.\n * - `'error'`: Job failed with an error.\n * - `'success'`: Job completed successfully.\n * - `'partial'`: Job returned partial results (more to come).\n */\nexport type DvmJobStatus = 'processing' | 'error' | 'success' | 'partial';\n\n/**\n * Parameters for building a Kind 5xxx DVM job request event.\n *\n * The `kind` field must be in the 5000-5999 range (NIP-90 job request range).\n * The `bid` field is the amount the requester is willing to pay, expressed as\n * a string of USDC micro-units (6 decimals) for bigint compatibility.\n */\nexport interface JobRequestParams {\n /** Event kind in 5000-5999 range (e.g., 5100 for text generation). */\n kind: number;\n /** Input data with type classification. */\n input: {\n /** The input data (text, URL, event ID, etc.). */\n data: string;\n /** Input type identifier (e.g., 'text', 'url', 'event', 'job'). */\n type: string;\n /** Optional relay URL where the input data can be found. */\n relay?: string;\n /** Optional marker for input disambiguation. */\n marker?: string;\n };\n /** Bid amount in USDC micro-units as string (bigint-compatible). */\n bid: string;\n /** Expected output MIME type (e.g., 'text/plain', 'image/png'). */\n output: string;\n /** Optional body text for the event content field. */\n content?: string;\n /** Optional 64-char hex pubkey of a specific target provider. */\n targetProvider?: string;\n /** Optional key-value parameters for the job (repeatable). */\n params?: { key: string; value: string }[];\n /** Optional preferred relay URLs for result delivery. */\n relays?: string[];\n}\n\n/**\n * Parameters for building a Kind 6xxx DVM job result event.\n *\n * The `kind` field must be in the 6000-6999 range. Result kind = request\n * kind + 1000 (e.g., Kind 5100 request -> Kind 6100 result).\n * The `amount` field is the actual compute cost in USDC micro-units.\n */\nexport interface JobResultParams {\n /** Event kind in 6000-6999 range (= request kind + 1000). */\n kind: number;\n /** 64-char hex event ID of the original Kind 5xxx request. */\n requestEventId: string;\n /** 64-char hex pubkey of the customer who posted the request. */\n customerPubkey: string;\n /** Compute cost in USDC micro-units as string (bigint-compatible). */\n amount: string;\n /** Result data (text, URL, etc.). */\n content: string;\n /** Optional 64-char hex event ID of the provider's latest kind:10033 attestation event. */\n attestationEventId?: string;\n}\n\n/**\n * Parameters for building a Kind 7000 DVM job feedback event.\n *\n * Feedback events carry status updates about in-progress jobs. The `status`\n * field must be one of the four NIP-90 status values.\n */\nexport interface JobFeedbackParams {\n /** 64-char hex event ID of the original Kind 5xxx request. */\n requestEventId: string;\n /** 64-char hex pubkey of the customer who posted the request. */\n customerPubkey: string;\n /** Job status value. */\n status: DvmJobStatus;\n /** Optional status details or error message. */\n content?: string;\n}\n\n/**\n * Parsed result from a Kind 5xxx DVM job request event.\n */\nexport interface ParsedJobRequest {\n /** Event kind (5000-5999). */\n kind: number;\n /** Input data with type classification. */\n input: {\n /** The input data. */\n data: string;\n /** Input type identifier. */\n type: string;\n /** Optional relay URL. */\n relay?: string;\n /** Optional marker. */\n marker?: string;\n };\n /** Bid amount in USDC micro-units as string. */\n bid: string;\n /** Expected output MIME type. */\n output: string;\n /** Event content field (may be empty string). */\n content: string;\n /** Target provider pubkey if this is a targeted request. */\n targetProvider?: string;\n /** Key-value parameters (may be empty array). */\n params: { key: string; value: string }[];\n /** Preferred relay URLs (may be empty array). */\n relays: string[];\n}\n\n/**\n * Parsed result from a Kind 6xxx DVM job result event.\n */\nexport interface ParsedJobResult {\n /** Event kind (6000-6999). */\n kind: number;\n /** Event ID of the original request. */\n requestEventId: string;\n /** Pubkey of the customer who posted the request. */\n customerPubkey: string;\n /** Compute cost in USDC micro-units as string. */\n amount: string;\n /** Result data from the content field. */\n content: string;\n /** Optional event ID of the provider's kind:10033 attestation event. */\n attestationEventId?: string;\n}\n\n/**\n * Parsed result from a Kind 7000 DVM job feedback event.\n */\nexport interface ParsedJobFeedback {\n /** Event ID of the original request. */\n requestEventId: string;\n /** Pubkey of the customer who posted the request. */\n customerPubkey: string;\n /** Job status value. */\n status: DvmJobStatus;\n /** Status details from the content field (may be empty string). */\n content: string;\n}\n\n// ---------- Builders ----------\n\n/**\n * Builds a Kind 5xxx DVM job request event (NIP-90).\n *\n * Constructs a signed Nostr event with NIP-90 required tags: `i` (input),\n * `bid` (payment offer), and `output` (expected MIME type). Optional tags\n * include `p` (target provider), `param` (key-value pairs), and `relays`\n * (preferred relay URLs).\n *\n * @param params - The job request parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if required params are missing or kind is out of range.\n */\nexport function buildJobRequestEvent(\n params: JobRequestParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate kind range (5000-5999)\n if (params.kind < 5000 || params.kind > 5999) {\n throw new ToonError(\n `Job request kind must be in range 5000-5999, got ${params.kind}`,\n 'DVM_INVALID_KIND'\n );\n }\n\n // Validate required input (allow empty string per NIP-90, reject undefined/null)\n if (params.input.data === undefined || params.input.data === null) {\n throw new ToonError(\n 'Job request input data is required',\n 'DVM_MISSING_INPUT'\n );\n }\n if (!params.input.type) {\n throw new ToonError(\n 'Job request input type is required',\n 'DVM_MISSING_INPUT'\n );\n }\n\n // Validate bid (non-empty string, bigint-compatible)\n if (typeof params.bid !== 'string' || params.bid === '') {\n throw new ToonError(\n 'Job request bid must be a non-empty string (USDC micro-units)',\n 'DVM_INVALID_BID'\n );\n }\n\n // Validate output (non-empty string)\n if (typeof params.output !== 'string' || params.output === '') {\n throw new ToonError(\n 'Job request output MIME type must be a non-empty string',\n 'DVM_MISSING_OUTPUT'\n );\n }\n\n // Build tags\n const tags: string[][] = [];\n\n // Required: ['i', data, type, relay?, marker?]\n const iTag: string[] = ['i', params.input.data, params.input.type];\n if (params.input.relay !== undefined) {\n iTag.push(params.input.relay);\n }\n if (params.input.marker !== undefined) {\n // If marker is set but relay is not, push empty relay placeholder\n if (params.input.relay === undefined) {\n iTag.push('');\n }\n iTag.push(params.input.marker);\n }\n tags.push(iTag);\n\n // Required: ['bid', amount, 'usdc']\n tags.push(['bid', params.bid, 'usdc']);\n\n // Required: ['output', mimeType]\n tags.push(['output', params.output]);\n\n // Optional: ['p', targetProvider] -- validate hex format if provided\n if (params.targetProvider !== undefined) {\n if (!HEX_64_REGEX.test(params.targetProvider)) {\n throw new ToonError(\n 'Job request targetProvider must be a 64-character lowercase hex string',\n 'DVM_INVALID_PUBKEY'\n );\n }\n tags.push(['p', params.targetProvider]);\n }\n\n // Optional: ['param', key, value] for each param\n if (params.params !== undefined) {\n for (const p of params.params) {\n tags.push(['param', p.key, p.value]);\n }\n }\n\n // Optional: ['relays', url1, url2, ...]\n if (params.relays !== undefined && params.relays.length > 0) {\n tags.push(['relays', ...params.relays]);\n }\n\n return finalizeEvent(\n {\n kind: params.kind,\n content: params.content ?? '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Builds a Kind 6xxx DVM job result event (NIP-90).\n *\n * Constructs a signed Nostr event with NIP-90 required tags: `e` (request\n * reference), `p` (customer pubkey), and `amount` (compute cost). The\n * content field carries the result data.\n *\n * @param params - The job result parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if required params are missing or kind is out of range.\n */\nexport function buildJobResultEvent(\n params: JobResultParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate kind range (6000-6999)\n if (params.kind < 6000 || params.kind > 6999) {\n throw new ToonError(\n `Job result kind must be in range 6000-6999, got ${params.kind}`,\n 'DVM_INVALID_KIND'\n );\n }\n\n // Validate requestEventId (64-char hex)\n if (!HEX_64_REGEX.test(params.requestEventId)) {\n throw new ToonError(\n 'Job result requestEventId must be a 64-character lowercase hex string',\n 'DVM_INVALID_EVENT_ID'\n );\n }\n\n // Validate customerPubkey (64-char hex)\n if (!HEX_64_REGEX.test(params.customerPubkey)) {\n throw new ToonError(\n 'Job result customerPubkey must be a 64-character lowercase hex string',\n 'DVM_INVALID_PUBKEY'\n );\n }\n\n // Validate amount (non-empty string, bigint-compatible)\n if (typeof params.amount !== 'string' || params.amount === '') {\n throw new ToonError(\n 'Job result amount must be a non-empty string (USDC micro-units)',\n 'DVM_INVALID_AMOUNT'\n );\n }\n\n // Validate content (string)\n if (typeof params.content !== 'string') {\n throw new ToonError(\n 'Job result content must be a string',\n 'DVM_MISSING_CONTENT'\n );\n }\n\n // Validate optional attestationEventId (64-char hex)\n if (params.attestationEventId !== undefined) {\n if (!HEX_64_REGEX.test(params.attestationEventId)) {\n throw new ToonError(\n 'Job result attestationEventId must be a 64-character lowercase hex string',\n 'DVM_INVALID_ATTESTATION_EVENT_ID'\n );\n }\n }\n\n const tags: string[][] = [\n ['e', params.requestEventId],\n ['p', params.customerPubkey],\n ['amount', params.amount, 'usdc'],\n ];\n\n // Optional: ['attestation', attestationEventId] -- TEE attestation reference\n if (params.attestationEventId !== undefined) {\n tags.push(['attestation', params.attestationEventId]);\n }\n\n return finalizeEvent(\n {\n kind: params.kind,\n content: params.content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Builds a Kind 7000 DVM job feedback event (NIP-90).\n *\n * Constructs a signed Nostr event with NIP-90 required tags: `e` (request\n * reference), `p` (customer pubkey), and `status` (job state). The content\n * field carries optional status details or error messages.\n *\n * @param params - The job feedback parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if required params are missing or status is invalid.\n */\nexport function buildJobFeedbackEvent(\n params: JobFeedbackParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate requestEventId (64-char hex)\n if (!HEX_64_REGEX.test(params.requestEventId)) {\n throw new ToonError(\n 'Job feedback requestEventId must be a 64-character lowercase hex string',\n 'DVM_INVALID_EVENT_ID'\n );\n }\n\n // Validate customerPubkey (64-char hex)\n if (!HEX_64_REGEX.test(params.customerPubkey)) {\n throw new ToonError(\n 'Job feedback customerPubkey must be a 64-character lowercase hex string',\n 'DVM_INVALID_PUBKEY'\n );\n }\n\n // Validate status\n if (!VALID_STATUSES.has(params.status)) {\n throw new ToonError(\n `Job feedback status must be one of: processing, error, success, partial. Got: ${String(params.status)}`,\n 'DVM_INVALID_STATUS'\n );\n }\n\n const tags: string[][] = [\n ['e', params.requestEventId],\n ['p', params.customerPubkey],\n ['status', params.status],\n ];\n\n return finalizeEvent(\n {\n kind: JOB_FEEDBACK_KIND,\n content: params.content ?? '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parsers ----------\n\n/**\n * Parses a Kind 5xxx event into a ParsedJobRequest.\n *\n * Validates the event kind is in the 5000-5999 range and extracts required\n * NIP-90 tags: `i` (input), `bid` (amount + currency), and `output` (MIME\n * type). Also extracts optional tags: `p` (target provider), `param`\n * (key-value pairs), and `relays` (preferred URLs).\n *\n * Returns `null` for malformed events (missing required tags, invalid kind\n * range). Follows the lenient parse pattern established by\n * `parseServiceDiscovery()` and `parseAttestation()`.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed job request, or null if invalid.\n */\nexport function parseJobRequest(event: NostrEvent): ParsedJobRequest | null {\n // Validate kind range\n if (event.kind < 5000 || event.kind > 5999) {\n return null;\n }\n\n // Extract required 'i' tag: ['i', data, type, relay?, marker?]\n const iTag = event.tags.find((t: string[]) => t[0] === 'i');\n if (!iTag) return null;\n const inputData = iTag[1];\n const inputType = iTag[2];\n if (inputData === undefined || inputType === undefined) return null;\n\n // Extract required 'bid' tag: ['bid', amount, 'usdc']\n const bidTag = event.tags.find((t: string[]) => t[0] === 'bid');\n if (!bidTag) return null;\n const bidAmount = bidTag[1];\n if (bidAmount === undefined || bidAmount === '') return null;\n\n // Extract required 'output' tag: ['output', mimeType]\n const outputTag = event.tags.find((t: string[]) => t[0] === 'output');\n if (!outputTag) return null;\n const outputMime = outputTag[1];\n if (outputMime === undefined || outputMime === '') return null;\n\n // Build input object with optional relay and marker\n const input: ParsedJobRequest['input'] = {\n data: inputData,\n type: inputType,\n };\n const inputRelay = iTag[3];\n if (inputRelay !== undefined && inputRelay !== '') {\n input.relay = inputRelay;\n }\n const inputMarker = iTag[4];\n if (inputMarker !== undefined && inputMarker !== '') {\n input.marker = inputMarker;\n }\n\n // Extract optional 'p' tag (target provider) -- validate hex format if present\n const pTag = event.tags.find((t: string[]) => t[0] === 'p');\n const targetProvider = pTag?.[1];\n if (targetProvider !== undefined && !HEX_64_REGEX.test(targetProvider))\n return null;\n\n // Extract optional 'param' tags (collect all)\n const paramTags = event.tags.filter((t: string[]) => t[0] === 'param');\n const params: { key: string; value: string }[] = [];\n for (const pt of paramTags) {\n const key = pt[1];\n const value = pt[2];\n if (key !== undefined && value !== undefined) {\n params.push({ key, value });\n }\n }\n\n // Extract optional 'relays' tag\n const relaysTag = event.tags.find((t: string[]) => t[0] === 'relays');\n const relays: string[] = [];\n if (relaysTag) {\n for (let i = 1; i < relaysTag.length; i++) {\n const url = relaysTag[i];\n if (url !== undefined) {\n relays.push(url);\n }\n }\n }\n\n const result: ParsedJobRequest = {\n kind: event.kind,\n input,\n bid: bidAmount,\n output: outputMime,\n content: event.content,\n params,\n relays,\n };\n\n if (targetProvider !== undefined) {\n result.targetProvider = targetProvider;\n }\n\n return result;\n}\n\n/**\n * Parses a Kind 6xxx event into a ParsedJobResult.\n *\n * Validates the event kind is in the 6000-6999 range and extracts required\n * NIP-90 tags: `e` (request event ID), `p` (customer pubkey), and `amount`\n * (compute cost + currency).\n *\n * The `amount` tag is **informational** (a receipt of the agreed price).\n * In the prepaid protocol model, payment is sent with the job request\n * (via `publishEvent()` with the `amount` option), NOT triggered by\n * parsing the result event's amount tag. The amount tag serves as a\n * record of what was agreed upon, not as a payment trigger.\n *\n * Returns `null` for malformed events. Follows the lenient parse pattern.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed job result, or null if invalid.\n */\nexport function parseJobResult(event: NostrEvent): ParsedJobResult | null {\n // Validate kind range\n if (event.kind < 6000 || event.kind > 6999) {\n return null;\n }\n\n // Extract required 'e' tag: ['e', requestEventId]\n const eTag = event.tags.find((t: string[]) => t[0] === 'e');\n if (!eTag) return null;\n const requestEventId = eTag[1];\n if (requestEventId === undefined || !HEX_64_REGEX.test(requestEventId))\n return null;\n\n // Extract required 'p' tag: ['p', customerPubkey]\n const pTag = event.tags.find((t: string[]) => t[0] === 'p');\n if (!pTag) return null;\n const customerPubkey = pTag[1];\n if (customerPubkey === undefined || !HEX_64_REGEX.test(customerPubkey))\n return null;\n\n // Extract required 'amount' tag: ['amount', cost, 'usdc']\n const amountTag = event.tags.find((t: string[]) => t[0] === 'amount');\n if (!amountTag) return null;\n const amount = amountTag[1];\n if (amount === undefined || amount === '') return null;\n\n // Validate amount is numeric (must be parseable as a non-negative integer).\n // USDC micro-units are always whole numbers; reject decimals, negatives,\n // and non-numeric strings to prevent downstream BigInt/arithmetic errors.\n if (!/^\\d+$/.test(amount)) return null;\n\n // Extract optional 'attestation' tag: ['attestation', eventId]\n const attestationTag = event.tags.find(\n (t: string[]) => t[0] === 'attestation'\n );\n const attestationEventId = attestationTag?.[1];\n\n const result: ParsedJobResult = {\n kind: event.kind,\n requestEventId,\n customerPubkey,\n amount,\n content: event.content,\n };\n\n if (\n attestationEventId !== undefined &&\n HEX_64_REGEX.test(attestationEventId)\n ) {\n result.attestationEventId = attestationEventId;\n }\n\n return result;\n}\n\n/**\n * Parses a Kind 7000 event into a ParsedJobFeedback.\n *\n * Validates the event kind is exactly 7000 and extracts required NIP-90\n * tags: `e` (request event ID), `p` (customer pubkey), and `status`\n * (job state). The status value must be one of: `'processing'`, `'error'`,\n * `'success'`, `'partial'`.\n *\n * Returns `null` for malformed events or invalid status values.\n * Follows the lenient parse pattern.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed job feedback, or null if invalid.\n */\nexport function parseJobFeedback(event: NostrEvent): ParsedJobFeedback | null {\n // Validate kind is exactly 7000\n if (event.kind !== JOB_FEEDBACK_KIND) {\n return null;\n }\n\n // Extract required 'e' tag: ['e', requestEventId]\n const eTag = event.tags.find((t: string[]) => t[0] === 'e');\n if (!eTag) return null;\n const requestEventId = eTag[1];\n if (requestEventId === undefined || !HEX_64_REGEX.test(requestEventId))\n return null;\n\n // Extract required 'p' tag: ['p', customerPubkey]\n const pTag = event.tags.find((t: string[]) => t[0] === 'p');\n if (!pTag) return null;\n const customerPubkey = pTag[1];\n if (customerPubkey === undefined || !HEX_64_REGEX.test(customerPubkey))\n return null;\n\n // Extract required 'status' tag: ['status', statusValue]\n const statusTag = event.tags.find((t: string[]) => t[0] === 'status');\n if (!statusTag) return null;\n const statusValue = statusTag[1];\n if (statusValue === undefined) return null;\n\n // Validate status value\n if (!VALID_STATUSES.has(statusValue)) {\n return null;\n }\n\n return {\n requestEventId,\n customerPubkey,\n status: statusValue as DvmJobStatus,\n content: event.content,\n };\n}\n","/**\n * Event builder and parser for Workflow Chain events (kind:10040).\n *\n * Workflow chains define multi-step DVM pipelines where each step's output\n * automatically feeds into the next step's input. The workflow definition\n * event contains an ordered list of steps, initial input, and a total bid\n * that is split across steps.\n *\n * Kind:10040 is in the TOON-specific replaceable range (10032-10099).\n * Each workflow instance uses a unique `d` tag to avoid unintentional\n * replacement of in-progress workflows.\n *\n * Tag reference (kind:10040):\n * Content: JSON-serialized workflow definition body\n * Required: ['d', uniqueWorkflowId], ['bid', totalBid, 'usdc']\n * Content JSON: { steps: WorkflowStep[], initialInput: { data, type }, totalBid }\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { WORKFLOW_CHAIN_KIND } from '../constants.js';\nimport { ToonError } from '../errors.js';\n\n/** Regex for 64-char lowercase hex string (matches dvm.ts validation). */\nconst HEX_64_REGEX = /^[0-9a-f]{64}$/;\n\n// Re-export constant for convenient co-located imports (follows dvm.ts pattern)\nexport { WORKFLOW_CHAIN_KIND };\n\n// ---------- Types ----------\n\n/**\n * A single step in a workflow chain.\n *\n * Each step specifies a DVM kind (5000-5999) and a description.\n * Optionally, a step can target a specific provider and allocate\n * an explicit portion of the total bid.\n */\nexport interface WorkflowStep {\n /** DVM job request kind (5000-5999 range). */\n kind: number;\n /** Human-readable description of the step's purpose. */\n description: string;\n /** Optional 64-char hex pubkey of a specific target provider. */\n targetProvider?: string;\n /** Optional explicit bid allocation in USDC micro-units as string. */\n bidAllocation?: string;\n}\n\n/**\n * Parameters for building a kind:10040 Workflow Chain event.\n */\nexport interface WorkflowDefinitionParams {\n /** Ordered list of workflow steps (at least one required). */\n steps: WorkflowStep[];\n /** Initial input for the first step. */\n initialInput: {\n /** The input data (text, JSON, etc.). */\n data: string;\n /** Input type identifier (e.g., 'text', 'json'). */\n type: string;\n };\n /** Total bid for the entire workflow in USDC micro-units as string. */\n totalBid: string;\n /** Optional body text for the event content field. */\n content?: string;\n /** Optional workflow ID for deterministic `d` tag (defaults to timestamp-based). */\n workflowId?: string;\n}\n\n/**\n * Parsed result from a kind:10040 Workflow Chain event.\n */\nexport interface ParsedWorkflowDefinition {\n /** Ordered list of workflow steps. */\n steps: WorkflowStep[];\n /** Initial input for the first step. */\n initialInput: {\n /** The input data. */\n data: string;\n /** Input type identifier. */\n type: string;\n };\n /** Total bid for the entire workflow in USDC micro-units as string. */\n totalBid: string;\n /** Event content field (JSON-serialized workflow body). */\n content: string;\n}\n\n// ---------- Builder ----------\n\n/**\n * Builds a kind:10040 Workflow Chain event.\n *\n * Validates steps, initial input, total bid, and per-step bid allocations\n * (if provided). Serializes the workflow definition as JSON content.\n *\n * @param params - The workflow definition parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if validation fails.\n */\nexport function buildWorkflowDefinitionEvent(\n params: WorkflowDefinitionParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate steps non-empty\n if (!params.steps || params.steps.length === 0) {\n throw new ToonError(\n 'Workflow definition must have at least one step',\n 'DVM_WORKFLOW_INVALID_STEPS'\n );\n }\n\n // Validate each step kind in 5000-5999 range and optional targetProvider format\n for (const step of params.steps) {\n if (step.kind < 5000 || step.kind > 5999) {\n throw new ToonError(\n `Workflow step kind must be in range 5000-5999, got ${step.kind}`,\n 'DVM_WORKFLOW_INVALID_STEPS'\n );\n }\n if (typeof step.description !== 'string' || step.description === '') {\n throw new ToonError(\n 'Workflow step description must be a non-empty string',\n 'DVM_WORKFLOW_INVALID_STEPS'\n );\n }\n if (\n step.targetProvider !== undefined &&\n !HEX_64_REGEX.test(step.targetProvider)\n ) {\n throw new ToonError(\n 'Workflow step targetProvider must be a 64-character lowercase hex string',\n 'DVM_WORKFLOW_INVALID_PROVIDER'\n );\n }\n }\n\n // Validate initial input data is a string.\n // Empty string is intentionally allowed per NIP-90 convention (same as\n // buildJobRequestEvent). Only non-string types are rejected.\n if (typeof params.initialInput.data !== 'string') {\n throw new ToonError(\n 'Workflow definition initial input data must be a string',\n 'DVM_WORKFLOW_MISSING_INPUT'\n );\n }\n\n // Validate initial input type is non-empty\n if (!params.initialInput.type) {\n throw new ToonError(\n 'Workflow definition initial input type is required',\n 'DVM_WORKFLOW_MISSING_INPUT'\n );\n }\n\n // Validate totalBid is non-empty string\n if (typeof params.totalBid !== 'string' || params.totalBid === '') {\n throw new ToonError(\n 'Workflow definition totalBid must be a non-empty string (USDC micro-units)',\n 'DVM_WORKFLOW_INVALID_BID'\n );\n }\n\n // Validate per-step bid allocations: sum(allocations) <= totalBid\n const stepsWithAllocation = params.steps.filter(\n (s) => s.bidAllocation !== undefined\n );\n if (stepsWithAllocation.length > 0) {\n let allocationSum: bigint;\n let totalBidBigInt: bigint;\n try {\n allocationSum = 0n;\n for (const step of stepsWithAllocation) {\n allocationSum += BigInt(step.bidAllocation as string);\n }\n totalBidBigInt = BigInt(params.totalBid);\n } catch {\n throw new ToonError(\n 'Workflow bid amounts must be valid numeric strings (USDC micro-units)',\n 'DVM_WORKFLOW_INVALID_BID'\n );\n }\n if (allocationSum > totalBidBigInt) {\n throw new ToonError(\n `Sum of step bid allocations (${allocationSum}) exceeds total bid (${totalBidBigInt})`,\n 'DVM_WORKFLOW_BID_OVERFLOW'\n );\n }\n }\n\n // Build content JSON\n const contentBody = {\n steps: params.steps.map((step) => {\n const s: Record<string, unknown> = {\n kind: step.kind,\n description: step.description,\n };\n if (step.targetProvider !== undefined) {\n s['targetProvider'] = step.targetProvider;\n }\n if (step.bidAllocation !== undefined) {\n s['bidAllocation'] = step.bidAllocation;\n }\n return s;\n }),\n initialInput: params.initialInput,\n totalBid: params.totalBid,\n };\n\n const contentJson = JSON.stringify(contentBody);\n\n // Build tags\n const tags: string[][] = [];\n\n // Unique `d` tag for NIP-33 parameterized replaceable semantics.\n // Use explicit workflowId if provided (deterministic), otherwise\n // fall back to timestamp-based identifier with random suffix to\n // guarantee uniqueness across concurrent or rapid-fire creations.\n const randomSuffix = Math.random().toString(36).slice(2, 8);\n const dTag =\n params.workflowId ??\n `wf-${Date.now()}-${params.steps.length}-${randomSuffix}`;\n tags.push(['d', dTag]);\n\n // Total bid tag for easy extraction without full JSON parse\n tags.push(['bid', params.totalBid, 'usdc']);\n\n return finalizeEvent(\n {\n kind: WORKFLOW_CHAIN_KIND,\n content: contentJson,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parser ----------\n\n/**\n * Parses a kind:10040 event into a ParsedWorkflowDefinition.\n *\n * Validates the event kind, parses JSON content, validates the steps\n * array, initialInput, and totalBid. Returns `null` for malformed events.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed workflow definition, or null if invalid.\n */\nexport function parseWorkflowDefinition(\n event: NostrEvent\n): ParsedWorkflowDefinition | null {\n // Validate kind\n if (event.kind !== WORKFLOW_CHAIN_KIND) {\n return null;\n }\n\n // Parse content JSON\n let body: unknown;\n try {\n body = JSON.parse(event.content);\n } catch {\n return null;\n }\n\n if (typeof body !== 'object' || body === null) {\n return null;\n }\n\n const obj = body as Record<string, unknown>;\n\n // Validate steps array\n const rawSteps = obj['steps'];\n if (!Array.isArray(rawSteps) || rawSteps.length === 0) {\n return null;\n }\n\n const steps: WorkflowStep[] = [];\n for (const rawStep of rawSteps) {\n if (typeof rawStep !== 'object' || rawStep === null) {\n return null;\n }\n const stepObj = rawStep as Record<string, unknown>;\n const kind = stepObj['kind'];\n const description = stepObj['description'];\n if (typeof kind !== 'number' || typeof description !== 'string') {\n return null;\n }\n if (kind < 5000 || kind > 5999) {\n return null;\n }\n\n const step: WorkflowStep = { kind, description };\n\n const targetProvider = stepObj['targetProvider'];\n if (typeof targetProvider === 'string' && targetProvider.length > 0) {\n step.targetProvider = targetProvider;\n }\n\n const bidAllocation = stepObj['bidAllocation'];\n if (typeof bidAllocation === 'string' && bidAllocation.length > 0) {\n step.bidAllocation = bidAllocation;\n }\n\n steps.push(step);\n }\n\n // Validate initialInput\n const rawInput = obj['initialInput'];\n if (typeof rawInput !== 'object' || rawInput === null) {\n return null;\n }\n const inputObj = rawInput as Record<string, unknown>;\n const inputData = inputObj['data'];\n const inputType = inputObj['type'];\n if (typeof inputData !== 'string' || typeof inputType !== 'string') {\n return null;\n }\n\n // Validate totalBid\n const totalBid = obj['totalBid'];\n if (typeof totalBid !== 'string' || totalBid === '') {\n return null;\n }\n\n return {\n steps,\n initialInput: { data: inputData, type: inputType },\n totalBid,\n content: event.content,\n };\n}\n","/**\n * Event builders and parsers for DVM Agent Swarm events (Story 6.2).\n *\n * Swarm events extend NIP-90 DVM events with competitive bidding tags:\n * - `['swarm', maxProviders]` -- maximum number of providers in the swarm\n * - `['judge', judgeIdentifier]` -- who selects the winner (default: 'customer')\n *\n * Swarm request events are standard Kind 5xxx events with additional tags.\n * Non-swarm-aware providers can still participate via the standard Kind 5xxx path.\n *\n * Selection events are Kind 7000 feedback events with a `winner` tag\n * referencing the winning Kind 6xxx result event ID.\n *\n * Tag reference (swarm-specific additions):\n *\n * Kind 5xxx (Swarm Job Request):\n * Standard NIP-90 tags + ['swarm', maxProviders], ['judge', judgeId]\n *\n * Kind 7000 (Swarm Selection):\n * Standard NIP-90 feedback tags + ['winner', winnerResultEventId]\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { JOB_FEEDBACK_KIND } from '../constants.js';\nimport { ToonError } from '../errors.js';\nimport { buildJobRequestEvent, parseJobRequest } from './dvm.js';\nimport type { JobRequestParams, ParsedJobRequest } from './dvm.js';\n\n// ---------- Validation Helpers ----------\n\n/** Regex for 64-char lowercase hex string. */\nconst HEX_64_REGEX = /^[0-9a-f]{64}$/;\n\n// ---------- Types ----------\n\n/**\n * Parameters for building a swarm request event (Kind 5xxx with swarm tags).\n *\n * Extends `JobRequestParams` with swarm-specific fields:\n * - `maxProviders`: Maximum number of providers in the competitive swarm (>= 1).\n * - `judge`: Who selects the winner (default: 'customer'). Can be 'customer',\n * 'auto', or a specific pubkey.\n */\nexport interface SwarmRequestParams extends JobRequestParams {\n /** Maximum number of providers to collect submissions from (>= 1). */\n maxProviders: number;\n /** Who selects the winner (default: 'customer'). */\n judge?: string;\n}\n\n/**\n * Parameters for building a swarm selection event (Kind 7000 with winner tag).\n */\nexport interface SwarmSelectionParams {\n /** 64-char hex event ID of the original swarm request. */\n swarmRequestEventId: string;\n /** 64-char hex event ID of the winning Kind 6xxx result. */\n winnerResultEventId: string;\n /** 64-char hex pubkey of the customer who posted the swarm request. */\n customerPubkey: string;\n}\n\n/**\n * Parsed result from a Kind 5xxx swarm request event.\n * Extends `ParsedJobRequest` with swarm-specific fields.\n */\nexport interface ParsedSwarmRequest extends ParsedJobRequest {\n /** Maximum number of providers in the swarm. */\n maxProviders: number;\n /** Who selects the winner. */\n judge: string;\n}\n\n/**\n * Parsed result from a Kind 7000 swarm selection event.\n */\nexport interface ParsedSwarmSelection {\n /** Event ID of the original swarm request. */\n swarmRequestEventId: string;\n /** Event ID of the winning Kind 6xxx result. */\n winnerResultEventId: string;\n}\n\n// ---------- Builders ----------\n\n/**\n * Builds a Kind 5xxx swarm request event.\n *\n * Delegates to `buildJobRequestEvent()` for the base NIP-90 event, then\n * appends swarm-specific tags: `swarm` (max providers) and `judge` (selector).\n *\n * The resulting event is a valid Kind 5xxx event that non-swarm-aware\n * providers can also parse via `parseJobRequest()`.\n *\n * @param params - The swarm request parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if maxProviders < 1 or base params are invalid.\n */\nexport function buildSwarmRequestEvent(\n params: SwarmRequestParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate maxProviders: must be a positive integer\n if (!Number.isInteger(params.maxProviders) || params.maxProviders < 1) {\n throw new ToonError(\n `Swarm maxProviders must be a positive integer >= 1, got ${params.maxProviders}`,\n 'DVM_SWARM_INVALID_MAX_PROVIDERS'\n );\n }\n\n // Build the base Kind 5xxx event (validates kind range, input, bid, output)\n const baseEvent = buildJobRequestEvent(params, secretKey);\n\n // Append swarm-specific tags\n const judge = params.judge ?? 'customer';\n const tags: string[][] = [\n ...baseEvent.tags,\n ['swarm', params.maxProviders.toString()],\n ['judge', judge],\n ];\n\n // Re-finalize with the additional tags\n return finalizeEvent(\n {\n kind: baseEvent.kind,\n content: baseEvent.content,\n tags,\n created_at: baseEvent.created_at,\n },\n secretKey\n );\n}\n\n/**\n * Builds a Kind 7000 swarm selection event.\n *\n * Creates a feedback event with `status: 'success'`, an `e` tag referencing\n * the swarm request, and a `winner` tag referencing the winning result.\n *\n * @param params - The swarm selection parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if event IDs or pubkey are not valid 64-char hex.\n */\nexport function buildSwarmSelectionEvent(\n params: SwarmSelectionParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate swarmRequestEventId\n if (!HEX_64_REGEX.test(params.swarmRequestEventId)) {\n throw new ToonError(\n 'Swarm selection swarmRequestEventId must be a 64-character lowercase hex string',\n 'DVM_INVALID_EVENT_ID'\n );\n }\n\n // Validate winnerResultEventId\n if (!HEX_64_REGEX.test(params.winnerResultEventId)) {\n throw new ToonError(\n 'Swarm selection winnerResultEventId must be a 64-character lowercase hex string',\n 'DVM_INVALID_EVENT_ID'\n );\n }\n\n // Validate customerPubkey\n if (!HEX_64_REGEX.test(params.customerPubkey)) {\n throw new ToonError(\n 'Swarm selection customerPubkey must be a 64-character lowercase hex string',\n 'DVM_INVALID_PUBKEY'\n );\n }\n\n const tags: string[][] = [\n ['e', params.swarmRequestEventId],\n ['p', params.customerPubkey],\n ['status', 'success'],\n ['winner', params.winnerResultEventId],\n ];\n\n return finalizeEvent(\n {\n kind: JOB_FEEDBACK_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parsers ----------\n\n/**\n * Parses a Kind 5xxx event into a ParsedSwarmRequest.\n *\n * Delegates to `parseJobRequest()` for base fields, then extracts the\n * `swarm` and `judge` tags. Returns `null` if the event is not a valid\n * swarm request (missing `swarm` tag, invalid kind range, etc.).\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed swarm request, or null if invalid/not a swarm request.\n */\nexport function parseSwarmRequest(\n event: NostrEvent\n): ParsedSwarmRequest | null {\n // Parse base job request fields\n const base = parseJobRequest(event);\n if (!base) return null;\n\n // Extract swarm tag: ['swarm', maxProviders]\n const swarmTag = event.tags.find((t: string[]) => t[0] === 'swarm');\n if (!swarmTag) return null;\n const maxProvidersStr = swarmTag[1];\n if (maxProvidersStr === undefined) return null;\n const maxProviders = parseInt(maxProvidersStr, 10);\n if (isNaN(maxProviders) || maxProviders < 1) return null;\n\n // Extract judge tag: ['judge', judgeId] (default: 'customer')\n const judgeTag = event.tags.find((t: string[]) => t[0] === 'judge');\n const judge = judgeTag?.[1] ?? 'customer';\n\n return {\n ...base,\n maxProviders,\n judge,\n };\n}\n\n/**\n * Parses a Kind 7000 event into a ParsedSwarmSelection.\n *\n * Validates the event kind is exactly 7000 and extracts the `winner` tag\n * and `e` tag (swarm request reference). Returns `null` if the event is\n * not a valid swarm selection (missing `winner` tag, wrong kind, etc.).\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed swarm selection, or null if invalid.\n */\nexport function parseSwarmSelection(\n event: NostrEvent\n): ParsedSwarmSelection | null {\n // Validate kind is exactly 7000\n if (event.kind !== JOB_FEEDBACK_KIND) return null;\n\n // Extract winner tag: ['winner', winnerResultEventId]\n const winnerTag = event.tags.find((t: string[]) => t[0] === 'winner');\n if (!winnerTag) return null;\n const winnerResultEventId = winnerTag[1];\n if (\n winnerResultEventId === undefined ||\n !HEX_64_REGEX.test(winnerResultEventId)\n )\n return null;\n\n // Extract e tag: ['e', swarmRequestEventId]\n const eTag = event.tags.find((t: string[]) => t[0] === 'e');\n if (!eTag) return null;\n const swarmRequestEventId = eTag[1];\n if (\n swarmRequestEventId === undefined ||\n !HEX_64_REGEX.test(swarmRequestEventId)\n )\n return null;\n\n return {\n swarmRequestEventId,\n winnerResultEventId,\n };\n}\n","/**\n * Event builders and parsers for prefix claim (kind 10034) and\n * prefix grant (kind 10037) events.\n *\n * Prefix claims are part of the TOON prefix marketplace: a node sends a\n * kind 10034 event with payment to claim a prefix from an upstream peer.\n * The upstream responds with a kind 10037 grant confirmation.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { PREFIX_CLAIM_KIND, PREFIX_GRANT_KIND } from '../constants.js';\n\n// Re-export for convenient co-located imports\nexport { PREFIX_CLAIM_KIND, PREFIX_GRANT_KIND };\n\n// ---------- Types ----------\n\n/** Content payload for a kind 10034 prefix claim event. */\nexport interface PrefixClaimContent {\n /** The prefix string being requested (e.g., 'useast'). */\n requestedPrefix: string;\n}\n\n/** Content payload for a kind 10037 prefix grant event. */\nexport interface PrefixGrantContent {\n /** The prefix that was granted. */\n grantedPrefix: string;\n /** Pubkey of the node that received the prefix. */\n claimerPubkey: string;\n /** The ILP address derived from the granted prefix. */\n ilpAddress: string;\n}\n\n// ---------- Builders ----------\n\n/**\n * Builds and signs a kind 10034 prefix claim event.\n *\n * @param content - The prefix claim content with the requested prefix\n * @param secretKey - The 32-byte secret key to sign the event with\n * @returns A signed Nostr event of kind 10034\n */\nexport function buildPrefixClaimEvent(\n content: PrefixClaimContent,\n secretKey: Uint8Array\n): NostrEvent {\n return finalizeEvent(\n {\n kind: PREFIX_CLAIM_KIND,\n content: JSON.stringify(content),\n tags: [],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Parses a kind 10034 prefix claim event into PrefixClaimContent.\n *\n * Returns null for malformed events. Follows the lenient parse pattern.\n *\n * @param event - The Nostr event to parse\n * @returns The parsed prefix claim content, or null if invalid\n */\nexport function parsePrefixClaimEvent(\n event: NostrEvent\n): PrefixClaimContent | null {\n if (event.kind !== PREFIX_CLAIM_KIND) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(event.content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n const obj = parsed as Record<string, unknown>;\n const requestedPrefix = obj['requestedPrefix'];\n if (typeof requestedPrefix !== 'string' || requestedPrefix.length === 0) {\n return null;\n }\n return { requestedPrefix };\n } catch {\n return null;\n }\n}\n\n/**\n * Builds and signs a kind 10037 prefix grant event.\n *\n * @param content - The prefix grant content with granted prefix details\n * @param secretKey - The 32-byte secret key to sign the event with\n * @returns A signed Nostr event of kind 10037\n */\nexport function buildPrefixGrantEvent(\n content: PrefixGrantContent,\n secretKey: Uint8Array\n): NostrEvent {\n return finalizeEvent(\n {\n kind: PREFIX_GRANT_KIND,\n content: JSON.stringify(content),\n tags: [],\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Parses a kind 10037 prefix grant event into PrefixGrantContent.\n *\n * Returns null for malformed events. Follows the lenient parse pattern.\n *\n * @param event - The Nostr event to parse\n * @returns The parsed prefix grant content, or null if invalid\n */\nexport function parsePrefixGrantEvent(\n event: NostrEvent\n): PrefixGrantContent | null {\n if (event.kind !== PREFIX_GRANT_KIND) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(event.content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n const obj = parsed as Record<string, unknown>;\n const grantedPrefix = obj['grantedPrefix'];\n const claimerPubkey = obj['claimerPubkey'];\n const ilpAddress = obj['ilpAddress'];\n if (typeof grantedPrefix !== 'string' || grantedPrefix.length === 0) {\n return null;\n }\n if (typeof claimerPubkey !== 'string' || claimerPubkey.length === 0) {\n return null;\n }\n if (typeof ilpAddress !== 'string' || ilpAddress.length === 0) {\n return null;\n }\n return {\n grantedPrefix,\n claimerPubkey,\n ilpAddress,\n };\n } catch {\n return null;\n }\n}\n","/**\n * AttestationVerifier -- TEE attestation verification, state lifecycle,\n * and attestation-aware peer ranking for Story 4.3.\n *\n * This is a pure logic class with no transport layer. It receives parsed\n * attestation data and returns verification results. The transport layer\n * (subscribing to kind:10033 events on relays) is a Story 4.6 concern.\n *\n * The AttestationVerifier is the single source of truth for attestation\n * state (R-E4-008). Both the kind:10033 Nostr event path and the /health\n * HTTP endpoint derive their TEE state from the same verifier instance.\n *\n * Attestation State Machine (Decision 12):\n * VALID (within validitySeconds)\n * -> STALE (within graceSeconds after validity expires)\n * -> UNATTESTED (after grace period expires)\n *\n * Trust degrades; money doesn't -- attestation state changes never\n * trigger payment channel closure.\n */\n\nimport type { TeeAttestation } from '../types.js';\n\n// ---------- Enums ----------\n\n/**\n * Attestation lifecycle state.\n *\n * Transitions: VALID -> STALE -> UNATTESTED.\n * A peer that was never attested starts as UNATTESTED.\n */\nexport enum AttestationState {\n /** Attestation is within validity period. */\n VALID = 'valid',\n /** Attestation has expired but is within the grace period. */\n STALE = 'stale',\n /** Attestation has expired past the grace period or was never attested. */\n UNATTESTED = 'unattested',\n}\n\n// ---------- Types ----------\n\n/** Result of PCR verification against a known-good registry. */\nexport interface VerificationResult {\n valid: boolean;\n reason?: string;\n}\n\n/**\n * Descriptor for a peer in the attestation-aware ranking system.\n * Used by `rankPeers()` to order peers by attestation status.\n */\nexport interface PeerDescriptor {\n pubkey: string;\n relayUrl: string;\n attested: boolean;\n attestationTimestamp?: number;\n}\n\n/**\n * Configuration for the AttestationVerifier.\n */\nexport interface AttestationVerifierConfig {\n /** Map of known-good PCR values. Key is PCR hash, value is trust status. */\n knownGoodPcrs: Map<string, boolean>;\n /** Attestation validity period in seconds (default: 300). */\n validitySeconds?: number;\n /** Grace period in seconds after validity expires before marking as unattested (default: 30). */\n graceSeconds?: number;\n}\n\n// ---------- Default Constants ----------\n\n/** Default attestation validity period: 5 minutes. */\nconst DEFAULT_VALIDITY_SECONDS = 300;\n\n/** Default grace period: 30 seconds. */\nconst DEFAULT_GRACE_SECONDS = 30;\n\n// ---------- Class ----------\n\n/**\n * Verifies TEE attestations, computes attestation lifecycle state,\n * and ranks peers by attestation status.\n *\n * Single source of truth for attestation state (R-E4-008).\n */\nexport class AttestationVerifier {\n private readonly knownGoodPcrs: Map<string, boolean>;\n private readonly validitySeconds: number;\n private readonly graceSeconds: number;\n\n constructor(config: AttestationVerifierConfig) {\n // Defensive copy: prevent external mutation from affecting verifier behavior\n this.knownGoodPcrs = new Map(config.knownGoodPcrs);\n\n const validity = config.validitySeconds ?? DEFAULT_VALIDITY_SECONDS;\n const grace = config.graceSeconds ?? DEFAULT_GRACE_SECONDS;\n\n if (!Number.isFinite(validity) || validity < 0) {\n throw new Error(\n `validitySeconds must be a non-negative finite number, got ${String(validity)}`\n );\n }\n if (!Number.isFinite(grace) || grace < 0) {\n throw new Error(\n `graceSeconds must be a non-negative finite number, got ${String(grace)}`\n );\n }\n\n this.validitySeconds = validity;\n this.graceSeconds = grace;\n }\n\n /**\n * Verifies a TEE attestation's PCR values against the known-good registry.\n *\n * All three PCR values (pcr0, pcr1, pcr2) must be present and truthy in\n * the registry for verification to pass.\n *\n * @param attestation - The TEE attestation to verify.\n * @returns Verification result with `valid: true` or `valid: false` with reason.\n */\n verify(attestation: TeeAttestation): VerificationResult {\n const pcr0Valid = this.knownGoodPcrs.get(attestation.pcr0) === true;\n const pcr1Valid = this.knownGoodPcrs.get(attestation.pcr1) === true;\n const pcr2Valid = this.knownGoodPcrs.get(attestation.pcr2) === true;\n\n if (pcr0Valid && pcr1Valid && pcr2Valid) {\n return { valid: true };\n }\n\n return { valid: false, reason: 'PCR mismatch' };\n }\n\n /**\n * Computes the attestation lifecycle state based on timing.\n *\n * Boundary behavior:\n * - At exactly `attestedAt + validitySeconds`: VALID (inclusive <=)\n * - At exactly `attestedAt + validitySeconds + graceSeconds`: STALE (inclusive <=)\n * - After grace expires: UNATTESTED\n *\n * @param _attestation - The TEE attestation (unused, reserved for future per-attestation logic).\n * @param attestedAt - Unix timestamp when the attestation was created.\n * @param now - Optional current unix timestamp (defaults to real clock).\n * @returns The current attestation state.\n */\n getAttestationState(\n _attestation: TeeAttestation,\n attestedAt: number,\n now?: number\n ): AttestationState {\n if (!Number.isFinite(attestedAt)) {\n return AttestationState.UNATTESTED;\n }\n const currentTime = now ?? Math.floor(Date.now() / 1000);\n const validityEnd = attestedAt + this.validitySeconds;\n const graceEnd = validityEnd + this.graceSeconds;\n\n if (currentTime <= validityEnd) {\n return AttestationState.VALID;\n }\n\n if (currentTime <= graceEnd) {\n return AttestationState.STALE;\n }\n\n return AttestationState.UNATTESTED;\n }\n\n /**\n * Ranks peers by attestation status: attested peers first, then non-attested.\n *\n * Preserves relative order within each group (stable sort via filter).\n * Does NOT mutate the input array -- returns a new sorted array.\n *\n * Attestation is a preference, not a requirement. Non-attested peers\n * remain in the result and are connectable.\n *\n * @param peers - Array of peer descriptors to rank.\n * @returns New array with attested peers first, preserving relative order.\n */\n rankPeers(peers: PeerDescriptor[]): PeerDescriptor[] {\n const attested = peers.filter((p) => p.attested);\n const nonAttested = peers.filter((p) => !p.attested);\n return [...attested, ...nonAttested];\n }\n}\n","/**\n * Customer-side attestation result verifier for TEE-attested DVM results.\n *\n * Verifies that a Kind 6xxx DVM result was computed in a valid TEE enclave\n * by checking the referenced kind:10033 attestation event. Three checks:\n * 1. Pubkey match: kind:10033 author === Kind 6xxx author (same provider)\n * 2. PCR validity: PCR values pass AttestationVerifier.verify()\n * 3. Time validity: attestation was VALID at result creation time\n *\n * This is a pure logic class with no transport concerns. The caller is\n * responsible for fetching the attestation event from the relay.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { ParsedAttestation } from './attestation.js';\nimport type { ParsedJobResult } from './dvm.js';\nimport type { AttestationVerifier } from '../bootstrap/AttestationVerifier.js';\nimport { AttestationState } from '../bootstrap/AttestationVerifier.js';\n\n// ---------- Types ----------\n\n/** Configuration for constructing an AttestedResultVerifier. */\nexport interface AttestedResultVerificationOptions {\n /** The AttestationVerifier instance for PCR and state checks. */\n attestationVerifier: AttestationVerifier;\n}\n\n/** Result of verifying an attested DVM result. */\nexport interface AttestedResultVerificationResult {\n /** Whether the attestation verification passed all checks. */\n valid: boolean;\n /** Reason for failure (undefined when valid). */\n reason?: string;\n /** Attestation lifecycle state (undefined when verification fails before state check). */\n attestationState?: AttestationState;\n}\n\n// ---------- Class ----------\n\n/**\n * Verifies TEE attestation on Kind 6xxx DVM result events.\n *\n * Follows the same pure-logic pattern as AttestationVerifier (Story 4.3).\n * Time injection is NOT at construction -- `resultEvent.created_at` is\n * used as the `now` parameter at call site.\n */\nexport class AttestedResultVerifier {\n private readonly attestationVerifier: AttestationVerifier;\n\n constructor(options: AttestedResultVerificationOptions) {\n this.attestationVerifier = options.attestationVerifier;\n }\n\n /**\n * Verifies that a Kind 6xxx result was computed in a valid TEE enclave.\n *\n * Performs three checks:\n * (a) Pubkey match: attestationEvent.pubkey === resultEvent.pubkey\n * (b) PCR validity: AttestationVerifier.verify(parsedAttestation.attestation)\n * (c) Time validity: attestation was VALID at resultEvent.created_at\n *\n * @param resultEvent - The Kind 6xxx result Nostr event.\n * @param _parsedResult - The parsed job result (reserved for future use).\n * @param attestationEvent - The kind:10033 attestation Nostr event.\n * @param parsedAttestation - The parsed attestation data.\n * @returns Verification result with valid flag, reason, and attestation state.\n */\n verifyAttestedResult(\n resultEvent: NostrEvent,\n _parsedResult: ParsedJobResult,\n attestationEvent: NostrEvent,\n parsedAttestation: ParsedAttestation\n ): AttestedResultVerificationResult {\n // Check (a): Pubkey match\n if (attestationEvent.pubkey !== resultEvent.pubkey) {\n return { valid: false, reason: 'pubkey mismatch' };\n }\n\n // Check (b): PCR validity\n const pcrResult = this.attestationVerifier.verify(\n parsedAttestation.attestation\n );\n if (!pcrResult.valid) {\n return { valid: false, reason: 'PCR mismatch' };\n }\n\n // Check (c): Time validity -- use resultEvent.created_at as `now`\n const state = this.attestationVerifier.getAttestationState(\n parsedAttestation.attestation,\n attestationEvent.created_at,\n resultEvent.created_at\n );\n if (state !== AttestationState.VALID) {\n return {\n valid: false,\n reason: 'attestation expired at result creation time',\n attestationState: state,\n };\n }\n\n return { valid: true, attestationState: AttestationState.VALID };\n }\n}\n\n// ---------- Utility Functions ----------\n\n/**\n * Checks whether a parsed job request's params include `require_attestation=true`.\n *\n * @param params - The params array from a parsed job request.\n * @returns true if `require_attestation` is set to `'true'`.\n */\nexport function hasRequireAttestation(\n params: { key: string; value: string }[]\n): boolean {\n return params.some(\n (p) => p.key === 'require_attestation' && p.value === 'true'\n );\n}\n","/**\n * Event builders and parsers for kind:5094 Blob Storage DVM requests.\n *\n * Kind 5094 is a NIP-90 DVM job request for permanent blob storage (Arweave).\n * The blob data is base64-encoded in the `i` tag with type `blob`.\n * Payment is carried in the ILP PREPARE packet (prepaid model, D7-001).\n *\n * Tag layout:\n * Required: ['i', base64Blob, 'blob'], ['bid', amount, 'usdc'], ['output', contentType]\n * Optional: ['param', 'uploadId', uuid], ['param', 'chunkIndex', idx],\n * ['param', 'totalChunks', total], ['param', 'contentType', type]\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport {\n BLOB_STORAGE_REQUEST_KIND,\n BLOB_STORAGE_RESULT_KIND,\n} from '../constants.js';\nimport { ToonError } from '../errors.js';\n\n// Re-export constants for convenient co-located imports\nexport { BLOB_STORAGE_REQUEST_KIND, BLOB_STORAGE_RESULT_KIND };\n\n// ---------- Types ----------\n\n/**\n * Parameters for building a kind:5094 Blob Storage DVM request event.\n */\nexport interface BlobStorageRequestParams {\n /** The raw blob data to store. */\n blobData: Buffer;\n /** MIME type of the blob (default: 'application/octet-stream'). */\n contentType?: string;\n /** Bid amount in USDC micro-units as string (bigint-compatible). */\n bid: string;\n /** Optional key-value parameters (e.g., uploadId, chunkIndex, totalChunks). */\n params?: { key: string; value: string }[];\n}\n\n/**\n * Parsed result from a kind:5094 Blob Storage DVM request event.\n */\nexport interface ParsedBlobStorageRequest {\n /** The decoded blob data. */\n blobData: Buffer;\n /** MIME type of the blob. */\n contentType: string;\n /** Upload ID for chunked uploads. */\n uploadId?: string;\n /** Chunk index for chunked uploads. */\n chunkIndex?: number;\n /** Total number of chunks for chunked uploads. */\n totalChunks?: number;\n}\n\n// ---------- Validation ----------\n\n/** Regex for valid base64 strings (standard alphabet, optional padding). */\nconst BASE64_REGEX = /^[A-Za-z0-9+/]*={0,2}$/;\n\n/** Regex for UUID v4 format (used for uploadId validation). */\nconst UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Validates that a string is valid base64 and can be decoded.\n */\nfunction isValidBase64(str: string): boolean {\n if (str.length === 0) return false;\n if (!BASE64_REGEX.test(str)) return false;\n try {\n Buffer.from(str, 'base64');\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------- Builder ----------\n\n/**\n * Builds a kind:5094 Blob Storage DVM request event.\n *\n * Constructs a signed Nostr event with the blob base64-encoded in the `i` tag,\n * a `bid` tag for payment declaration, and an `output` tag for content type.\n *\n * @param params - The blob storage request parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError if required params are missing.\n */\nexport function buildBlobStorageRequest(\n params: BlobStorageRequestParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate blobData\n if (!params.blobData || params.blobData.length === 0) {\n throw new ToonError(\n 'Blob storage request blobData is required and must not be empty',\n 'DVM_MISSING_INPUT'\n );\n }\n\n // Validate bid\n if (typeof params.bid !== 'string' || params.bid === '') {\n throw new ToonError(\n 'Blob storage request bid must be a non-empty string (USDC micro-units)',\n 'DVM_INVALID_BID'\n );\n }\n\n const contentType = params.contentType ?? 'application/octet-stream';\n const base64Blob = params.blobData.toString('base64');\n\n // Build tags\n const tags: string[][] = [];\n\n // Required: ['i', base64Blob, 'blob']\n tags.push(['i', base64Blob, 'blob']);\n\n // Required: ['bid', amount, 'usdc']\n tags.push(['bid', params.bid, 'usdc']);\n\n // Required: ['output', contentType]\n tags.push(['output', contentType]);\n\n // Optional: ['param', key, value] for each param\n if (params.params !== undefined) {\n for (const p of params.params) {\n tags.push(['param', p.key, p.value]);\n }\n }\n\n return finalizeEvent(\n {\n kind: BLOB_STORAGE_REQUEST_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n// ---------- Parser ----------\n\n/**\n * Parses a kind:5094 event into a ParsedBlobStorageRequest.\n *\n * Validates the event kind is 5094, extracts the base64-encoded blob from the\n * `i` tag, the content type from the `output` tag, and optional chunked upload\n * params from `param` tags.\n *\n * Returns `null` for malformed events (wrong kind, missing required tags,\n * invalid base64). Follows the lenient parse pattern.\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed blob storage request, or null if invalid.\n */\nexport function parseBlobStorageRequest(\n event: NostrEvent\n): ParsedBlobStorageRequest | null {\n // Validate kind\n if (event.kind !== BLOB_STORAGE_REQUEST_KIND) {\n return null;\n }\n\n // Extract required 'i' tag: ['i', base64Blob, 'blob']\n const iTag = event.tags.find((t: string[]) => t[0] === 'i');\n if (!iTag) return null;\n const base64Data = iTag[1];\n const inputType = iTag[2];\n if (base64Data === undefined || base64Data === '') return null;\n if (inputType !== 'blob') return null;\n\n // Validate base64\n if (!isValidBase64(base64Data)) {\n return null;\n }\n\n // Extract required 'bid' tag: ['bid', amount, 'usdc']\n const bidTag = event.tags.find((t: string[]) => t[0] === 'bid');\n if (!bidTag) return null;\n const bidAmount = bidTag[1];\n if (bidAmount === undefined || bidAmount === '') return null;\n\n // Extract content type from 'output' tag (default: 'application/octet-stream')\n const outputTag = event.tags.find((t: string[]) => t[0] === 'output');\n const contentType =\n outputTag?.[1] && outputTag[1] !== ''\n ? outputTag[1]\n : 'application/octet-stream';\n\n // Decode blob\n const blobData = Buffer.from(base64Data, 'base64');\n\n // Extract optional chunked upload params\n const paramTags = event.tags.filter((t: string[]) => t[0] === 'param');\n const paramMap = new Map<string, string>();\n for (const pt of paramTags) {\n const key = pt[1];\n const value = pt[2];\n if (key !== undefined && value !== undefined) {\n paramMap.set(key, value);\n }\n }\n\n const result: ParsedBlobStorageRequest = {\n blobData,\n contentType,\n };\n\n const uploadId = paramMap.get('uploadId');\n if (uploadId !== undefined && UUID_REGEX.test(uploadId)) {\n result.uploadId = uploadId;\n }\n\n const chunkIndexStr = paramMap.get('chunkIndex');\n if (chunkIndexStr !== undefined) {\n const chunkIndex = parseInt(chunkIndexStr, 10);\n if (!isNaN(chunkIndex) && chunkIndex >= 0) {\n result.chunkIndex = chunkIndex;\n }\n }\n\n const totalChunksStr = paramMap.get('totalChunks');\n if (totalChunksStr !== undefined) {\n const totalChunks = parseInt(totalChunksStr, 10);\n if (!isNaN(totalChunks) && totalChunks > 0) {\n result.totalChunks = totalChunks;\n }\n }\n\n return result;\n}\n","/**\n * Event builders, parsers, and reputation scoring for DVM reputation system.\n *\n * Defines two new event kinds:\n * - Kind 31117 (Job Review): NIP-33 parameterized replaceable event for\n * post-job reviews with integer 1-5 ratings.\n * - Kind 30382 (Web of Trust): NIP-33 parameterized replaceable event for\n * endorsing provider pubkeys.\n *\n * Also provides:\n * - `ReputationScoreCalculator`: Pure logic class computing composite\n * reputation scores from pre-gathered signals.\n * - `hasMinReputation()`: Utility for extracting `min_reputation` parameter\n * from parsed job request params.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { JOB_REVIEW_KIND, WEB_OF_TRUST_KIND } from '../constants.js';\nimport { ToonError } from '../errors.js';\n\n// Re-export constants for convenient co-located imports\nexport { JOB_REVIEW_KIND, WEB_OF_TRUST_KIND };\n\n// ---------- Validation Helpers ----------\n\n/** Regex for 64-char lowercase hex string. */\nconst HEX_64_REGEX = /^[0-9a-f]{64}$/;\n\n// ---------- Types ----------\n\n/** Parameters for building a Kind 31117 Job Review event. */\nexport interface JobReviewParams {\n /** 64-char hex event ID of the original Kind 5xxx job request. */\n jobRequestEventId: string;\n /** 64-char hex pubkey of the target provider being reviewed. */\n targetPubkey: string;\n /** Integer rating 1-5. */\n rating: number;\n /** Role of the reviewer: 'customer' or 'provider'. */\n role: 'customer' | 'provider';\n /** Optional text review content. */\n content?: string;\n}\n\n/** Parsed result from a Kind 31117 Job Review event. */\nexport interface ParsedJobReview {\n /** Job request event ID from the `d` tag. */\n jobRequestEventId: string;\n /** Target provider pubkey from the `p` tag. */\n targetPubkey: string;\n /** Integer rating 1-5. */\n rating: number;\n /** Role of the reviewer. */\n role: 'customer' | 'provider';\n /** Review text content. */\n content: string;\n}\n\n/** Parameters for building a Kind 30382 Web of Trust declaration event. */\nexport interface WotDeclarationParams {\n /** 64-char hex pubkey of the target provider being endorsed. */\n targetPubkey: string;\n /** Optional endorsement reason. */\n content?: string;\n}\n\n/** Parsed result from a Kind 30382 Web of Trust declaration event. */\nexport interface ParsedWotDeclaration {\n /** Target provider pubkey from the `p` tag. */\n targetPubkey: string;\n /** Declarer pubkey from the event's pubkey field. */\n declarerPubkey: string;\n /** Endorsement content. */\n content: string;\n}\n\n/** Individual reputation signal values. */\nexport interface ReputationSignals {\n /** Count of WoT declarations from non-zero-volume declarers. */\n trustedBy: number;\n /** Total USDC settled through the provider's payment channels. */\n channelVolumeUsdc: number;\n /** Count of Kind 6xxx result events published by the provider. */\n jobsCompleted: number;\n /** Mean rating from verified customer reviews (0 when no reviews). */\n avgRating: number;\n}\n\n/** Composite reputation score with individual signal values. */\nexport interface ReputationScore {\n /** The composite reputation score. */\n score: number;\n /** Individual signal values used to compute the score. */\n signals: ReputationSignals;\n}\n\n// ---------- Builders ----------\n\n/**\n * Builds a Kind 31117 Job Review event (NIP-33 parameterized replaceable).\n *\n * The `d` tag = job request event ID enforces one review per job per reviewer.\n * Tags: `d` (job request ID), `p` (target pubkey), `rating`, `role`.\n *\n * @param params - The job review parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError for invalid inputs.\n */\nexport function buildJobReviewEvent(\n params: JobReviewParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate jobRequestEventId\n if (!HEX_64_REGEX.test(params.jobRequestEventId)) {\n throw new ToonError(\n 'Job review jobRequestEventId must be a 64-character lowercase hex string',\n 'REPUTATION_INVALID_JOB_REQUEST_EVENT_ID'\n );\n }\n\n // Validate targetPubkey\n if (!HEX_64_REGEX.test(params.targetPubkey)) {\n throw new ToonError(\n 'Job review targetPubkey must be a 64-character lowercase hex string',\n 'REPUTATION_INVALID_TARGET_PUBKEY'\n );\n }\n\n // Validate rating (integer 1-5)\n if (\n typeof params.rating !== 'number' ||\n !Number.isInteger(params.rating) ||\n params.rating < 1 ||\n params.rating > 5\n ) {\n throw new ToonError(\n `Job review rating must be an integer 1-5, got ${String(params.rating)}`,\n 'REPUTATION_INVALID_RATING'\n );\n }\n\n // Validate role\n if (params.role !== 'customer' && params.role !== 'provider') {\n throw new ToonError(\n `Job review role must be 'customer' or 'provider', got '${String(params.role)}'`,\n 'REPUTATION_INVALID_ROLE'\n );\n }\n\n const tags: string[][] = [\n ['d', params.jobRequestEventId],\n ['p', params.targetPubkey],\n ['rating', params.rating.toString()],\n ['role', params.role],\n ];\n\n return finalizeEvent(\n {\n kind: JOB_REVIEW_KIND,\n content: params.content ?? '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Parses a Kind 31117 event into a ParsedJobReview.\n *\n * Returns `null` for malformed events (wrong kind, missing tags, invalid rating).\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed job review, or null if invalid.\n */\nexport function parseJobReview(event: NostrEvent): ParsedJobReview | null {\n if (event.kind !== JOB_REVIEW_KIND) {\n return null;\n }\n\n // Extract d tag (job request event ID)\n const dTag = event.tags.find((t: string[]) => t[0] === 'd');\n if (!dTag) return null;\n const jobRequestEventId = dTag[1];\n if (jobRequestEventId === undefined || !HEX_64_REGEX.test(jobRequestEventId))\n return null;\n\n // Extract p tag (target pubkey)\n const pTag = event.tags.find((t: string[]) => t[0] === 'p');\n if (!pTag) return null;\n const targetPubkey = pTag[1];\n if (targetPubkey === undefined || !HEX_64_REGEX.test(targetPubkey))\n return null;\n\n // Extract rating tag\n const ratingTag = event.tags.find((t: string[]) => t[0] === 'rating');\n if (!ratingTag) return null;\n const ratingStr = ratingTag[1];\n if (ratingStr === undefined) return null;\n const rating = Number(ratingStr);\n if (!Number.isInteger(rating) || rating < 1 || rating > 5) return null;\n\n // Extract role tag\n const roleTag = event.tags.find((t: string[]) => t[0] === 'role');\n if (!roleTag) return null;\n const role = roleTag[1];\n if (role !== 'customer' && role !== 'provider') return null;\n\n return {\n jobRequestEventId,\n targetPubkey,\n rating,\n role,\n content: event.content,\n };\n}\n\n// ---------- Web of Trust ----------\n\n/**\n * Builds a Kind 30382 Web of Trust declaration event (NIP-33 parameterized replaceable).\n *\n * The `d` tag = target pubkey enforces one WoT declaration per declarer per target.\n *\n * @param params - The WoT declaration parameters.\n * @param secretKey - The secret key to sign the event with.\n * @returns A signed Nostr event.\n * @throws ToonError for invalid inputs.\n */\nexport function buildWotDeclarationEvent(\n params: WotDeclarationParams,\n secretKey: Uint8Array\n): NostrEvent {\n // Validate targetPubkey\n if (!HEX_64_REGEX.test(params.targetPubkey)) {\n throw new ToonError(\n 'WoT declaration targetPubkey must be a 64-character lowercase hex string',\n 'REPUTATION_INVALID_TARGET_PUBKEY'\n );\n }\n\n const tags: string[][] = [\n ['d', params.targetPubkey],\n ['p', params.targetPubkey],\n ];\n\n return finalizeEvent(\n {\n kind: WEB_OF_TRUST_KIND,\n content: params.content ?? '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n },\n secretKey\n );\n}\n\n/**\n * Parses a Kind 30382 event into a ParsedWotDeclaration.\n *\n * Returns `null` for malformed events (wrong kind, missing tags,\n * d tag not matching p tag).\n *\n * @param event - The Nostr event to parse.\n * @returns The parsed WoT declaration, or null if invalid.\n */\nexport function parseWotDeclaration(\n event: NostrEvent\n): ParsedWotDeclaration | null {\n if (event.kind !== WEB_OF_TRUST_KIND) {\n return null;\n }\n\n // Extract d tag\n const dTag = event.tags.find((t: string[]) => t[0] === 'd');\n if (!dTag) return null;\n const dValue = dTag[1];\n if (dValue === undefined) return null;\n\n // Extract p tag\n const pTag = event.tags.find((t: string[]) => t[0] === 'p');\n if (!pTag) return null;\n const targetPubkey = pTag[1];\n if (targetPubkey === undefined || !HEX_64_REGEX.test(targetPubkey))\n return null;\n\n // d tag must match p tag (NIP-33 consistency)\n if (dValue !== targetPubkey) return null;\n\n return {\n targetPubkey,\n declarerPubkey: event.pubkey,\n content: event.content,\n };\n}\n\n// ---------- Reputation Score Calculator ----------\n\n/**\n * Pure logic class for computing composite reputation scores.\n *\n * Receives pre-computed signals (WoT declarations, reviews, channel volume,\n * job count) and calculates the composite score. Does NOT perform relay\n * queries or on-chain reads. The caller is responsible for gathering signals.\n *\n * Formula: score = (trusted_by x 100) + (log10(max(1, channel_volume_usdc)) x 10)\n * + (jobs_completed x 5) + (avg_rating x 20)\n */\nexport class ReputationScoreCalculator {\n /**\n * Computes the composite reputation score from pre-computed signals.\n *\n * @param signals - The individual signal values.\n * @returns The composite score with individual signals.\n */\n calculateScore(signals: ReputationSignals): ReputationScore {\n const score =\n signals.trustedBy * 100 +\n Math.log10(Math.max(1, signals.channelVolumeUsdc)) * 10 +\n signals.jobsCompleted * 5 +\n signals.avgRating * 20;\n\n // Guard: ensure the composite score is always a finite number (AC #1).\n // NaN or Infinity signals would propagate silently without this check.\n if (!isFinite(score)) {\n return { score: 0, signals };\n }\n\n return { score, signals };\n }\n\n /**\n * Computes the threshold-filtered trusted_by count from WoT declarations.\n *\n * Declarers with non-zero channel volume contribute 1 to the count.\n * Declarers with zero channel volume contribute 0 (sybil defense).\n *\n * @param wotDeclarations - Parsed WoT declarations targeting the provider.\n * @param getChannelVolume - Callback to look up a declarer's channel volume.\n * @returns The count of WoT declarations from non-zero-volume declarers.\n */\n computeTrustedBy(\n wotDeclarations: ParsedWotDeclaration[],\n getChannelVolume: (pubkey: string) => number\n ): number {\n let count = 0;\n for (const declaration of wotDeclarations) {\n const volume = getChannelVolume(declaration.declarerPubkey);\n if (volume > 0) {\n count += 1;\n }\n }\n return count;\n }\n\n /**\n * Computes the average rating from verified customer reviews only.\n *\n * Reviews are provided as tuples of `{ review, reviewerPubkey }` so the\n * calculator can filter by the verified customer set. Reviews from pubkeys\n * NOT in `verifiedCustomerPubkeys` are excluded entirely (customer-gate\n * sybil defense per E6-R013).\n *\n * @param reviews - Parsed job reviews with reviewer pubkeys.\n * @param verifiedCustomerPubkeys - Set of pubkeys that authored Kind 5xxx requests.\n * @returns The mean rating from verified reviews, or 0 when no verified reviews exist.\n */\n computeAvgRating(\n reviews: { review: ParsedJobReview; reviewerPubkey: string }[],\n verifiedCustomerPubkeys: Set<string>\n ): number {\n let sum = 0;\n let count = 0;\n for (const { review, reviewerPubkey } of reviews) {\n // Customer-gate: only count reviews from verified customers\n if (!verifiedCustomerPubkeys.has(reviewerPubkey)) {\n continue;\n }\n if (\n Number.isInteger(review.rating) &&\n review.rating >= 1 &&\n review.rating <= 5\n ) {\n sum += review.rating;\n count += 1;\n }\n }\n\n return count === 0 ? 0 : sum / count;\n }\n}\n\n// ---------- Utility Functions ----------\n\n/**\n * Extracts the `min_reputation` parameter value from parsed job request params.\n *\n * Follows the same pattern as `hasRequireAttestation()`.\n *\n * @param params - The params array from a parsed job request.\n * @returns The numeric threshold value, null if not present, or throws on invalid value.\n * @throws ToonError with code REPUTATION_INVALID_MIN_REPUTATION if value is non-numeric.\n */\nexport function hasMinReputation(\n params: { key: string; value: string }[]\n): number | null {\n const param = params.find((p) => p.key === 'min_reputation');\n if (param === undefined) {\n return null;\n }\n\n const trimmed = param.value.trim();\n const numericValue = Number(trimmed);\n if (trimmed === '' || isNaN(numericValue) || !isFinite(numericValue)) {\n throw new ToonError(\n `min_reputation value must be numeric, got '${param.value}'`,\n 'REPUTATION_INVALID_MIN_REPUTATION'\n );\n }\n\n return numericValue;\n}\n","/**\n * Peer discovery using Nostr NIP-02 follow lists.\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport type { Filter } from 'nostr-tools/filter';\nimport { PeerDiscoveryError } from '../errors.js';\nimport { parseIlpPeerInfo } from '../events/index.js';\nimport { ILP_PEER_INFO_KIND } from '../constants.js';\nimport type { IlpPeerInfo, Subscription } from '../types.js';\n\n/** Regular expression for validating 64-character lowercase hex pubkeys */\nconst PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n\n/**\n * Discovers ILP peers by querying Nostr relays for NIP-02 follow lists.\n */\nexport class NostrPeerDiscovery {\n private readonly relayUrls: string[];\n private readonly pool: SimplePool;\n\n /**\n * Creates a new NostrPeerDiscovery instance.\n *\n * @param relayUrls - Array of relay WebSocket URLs to query\n * @param pool - Optional SimplePool instance (creates new one if not provided)\n */\n constructor(relayUrls: string[], pool?: SimplePool) {\n this.relayUrls = relayUrls;\n this.pool = pool ?? new SimplePool();\n }\n\n /**\n * Retrieves the list of pubkeys that a given pubkey follows.\n *\n * Queries NIP-02 kind:3 events from configured relays and returns\n * the followed pubkeys from the most recent event.\n *\n * @param pubkey - The 64-character hex pubkey to get follows for\n * @returns Array of followed pubkeys (deduplicated)\n * @throws PeerDiscoveryError if pubkey format is invalid\n */\n async getFollows(pubkey: string): Promise<string[]> {\n if (!PUBKEY_REGEX.test(pubkey)) {\n throw new PeerDiscoveryError(\n `Invalid pubkey format: must be 64-character lowercase hex string`\n );\n }\n\n const filter: Filter = {\n kinds: [3],\n authors: [pubkey],\n limit: 1,\n };\n\n try {\n const events = await this.pool.querySync(this.relayUrls, filter);\n\n if (events.length === 0) {\n return [];\n }\n\n // Sort by created_at descending and use the most recent\n const sortedEvents = events.sort((a, b) => b.created_at - a.created_at);\n const mostRecent = sortedEvents[0];\n\n // This should never happen since we check length above, but TypeScript needs it\n if (!mostRecent) {\n return [];\n }\n\n // Extract pubkeys from 'p' tags and deduplicate\n const pubkeys = mostRecent.tags\n .filter(\n (tag): tag is [string, string, ...string[]] =>\n tag[0] === 'p' && typeof tag[1] === 'string'\n )\n .map((tag) => tag[1]);\n\n return [...new Set(pubkeys)];\n } catch (error) {\n // querySync handles individual relay failures internally\n // If we get here, something else went wrong\n throw new PeerDiscoveryError(\n 'Failed to query relays for follow list',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Discovers ILP peers by querying follow list and their ILP peer info events.\n *\n * For each followed pubkey, queries kind:10032 events to retrieve ILP connection info.\n * Peers without kind:10032 events or with malformed events are silently excluded.\n *\n * @param pubkey - The 64-character hex pubkey to discover peers for\n * @returns Map of pubkey → IlpPeerInfo for peers with valid ILP info\n * @throws PeerDiscoveryError if pubkey format is invalid\n */\n async discoverPeers(pubkey: string): Promise<Map<string, IlpPeerInfo>> {\n if (!PUBKEY_REGEX.test(pubkey)) {\n throw new PeerDiscoveryError(\n `Invalid pubkey format: must be 64-character lowercase hex string`\n );\n }\n\n // Get list of followed pubkeys\n const follows = await this.getFollows(pubkey);\n\n // Return empty map if no follows\n if (follows.length === 0) {\n return new Map();\n }\n\n // Query kind:10032 events for all followed pubkeys in a single request\n const filter: Filter = {\n kinds: [ILP_PEER_INFO_KIND],\n authors: follows,\n };\n\n const events = await this.pool.querySync(this.relayUrls, filter);\n\n // Group events by pubkey, keeping only the most recent for each (replaceable event semantics)\n const eventsByPubkey = new Map<string, (typeof events)[0]>();\n for (const event of events) {\n const existing = eventsByPubkey.get(event.pubkey);\n if (!existing || event.created_at > existing.created_at) {\n eventsByPubkey.set(event.pubkey, event);\n }\n }\n\n // Parse events and build result map, silently skipping parse failures\n const result = new Map<string, IlpPeerInfo>();\n for (const [peerPubkey, event] of eventsByPubkey) {\n try {\n const info = parseIlpPeerInfo(event);\n result.set(peerPubkey, info);\n } catch {\n // Silently skip events that fail to parse (AC: 4)\n }\n }\n\n return result;\n }\n\n /**\n * Subscribes to ILP peer info updates from followed pubkeys.\n *\n * Sets up a real-time subscription for kind:10032 events from all pubkeys\n * that the given pubkey follows. The callback is invoked whenever a new\n * or updated ILP peer info event is received.\n *\n * @param pubkey - The 64-character hex pubkey whose follows to monitor\n * @param callback - Function called with (peerPubkey, parsedInfo) for each update\n * @returns Subscription object with unsubscribe() method\n * @throws PeerDiscoveryError if pubkey format is invalid\n */\n async subscribeToPeerUpdates(\n pubkey: string,\n callback: (pubkey: string, info: IlpPeerInfo) => void\n ): Promise<Subscription> {\n if (!PUBKEY_REGEX.test(pubkey)) {\n throw new PeerDiscoveryError(\n `Invalid pubkey format: must be 64-character lowercase hex string`\n );\n }\n\n // Get list of followed pubkeys at subscription time\n const follows = await this.getFollows(pubkey);\n\n // Return no-op subscription for empty follow list\n if (follows.length === 0) {\n return {\n unsubscribe: () => {\n // No-op for empty follow list\n },\n };\n }\n\n // Per-subscription tracking of latest event timestamps (replaceable event semantics)\n const peerTimestamps = new Map<string, number>();\n\n // Track subscription state to prevent double-unsubscribe\n let isUnsubscribed = false;\n\n // Set up subscription for kind:10032 events from followed pubkeys\n const filter = {\n kinds: [ILP_PEER_INFO_KIND],\n authors: follows,\n };\n\n const subCloser = this.pool.subscribeMany(this.relayUrls, filter, {\n onevent: (event) => {\n // Skip if already unsubscribed\n if (isUnsubscribed) return;\n\n // Check replaceable event timestamp\n const lastSeen = peerTimestamps.get(event.pubkey) ?? 0;\n if (event.created_at <= lastSeen) {\n // Stale event, skip\n return;\n }\n peerTimestamps.set(event.pubkey, event.created_at);\n\n // Try to parse and invoke callback\n try {\n const info = parseIlpPeerInfo(event);\n callback(event.pubkey, info);\n } catch {\n // Silently skip malformed events\n }\n },\n });\n\n return {\n unsubscribe: () => {\n if (!isUnsubscribed) {\n isUnsubscribed = true;\n subCloser.close();\n }\n },\n };\n }\n}\n","[]\n","/**\n * Genesis peer loader for bootstrapping new nodes into the network.\n *\n * Loads well-known genesis peers from a bundled JSON file and supports\n * merging with runtime-provided additional peers.\n */\n\nimport genesisPeersJson from './genesis-peers.json';\n\n/** A genesis peer entry used for network bootstrapping. */\nexport interface GenesisPeer {\n pubkey: string;\n relayUrl: string;\n ilpAddress: string;\n btpEndpoint: string;\n}\n\nconst PUBKEY_REGEX = /^[0-9a-f]{64}$/;\nconst ILP_ADDRESS_REGEX = /^g\\.[a-zA-Z0-9.-]+$/;\n\nexport function isValidPubkey(pubkey: string): boolean {\n return PUBKEY_REGEX.test(pubkey);\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n return url.startsWith('wss://') || url.startsWith('ws://');\n}\n\nexport function isValidIlpAddress(address: string): boolean {\n return ILP_ADDRESS_REGEX.test(address);\n}\n\nexport function isValidBtpEndpoint(url: string): boolean {\n return url.startsWith('wss://') || url.startsWith('ws://');\n}\n\nfunction isValidGenesisPeer(entry: unknown): entry is GenesisPeer {\n if (typeof entry !== 'object' || entry === null) return false;\n const obj = entry as Record<string, unknown>;\n return (\n typeof obj['pubkey'] === 'string' &&\n typeof obj['relayUrl'] === 'string' &&\n typeof obj['ilpAddress'] === 'string' &&\n typeof obj['btpEndpoint'] === 'string' &&\n isValidPubkey(obj['pubkey']) &&\n isValidRelayUrl(obj['relayUrl']) &&\n isValidIlpAddress(obj['ilpAddress']) &&\n isValidBtpEndpoint(obj['btpEndpoint'])\n );\n}\n\nfunction deduplicateByPubkey(peers: GenesisPeer[]): GenesisPeer[] {\n const map = new Map<string, GenesisPeer>();\n for (const peer of peers) {\n map.set(peer.pubkey, peer);\n }\n return [...map.values()];\n}\n\n/** Load and validate genesis peers from the bundled JSON file. */\nfunction loadGenesisPeers(): GenesisPeer[] {\n const raw: unknown[] = genesisPeersJson;\n const valid: GenesisPeer[] = [];\n for (const entry of raw) {\n if (isValidGenesisPeer(entry)) {\n valid.push(entry);\n } else {\n console.warn('Skipping invalid genesis peer entry:', entry);\n }\n }\n return deduplicateByPubkey(valid);\n}\n\n/** Parse and validate additional peers from a JSON string. */\nfunction loadAdditionalPeers(json: string): GenesisPeer[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n console.warn('Failed to parse additional peers JSON:', json);\n return [];\n }\n if (!Array.isArray(parsed)) {\n console.warn('Additional peers JSON is not an array');\n return [];\n }\n const valid: GenesisPeer[] = [];\n for (const entry of parsed as unknown[]) {\n if (isValidGenesisPeer(entry)) {\n valid.push(entry);\n } else {\n console.warn('Skipping invalid additional peer entry:', entry);\n }\n }\n return valid;\n}\n\n/** Load genesis peers and optionally merge with additional peers. */\nfunction loadAllPeers(additionalPeersJson?: string): GenesisPeer[] {\n const genesis = loadGenesisPeers();\n if (!additionalPeersJson) {\n return genesis;\n }\n const additional = loadAdditionalPeers(additionalPeersJson);\n return deduplicateByPubkey([...genesis, ...additional]);\n}\n\nexport const GenesisPeerLoader = {\n loadGenesisPeers,\n loadAdditionalPeers,\n loadAllPeers,\n} as const;\n","/**\n * ArDrive-based peer registry for permanent, decentralized storage\n * of ILP peer info on Arweave.\n *\n * Read path: queries Arweave GraphQL gateway (free, no wallet needed).\n * Write path: uploads via @ardrive/turbo-sdk (caller provides authenticated client).\n */\n\nimport type { TurboAuthenticatedClient } from '@ardrive/turbo-sdk';\nimport type { IlpPeerInfo } from '../types.js';\nimport { PeerDiscoveryError } from '../errors.js';\nimport {\n isValidPubkey,\n isValidIlpAddress,\n isValidBtpEndpoint,\n} from './GenesisPeerLoader.js';\n\nconst DEFAULT_GATEWAY_URL = 'https://arweave.net/graphql';\n\nconst GRAPHQL_QUERY = `\nquery {\n transactions(\n tags: [\n { name: \"App-Name\", values: [\"toon\"] },\n { name: \"type\", values: [\"ilp-peer-info\"] }\n ],\n first: 100\n ) {\n edges {\n node {\n id\n tags {\n name\n value\n }\n }\n }\n }\n}\n`;\n\ninterface GraphQlTag {\n name: string;\n value: string;\n}\n\ninterface GraphQlNode {\n id: string;\n tags: GraphQlTag[];\n}\n\ninterface GraphQlEdge {\n node: GraphQlNode;\n}\n\ninterface GraphQlResponse {\n data?: {\n transactions?: {\n edges?: GraphQlEdge[];\n };\n };\n}\n\nfunction isValidIlpPeerInfo(entry: unknown): entry is IlpPeerInfo {\n if (typeof entry !== 'object' || entry === null) return false;\n const obj = entry as Record<string, unknown>;\n return (\n typeof obj['ilpAddress'] === 'string' &&\n isValidIlpAddress(obj['ilpAddress']) &&\n typeof obj['btpEndpoint'] === 'string' &&\n isValidBtpEndpoint(obj['btpEndpoint']) &&\n typeof obj['assetCode'] === 'string' &&\n (obj['assetCode'] as string).length > 0 &&\n typeof obj['assetScale'] === 'number' &&\n Number.isInteger(obj['assetScale']) &&\n (obj['assetScale'] as number) >= 0 &&\n (obj['settlementEngine'] === undefined ||\n typeof obj['settlementEngine'] === 'string')\n );\n}\n\nfunction getPubkeyFromTags(tags: GraphQlTag[]): string | undefined {\n const tag = tags.find((t) => t.name === 'pubkey');\n return tag?.value;\n}\n\nasync function fetchPeers(\n gatewayUrl: string = DEFAULT_GATEWAY_URL\n): Promise<Map<string, IlpPeerInfo>> {\n const result = new Map<string, IlpPeerInfo>();\n\n try {\n const response = await fetch(gatewayUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query: GRAPHQL_QUERY }),\n });\n\n const json = (await response.json()) as GraphQlResponse;\n const edges = json?.data?.transactions?.edges;\n\n if (!Array.isArray(edges)) {\n return result;\n }\n\n const baseUrl = gatewayUrl.replace(/\\/graphql$/, '');\n\n for (const edge of edges) {\n const txId = edge?.node?.id;\n const tags = edge?.node?.tags;\n\n if (!txId || !Array.isArray(tags)) continue;\n\n const pubkey = getPubkeyFromTags(tags);\n if (!pubkey || !isValidPubkey(pubkey)) continue;\n\n // Deduplication: first occurrence wins (newest-first from Arweave)\n if (result.has(pubkey)) continue;\n\n try {\n const dataResponse = await fetch(`${baseUrl}/${txId}`);\n const data = (await dataResponse.json()) as unknown;\n\n if (isValidIlpPeerInfo(data)) {\n result.set(pubkey, data);\n } else {\n console.warn(\n `Skipping transaction ${txId}: invalid IlpPeerInfo data`\n );\n }\n } catch (err) {\n console.warn(`Failed to fetch transaction data for ${txId}:`, err);\n }\n }\n } catch (err) {\n console.warn('ArDrive peer registry unavailable:', err);\n }\n\n return result;\n}\n\nasync function publishPeerInfo(\n peerInfo: IlpPeerInfo,\n pubkey: string,\n turboClient: TurboAuthenticatedClient\n): Promise<string> {\n if (!isValidPubkey(pubkey)) {\n throw new PeerDiscoveryError(`Invalid pubkey: ${pubkey}`);\n }\n\n try {\n const json = JSON.stringify(peerInfo);\n const result = await turboClient.uploadFile({\n fileStreamFactory: () => Buffer.from(json, 'utf-8'),\n fileSizeFactory: () => Buffer.byteLength(json, 'utf-8'),\n dataItemOpts: {\n tags: [\n { name: 'App-Name', value: 'toon' },\n { name: 'type', value: 'ilp-peer-info' },\n { name: 'pubkey', value: pubkey },\n { name: 'version', value: '1' },\n { name: 'Content-Type', value: 'application/json' },\n ],\n },\n });\n\n return result.id;\n } catch (err) {\n throw new PeerDiscoveryError(\n 'Failed to publish peer info to ArDrive',\n err instanceof Error ? err : undefined\n );\n }\n}\n\nexport const ArDrivePeerRegistry = {\n fetchPeers,\n publishPeerInfo,\n} as const;\n","/**\n * Social graph-based peer discovery (passive).\n *\n * Subscribes to NIP-02 follow list changes and emits events\n * for new follows and unfollows. Does NOT auto-peer — the caller\n * decides when and whether to initiate peering.\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { getPublicKey } from 'nostr-tools/pure';\nimport { PeerDiscoveryError } from '../errors.js';\nimport type { Subscription } from '../types.js';\n\n/**\n * Events emitted by SocialPeerDiscovery.\n */\nexport type SocialDiscoveryEvent =\n | { type: 'social:follow-discovered'; pubkey: string }\n | { type: 'social:follow-removed'; pubkey: string };\n\n/**\n * Listener callback for social discovery events.\n */\nexport type SocialDiscoveryEventListener = (\n event: SocialDiscoveryEvent\n) => void;\n\n/**\n * Configuration for SocialPeerDiscovery.\n */\nexport interface SocialPeerDiscoveryConfig {\n /** Relays to subscribe to for kind:3 events */\n relayUrls: string[];\n}\n\n/**\n * Passive social graph peer discovery.\n *\n * Subscribes to NIP-02 kind:3 follow list events and emits:\n * - `social:follow-discovered` when a new pubkey appears in the follow list\n * - `social:follow-removed` when a pubkey disappears from the follow list\n *\n * The caller decides when to peer via `on()` listener.\n */\nexport class SocialPeerDiscovery {\n private readonly config: SocialPeerDiscoveryConfig;\n private readonly pubkey: string;\n private readonly pool: SimplePool;\n private readonly listeners: SocialDiscoveryEventListener[] = [];\n private previousFollows = new Set<string>();\n private started = false;\n\n /**\n * Creates a new SocialPeerDiscovery instance.\n *\n * @param config - Discovery configuration\n * @param secretKey - Our Nostr secret key (used to derive pubkey)\n * @param pool - Optional SimplePool instance (creates new one if not provided)\n */\n constructor(\n config: SocialPeerDiscoveryConfig,\n secretKey: Uint8Array,\n pool?: SimplePool\n ) {\n this.config = config;\n this.pubkey = getPublicKey(secretKey);\n this.pool = pool ?? new SimplePool();\n }\n\n /**\n * Register a listener for social discovery events.\n */\n on(listener: SocialDiscoveryEventListener): void {\n this.listeners.push(listener);\n }\n\n /**\n * Remove a previously registered listener.\n */\n off(listener: SocialDiscoveryEventListener): void {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) {\n this.listeners.splice(idx, 1);\n }\n }\n\n /**\n * Start subscribing to kind:3 follow list events for the node's pubkey.\n *\n * @returns Subscription with unsubscribe() to stop discovery\n * @throws PeerDiscoveryError if already started\n */\n start(): Subscription {\n if (this.started) {\n throw new PeerDiscoveryError('SocialPeerDiscovery already started');\n }\n this.started = true;\n\n const subCloser = this.pool.subscribeMany(\n this.config.relayUrls,\n { kinds: [3], authors: [this.pubkey] },\n {\n onevent: (event) => {\n const followedPubkeys = event.tags\n .filter(\n (tag): tag is [string, string, ...string[]] =>\n tag[0] === 'p' && typeof tag[1] === 'string'\n )\n .map((tag) => tag[1]);\n\n this.processFollowListUpdate(followedPubkeys);\n },\n }\n );\n\n return {\n unsubscribe: () => {\n subCloser.close();\n this.started = false;\n },\n };\n }\n\n /**\n * Process a follow list update by diffing against previous state.\n */\n private processFollowListUpdate(followedPubkeys: string[]): void {\n const currentFollows = new Set(followedPubkeys);\n\n // Emit events for new follows\n for (const pubkey of currentFollows) {\n if (!this.previousFollows.has(pubkey)) {\n this.emit({ type: 'social:follow-discovered', pubkey });\n }\n }\n\n // Emit events for unfollows\n for (const pubkey of this.previousFollows) {\n if (!currentFollows.has(pubkey)) {\n this.emit({ type: 'social:follow-removed', pubkey });\n }\n }\n\n // Update previous follows to current\n this.previousFollows = currentFollows;\n }\n\n /**\n * Emit an event to all registered listeners.\n */\n private emit(event: SocialDiscoveryEvent): void {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch (error) {\n console.warn(\n '[SocialDiscovery] Listener error:',\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n }\n }\n}\n","/**\n * Seed Relay Discovery -- discovers peers via kind:10036 seed relay list events.\n *\n * Uses raw `ws` WebSocket connections to avoid the `ReferenceError: window is\n * not defined` issue that occurs with nostr-tools pool utilities in Node.js\n * containers.\n *\n * Discovery flow:\n * 1. Connect to public Nostr relays and query for kind:10036 events.\n * 2. Parse seed relay entries from kind:10036 event content.\n * 3. Connect to seed relays sequentially (fallback on failure).\n * 4. Subscribe to kind:10032 on the first connected seed relay.\n * 5. Return discovered peers as IlpPeerInfo[].\n */\n\nimport WebSocket from 'ws';\nimport { getPublicKey, verifyEvent } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { PeerDiscoveryError } from '../errors.js';\nimport { SEED_RELAY_LIST_KIND, ILP_PEER_INFO_KIND } from '../constants.js';\nimport {\n parseSeedRelayList,\n buildSeedRelayListEvent,\n} from '../events/seed-relay.js';\nimport { parseIlpPeerInfo } from '../events/parsers.js';\nimport type { IlpPeerInfo } from '../types.js';\nimport type { SeedRelayEntry } from '../events/seed-relay.js';\n\n// Re-export types for consumer convenience\nexport type { SeedRelayEntry } from '../events/seed-relay.js';\n\n// ---------- Types ----------\n\n/** Configuration for seed relay discovery. */\nexport interface SeedRelayDiscoveryConfig {\n /** Public Nostr relay URLs to query for kind:10036 events. */\n publicRelays: string[];\n /** Timeout for relay connections in ms (default: 10000). */\n connectionTimeout?: number;\n /** Timeout for kind:10036 queries in ms (default: 5000). */\n queryTimeout?: number;\n}\n\n/** Result of seed relay discovery. */\nexport interface SeedRelayDiscoveryResult {\n /** Number of seed relays successfully connected to. */\n seedRelaysConnected: number;\n /** Total seed relays attempted. */\n attemptedSeeds: number;\n /** WebSocket URLs of connected seed relays. */\n connectedUrls: string[];\n /** Peers discovered via kind:10032 from seed relays. */\n discoveredPeers: IlpPeerInfo[];\n}\n\n/** Configuration for publishing a seed relay entry. */\nexport interface PublishSeedRelayConfig {\n /** Secret key for signing the event. */\n secretKey: Uint8Array;\n /** This node's WebSocket relay URL (e.g., wss://my-relay.example.com). */\n relayUrl: string;\n /** Public Nostr relay URLs to publish to. */\n publicRelays: string[];\n /** Optional metadata. */\n metadata?: SeedRelayEntry['metadata'];\n}\n\n// ---------- Constants ----------\n\n/** Default connection timeout for publish operations (ms). */\nconst DEFAULT_PUBLISH_CONNECTION_TIMEOUT = 10000;\n\n/** Default timeout for waiting for OK acknowledgment on publish (ms). */\nconst DEFAULT_PUBLISH_OK_TIMEOUT = 5000;\n\n// ---------- Helpers ----------\n\n/**\n * Generate a random subscription ID for Nostr protocol REQ messages.\n */\nfunction generateSubId(): string {\n return `ct-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/**\n * Connect to a WebSocket URL with a timeout.\n * Returns the connected WebSocket or throws on failure.\n */\nfunction connectWebSocket(url: string, timeout: number): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url);\n\n const timer = setTimeout(() => {\n ws.close();\n reject(new Error(`Connection timeout after ${timeout}ms: ${url}`));\n }, timeout);\n\n ws.on('open', () => {\n clearTimeout(timer);\n resolve(ws);\n });\n\n ws.on('error', (err: Error) => {\n clearTimeout(timer);\n ws.close();\n reject(new Error(`WebSocket error connecting to ${url}: ${err.message}`));\n });\n });\n}\n\n/**\n * Subscribe to a Nostr relay and collect events matching a filter.\n * Returns collected events when EOSE is received or timeout expires.\n */\nfunction subscribeAndCollect(\n ws: WebSocket,\n filter: Record<string, unknown>,\n timeout: number\n): Promise<NostrEvent[]> {\n return new Promise((resolve) => {\n const events: NostrEvent[] = [];\n const subId = generateSubId();\n let settled = false;\n\n const cleanup = () => {\n if (settled) return;\n settled = true;\n ws.removeListener('message', messageHandler);\n };\n\n const messageHandler = (data: WebSocket.Data) => {\n try {\n const msg = JSON.parse(String(data)) as unknown[];\n if (!Array.isArray(msg)) return;\n\n const msgType = msg[0];\n\n if (msgType === 'EVENT' && msg[1] === subId && msg[2]) {\n events.push(msg[2] as NostrEvent);\n }\n\n if (msgType === 'EOSE' && msg[1] === subId) {\n clearTimeout(timer);\n cleanup();\n // Send CLOSE to clean up subscription on the relay\n try {\n ws.send(JSON.stringify(['CLOSE', subId]));\n } catch {\n // Ignore send errors during cleanup\n }\n resolve(events);\n }\n } catch {\n // Ignore malformed messages\n }\n };\n\n const timer = setTimeout(() => {\n cleanup();\n // Send CLOSE to clean up subscription\n try {\n ws.send(JSON.stringify(['CLOSE', subId]));\n } catch {\n // Ignore send errors during cleanup\n }\n resolve(events);\n }, timeout);\n\n ws.on('message', messageHandler);\n\n // Send the REQ\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n}\n\n// ---------- SeedRelayDiscovery ----------\n\n/**\n * Discovers peers via the seed relay list model.\n *\n * Queries public Nostr relays for kind:10036 events, parses seed relay\n * entries, connects to seed relays, and subscribes to kind:10032 events\n * to discover network peers.\n */\nexport class SeedRelayDiscovery {\n private readonly config: SeedRelayDiscoveryConfig;\n private readonly connectionTimeout: number;\n private readonly queryTimeout: number;\n private readonly openSockets: WebSocket[] = [];\n\n constructor(config: SeedRelayDiscoveryConfig) {\n this.config = config;\n this.connectionTimeout = config.connectionTimeout ?? 10000;\n this.queryTimeout = config.queryTimeout ?? 5000;\n }\n\n /**\n * Discover peers via seed relay list.\n *\n * 1. Query publicRelays for kind:10036 events\n * 2. Parse seed relay entries\n * 3. Connect to seed relays sequentially (fallback on failure)\n * 4. Subscribe to kind:10032 on connected seed relay\n * 5. Return discovered peers\n *\n * @throws PeerDiscoveryError if all seed relays are exhausted\n */\n async discover(): Promise<SeedRelayDiscoveryResult> {\n // Step 1: Query public relays for kind:10036 events\n const seedRelayEntries = await this.querySeedRelayLists();\n\n if (seedRelayEntries.length === 0) {\n throw new PeerDiscoveryError(\n 'All seed relays exhausted -- unable to bootstrap. ' +\n `Tried 0 seed relays from 0 kind:10036 events. ` +\n `Queried ${this.config.publicRelays.length} public relay(s).`\n );\n }\n\n // Step 2: Deduplicate seed entries by URL\n const uniqueEntries = this.deduplicateByUrl(seedRelayEntries);\n\n // Step 3: Try connecting to seed relays sequentially\n let connectedSocket: WebSocket | undefined;\n let connectedUrl = '';\n let attemptedSeeds = 0;\n\n for (const entry of uniqueEntries) {\n attemptedSeeds++;\n try {\n const ws = await connectWebSocket(entry.url, this.connectionTimeout);\n connectedSocket = ws;\n connectedUrl = entry.url;\n this.openSockets.push(ws);\n break;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n console.warn(\n `[SeedRelayDiscovery] Failed to connect to seed relay ${entry.url}: ${msg}`\n );\n }\n }\n\n if (!connectedSocket) {\n throw new PeerDiscoveryError(\n 'All seed relays exhausted -- unable to bootstrap. ' +\n `Tried ${attemptedSeeds} seed relays from ${seedRelayEntries.length} kind:10036 events.`\n );\n }\n\n // Step 4: Subscribe to kind:10032 on the connected seed relay\n const peerEvents = await subscribeAndCollect(\n connectedSocket,\n { kinds: [ILP_PEER_INFO_KIND] },\n this.queryTimeout\n );\n\n // Step 5: Parse kind:10032 events into IlpPeerInfo[]\n const discoveredPeers: IlpPeerInfo[] = [];\n for (const event of peerEvents) {\n try {\n // Verify event signature before trusting content (CWE-345)\n if (!verifyEvent(event)) {\n console.warn(\n `[SeedRelayDiscovery] Skipping kind:10032 event with invalid signature: ${event.id}`\n );\n continue;\n }\n const info = parseIlpPeerInfo(event);\n // Set pubkey from the event's outer pubkey field\n // (parseIlpPeerInfo does NOT populate pubkey from event content)\n info.pubkey = event.pubkey;\n discoveredPeers.push(info);\n } catch {\n // Skip malformed kind:10032 events\n }\n }\n\n return {\n seedRelaysConnected: 1,\n attemptedSeeds,\n connectedUrls: [connectedUrl],\n discoveredPeers,\n };\n }\n\n /**\n * Stop discovery and close all open WebSocket connections.\n */\n async close(): Promise<void> {\n for (const ws of this.openSockets) {\n try {\n if (\n ws.readyState === WebSocket.OPEN ||\n ws.readyState === WebSocket.CONNECTING\n ) {\n ws.close();\n }\n } catch {\n // Ignore close errors\n }\n }\n this.openSockets.length = 0;\n }\n\n /**\n * Query public relays for kind:10036 events and parse seed relay entries.\n */\n private async querySeedRelayLists(): Promise<SeedRelayEntry[]> {\n const allEntries: SeedRelayEntry[] = [];\n\n for (const relayUrl of this.config.publicRelays) {\n try {\n const ws = await connectWebSocket(relayUrl, this.connectionTimeout);\n this.openSockets.push(ws);\n\n const events = await subscribeAndCollect(\n ws,\n { kinds: [SEED_RELAY_LIST_KIND] },\n this.queryTimeout\n );\n\n for (const event of events) {\n // Verify event signature before trusting content (CWE-345:\n // Insufficient Verification of Data Authenticity)\n if (!verifyEvent(event)) {\n console.warn(\n `[SeedRelayDiscovery] Skipping kind:10036 event with invalid signature: ${event.id}`\n );\n continue;\n }\n const entries = parseSeedRelayList(event);\n allEntries.push(...entries);\n }\n\n // Close connection to public relay after querying and remove\n // from tracked sockets (already closed, no need for close() cleanup)\n ws.close();\n const idx = this.openSockets.indexOf(ws);\n if (idx !== -1) {\n this.openSockets.splice(idx, 1);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n console.warn(\n `[SeedRelayDiscovery] Failed to query public relay ${relayUrl}: ${msg}`\n );\n }\n }\n\n return allEntries;\n }\n\n /**\n * Deduplicate seed relay entries by URL, keeping the first occurrence.\n */\n private deduplicateByUrl(entries: SeedRelayEntry[]): SeedRelayEntry[] {\n const seen = new Set<string>();\n const unique: SeedRelayEntry[] = [];\n\n for (const entry of entries) {\n if (!seen.has(entry.url)) {\n seen.add(entry.url);\n unique.push(entry);\n }\n }\n\n return unique;\n }\n}\n\n// ---------- publishSeedRelayEntry ----------\n\n/**\n * Publish a kind:10036 event advertising this node as a seed relay.\n * Connects to each publicRelay and publishes the event.\n * Returns the number of relays the event was published to.\n *\n * @param config - Configuration for publishing the seed relay entry.\n * @returns The count of successful publishes and the event ID.\n */\nexport async function publishSeedRelayEntry(\n config: PublishSeedRelayConfig\n): Promise<{ publishedTo: number; eventId: string }> {\n const pubkey = getPublicKey(config.secretKey);\n\n const entry: SeedRelayEntry = {\n url: config.relayUrl,\n pubkey,\n ...(config.metadata && { metadata: config.metadata }),\n };\n\n const event = buildSeedRelayListEvent(config.secretKey, [entry]);\n let publishedTo = 0;\n\n for (const relayUrl of config.publicRelays) {\n try {\n const ws = await connectWebSocket(\n relayUrl,\n DEFAULT_PUBLISH_CONNECTION_TIMEOUT\n );\n\n // Publish the event\n const published = await new Promise<boolean>((resolve) => {\n let settled = false;\n\n const cleanup = () => {\n if (settled) return;\n settled = true;\n ws.removeListener('message', messageHandler);\n };\n\n const timer = setTimeout(() => {\n cleanup();\n resolve(false);\n }, DEFAULT_PUBLISH_OK_TIMEOUT);\n\n const messageHandler = (data: WebSocket.Data) => {\n try {\n const msg = JSON.parse(String(data)) as unknown[];\n if (Array.isArray(msg) && msg[0] === 'OK' && msg[1] === event.id) {\n clearTimeout(timer);\n cleanup();\n resolve(msg[2] === true);\n }\n } catch {\n // Ignore parse errors\n }\n };\n\n ws.on('message', messageHandler);\n ws.send(JSON.stringify(['EVENT', event]));\n });\n\n if (published) {\n publishedTo++;\n }\n\n ws.close();\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n console.warn(\n `[SeedRelayDiscovery] Failed to publish to ${relayUrl}: ${msg}`\n );\n }\n }\n\n return { publishedTo, eventId: event.id };\n}\n","/**\n * Pure function for calculating ILP PREPARE amount including intermediary routing fees.\n *\n * Formula: totalAmount = basePricePerByte * packetBytes + SUM(hopFees[i] * packetBytes)\n *\n * All arithmetic uses bigint -- no floating point, no overflow risk.\n */\n\n/**\n * Parameters for route-aware fee calculation.\n */\nexport interface CalculateRouteAmountParams {\n /** Base price per byte charged by the destination node. */\n basePricePerByte: bigint;\n /** Length of the TOON-encoded packet in bytes. */\n packetByteLength: number;\n /** Per-byte fees for each intermediary hop on the route (ordered sender-to-destination). */\n hopFees: bigint[];\n}\n\n/**\n * Calculates the total ILP PREPARE amount including intermediary routing fees.\n *\n * For a direct route (empty hopFees), returns basePricePerByte * packetByteLength.\n * For multi-hop routes, adds each intermediary's feePerByte * packetByteLength.\n *\n * @returns Total amount as bigint.\n */\nexport function calculateRouteAmount(\n params: CalculateRouteAmountParams\n): bigint {\n const { basePricePerByte, packetByteLength, hopFees } = params;\n\n // Guard against negative byte length (public API defense)\n if (packetByteLength < 0) {\n return 0n;\n }\n\n const bytes = BigInt(packetByteLength);\n\n const baseAmount = basePricePerByte * bytes;\n const intermediaryFees = hopFees.reduce((sum, fee) => sum + fee * bytes, 0n);\n\n return baseAmount + intermediaryFees;\n}\n","/**\n * Resolves intermediary routing fees from discovered peers using LCA-based\n * route resolution on the ILP address tree.\n *\n * Algorithm:\n * 1. Split sender and destination ILP addresses into segments.\n * 2. Find the longest common ancestor (LCA) -- shared prefix of segments.\n * 3. Intermediaries are the segments on the path from LCA down to destination's parent.\n * 4. For each intermediary, look up feePerByte from discovered peers.\n * 5. Unknown intermediaries default to feePerByte 0n with a warning.\n */\n\nimport type { DiscoveredPeer } from '../bootstrap/types.js';\n\n/**\n * Parameters for resolving route fees.\n */\nexport interface ResolveRouteFeesParams {\n /** ILP address of the destination node. */\n destination: string;\n /** ILP address of the sender (own node). */\n ownIlpAddress: string;\n /** All discovered peers (including peered ones) with their ILP peer info. */\n discoveredPeers: DiscoveredPeer[];\n}\n\n/**\n * Result of route fee resolution.\n */\nexport interface ResolveRouteFeesResult {\n /** Per-byte fees for each intermediary hop, ordered sender-to-destination. */\n hopFees: bigint[];\n /** Warning messages for unknown intermediaries that defaulted to 0. */\n warnings: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Peer lookup cache: avoids rebuilding the ILP-address-keyed Map when the\n// same discoveredPeers array reference is passed across consecutive calls\n// (the common case during publishEvent bursts).\n// ---------------------------------------------------------------------------\nlet _cachedPeersRef: DiscoveredPeer[] | null = null;\nlet _cachedPeerFingerprint = '';\nlet _cachedPeerMap: Map<string, DiscoveredPeer> | null = null;\n\n/**\n * Compute a lightweight fingerprint of discovered peers for cache invalidation.\n * Uses ILP addresses + feePerByte values so mutations to the array are detected.\n */\nfunction peerFingerprint(peers: DiscoveredPeer[]): string {\n let fp = `${peers.length}:`;\n for (const p of peers) {\n fp += `${p.peerInfo.ilpAddress}=${p.peerInfo.feePerByte};`;\n }\n return fp;\n}\n\n/**\n * Build (or return cached) ILP-address-keyed lookup from discovered peers.\n * Cache is invalidated when the array reference changes or contents are mutated.\n */\nfunction getPeerByAddressMap(\n discoveredPeers: DiscoveredPeer[]\n): Map<string, DiscoveredPeer> {\n const fp = peerFingerprint(discoveredPeers);\n if (\n _cachedPeersRef === discoveredPeers &&\n _cachedPeerFingerprint === fp &&\n _cachedPeerMap\n ) {\n return _cachedPeerMap;\n }\n\n const peerByAddress = new Map<string, DiscoveredPeer>();\n for (const peer of discoveredPeers) {\n peerByAddress.set(peer.peerInfo.ilpAddress, peer);\n if (peer.peerInfo.ilpAddresses) {\n for (const addr of peer.peerInfo.ilpAddresses) {\n peerByAddress.set(addr, peer);\n }\n }\n }\n\n _cachedPeersRef = discoveredPeers;\n _cachedPeerFingerprint = fp;\n _cachedPeerMap = peerByAddress;\n return peerByAddress;\n}\n\n/**\n * Clear the peer lookup cache. Useful in tests or when peer list changes\n * without creating a new array reference.\n */\nexport function clearRouteFeesCache(): void {\n _cachedPeersRef = null;\n _cachedPeerFingerprint = '';\n _cachedPeerMap = null;\n}\n\n/**\n * Resolves intermediary routing fees for a route from sender to destination.\n *\n * Uses LCA-based route resolution: intermediary hops are the ILP address\n * segments between the longest common ancestor and the destination's parent.\n *\n * The peer lookup map is cached by array reference to avoid rebuilding it\n * on every call when the same discoveredPeers array is passed repeatedly.\n *\n * @returns Hop fees and any warnings about unknown intermediaries.\n */\nexport function resolveRouteFees(\n params: ResolveRouteFeesParams\n): ResolveRouteFeesResult {\n const { destination, ownIlpAddress, discoveredPeers } = params;\n\n // Guard against empty or whitespace-only addresses\n if (\n !destination ||\n !destination.trim() ||\n !ownIlpAddress ||\n !ownIlpAddress.trim()\n ) {\n return { hopFees: [], warnings: [] };\n }\n\n const senderSegments = ownIlpAddress.split('.');\n const destSegments = destination.split('.');\n\n // Find longest common ancestor (LCA)\n let lcaLength = 0;\n const minLength = Math.min(senderSegments.length, destSegments.length);\n for (let i = 0; i < minLength; i++) {\n if (senderSegments[i] === destSegments[i]) {\n lcaLength = i + 1;\n } else {\n break;\n }\n }\n\n // Intermediaries are segments from LCA+1 to destination's parent (exclusive of final segment).\n // Example: LCA = g.toon (length 2), dest = g.toon.euwest.relay42 (length 4)\n // -> intermediary prefixes: g.toon.euwest (segments 0..2, i.e., index 2)\n const intermediaryPrefixes: string[] = [];\n for (let i = lcaLength; i < destSegments.length - 1; i++) {\n const prefix = destSegments.slice(0, i + 1).join('.');\n intermediaryPrefixes.push(prefix);\n }\n\n // If no intermediaries (direct route), return empty\n if (intermediaryPrefixes.length === 0) {\n return { hopFees: [], warnings: [] };\n }\n\n // Use cached peer lookup map (avoids O(n) rebuild per call)\n const peerByAddress = getPeerByAddressMap(discoveredPeers);\n\n // Resolve fees for each intermediary\n const hopFees: bigint[] = [];\n const warnings: string[] = [];\n\n for (const prefix of intermediaryPrefixes) {\n const peer = peerByAddress.get(prefix);\n if (peer) {\n let fee: bigint;\n try {\n fee = BigInt(peer.peerInfo.feePerByte ?? '0');\n } catch {\n // Malformed feePerByte (non-numeric string) -- treat as 0\n fee = 0n;\n warnings.push(\n `Invalid feePerByte \"${peer.peerInfo.feePerByte}\" at ${prefix}: defaulting to 0`\n );\n }\n // Guard against malicious negative feePerByte from kind:10032 events\n hopFees.push(fee < 0n ? 0n : fee);\n } else {\n hopFees.push(0n);\n warnings.push(\n `Unknown intermediary at ${prefix}: defaulting feePerByte to 0`\n );\n }\n }\n\n return { hopFees, warnings };\n}\n","/**\n * Pure functions for settlement chain negotiation.\n * No I/O or side effects — used during peer registration to determine\n * the best matching settlement chain and token.\n */\n\n/**\n * Negotiates the best matching settlement chain between requester and responder.\n *\n * Preference order:\n * 1. Chain in intersection with requester's preferred token\n * 2. Chain in intersection with responder's preferred token\n * 3. First chain in intersection (requester's order preserved)\n *\n * @param requesterChains - Chain identifiers the requester supports\n * @param responderChains - Chain identifiers the responder supports\n * @param requesterPreferredTokens - Requester's preferred tokens by chain\n * @param responderPreferredTokens - Responder's preferred tokens by chain\n * @returns The negotiated chain identifier, or null if no intersection\n */\nexport function negotiateSettlementChain(\n requesterChains: string[],\n responderChains: string[],\n requesterPreferredTokens?: Record<string, string>,\n responderPreferredTokens?: Record<string, string>\n): string | null {\n // Compute intersection preserving requester's order\n const responderSet = new Set(responderChains);\n const intersection = requesterChains.filter((chain) =>\n responderSet.has(chain)\n );\n\n if (intersection.length === 0) {\n return null;\n }\n\n // Prefer chain with requester's preferred token\n if (requesterPreferredTokens) {\n const requesterMatch = intersection.find(\n (chain) => requesterPreferredTokens[chain] !== undefined\n );\n if (requesterMatch) {\n return requesterMatch;\n }\n }\n\n // Prefer chain with responder's preferred token\n if (responderPreferredTokens) {\n const responderMatch = intersection.find(\n (chain) => responderPreferredTokens[chain] !== undefined\n );\n if (responderMatch) {\n return responderMatch;\n }\n }\n\n // Fall back to first intersection match (length > 0 guaranteed by check above)\n return intersection[0] ?? null;\n}\n\n/**\n * Resolves which token to use for a given chain.\n *\n * Priority: requester's preference > responder's preference > undefined\n *\n * @param chain - The chain identifier to resolve token for\n * @param requesterPreferredTokens - Requester's preferred tokens by chain\n * @param responderPreferredTokens - Responder's preferred tokens by chain\n * @returns The token address, or undefined if neither party has a preference\n */\nexport function resolveTokenForChain(\n chain: string,\n requesterPreferredTokens?: Record<string, string>,\n responderPreferredTokens?: Record<string, string>\n): string | undefined {\n if (requesterPreferredTokens?.[chain] !== undefined) {\n return requesterPreferredTokens[chain];\n }\n if (responderPreferredTokens?.[chain] !== undefined) {\n return responderPreferredTokens[chain];\n }\n return undefined;\n}\n","/**\n * Shared balance-proof hash helpers — the single source of truth for the\n * byte/field layout that ALL signers and verifiers across the monorepo depend\n * on:\n * - the Mill-side signer (`packages/mill/src/payment-channel-signer.ts`)\n * - the sender-side settlement verifier (`packages/sdk/src/settlement/{evm,solana,mina}.ts`)\n * - the client-side balance-proof signers (`packages/client/src/signing/{solana,mina}-signer.ts`)\n *\n * Originally extracted from the Mill signer (Story 12.4) into `@toon-protocol/sdk`\n * (Story 12.6 AC-6). Relocated here to `@toon-protocol/core` so the client can\n * consume the canonical hashes WITHOUT taking a dependency on `@toon-protocol/sdk`\n * (the client only depends on core). `@toon-protocol/sdk` re-exports these names\n * unchanged, so Mill and existing SDK consumers are unaffected.\n *\n * Any change to a hash layout here automatically applies to every signer AND\n * verifier — they cannot drift.\n *\n * @module\n */\n\nimport { keccak_256 } from '@noble/hashes/sha3.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport {\n bytesToHex,\n hexToBytes as nobleHexToBytes,\n} from '@noble/hashes/utils.js';\n\n/**\n * Convert a hex string (with or without `0x` prefix) to bytes. Rejects\n * odd-length and non-hex input.\n */\nexport function hexToBytes(hex: string): Uint8Array {\n const clean = hex.startsWith('0x') ? hex.slice(2) : hex;\n if (clean.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(clean)) {\n throw new Error(`Invalid hex string: ${hex}`);\n }\n return nobleHexToBytes(clean);\n}\n\n/**\n * Encode a non-negative bigint as 32-byte big-endian. Throws if negative or\n * exceeds 256 bits.\n */\nexport function bigintToBytes32BE(x: bigint): Uint8Array {\n if (x < 0n) {\n throw new Error('bigint must be non-negative for balance-proof encoding');\n }\n const out = new Uint8Array(32);\n let v = x;\n for (let i = 31; i >= 0; i--) {\n out[i] = Number(v & 0xffn);\n v >>= 8n;\n }\n if (v !== 0n) {\n throw new Error('bigint exceeds 256 bits');\n }\n return out;\n}\n\n/**\n * Concat N Uint8Arrays into one new Uint8Array.\n */\nexport function concatBytes(...parts: Uint8Array[]): Uint8Array {\n let len = 0;\n for (const p of parts) len += p.length;\n const out = new Uint8Array(len);\n let o = 0;\n for (const p of parts) {\n out.set(p, o);\n o += p.length;\n }\n return out;\n}\n\n/**\n * Compute the EVM balance-proof message hash:\n * keccak256(channelId || cumulativeAmount(32BE) || nonce(32BE) || recipient)\n *\n * `channelIdBytes` MUST be 32 bytes. `recipientBytes` MUST be 20 bytes.\n * This hash is what `EvmPaymentChannelSigner.signBalanceProof` signs and\n * what `recoverEvmSignerAddress` recovers against.\n *\n * @stable — signer and verifier depend on the exact byte layout.\n */\nexport function balanceProofHashEvm(\n channelIdBytes: Uint8Array,\n cumulativeAmount: bigint,\n nonce: bigint,\n recipientBytes: Uint8Array\n): Uint8Array {\n return keccak_256(\n concatBytes(\n channelIdBytes,\n bigintToBytes32BE(cumulativeAmount),\n bigintToBytes32BE(nonce),\n recipientBytes\n )\n );\n}\n\n/**\n * Compute the Solana balance-proof message hash:\n * sha256(utf8(channelId) || cumulativeAmount(32BE) || nonce(32BE) || utf8(recipient))\n *\n * `channelId` and `recipient` are base58-encoded strings (ASCII-subset of\n * UTF-8). This hash is what `SolanaPaymentChannelSigner.signBalanceProof`\n * signs and what `verifyEd25519Signature` verifies against.\n *\n * @stable — signer and verifier depend on the exact byte layout.\n */\nexport function balanceProofHashSolana(\n channelId: string,\n cumulativeAmount: bigint,\n nonce: bigint,\n recipient: string\n): Uint8Array {\n return sha256(\n concatBytes(\n new TextEncoder().encode(channelId),\n bigintToBytes32BE(cumulativeAmount),\n bigintToBytes32BE(nonce),\n new TextEncoder().encode(recipient)\n )\n );\n}\n\n/**\n * Hash an arbitrary string to a Pallas-field-safe bigint.\n *\n * The Pallas base field order is slightly below 2^254, so we take the first\n * 240 bits (60 hex chars / 30 bytes) of `sha256(utf8(s))` as a conservative,\n * guaranteed-in-field representation. Used to fold the variable-length\n * `channelId` / `recipient` strings into the fixed field-element array a Mina\n * Schnorr signature is computed over.\n *\n * @stable — Mill signer and SDK verifier depend on the exact derivation.\n */\nexport function minaHashToField(s: string): bigint {\n const digestHex = bytesToHex(sha256(new TextEncoder().encode(s)));\n return BigInt('0x' + digestHex.slice(0, 60));\n}\n\n/**\n * Compute the Mina balance-proof field-element message:\n * [ minaHashToField(channelId),\n * cumulativeAmount,\n * nonce,\n * minaHashToField(recipient) ]\n *\n * This is the EXACT `fields` array that the Mill's `MinaPaymentChannelSigner`\n * passes to `mina-signer`'s `signFields(...)`, and that the sender-side\n * `verifyMinaSignature` re-derives and passes to `verifyFields(...)`. Keeping\n * the derivation here (shared across `@toon-protocol/mill`, `@toon-protocol/sdk`,\n * and `@toon-protocol/client`) prevents signer/verifier drift — mirroring the\n * EVM/Solana hash helpers above.\n *\n * NOTE: this is the Mill↔sender wire contract (a Schnorr signature over four\n * field elements), NOT the connector's on-chain `MinaPaymentChannelSDK`\n * Poseidon-commitment proof shape. The two are distinct; see\n * `packages/sdk/src/settlement/mina.ts` for the relationship + the\n * remaining on-chain-settlement gap.\n *\n * @stable — Mill signer and SDK verifier depend on the exact byte layout.\n */\nexport function balanceProofFieldsMina(\n channelId: string,\n cumulativeAmount: bigint,\n nonce: bigint,\n recipient: string\n): bigint[] {\n return [\n minaHashToField(channelId),\n cumulativeAmount,\n nonce,\n minaHashToField(recipient),\n ];\n}\n","/**\n * Base58 (Bitcoin/Solana alphabet) encode/decode.\n *\n * Identical implementation to `@toon-protocol/sdk`'s `identity.ts` helpers —\n * relocated/duplicated here so `@toon-protocol/core` (and its `client`\n * consumer) can base58-encode Solana addresses and the Mina base58check\n * private-key format without depending on the SDK.\n *\n * @module\n */\n\nconst BASE58_ALPHABET =\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';\n\n/**\n * Encodes a byte array to a Base58 string (Bitcoin/Solana alphabet).\n */\nexport function base58Encode(bytes: Uint8Array): string {\n // Count leading zeros\n let zeros = 0;\n for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;\n\n let value = 0n;\n for (const byte of bytes) {\n value = value * 256n + BigInt(byte);\n }\n\n let result = '';\n while (value > 0n) {\n result = BASE58_ALPHABET[Number(value % 58n)] + result;\n value = value / 58n;\n }\n\n // Add leading '1's for leading zero bytes\n for (let i = 0; i < zeros; i++) {\n result = '1' + result;\n }\n\n return result || '1';\n}\n\n/**\n * Decodes a Base58 string to a byte array (Bitcoin/Solana alphabet).\n */\nexport function base58Decode(str: string): Uint8Array {\n // Count leading '1's (zero bytes)\n let zeros = 0;\n for (let i = 0; i < str.length && str[i] === '1'; i++) zeros++;\n\n let value = 0n;\n for (const ch of str) {\n const idx = BASE58_ALPHABET.indexOf(ch);\n if (idx === -1) throw new Error(`Invalid base58 character: ${ch}`);\n value = value * 58n + BigInt(idx);\n }\n\n // Convert bigint to bytes\n const hex = value === 0n ? '' : value.toString(16);\n const hexPadded = hex.length % 2 ? '0' + hex : hex;\n const rawBytes: number[] = [];\n for (let i = 0; i < hexPadded.length; i += 2) {\n rawBytes.push(parseInt(hexPadded.slice(i, i + 2), 16));\n }\n\n const result = new Uint8Array(zeros + rawBytes.length);\n result.set(rawBytes, zeros);\n return result;\n}\n","/**\n * Mina private-key format conversion.\n *\n * `deriveFullIdentity()` / `deriveMillKeys()` emit a Mina Pallas scalar as a\n * big-endian hex string, but `mina-signer`'s `signFields`/`derivePublicKey`\n * require the Mina base58check (`EK…`) private-key format. This helper bridges\n * the two so a hex-derived Mina key produces signatures verifiable by the\n * sender-side `verifyMinaSignature`.\n *\n * Mirrors `hexToMinaBase58PrivateKey` in `packages/mill/src/payment-channel-signer.ts`\n * (same fixed Mina base58check wire standard — version byte `0x5a`, non-zero\n * tag `0x01`, little-endian scalar, double-sha256 checksum).\n *\n * @module\n */\n\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { base58Encode } from './base58.js';\nimport { concatBytes, hexToBytes } from './hashes.js';\n\n/**\n * Mina private-key version byte for the base58check encoding mina-signer\n * expects (the `EK…` prefix). Followed by a `0x01` non-zero tag byte and the\n * 32-byte field scalar in LITTLE-ENDIAN order, then a 4-byte double-sha256\n * checksum.\n */\nconst MINA_PRIVATE_KEY_VERSION = 0x5a;\n\n/**\n * Convert a big-endian 32-byte hex scalar (the form `deriveFullIdentity()`\n * emits for Mina) into the Mina base58check private-key string mina-signer's\n * `signFields`/`derivePublicKey` require. If the input already looks like a\n * base58 `EK…` key it is returned unchanged.\n *\n * Layout (pre-checksum): `[0x5a, 0x01, <scalar bytes little-endian>]`, then\n * append the first 4 bytes of `sha256(sha256(payload))` and base58-encode.\n */\nexport function hexToMinaBase58PrivateKey(privateKey: string): string {\n // Already a Mina base58 private key (EK… ~52 chars) — pass through.\n if (!/^(0x)?[0-9a-fA-F]{64}$/.test(privateKey)) {\n return privateKey;\n }\n const beScalar = hexToBytes(privateKey); // 32 bytes, big-endian\n // mina-signer/Pallas serializes the scalar little-endian.\n const leScalar = Uint8Array.from(beScalar).reverse();\n const payload = concatBytes(\n Uint8Array.from([MINA_PRIVATE_KEY_VERSION, 0x01]),\n leScalar\n );\n const checksum = sha256(sha256(payload)).slice(0, 4);\n return base58Encode(concatBytes(payload, checksum));\n}\n","/**\n * Bootstrap service for peer discovery and network initialization.\n *\n * Handles the initial peer discovery and registration process\n * with known peers to bootstrap into the ILP network.\n *\n * Two-phase bootstrap:\n * 1. Discover & Register: Load peers, query relays for kind:10032,\n * register with connector, select chain locally, open channel unilaterally\n * 2. Announce: Publish own kind:10032 as paid ILP PREPARE\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport type { Filter } from 'nostr-tools/filter';\nimport { getPublicKey } from 'nostr-tools/pure';\nimport WebSocket from 'ws';\nimport { ToonError } from '../errors.js';\nimport { GenesisPeerLoader, ArDrivePeerRegistry } from '../discovery/index.js';\nimport type { GenesisPeer } from '../discovery/index.js';\nimport { ILP_PEER_INFO_KIND } from '../constants.js';\nimport { parseIlpPeerInfo, buildIlpPeerInfoEvent } from '../events/index.js';\nimport {\n negotiateSettlementChain,\n resolveTokenForChain,\n} from '../settlement/index.js';\nimport type { IlpPeerInfo, ConnectorChannelClient } from '../types.js';\nimport type {\n KnownPeer,\n BootstrapResult,\n ConnectorAdminClient,\n BootstrapConfig,\n BootstrapServiceConfig,\n BootstrapPhase,\n BootstrapEvent,\n BootstrapEventListener,\n IlpClient,\n IlpSendResult,\n} from './types.js';\n\n/**\n * Error thrown when bootstrap operations fail.\n */\nexport class BootstrapError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'BOOTSTRAP_FAILED', cause);\n this.name = 'BootstrapError';\n }\n}\n\n/**\n * Service for bootstrapping into the ILP network via known Nostr peers.\n *\n * The bootstrap process:\n * Phase 1 (Discover & Register): Load peers from config, ArDrive, and env var.\n * For each peer, query their relay for kind:10032 (ILP Peer Info).\n * Register peer via connector admin API.\n * Select chain locally and open channel unilaterally.\n * Phase 2 (Announce): Publish own kind:10032 as paid ILP PREPARE.\n */\nexport class BootstrapService {\n private readonly config: Required<BootstrapConfig> & { btpSecret?: string };\n private readonly secretKey: Uint8Array;\n private readonly pubkey: string;\n private readonly ownIlpInfo: IlpPeerInfo;\n private readonly pool: SimplePool;\n private connectorAdmin?: ConnectorAdminClient;\n private channelClient?: ConnectorChannelClient;\n private claimSigner?: (channelId: string, amount: bigint) => Promise<unknown>;\n\n // ILP-first flow additions\n private readonly ilpClient?: IlpClient;\n private readonly settlementInfo?: BootstrapServiceConfig['settlementInfo'];\n private readonly ownIlpAddress?: string;\n private readonly toonEncoder?: BootstrapServiceConfig['toonEncoder'];\n private readonly toonDecoder?: BootstrapServiceConfig['toonDecoder'];\n private readonly basePricePerByte: bigint;\n\n // Event emitter\n private listeners: BootstrapEventListener[] = [];\n private phase: BootstrapPhase = 'discovering';\n\n /**\n * Creates a new BootstrapService instance.\n *\n * @param config - Bootstrap configuration with known peers and optional ILP-first settings\n * @param secretKey - Our Nostr secret key for signing events\n * @param ownIlpInfo - Our ILP peer info to publish\n * @param pool - Optional SimplePool instance (creates new one if not provided)\n */\n constructor(\n config: BootstrapServiceConfig,\n secretKey: Uint8Array,\n ownIlpInfo: IlpPeerInfo,\n pool?: SimplePool\n ) {\n this.config = {\n knownPeers: config.knownPeers,\n queryTimeout: config.queryTimeout ?? 5000,\n ardriveEnabled: config.ardriveEnabled ?? true,\n defaultRelayUrl: config.defaultRelayUrl ?? '',\n };\n this.secretKey = secretKey;\n this.pubkey = getPublicKey(secretKey);\n this.ownIlpInfo = ownIlpInfo;\n this.pool = pool ?? new SimplePool();\n\n // ILP-first flow config\n this.settlementInfo = config.settlementInfo;\n this.ownIlpAddress = config.ownIlpAddress;\n this.toonEncoder = config.toonEncoder;\n this.toonDecoder = config.toonDecoder;\n this.basePricePerByte = config.basePricePerByte ?? 10n;\n }\n\n /**\n * Set the ILP client for sending packets via the connector.\n * Kept separate from constructor for backward compatibility with existing code\n * that creates the client after construction.\n */\n setIlpClient(client: IlpClient): void {\n (this as unknown as { ilpClient?: IlpClient }).ilpClient = client;\n }\n\n /**\n * @deprecated Use setIlpClient instead\n */\n setAgentRuntimeClient(client: IlpClient): void {\n this.setIlpClient(client);\n }\n\n /**\n * Set the connector admin client for adding peers/routes.\n */\n setConnectorAdmin(admin: ConnectorAdminClient): void {\n this.connectorAdmin = admin;\n }\n\n /**\n * Set the channel client for opening payment channels.\n */\n setChannelClient(client: ConnectorChannelClient): void {\n this.channelClient = client;\n }\n\n /**\n * Set the claim signer for creating signed balance proofs.\n * Used by clients to sign payment channel claims for ILP packets.\n */\n setClaimSigner(\n signer: (channelId: string, amount: bigint) => Promise<unknown>\n ): void {\n this.claimSigner = signer;\n }\n\n /**\n * Get the current bootstrap phase.\n */\n getPhase(): BootstrapPhase {\n return this.phase;\n }\n\n /**\n * Register an event listener.\n */\n on(listener: BootstrapEventListener): void {\n this.listeners.push(listener);\n }\n\n /**\n * Unregister an event listener.\n */\n off(listener: BootstrapEventListener): void {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n /**\n * Emit a bootstrap event to all listeners.\n */\n private emit(event: BootstrapEvent): void {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch {\n // Don't let listener errors break bootstrap\n }\n }\n }\n\n /**\n * Transition to a new phase, emitting phase change event.\n */\n private setPhase(newPhase: BootstrapPhase): void {\n const previousPhase = this.phase;\n this.phase = newPhase;\n this.emit({ type: 'bootstrap:phase', phase: newPhase, previousPhase });\n }\n\n /**\n * Load peers from genesis config, ArDrive, and optional env var JSON.\n * Merges all sources, deduplicating by pubkey (ArDrive overrides genesis for matching pubkeys).\n */\n async loadPeers(additionalPeersJson?: string): Promise<GenesisPeer[]> {\n const genesisPeers = GenesisPeerLoader.loadAllPeers(additionalPeersJson);\n\n const ardrivePeers: GenesisPeer[] = [];\n if (this.config.ardriveEnabled) {\n try {\n const ardriveMap = await ArDrivePeerRegistry.fetchPeers();\n for (const [pubkey, info] of ardriveMap) {\n if (!this.config.defaultRelayUrl) continue;\n ardrivePeers.push({\n pubkey,\n relayUrl: this.config.defaultRelayUrl,\n ilpAddress: info.ilpAddress,\n btpEndpoint: info.btpEndpoint,\n });\n }\n } catch (error) {\n console.warn(\n '[Bootstrap] ArDrive peer fetch failed, using genesis peers only:',\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n }\n\n // Merge: ArDrive overrides genesis for matching pubkeys\n const merged = new Map<string, GenesisPeer>();\n for (const peer of genesisPeers) {\n merged.set(peer.pubkey, peer);\n }\n for (const peer of ardrivePeers) {\n merged.set(peer.pubkey, peer);\n }\n\n return [...merged.values()];\n }\n\n /**\n * Bootstrap with all known peers.\n *\n * Loads peers from genesis config, ArDrive, and optional env var JSON,\n * then attempts to bootstrap with each peer in order.\n * Returns results for successfully bootstrapped peers.\n * Continues to next peer on failure.\n *\n * @param additionalPeersJson - Optional JSON string of additional peers to merge\n * @returns Array of successful bootstrap results\n */\n async bootstrap(additionalPeersJson?: string): Promise<BootstrapResult[]> {\n const results: BootstrapResult[] = [];\n\n try {\n // Phase 1: Discover and register (with settlement)\n this.setPhase('discovering');\n\n // Load and merge peers from all sources\n const allPeers = await this.loadPeers(additionalPeersJson);\n\n // Convert GenesisPeers to KnownPeers and merge with config peers\n const knownPeersMap = new Map<string, KnownPeer>();\n for (const peer of this.config.knownPeers) {\n knownPeersMap.set(peer.pubkey, peer);\n }\n for (const peer of allPeers) {\n if (!knownPeersMap.has(peer.pubkey)) {\n knownPeersMap.set(peer.pubkey, {\n pubkey: peer.pubkey,\n relayUrl: peer.relayUrl,\n btpEndpoint: peer.btpEndpoint,\n });\n }\n }\n\n this.setPhase('registering');\n\n for (const knownPeer of knownPeersMap.values()) {\n try {\n const result = await this.bootstrapWithPeer(knownPeer);\n results.push(result);\n console.log(\n `[Bootstrap] Successfully bootstrapped with ${knownPeer.pubkey.slice(0, 16)}...`\n );\n } catch (error) {\n console.warn(\n `[Bootstrap] Failed to bootstrap with ${knownPeer.pubkey.slice(0, 16)}...:`,\n error instanceof Error ? error.message : 'Unknown error'\n );\n // Continue to next peer\n }\n }\n\n // Phase 2: Announce own kind:10032 as paid ILP PREPARE\n if (this.ilpClient && results.length > 0) {\n this.setPhase('announcing');\n\n for (const result of results) {\n try {\n await this.announceViaIlp(result);\n } catch (error) {\n const reason =\n error instanceof Error ? error.message : 'Unknown error';\n this.emit({\n type: 'bootstrap:announce-failed',\n peerId: result.registeredPeerId,\n reason,\n });\n console.warn(\n `[Bootstrap] Announce failed for ${result.registeredPeerId}:`,\n reason\n );\n // Non-fatal\n }\n }\n }\n\n const channelCount = results.filter((r) => r.channelId).length;\n this.setPhase('ready');\n this.emit({\n type: 'bootstrap:ready',\n peerCount: results.length,\n channelCount,\n });\n } catch (error) {\n this.setPhase('failed');\n console.error(\n '[Bootstrap] Bootstrap failed:',\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n\n return results;\n }\n\n /**\n * Bootstrap with a single known peer.\n *\n * @param knownPeer - The known peer to bootstrap with\n * @returns Bootstrap result with peer info and registered peer ID\n * @throws BootstrapError if pubkey is invalid or peer info query fails\n */\n async bootstrapWithPeer(knownPeer: KnownPeer): Promise<BootstrapResult> {\n // Validate pubkey format\n const PUBKEY_REGEX = /^[0-9a-f]{64}$/;\n if (!PUBKEY_REGEX.test(knownPeer.pubkey)) {\n throw new BootstrapError(\n `Invalid pubkey format for known peer: ${knownPeer.pubkey}`\n );\n }\n\n // Step 1: Query peer's relay for their kind:10032\n console.log(`[Bootstrap] Querying ${knownPeer.relayUrl} for peer info...`);\n const peerInfo = await this.queryPeerInfo(knownPeer);\n\n // Step 2: Add peer to connector if admin client is set (non-fatal)\n const registeredPeerId = `nostr-${knownPeer.pubkey.slice(0, 16)}`;\n if (this.connectorAdmin) {\n try {\n console.log(`[Bootstrap] Adding peer to connector routing table...`);\n await this.addPeerToConnector(knownPeer, peerInfo);\n this.emit({\n type: 'bootstrap:peer-registered',\n peerId: registeredPeerId,\n peerPubkey: knownPeer.pubkey,\n ilpAddress: peerInfo.ilpAddress,\n });\n } catch (error) {\n console.warn(\n `[Bootstrap] Failed to register peer ${registeredPeerId} with connector:`,\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n }\n\n const result: BootstrapResult = {\n knownPeer,\n peerInfo,\n registeredPeerId,\n };\n\n // Step 3: Local chain selection + unilateral channel opening during registration\n if (\n this.channelClient &&\n this.settlementInfo?.supportedChains?.length &&\n peerInfo.supportedChains?.length &&\n peerInfo.settlementAddresses\n ) {\n try {\n const negotiatedChain = negotiateSettlementChain(\n this.settlementInfo.supportedChains,\n peerInfo.supportedChains,\n this.settlementInfo.preferredTokens,\n peerInfo.preferredTokens\n );\n\n if (negotiatedChain) {\n const peerAddress = peerInfo.settlementAddresses[negotiatedChain];\n const tokenAddress = resolveTokenForChain(\n negotiatedChain,\n this.settlementInfo.preferredTokens,\n peerInfo.preferredTokens\n );\n const tokenNetwork = peerInfo.tokenNetworks?.[negotiatedChain];\n\n if (peerAddress) {\n // Lazy channels: store negotiation metadata instead of opening channel\n console.log(\n `[Bootstrap] Negotiated ${negotiatedChain} with ${registeredPeerId} (lazy — channel deferred)`\n );\n result.negotiatedChain = negotiatedChain;\n result.settlementAddress = peerAddress;\n result.tokenAddress = tokenAddress;\n result.tokenNetwork = tokenNetwork;\n\n // Update peer registration with settlement info (no channelId yet)\n if (this.connectorAdmin) {\n await this.connectorAdmin.addPeer({\n id: registeredPeerId,\n url: peerInfo.btpEndpoint,\n authToken: '',\n routes: [{ prefix: peerInfo.ilpAddress }],\n settlement: {\n preference: negotiatedChain,\n ...(peerAddress && { evmAddress: peerAddress }),\n ...(tokenAddress && { tokenAddress }),\n ...(tokenNetwork && { tokenNetworkAddress: tokenNetwork }),\n },\n });\n }\n }\n }\n } catch (error) {\n const reason = error instanceof Error ? error.message : 'Unknown error';\n console.warn(\n `[Bootstrap] Settlement failed for ${registeredPeerId}:`,\n reason\n );\n this.emit({\n type: 'bootstrap:settlement-failed',\n peerId: registeredPeerId,\n reason,\n });\n // Non-fatal: peer remains registered without channel\n }\n }\n\n // Step 4: Publish our own kind:10032 to their relay (non-fatal)\n // Only do direct publish if NOT using ILP-first flow (Phase 2 handles it via ILP)\n if (!this.ilpClient) {\n try {\n console.log(\n `[Bootstrap] Publishing our ILP info to ${knownPeer.relayUrl}...`\n );\n await this.publishOurInfo(knownPeer.relayUrl);\n } catch (error) {\n console.warn(\n `[Bootstrap] Failed to publish ILP info to ${knownPeer.relayUrl}:`,\n error instanceof Error ? error.message : 'Unknown error'\n );\n }\n }\n\n return result;\n }\n\n /**\n * Announce own kind:10032 as paid ILP PREPARE (Phase 2).\n */\n private async announceViaIlp(result: BootstrapResult): Promise<void> {\n if (!this.ilpClient || !this.toonEncoder) {\n return;\n }\n\n // Build kind:10032 event\n const ilpInfoEvent = buildIlpPeerInfoEvent(this.ownIlpInfo, this.secretKey);\n\n // TOON-encode the event\n const toonBytes = this.toonEncoder(ilpInfoEvent);\n const base64Toon = Buffer.from(toonBytes).toString('base64');\n\n // Calculate amount: base price per byte * TOON byte length\n const amount = String(BigInt(toonBytes.length) * this.basePricePerByte);\n\n // Send announce via ILP — use claim-attached path when available\n let ilpResult: IlpSendResult;\n if (\n this.claimSigner &&\n result.channelId &&\n this.ilpClient.sendIlpPacketWithClaim\n ) {\n const claim = await this.claimSigner(result.channelId, BigInt(amount));\n ilpResult = await this.ilpClient.sendIlpPacketWithClaim(\n { destination: result.peerInfo.ilpAddress, amount, data: base64Toon },\n claim\n );\n } else {\n ilpResult = await this.ilpClient.sendIlpPacket({\n destination: result.peerInfo.ilpAddress,\n amount,\n data: base64Toon,\n });\n }\n\n if (ilpResult.accepted) {\n console.log(\n `[Bootstrap] Announced to ${result.registeredPeerId} via ILP (eventId: ${ilpInfoEvent.id})`\n );\n this.emit({\n type: 'bootstrap:announced',\n peerId: result.registeredPeerId,\n eventId: ilpInfoEvent.id,\n amount,\n });\n } else {\n const reason = `${ilpResult.code} ${ilpResult.message}`;\n this.emit({\n type: 'bootstrap:announce-failed',\n peerId: result.registeredPeerId,\n reason,\n });\n console.warn(\n `[Bootstrap] Announce rejected by ${result.registeredPeerId}: ${reason}`\n );\n }\n }\n\n /**\n * Query a peer's relay for their kind:10032 ILP Peer Info event.\n * Uses direct WebSocket connection for reliable container-to-container communication.\n */\n private async queryPeerInfo(knownPeer: KnownPeer): Promise<IlpPeerInfo> {\n const filter: Filter = {\n kinds: [ILP_PEER_INFO_KIND],\n authors: [knownPeer.pubkey],\n limit: 1,\n };\n\n console.log(`[Bootstrap] Query filter:`, JSON.stringify(filter));\n console.log(`[Bootstrap] Connecting to ${knownPeer.relayUrl}...`);\n\n return new Promise((resolve, reject) => {\n const events: unknown[] = [];\n const timeout = this.config.queryTimeout ?? 5000;\n const ws = new WebSocket(knownPeer.relayUrl);\n const subId = `bootstrap-${Date.now()}`;\n let resolved = false;\n\n const cleanup = () => {\n if (!resolved) {\n resolved = true;\n try {\n ws.close();\n } catch {\n // Ignore close errors\n }\n }\n };\n\n ws.on('open', () => {\n console.log(\n `[Bootstrap] Connected to ${knownPeer.relayUrl}, sending REQ`\n );\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.on('message', (data: Buffer | string) => {\n let msg: unknown[];\n try {\n msg = JSON.parse(data.toString()) as unknown[];\n } catch {\n console.warn('[Bootstrap] Received malformed JSON from relay');\n return;\n }\n console.log(`[Bootstrap] Received message type: ${msg[0]}`);\n\n if (msg[0] === 'EVENT' && msg[1] === subId) {\n let event: Record<string, unknown> | undefined;\n const rawEvent = msg[2];\n\n // Handle TOON format events (string) by parsing them\n if (typeof rawEvent === 'string') {\n try {\n // Simple TOON parser for bootstrap events\n const lines = rawEvent.trim().split('\\n');\n const parsed: Record<string, unknown> = {};\n for (const line of lines) {\n const colonIndex = line.indexOf(':');\n if (colonIndex > 0) {\n const key = line.substring(0, colonIndex).trim();\n const value = line.substring(colonIndex + 1).trim();\n if (key.startsWith('tags[')) {\n // Skip tags for now\n continue;\n }\n if (value.startsWith('\"') && value.endsWith('\"')) {\n parsed[key] = JSON.parse(value);\n } else if (!isNaN(Number(value))) {\n parsed[key] = Number(value);\n } else {\n parsed[key] = value;\n }\n }\n }\n event = parsed;\n } catch (error) {\n console.warn(`[Bootstrap] Failed to parse TOON event:`, error);\n return;\n }\n } else if (typeof rawEvent === 'object' && rawEvent !== null) {\n event = rawEvent as Record<string, unknown>;\n }\n\n if (event && typeof event['id'] === 'string') {\n console.log(\n `[Bootstrap] Received event: ${(event['id'] as string).slice(0, 16)}...`\n );\n events.push(event);\n } else {\n console.warn(\n `[Bootstrap] Received EVENT message with invalid event data:`,\n msg\n );\n }\n } else if (msg[0] === 'EOSE' && msg[1] === subId) {\n console.log(\n `[Bootstrap] EOSE received, found ${events.length} events`\n );\n cleanup();\n\n if (events.length === 0) {\n reject(\n new BootstrapError(\n `No kind:${ILP_PEER_INFO_KIND} event found for peer ${knownPeer.pubkey.slice(0, 16)}...`\n )\n );\n return;\n }\n\n // Sort by created_at descending and use most recent\n const sortedEvents = (events as { created_at: number }[]).sort(\n (a, b) => b.created_at - a.created_at\n );\n const mostRecent = sortedEvents[0];\n\n try {\n const peerInfo = parseIlpPeerInfo(\n mostRecent as Parameters<typeof parseIlpPeerInfo>[0]\n );\n resolve(peerInfo);\n } catch (error) {\n reject(\n new BootstrapError(\n `Failed to parse peer info`,\n error instanceof Error ? error : undefined\n )\n );\n }\n } else if (msg[0] === 'NOTICE') {\n console.log(`[Bootstrap] Notice from relay: ${msg[1]}`);\n }\n });\n\n ws.on('error', (error: Error) => {\n console.error(`[Bootstrap] WebSocket error:`, error.message);\n cleanup();\n reject(\n new BootstrapError(\n `Failed to connect to ${knownPeer.relayUrl}: ${error.message}`,\n error\n )\n );\n });\n\n ws.on('close', () => {\n console.log(`[Bootstrap] Connection closed`);\n if (!resolved) {\n cleanup();\n reject(\n new BootstrapError(\n `Connection closed before receiving events from ${knownPeer.relayUrl}`\n )\n );\n }\n });\n\n // Set timeout\n setTimeout(() => {\n if (resolved) return;\n console.log(`[Bootstrap] Query timeout after ${timeout}ms`);\n cleanup();\n\n if (events.length > 0) {\n const sortedEvents = (events as { created_at: number }[]).sort(\n (a, b) => b.created_at - a.created_at\n );\n const mostRecent = sortedEvents[0];\n\n try {\n const peerInfo = parseIlpPeerInfo(\n mostRecent as Parameters<typeof parseIlpPeerInfo>[0]\n );\n resolve(peerInfo);\n } catch (error) {\n reject(\n new BootstrapError(\n `Failed to parse peer info`,\n error instanceof Error ? error : undefined\n )\n );\n }\n } else {\n reject(\n new BootstrapError(\n `Query timeout: No events received from ${knownPeer.relayUrl} after ${timeout}ms`\n )\n );\n }\n }, timeout);\n });\n }\n\n /**\n * Add a peer to the connector via Admin API.\n */\n private async addPeerToConnector(\n knownPeer: KnownPeer,\n peerInfo: IlpPeerInfo\n ): Promise<void> {\n if (!this.connectorAdmin) {\n throw new BootstrapError('Connector admin client not set');\n }\n\n const peerId = `nostr-${knownPeer.pubkey.slice(0, 16)}`;\n\n // HTTP Connector Admin requires full BTP URL (btp+ws:// or btp+wss://)\n // Use empty string for authToken - BTP doesn't require authentication\n await this.connectorAdmin.addPeer({\n id: peerId,\n url: peerInfo.btpEndpoint,\n authToken: '', // BTP doesn't need auth\n routes: [{ prefix: peerInfo.ilpAddress }],\n });\n }\n\n /**\n * Publish our own kind:10032 ILP Peer Info to a relay.\n */\n private async publishOurInfo(relayUrl: string): Promise<void> {\n const event = buildIlpPeerInfoEvent(this.ownIlpInfo, this.secretKey);\n\n try {\n await this.pool.publish([relayUrl], event);\n } catch (error) {\n throw new BootstrapError(\n `Failed to publish ILP info to ${relayUrl}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Query a peer's relay for other peers' kind:10032 events.\n *\n * Used after bootstrapping to discover additional peers\n * that have also published to the bootstrap node's relay.\n *\n * @param relayUrl - The relay URL to query\n * @param excludePubkeys - Pubkeys to exclude from results (e.g., our own, known peers)\n * @returns Map of pubkey to IlpPeerInfo for discovered peers\n */\n async discoverPeersViaRelay(\n relayUrl: string,\n excludePubkeys: string[] = []\n ): Promise<Map<string, IlpPeerInfo>> {\n const excludeSet = new Set([this.pubkey, ...excludePubkeys]);\n\n const filter: Filter = {\n kinds: [ILP_PEER_INFO_KIND],\n };\n\n try {\n const events = await this.pool.querySync([relayUrl], filter);\n\n // Group by pubkey, keeping most recent\n const eventsByPubkey = new Map<string, (typeof events)[0]>();\n for (const event of events) {\n if (excludeSet.has(event.pubkey)) continue;\n\n const existing = eventsByPubkey.get(event.pubkey);\n if (!existing || event.created_at > existing.created_at) {\n eventsByPubkey.set(event.pubkey, event);\n }\n }\n\n // Parse events\n const result = new Map<string, IlpPeerInfo>();\n for (const [pubkey, event] of eventsByPubkey) {\n try {\n const info = parseIlpPeerInfo(event);\n result.set(pubkey, info);\n } catch {\n // Skip malformed events\n }\n }\n\n return result;\n } catch (error) {\n throw new BootstrapError(\n `Failed to discover peers from ${relayUrl}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Re-advertise own kind:10032 to all previously bootstrapped peers.\n *\n * Called after topology changes (addUpstreamPeer/removeUpstreamPeer) to\n * propagate updated ILP address lists to the network. Uses the same\n * ILP-first announcement path as the initial Phase 2 bootstrap.\n *\n * For non-ILP mode, publishes directly to each peer's relay URL.\n *\n * @param results - The bootstrap results from the initial bootstrap() call\n * @returns Number of peers successfully re-announced to\n */\n async republish(results: BootstrapResult[]): Promise<number> {\n if (results.length === 0) {\n return 0;\n }\n\n let successCount = 0;\n\n if (this.ilpClient && this.toonEncoder) {\n // ILP-first flow: re-announce via ILP PREPARE\n for (const result of results) {\n try {\n await this.announceViaIlp(result);\n successCount++;\n } catch (error) {\n const reason =\n error instanceof Error ? error.message : 'Unknown error';\n this.emit({\n type: 'bootstrap:announce-failed',\n peerId: result.registeredPeerId,\n reason: `republish: ${reason}`,\n });\n console.warn(\n `[Bootstrap] Republish failed for ${result.registeredPeerId}:`,\n reason\n );\n // Non-fatal: continue to next peer\n }\n }\n } else {\n // Direct publish to relay URLs\n for (const result of results) {\n try {\n await this.publishOurInfo(result.knownPeer.relayUrl);\n successCount++;\n } catch (error) {\n console.warn(\n `[Bootstrap] Republish to ${result.knownPeer.relayUrl} failed:`,\n error instanceof Error ? error.message : 'Unknown error'\n );\n // Non-fatal: continue to next peer\n }\n }\n }\n\n return successCount;\n }\n\n /**\n * Get our pubkey.\n */\n getPubkey(): string {\n return this.pubkey;\n }\n\n /**\n * Publish our ILP info to a specific relay.\n *\n * @param relayUrl - The relay URL to publish to (defaults to 'ws://localhost:7100')\n */\n async publishToRelay(relayUrl = 'ws://localhost:7100'): Promise<void> {\n await this.publishOurInfo(relayUrl);\n }\n}\n","/**\n * Discovery tracker for processing kind:10032 events and managing peer discovery.\n *\n * Unlike RelayMonitor, the tracker does NOT own a subscription — callers feed\n * events in via processEvent(). This enables use from any event source:\n * relay subscriptions, ILP handlers, or test harnesses.\n */\n\nimport { getPublicKey } from 'nostr-tools/pure';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { ILP_PEER_INFO_KIND } from '../constants.js';\nimport { parseIlpPeerInfo } from '../events/index.js';\nimport {\n negotiateSettlementChain,\n resolveTokenForChain,\n} from '../settlement/index.js';\nimport type { ConnectorChannelClient } from '../types.js';\nimport { BootstrapError } from './BootstrapService.js';\nimport type {\n ConnectorAdminClient,\n BootstrapEvent,\n BootstrapEventListener,\n DiscoveredPeer,\n SettlementConfig,\n} from './types.js';\n\n/**\n * Configuration for creating a DiscoveryTracker.\n */\nexport interface DiscoveryTrackerConfig {\n /** Nostr secret key for deriving own pubkey (excluded from discovery) */\n secretKey: Uint8Array;\n /** Own settlement preferences for local chain negotiation */\n settlementInfo?: SettlementConfig;\n}\n\n/**\n * Discovery tracker interface — processes kind:10032 events and manages\n * peer discovery state without owning a subscription.\n */\nexport interface DiscoveryTracker {\n /** Process a kind:10032 event for discovery. Called by relay subscription or ILP handler. */\n processEvent(event: NostrEvent): void;\n /** Explicitly peer with a discovered peer (register + open channel). */\n peerWith(pubkey: string): Promise<void>;\n /** Get discovered peers not yet peered with. */\n getDiscoveredPeers(): DiscoveredPeer[];\n /** Get all discovered peers regardless of peering status (for fee calculation). */\n getAllDiscoveredPeers(): DiscoveredPeer[];\n /** Check if a pubkey has been actively peered with. */\n isPeered(pubkey: string): boolean;\n /** Count of registered (peered) peers. */\n getPeerCount(): number;\n /** Count of all discovered peers (including peered). */\n getDiscoveredCount(): number;\n /** Register an event listener. */\n on(listener: BootstrapEventListener): void;\n /** Unregister an event listener. */\n off(listener: BootstrapEventListener): void;\n /** Set connector admin for peer registration (required before peerWith). */\n setConnectorAdmin(admin: ConnectorAdminClient): void;\n /** Set channel client for payment channel opening (optional). */\n setChannelClient(client: ConnectorChannelClient): void;\n /** Get negotiation metadata for a peer (for lazy channel opening). */\n getPeerNegotiation(peerId: string):\n | {\n chain: string;\n chainType: string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n }\n | undefined;\n /** Mark pubkeys as already-peered (e.g., from bootstrap phase). */\n addExcludedPubkeys(pubkeys: string[]): void;\n}\n\n/**\n * Create a discovery tracker that processes kind:10032 events.\n *\n * The tracker does not own a subscription — callers feed events in via\n * processEvent(). It handles discovery, stale-event filtering,\n * deregistration, and explicit peering via peerWith().\n */\nexport function createDiscoveryTracker(\n config: DiscoveryTrackerConfig\n): DiscoveryTracker {\n const pubkey = getPublicKey(config.secretKey);\n const excludedPubkeys = new Set<string>([pubkey]);\n\n let connectorAdmin: ConnectorAdminClient | undefined;\n let _channelClient: ConnectorChannelClient | undefined;\n let listeners: BootstrapEventListener[] = [];\n\n /** Peers discovered via kind:10032 events (keyed by pubkey). */\n const discoveredPeers = new Map<string, DiscoveredPeer>();\n /** Pubkeys that have been actively peered with via peerWith(). */\n const peeredPubkeys = new Set<string>();\n /** Negotiation metadata per peer (for lazy channel opening). */\n const peerNegotiations = new Map<\n string,\n {\n chain: string;\n chainType: string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n }\n >();\n /** Timestamps of the latest kind:10032 event per pubkey (stale-event filtering). */\n const peerTimestamps = new Map<string, number>();\n\n function emit(event: BootstrapEvent): void {\n for (const listener of listeners) {\n try {\n listener(event);\n } catch {\n // Don't let listener errors break tracking\n }\n }\n }\n\n function handleDeregistration(peerPubkey: string, peerId: string): void {\n discoveredPeers.delete(peerPubkey);\n\n if (peeredPubkeys.has(peerPubkey)) {\n peeredPubkeys.delete(peerPubkey);\n\n if (connectorAdmin?.removePeer) {\n connectorAdmin\n .removePeer(peerId)\n .then(() => {\n emit({\n type: 'bootstrap:peer-deregistered',\n peerId,\n peerPubkey,\n reason: 'empty-content',\n });\n })\n .catch((error) => {\n console.warn(\n '[DiscoveryTracker] Failed to deregister %s: %s',\n peerId,\n error instanceof Error ? error.message : 'Unknown error'\n );\n });\n } else {\n emit({\n type: 'bootstrap:peer-deregistered',\n peerId,\n peerPubkey,\n reason: 'empty-content',\n });\n }\n }\n }\n\n function processDiscovery(event: NostrEvent): void {\n const peerId = `nostr-${event.pubkey.slice(0, 16)}`;\n\n let peerInfo;\n try {\n peerInfo = parseIlpPeerInfo(event);\n } catch {\n // Parse failure — treat as empty content\n }\n\n // Deregistration: empty content or missing ilpAddress\n if (\n !peerInfo ||\n !peerInfo.ilpAddress ||\n !event.content ||\n event.content.trim() === ''\n ) {\n handleDeregistration(event.pubkey, peerId);\n return;\n }\n\n // Track discovered peer (update if already known — newer timestamp)\n discoveredPeers.set(event.pubkey, {\n pubkey: event.pubkey,\n peerId,\n peerInfo,\n discoveredAt: event.created_at,\n });\n\n emit({\n type: 'bootstrap:peer-discovered',\n peerPubkey: event.pubkey,\n ilpAddress: peerInfo.ilpAddress,\n });\n }\n\n const tracker: DiscoveryTracker = {\n processEvent(event: NostrEvent): void {\n // Only process kind:10032 events\n if (event.kind !== ILP_PEER_INFO_KIND) return;\n\n // Exclude own pubkey and specified pubkeys\n if (excludedPubkeys.has(event.pubkey)) return;\n\n // Replaceable event semantics: skip stale events\n const lastSeen = peerTimestamps.get(event.pubkey) ?? 0;\n if (event.created_at <= lastSeen) return;\n peerTimestamps.set(event.pubkey, event.created_at);\n\n processDiscovery(event);\n },\n\n async peerWith(targetPubkey: string): Promise<void> {\n // Idempotent: skip if already peered\n if (peeredPubkeys.has(targetPubkey)) {\n return;\n }\n\n const discovered = discoveredPeers.get(targetPubkey);\n if (!discovered) {\n throw new BootstrapError(\n `Peer ${targetPubkey.slice(0, 16)}... not discovered yet`\n );\n }\n\n if (!connectorAdmin) {\n throw new BootstrapError(\n 'connectorAdmin must be set before calling peerWith()'\n );\n }\n\n const { peerId, peerInfo } = discovered;\n const admin = connectorAdmin;\n\n // Mark as peered immediately to prevent concurrent peerWith() calls\n // from double-registering the same peer.\n peeredPubkeys.add(targetPubkey);\n\n // Register peer via connector admin\n try {\n await admin.addPeer({\n id: peerId,\n url: peerInfo.btpEndpoint,\n authToken: '',\n routes: [{ prefix: peerInfo.ilpAddress }],\n });\n\n emit({\n type: 'bootstrap:peer-registered',\n peerId,\n peerPubkey: targetPubkey,\n ilpAddress: peerInfo.ilpAddress,\n });\n } catch (error) {\n // Rollback peered state on registration failure\n peeredPubkeys.delete(targetPubkey);\n const reason = error instanceof Error ? error.message : 'Unknown error';\n console.warn(\n '[DiscoveryTracker] Failed to register %s: %s',\n peerId,\n reason\n );\n emit({\n type: 'bootstrap:settlement-failed',\n peerId,\n reason: `Registration failed: ${reason}`,\n });\n return;\n }\n\n // Local chain selection — store negotiation metadata for lazy channel opening\n if (\n config.settlementInfo?.supportedChains?.length &&\n peerInfo.supportedChains?.length &&\n peerInfo.settlementAddresses\n ) {\n try {\n const negotiatedChain = negotiateSettlementChain(\n config.settlementInfo.supportedChains,\n peerInfo.supportedChains,\n config.settlementInfo.preferredTokens,\n peerInfo.preferredTokens\n );\n\n if (negotiatedChain) {\n const peerAddress = peerInfo.settlementAddresses[negotiatedChain];\n const tokenAddress = resolveTokenForChain(\n negotiatedChain,\n config.settlementInfo.preferredTokens,\n peerInfo.preferredTokens\n );\n const tokenNetwork = peerInfo.tokenNetworks?.[negotiatedChain];\n\n if (peerAddress) {\n // Store negotiation metadata for lazy channel opening\n peerNegotiations.set(peerId, {\n chain: negotiatedChain,\n chainType: negotiatedChain.split(':')[0] ?? negotiatedChain,\n settlementAddress: peerAddress,\n tokenAddress,\n tokenNetwork,\n });\n\n console.log(\n '[DiscoveryTracker] Negotiated %s with %s (lazy — channel deferred)',\n negotiatedChain,\n peerId\n );\n }\n }\n } catch (error) {\n const reason =\n error instanceof Error ? error.message : 'Unknown error';\n console.warn(\n '[DiscoveryTracker] Settlement negotiation failed for %s: %s',\n peerId,\n reason\n );\n emit({\n type: 'bootstrap:settlement-failed',\n peerId,\n reason,\n });\n // Non-fatal: peer remains registered\n }\n }\n },\n\n getDiscoveredPeers(): DiscoveredPeer[] {\n return [...discoveredPeers.values()].filter(\n (p) => !peeredPubkeys.has(p.pubkey)\n );\n },\n\n getAllDiscoveredPeers(): DiscoveredPeer[] {\n return [...discoveredPeers.values()];\n },\n\n isPeered(targetPubkey: string): boolean {\n return peeredPubkeys.has(targetPubkey);\n },\n\n getPeerCount(): number {\n return peeredPubkeys.size;\n },\n\n getDiscoveredCount(): number {\n return discoveredPeers.size;\n },\n\n on(listener: BootstrapEventListener): void {\n listeners.push(listener);\n },\n\n off(listener: BootstrapEventListener): void {\n listeners = listeners.filter((l) => l !== listener);\n },\n\n setConnectorAdmin(admin: ConnectorAdminClient): void {\n connectorAdmin = admin;\n },\n\n setChannelClient(client: ConnectorChannelClient): void {\n _channelClient = client;\n },\n\n getPeerNegotiation(peerId: string):\n | {\n chain: string;\n chainType: string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n }\n | undefined {\n return peerNegotiations.get(peerId);\n },\n\n addExcludedPubkeys(pubkeys: string[]): void {\n for (const pk of pubkeys) {\n excludedPubkeys.add(pk);\n }\n },\n };\n\n return tracker;\n}\n","/**\n * HTTP client for sending ILP packets via the connector's POST /ilp/send endpoint.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type { IlpClient, IlpSendResult } from './types.js';\n\n/**\n * Creates an HTTP-based IlpClient that sends ILP packets via POST /ilp/send.\n *\n * @param baseUrl - Base URL of the connector (e.g., \"http://localhost:3000\")\n * @returns An IlpClient instance\n * @throws BootstrapError if baseUrl is not a valid URL\n */\nexport function createHttpIlpClient(baseUrl: string): IlpClient {\n // Validate baseUrl is a valid URL\n try {\n new URL(baseUrl);\n } catch {\n throw new BootstrapError(`Invalid connector base URL: ${baseUrl}`);\n }\n\n // Normalize: remove trailing slash\n const normalizedUrl = baseUrl.replace(/\\/+$/, '');\n\n return {\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n let response: Response;\n try {\n response = await fetch(`${normalizedUrl}/admin/ilp/send`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n ...(params.timeout !== undefined && { timeout: params.timeout }),\n }),\n });\n } catch (error) {\n throw new BootstrapError(\n `Network error sending ILP packet: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error');\n throw new BootstrapError(\n `Connector error (${response.status}): ${text}`\n );\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return {\n accepted: (data['accepted'] ?? data['fulfilled'] ?? false) as boolean,\n data: data['data'] as string | undefined,\n code: data['code'] as string | undefined,\n message: data['message'] as string | undefined,\n };\n },\n };\n}\n\n/**\n * @deprecated Use createHttpIlpClient instead\n */\nexport const createHttpRuntimeClient = createHttpIlpClient;\n\n/**\n * @deprecated Use createHttpIlpClient instead\n */\nexport const createAgentRuntimeClient = createHttpIlpClient;\n","/**\n * Direct (in-process) client for sending ILP packets via a ConnectorNode.\n *\n * Unlike the HTTP-based createHttpIlpClient(), this factory wraps a\n * ConnectorNode's sendPacket() method as an IlpClient, enabling\n * zero-latency embedded mode without network overhead.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type { IlpClient, IlpSendResult } from './types.js';\n\n/**\n * Parameters accepted by ConnectorNode.sendPacket().\n */\nexport interface SendPacketParams {\n /** ILP destination address */\n destination: string;\n /** Amount as BigInt (not string) */\n amount: bigint;\n /** Binary data (not base64 string) */\n data?: Uint8Array;\n /**\n * Packet expiration. Included for structural compatibility with ConnectorNode\n * but is not set by the direct client — callers or the connector provide it\n * if needed.\n */\n expiresAt?: Date;\n}\n\n/**\n * Result returned by ConnectorNode.sendPacket().\n *\n * Accepts both string discriminants ('fulfill'/'reject') for backward\n * compatibility with test mocks, and numeric PacketType enum values\n * (13 = FULFILL, 14 = REJECT) used by @toon-protocol/connector@2.0.0+.\n */\nexport type SendPacketResult =\n | {\n type: 'fulfill';\n data?: Uint8Array | Buffer;\n }\n | { type: 13; data?: Uint8Array | Buffer }\n | {\n type: 'reject';\n code: string;\n message: string;\n data?: Uint8Array | Buffer;\n }\n | { type: 14; code: string; message: string; data?: Uint8Array | Buffer };\n\n/**\n * Structural interface matching ConnectorNode's sendPacket() method.\n *\n * Consumers pass a `@toon-protocol/connector` ConnectorNode instance without\n * needing to import `@toon-protocol/connector` as a dependency.\n * TypeScript's structural type system handles compatibility automatically.\n */\nexport interface ConnectorNodeLike {\n sendPacket(params: SendPacketParams): Promise<SendPacketResult>;\n}\n\n/**\n * @deprecated No longer used. Kept for backward compatibility.\n */\nexport interface DirectRuntimeClientConfig {\n /** @deprecated No longer used. */\n toonDecoder?: (bytes: Uint8Array) => { id: string };\n}\n\n/**\n * Creates an IlpClient that sends ILP packets by calling\n * `connector.sendPacket()` directly (no HTTP).\n *\n * @param connector - A ConnectorNode-like object with a sendPacket() method\n * @param config - Optional configuration (e.g., toonDecoder for condition computation)\n * @returns An IlpClient instance\n */\nexport function createDirectIlpClient(\n connector: ConnectorNodeLike,\n _config?: DirectRuntimeClientConfig\n): IlpClient {\n return {\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n try {\n // Convert string amount to BigInt\n const amount = BigInt(params.amount);\n\n // Convert base64 data to Uint8Array\n const data = Uint8Array.from(Buffer.from(params.data, 'base64'));\n\n // Call connector.sendPacket()\n // expiresAt is required by ConnectorNode.sendPacket() even though the\n // interface marks it optional. Default to 30 seconds from now.\n const result = await connector.sendPacket({\n destination: params.destination,\n amount,\n data,\n expiresAt: new Date(Date.now() + 30000),\n });\n\n // Map result to IlpSendResult\n // ConnectorNode returns PacketType enum (13=FULFILL, 14=REJECT)\n if (result.type === 'fulfill' || result.type === 13) {\n return {\n accepted: true,\n data: result.data\n ? Buffer.from(result.data).toString('base64')\n : undefined,\n };\n }\n\n // Reject\n return {\n accepted: false,\n code: result.code,\n message: result.message,\n data: result.data\n ? Buffer.from(result.data).toString('base64')\n : undefined,\n };\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Direct ILP packet send failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n\n/**\n * @deprecated Use createDirectIlpClient instead\n */\nexport const createDirectRuntimeClient = createDirectIlpClient;\n","import { BootstrapError } from './BootstrapService.js';\nimport type { ConnectorAdminClient } from './types.js';\n\n/**\n * Parameters for registering a peer on the connector.\n * Maps to ConnectorNode.registerPeer() params in @toon-protocol/connector.\n */\nexport interface RegisterPeerParams {\n id: string;\n url: string;\n authToken?: string;\n routes?: { prefix: string; priority?: number }[];\n settlement?: Record<string, unknown>;\n}\n\n/**\n * Structural interface for ConnectorNode admin methods.\n *\n * This is a structural interface — consumers pass an `@toon-protocol/connector`\n * ConnectorNode instance without importing it as a dependency. The ConnectorNode's\n * `registerPeer()` and `removePeer()` methods match this shape, allowing\n * zero-dependency coupling for embedded mode.\n */\nexport interface ConnectorAdminLike {\n registerPeer(params: RegisterPeerParams): Promise<void>;\n removePeer(peerId: string): Promise<void>;\n}\n\n/**\n * Creates a ConnectorAdminClient that calls ConnectorNode methods directly\n * instead of making HTTP requests.\n *\n * Used for embedded mode where the ConnectorNode runs in-process.\n * The returned client conforms to the ConnectorAdminClient interface,\n * making it a drop-in replacement for the HTTP-based admin client.\n *\n * @param connector - A ConnectorNode instance (or any object matching ConnectorAdminLike)\n * @returns A ConnectorAdminClient that manages peers via direct function calls\n *\n * @example\n * ```typescript\n * import { ConnectorNode } from '@toon-protocol/connector';\n * import { createDirectConnectorAdmin } from '@toon-protocol/core/bootstrap';\n *\n * const connector = new ConnectorNode({ ... });\n * const adminClient = createDirectConnectorAdmin(connector);\n *\n * await adminClient.addPeer({\n * id: 'peer1',\n * url: 'btp+ws://peer1.example.com',\n * authToken: 'secret',\n * routes: [{ prefix: 'g.alice', priority: 1 }],\n * });\n * ```\n */\nexport function createDirectConnectorAdmin(\n connector: ConnectorAdminLike\n): ConnectorAdminClient {\n return {\n async addPeer(config) {\n try {\n await connector.registerPeer({\n id: config.id,\n url: config.url,\n authToken: config.authToken,\n routes: config.routes,\n settlement: config.settlement,\n });\n } catch (error) {\n // Re-throw BootstrapError as-is; wrap other errors\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to register peer ${config.id}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async removePeer(peerId) {\n try {\n await connector.removePeer(peerId);\n } catch (error) {\n // Re-throw BootstrapError as-is; wrap other errors\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to remove peer ${peerId}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n","/**\n * Direct (in-process) client for payment channel operations via a ConnectorNode.\n *\n * Unlike HTTP-based channel clients, this factory wraps a ConnectorNode's\n * openChannel() and getChannelState() methods as a ConnectorChannelClient,\n * enabling zero-latency embedded mode without network overhead.\n *\n * Requires @toon-protocol/connector >=1.2.0 which exposes these methods\n * as public API on ConnectorNode.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '../types.js';\n\n/**\n * Structural interface matching ConnectorNode's channel methods.\n *\n * Consumers pass an `@toon-protocol/connector` ConnectorNode instance without\n * toon needing to import `@toon-protocol/connector` as a dependency.\n * TypeScript's structural type system handles compatibility automatically.\n */\nexport interface ConnectorChannelLike {\n openChannel(params: {\n peerId: string;\n chain: string;\n token?: string;\n tokenNetwork?: string;\n peerAddress: string;\n initialDeposit?: string;\n settlementTimeout?: number;\n }): Promise<{ channelId: string; status: string }>;\n\n getChannelState(channelId: string): Promise<{\n channelId: string;\n status: 'opening' | 'open' | 'closed' | 'settled';\n chain: string;\n }>;\n}\n\n/**\n * Creates a ConnectorChannelClient that calls ConnectorNode methods directly\n * instead of making HTTP requests.\n *\n * Used for embedded mode where the ConnectorNode runs in-process.\n * The returned client conforms to the ConnectorChannelClient interface,\n * making it a drop-in replacement for the HTTP-based channel client.\n *\n * @param connector - A ConnectorNode instance (or any object matching ConnectorChannelLike)\n * @returns A ConnectorChannelClient that manages channels via direct function calls\n *\n * @example\n * ```typescript\n * import { ConnectorNode } from '@toon-protocol/connector';\n * import { createDirectChannelClient } from '@toon-protocol/core/bootstrap';\n *\n * const connector = new ConnectorNode({ ... });\n * const channelClient = createDirectChannelClient(connector);\n *\n * const result = await channelClient.openChannel({\n * peerId: 'nostr-54dad746e52dab00',\n * chain: 'evm:base:84532',\n * peerAddress: '0x6AFbC4...',\n * });\n * ```\n */\nexport function createDirectChannelClient(\n connector: ConnectorChannelLike\n): ConnectorChannelClient {\n return {\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n try {\n return await connector.openChannel(params);\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to open channel for peer ${params.peerId}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async getChannelState(channelId: string): Promise<ChannelState> {\n try {\n return await connector.getChannelState(channelId);\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to get channel state for ${channelId}`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n","/**\n * HTTP-based client for connector admin operations.\n *\n * Calls the connector's Admin API over HTTP to register/remove peers.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type { ConnectorAdminClient } from './types.js';\n\n/**\n * Creates a ConnectorAdminClient that calls the Connector Admin API via HTTP.\n *\n * @param adminUrl - Base URL of the connector admin API (e.g., \"http://connector:8081\")\n * @param btpSecret - Shared secret for BTP authentication\n * @returns A ConnectorAdminClient that manages peers via HTTP\n *\n * @example\n * ```typescript\n * const adminClient = createHttpConnectorAdmin('http://localhost:8091', 'secret');\n *\n * await adminClient.addPeer({\n * id: 'peer2',\n * url: 'ws://connector-peer2:3000',\n * authToken: JSON.stringify({ peerId: 'peer2', secret: 'secret' }),\n * routes: [{ prefix: 'g.toon.peer2', priority: 0 }],\n * settlement: {\n * preference: 'evm',\n * evmAddress: '0x...',\n * chainId: 31337,\n * },\n * });\n * ```\n */\nexport function createHttpConnectorAdmin(\n adminUrl: string,\n btpSecret: string\n): ConnectorAdminClient {\n const baseUrl = adminUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n return {\n async addPeer(config) {\n try {\n const payload = {\n id: config.id,\n url: config.url,\n authToken:\n config.authToken ||\n JSON.stringify({\n peerId: config.id,\n secret: btpSecret,\n }),\n routes: config.routes,\n settlement: config.settlement,\n };\n\n const response = await fetch(`${baseUrl}/admin/peers`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n // Success - peer registered\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to register peer ${config.id} via HTTP`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async removePeer(peerId) {\n try {\n const response = await fetch(`${baseUrl}/admin/peers/${peerId}`, {\n method: 'DELETE',\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n // Success - peer removed\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to remove peer ${peerId} via HTTP`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n","/**\n * HTTP-based client for sending ILP packets via the connector API.\n *\n * Calls the connector's packet handling endpoint over HTTP.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type { IlpClient, IlpSendResult } from './types.js';\n\n/**\n * Creates an IlpClient that sends ILP packets via HTTP.\n *\n * @param connectorUrl - Base URL of the connector (e.g., \"http://connector:8080\")\n * @returns An IlpClient instance\n *\n * @example\n * ```typescript\n * const ilpClient = createHttpIlpClient('http://localhost:8081');\n *\n * const result = await ilpClient.sendIlpPacket({\n * destination: 'g.toon.peer2',\n * amount: '1000',\n * data: base64EncodedToon,\n * });\n * ```\n */\nexport function createHttpIlpClient(connectorUrl: string): IlpClient {\n const baseUrl = connectorUrl.replace(/\\/$/, '');\n\n return {\n async sendIlpPacket(params: {\n destination: string;\n amount: string;\n data: string;\n timeout?: number;\n }): Promise<IlpSendResult> {\n try {\n const response = await fetch(`${baseUrl}/send-packet`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n destination: params.destination,\n amount: params.amount,\n data: params.data,\n }),\n signal: params.timeout\n ? AbortSignal.timeout(params.timeout)\n : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n accepted: false,\n code: 'T00',\n message: `HTTP ${response.status}: ${text}`,\n };\n }\n\n const result = (await response.json()) as Record<string, unknown>;\n\n // Map HTTP response to IlpSendResult\n if (result['accept'] || result['accepted']) {\n return {\n accepted: true,\n data: result['data'] as string | undefined,\n };\n } else {\n return {\n accepted: false,\n code: (result['code'] as string) || 'T00',\n message: (result['message'] as string) || 'Unknown error',\n };\n }\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n return {\n accepted: false,\n code: 'T00',\n message: `HTTP request failed: ${error instanceof Error ? error.message : 'Unknown'}`,\n };\n }\n },\n };\n}\n","/**\n * HTTP-based client for payment channel operations.\n *\n * Calls the connector's Admin API to open channels and query channel state.\n */\n\nimport { BootstrapError } from './BootstrapService.js';\nimport type {\n ConnectorChannelClient,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n} from '../types.js';\n\n/**\n * Creates a ConnectorChannelClient that calls the Connector Admin API via HTTP.\n *\n * @param adminUrl - Base URL of the connector admin API (e.g., \"http://connector:8081\")\n * @returns A ConnectorChannelClient that manages channels via HTTP\n *\n * @example\n * ```typescript\n * const channelClient = createHttpChannelClient('http://localhost:8091');\n *\n * const result = await channelClient.openChannel({\n * peerId: 'peer2',\n * chain: 'evm:base:31337',\n * peerAddress: '0x90F79bf6EB2c4f870365E785982E1f101E93b906',\n * initialDeposit: '100000',\n * });\n * ```\n */\nexport function createHttpChannelClient(\n adminUrl: string\n): ConnectorChannelClient {\n const baseUrl = adminUrl.replace(/\\/$/, '');\n\n return {\n async openChannel(params: OpenChannelParams): Promise<OpenChannelResult> {\n try {\n const response = await fetch(`${baseUrl}/admin/channels`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n peerId: params.peerId,\n chain: params.chain,\n token: params.token,\n tokenNetwork: params.tokenNetwork,\n peerAddress: params.peerAddress,\n initialDeposit: params.initialDeposit,\n settlementTimeout: params.settlementTimeout,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n const result = (await response.json()) as Record<string, unknown>;\n return {\n channelId: result['channelId'] as string,\n status: result['status'] as string,\n };\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to open channel for peer ${params.peerId} via HTTP`,\n error instanceof Error ? error : undefined\n );\n }\n },\n\n async getChannelState(channelId: string): Promise<ChannelState> {\n try {\n const response = await fetch(`${baseUrl}/admin/channels/${channelId}`);\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n const result = (await response.json()) as Record<string, unknown>;\n return {\n channelId: result['channelId'] as string,\n status: result['status'] as ChannelState['status'],\n chain: result['chain'] as string,\n };\n } catch (error) {\n if (error instanceof BootstrapError) {\n throw error;\n }\n throw new BootstrapError(\n `Failed to get channel state for ${channelId} via HTTP`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n","/**\n * AttestationBootstrap -- attestation-first seed relay bootstrap for Story 4.6.\n *\n * Implements FR-TEE-6: the bootstrap trust flow that verifies kind:10033 TEE\n * attestation on each seed relay BEFORE trusting its kind:10032 peer list.\n * This prevents seed relay list poisoning (R-E4-004).\n *\n * Trust flow:\n * 1. Read seed relay list from kind:10036 (Story 3.4)\n * 2. Connect to seed relay\n * 3. Query kind:10033 attestation\n * 4. Verify PCR measurement (Story 4.3)\n * 5. If valid -> subscribe to kind:10032 -> discover peers\n * 6. If invalid -> fall back to next seed relay\n *\n * This is a pure orchestration class with no transport logic. The\n * `queryAttestation` and `subscribePeers` callbacks are injected via DI,\n * keeping the class fully testable without WebSocket mocks.\n *\n * Decision 12 invariant: \"Trust degrades; money doesn't.\" This class\n * never touches payment channel state.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { VerificationResult } from './AttestationVerifier.js';\n\n// ---------- Types ----------\n\n/**\n * Configuration for the attestation-first bootstrap flow.\n */\nexport interface AttestationBootstrapConfig {\n /** Seed relay WebSocket URLs from kind:10036 */\n seedRelays: string[];\n /**\n * Nostr secret key for signing (reserved for future use).\n * @remarks Stored in config but not accessed by bootstrap(). Maintained for\n * API consistency with BootstrapService and future subscription signing.\n */\n secretKey: Uint8Array;\n /**\n * Verifier instance (or mock) with verify method.\n *\n * The DI interface accepts NostrEvent (the raw attestation event) and\n * returns boolean | Promise<boolean> | VerificationResult to support\n * both the real verifier (after caller extracts TeeAttestation) and\n * test mocks (which return Promise<boolean> via mockResolvedValue).\n *\n * The implementation normalizes via: await Promise.resolve(verifier.verify(event))\n */\n verifier: {\n verify: (\n attestation: NostrEvent\n ) => boolean | VerificationResult | Promise<boolean | VerificationResult>;\n getState?: (...args: unknown[]) => unknown;\n };\n /** DI callback: query a relay for its kind:10033 attestation event */\n queryAttestation: (relayUrl: string) => Promise<NostrEvent | null>;\n /** DI callback: subscribe to a relay's kind:10032 peer info events */\n subscribePeers: (relayUrl: string) => Promise<NostrEvent[]>;\n}\n\n/**\n * Result of the attestation-first bootstrap flow.\n */\nexport interface AttestationBootstrapResult {\n /** 'attested' if at least one seed relay passed verification, 'degraded' otherwise */\n mode: 'attested' | 'degraded';\n /** URL of the first seed relay that passed attestation (undefined in degraded mode) */\n attestedSeedRelay?: string;\n /** Peer info events discovered from attested seed relays */\n discoveredPeers: NostrEvent[];\n}\n\n/**\n * Lifecycle events emitted during attestation-first bootstrap.\n */\nexport type AttestationBootstrapEvent =\n | { type: 'attestation:seed-connected'; relayUrl: string }\n | { type: 'attestation:verified'; relayUrl: string; pubkey: string }\n | {\n type: 'attestation:verification-failed';\n relayUrl: string;\n reason: string;\n }\n | {\n type: 'attestation:peers-discovered';\n relayUrl: string;\n peerCount: number;\n }\n | { type: 'attestation:degraded'; triedCount: number };\n\n/** Listener callback for attestation bootstrap events. */\nexport type AttestationBootstrapEventListener = (\n event: AttestationBootstrapEvent\n) => void;\n\n// ---------- Class ----------\n\n/**\n * Orchestrates attestation-first seed relay bootstrap.\n *\n * Iterates seed relays sequentially, verifying kind:10033 attestation\n * before subscribing to kind:10032 peer info events. Falls back to\n * degraded mode when all seed relays fail attestation verification.\n */\nexport class AttestationBootstrap {\n private readonly config: AttestationBootstrapConfig;\n private listeners: AttestationBootstrapEventListener[] = [];\n\n constructor(config: AttestationBootstrapConfig) {\n this.config = config;\n }\n\n /**\n * Register an event listener.\n */\n on(listener: AttestationBootstrapEventListener): void {\n this.listeners.push(listener);\n }\n\n /**\n * Unregister an event listener.\n */\n off(listener: AttestationBootstrapEventListener): void {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n /**\n * Emit an attestation bootstrap event to all listeners.\n */\n private emit(event: AttestationBootstrapEvent): void {\n // Defensive copy: safe if a listener calls on()/off() during emission\n const snapshot = [...this.listeners];\n for (const listener of snapshot) {\n try {\n listener(event);\n } catch {\n // Don't let listener errors break bootstrap\n }\n }\n }\n\n /**\n * Execute the attestation-first bootstrap flow.\n *\n * Iterates seed relays in order:\n * 1. Emit seed-connected, query attestation\n * 2. If null or verify fails or error: emit verification-failed, try next\n * 3. If valid: emit verified, subscribe peers, emit peers-discovered\n * 4. Return attested result with discovered peers\n * 5. If ALL fail: log warning, emit degraded, return degraded result\n */\n async bootstrap(): Promise<AttestationBootstrapResult> {\n const { seedRelays, verifier, queryAttestation, subscribePeers } =\n this.config;\n\n for (const relayUrl of seedRelays) {\n this.emit({ type: 'attestation:seed-connected', relayUrl });\n\n try {\n const attestationEvent = await queryAttestation(relayUrl);\n\n if (attestationEvent === null) {\n this.emit({\n type: 'attestation:verification-failed',\n relayUrl,\n reason: 'No attestation event found',\n });\n continue;\n }\n\n // Normalize verify result: handles boolean, Promise<boolean>,\n // VerificationResult, and Promise<VerificationResult>\n const raw = await Promise.resolve(verifier.verify(attestationEvent));\n const isValid =\n typeof raw === 'boolean'\n ? raw\n : typeof raw === 'object' && raw !== null && 'valid' in raw\n ? (raw as VerificationResult).valid\n : false;\n\n if (!isValid) {\n this.emit({\n type: 'attestation:verification-failed',\n relayUrl,\n reason: 'Attestation verification failed',\n });\n continue;\n }\n\n // Attestation passed -- proceed to peer discovery\n this.emit({\n type: 'attestation:verified',\n relayUrl,\n pubkey: attestationEvent.pubkey,\n });\n\n const peers = await subscribePeers(relayUrl);\n\n this.emit({\n type: 'attestation:peers-discovered',\n relayUrl,\n peerCount: peers.length,\n });\n\n return {\n mode: 'attested',\n attestedSeedRelay: relayUrl,\n discoveredPeers: peers,\n };\n } catch (error: unknown) {\n // Callback errors (WebSocket failures, DNS, timeouts) treated as\n // failed attestation -- fall back to next relay.\n // NOTE: This catch covers both queryAttestation and subscribePeers\n // errors. When subscribePeers throws, the emitted event type is\n // 'attestation:verification-failed' even though attestation may have\n // passed. This is a known simplification -- adding a separate\n // 'attestation:peers-discovery-failed' event type is deferred.\n const reason = error instanceof Error ? error.message : 'Unknown error';\n this.emit({\n type: 'attestation:verification-failed',\n relayUrl,\n reason,\n });\n }\n }\n\n // All seed relays failed attestation -- degraded mode\n console.warn(\n `No attested seed relays found. Tried ${String(seedRelays.length)} relays. Starting in degraded mode.`\n );\n\n this.emit({\n type: 'attestation:degraded',\n triedCount: seedRelays.length,\n });\n\n return {\n mode: 'degraded',\n attestedSeedRelay: undefined,\n discoveredPeers: [],\n };\n }\n}\n","/**\n * ILP wire-code → connector semantic-reason translation.\n *\n * Connector v3.3.2 introduced the contract: its payment-handler adapter\n * (`payment-handler.js`'s `mapRejectCode()`) takes a SEMANTIC reason key\n * (e.g. `'internal_error'`) and looks it up in `REJECT_CODE_MAP` to produce\n * the wire code (`'T00'`). If a caller passes a raw wire code (`'T00'`) it\n * is not a key in the map, so the connector falls back to the generic\n * `'F99'` code — collapsing every reject reason regardless of its true\n * cause.\n *\n * The TOON SDK / handlers express rejections via `ctx.reject(ilpCode, msg)`\n * using ILP wire codes (T00, F00, F02, F03, F04, F06, T04, R00, ...). This\n * helper inverts the connector's `REJECT_CODE_MAP` so callers can translate\n * back to the semantic key the connector now expects.\n *\n * Source of truth for the connector's accepted keys:\n * `@toon-protocol/connector` v3.3.3 — `core/payment-handler.ts`'s\n * `REJECT_CODE_MAP` and the `AcceptedSemanticCode` literal-union it\n * `satisfies`. v3.3.3 added `unreachable` (F02) and\n * `insufficient_destination_amount` (F04). The `satisfies` constraint\n * structurally prevents drift between the published vocabulary and the\n * wire-code mapping.\n */\n\n/**\n * Inverse of the connector's `REJECT_CODE_MAP`.\n *\n * Maps every ILP wire code the SDK currently emits to a semantic reason\n * the connector recognises. Codes outside this set fall back to\n * `'invalid_request'` (`'F00'`) so we still produce a meaningful,\n * non-`F99` wire code at the far side.\n */\nexport const ILP_TO_SEMANTIC: Readonly<Record<string, string>> = Object.freeze({\n T00: 'internal_error',\n T04: 'insufficient_funds',\n F00: 'invalid_request',\n // F01 (\"Invalid Packet\" in ILP terms — emitted by the swap handler for\n // \"Invalid gift wrap\" / \"Invalid amount\") has NO dedicated entry in the\n // connector's REJECT_CODE_MAP (accepted semantics are: insufficient_funds,\n // expired, unreachable, invalid_request, invalid_amount,\n // insufficient_destination_amount, unexpected_payment, application_error,\n // internal_error, timeout). The closest faithful reason is `invalid_request`,\n // which the connector re-encodes to wire code F00. We map F01 EXPLICITLY\n // (rather than relying on the fallback below) so the F01 -> F00 normalization\n // is intentional and test-pinned, not a silent collapse that misleads callers\n // into thinking they hit a different failure class. See issue #86.\n F01: 'invalid_request',\n F02: 'unreachable',\n F03: 'invalid_amount',\n F04: 'insufficient_destination_amount',\n F06: 'unexpected_payment',\n R00: 'expired',\n});\n\n/**\n * Translate an ILP wire code (T00, F00, ...) to the semantic reason the\n * connector's `mapRejectCode()` expects. Falls back to `'invalid_request'`\n * for unknown codes — yields `'F00'` at the wire instead of the generic\n * `'F99'`.\n */\nexport function ilpCodeToSemantic(ilpCode: string): string {\n return ILP_TO_SEMANTIC[ilpCode] ?? 'invalid_request';\n}\n","/**\n * TOON Node composition API.\n *\n * Provides createToonNode() — a single composition function that wires\n * ConnectorNode ↔ BLS ↔ BootstrapService ↔ DiscoveryTracker into one object\n * with start() / stop() lifecycle, enabling zero-latency embedded mode without\n * manually wiring each component.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { IlpPeerInfo, ConnectorChannelClient } from './types.js';\nimport type {\n KnownPeer,\n BootstrapResult,\n SettlementConfig,\n} from './bootstrap/types.js';\nimport type {\n SendPacketParams,\n SendPacketResult,\n} from './bootstrap/direct-ilp-client.js';\nimport type { RegisterPeerParams } from './bootstrap/direct-connector-admin.js';\nimport type { IlpClient } from './bootstrap/types.js';\nimport {\n BootstrapService,\n BootstrapError,\n} from './bootstrap/BootstrapService.js';\nimport { createDiscoveryTracker } from './bootstrap/discovery-tracker.js';\nimport type { DiscoveryTracker } from './bootstrap/discovery-tracker.js';\nimport { createDirectIlpClient } from './bootstrap/direct-ilp-client.js';\nimport { createDirectConnectorAdmin } from './bootstrap/direct-connector-admin.js';\nimport { createDirectChannelClient } from './bootstrap/direct-channel-client.js';\nimport { ilpCodeToSemantic } from './utils/reject-code.js';\n\n/**\n * Structural type for incoming ILP packet handler request.\n *\n * Matches the shape of BLS HandlePacketRequest without creating a cross-package\n * dependency from @toon-protocol/core → @toon-protocol/bls.\n */\nexport interface HandlePacketRequest {\n /** Payment amount as string (parsed to bigint) */\n amount: string;\n /** ILP destination address */\n destination: string;\n /** Base64-encoded TOON Nostr event */\n data: string;\n /** Source ILP address */\n sourceAccount?: string;\n}\n\n/**\n * Structural type for ILP packet handler accept response.\n */\nexport interface HandlePacketAcceptResponse {\n accept: true;\n /** Base64-encoded response data (e.g., TOON-encoded response for relay back in ILP FULFILL) */\n data?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Structural type for ILP packet handler reject response.\n */\nexport interface HandlePacketRejectResponse {\n accept: false;\n /** ILP error code (F00, F06, T00) */\n code: string;\n /** Human-readable error message */\n message: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Union type for ILP packet handler response.\n */\nexport type HandlePacketResponse =\n | HandlePacketAcceptResponse\n | HandlePacketRejectResponse;\n\n/**\n * Callback invoked by the connector for incoming ILP packets.\n *\n * **NOTE:** This is a **function** (not a BusinessLogicServer instance) because\n * packet handling logic lives in the caller's entrypoint code, not in\n * BusinessLogicServer. The caller provides the full handler that knows how to\n * process incoming events.\n */\nexport type PacketHandler = (\n request: HandlePacketRequest\n) => HandlePacketResponse | Promise<HandlePacketResponse>;\n\n/**\n * Structural interface for the full embedded connector API.\n *\n * Combines:\n * 1. ConnectorNodeLike (sendPacket) — for outbound ILP packets\n * 2. ConnectorAdminLike (registerPeer, removePeer) — for peer management\n * 3. setPacketHandler(handler) — for registering the incoming packet callback\n *\n * This structural interface allows @toon-protocol/connector's ConnectorNode to\n * be passed directly without importing it as a dependency.\n */\nexport interface EmbeddableConnectorLike {\n /** Send an outbound ILP packet */\n sendPacket(params: SendPacketParams): Promise<SendPacketResult>;\n /** Register a peer with the connector */\n registerPeer(params: RegisterPeerParams): Promise<void>;\n /** Remove a peer from the connector */\n removePeer(peerId: string): Promise<void>;\n /**\n * Register the incoming packet handler callback.\n * The connector invokes this handler for each incoming ILP packet.\n * Optional in HTTP mode where packets are delivered via local delivery HTTP endpoint.\n */\n setPacketHandler?(\n handler: (\n request: HandlePacketRequest\n ) => HandlePacketResponse | Promise<HandlePacketResponse>\n ): void;\n /**\n * Open a payment channel via the connector's settlement layer.\n * Optional — only available on ConnectorNode >=1.2.0.\n */\n openChannel?(params: {\n peerId: string;\n chain: string;\n token?: string;\n tokenNetwork?: string;\n peerAddress: string;\n initialDeposit?: string;\n settlementTimeout?: number;\n }): Promise<{ channelId: string; status: string }>;\n /**\n * Get the state of a payment channel.\n * Optional — only available on ConnectorNode >=1.2.0.\n */\n getChannelState?(channelId: string): Promise<{\n channelId: string;\n status: 'opening' | 'open' | 'closed' | 'settled';\n chain: string;\n }>;\n}\n\n/**\n * Configuration for creating an TOON Node.\n */\nexport interface ToonNodeConfig {\n /** The ConnectorNode instance (embeddable connector) */\n connector: EmbeddableConnectorLike;\n /**\n * Callback for incoming ILP packets.\n *\n * **NOTE:** Provided as a **function** (not a BLS instance) because packet\n * handling logic lives in the caller's entrypoint code.\n */\n handlePacket: PacketHandler;\n /** Nostr secret key (32 bytes) */\n secretKey: Uint8Array;\n /** Own ILP peer info (ilpAddress, btpEndpoint, assetCode, assetScale) */\n ilpInfo: IlpPeerInfo;\n /**\n * TOON encoder — **required** for encoding Nostr events to binary.\n * Used by BootstrapService.\n */\n toonEncoder: (event: NostrEvent) => Uint8Array;\n /**\n * TOON decoder — **required** for decoding binary to Nostr events.\n * Used by BootstrapService and DirectRuntimeClient.\n */\n toonDecoder: (bytes: Uint8Array) => NostrEvent;\n /** Relay WebSocket URL for monitoring (default: 'ws://localhost:7100') */\n relayUrl?: string;\n /** Initial bootstrap peers (default: []) */\n knownPeers?: KnownPeer[];\n /** Optional settlement preferences for peer registration */\n settlementInfo?: SettlementConfig;\n /** Base price per byte for ILP packet pricing (default: 10n) */\n basePricePerByte?: bigint;\n /** Enable ArDrive peer lookup (default: true) */\n ardriveEnabled?: boolean;\n /** Default relay URL for ArDrive-sourced peers that lack relay URLs (default: '') */\n defaultRelayUrl?: string;\n /** Timeout for relay queries in milliseconds (default: 5000) */\n queryTimeout?: number;\n /** Optional extra peers JSON for bootstrap */\n additionalPeersJson?: string;\n}\n\n/**\n * Result returned by ToonNode.start().\n */\nexport interface ToonNodeStartResult {\n /** Results from the bootstrap phase */\n bootstrapResults: BootstrapResult[];\n /** Number of peers successfully bootstrapped */\n peerCount: number;\n /** Number of payment channels opened */\n channelCount: number;\n}\n\n/**\n * TOON Node instance with lifecycle methods.\n */\nexport interface ToonNode {\n /**\n * Wire components and run bootstrap.\n * Throws BootstrapError if already started or on bootstrap failure.\n */\n start(): Promise<ToonNodeStartResult>;\n /**\n * Tear down and clean up.\n * Safe to call when not started (no-op).\n */\n stop(): Promise<void>;\n /**\n * Read-only access to the bootstrap service.\n * Allows attaching event listeners before calling start().\n */\n readonly bootstrapService: BootstrapService;\n /**\n * Read-only access to the discovery tracker.\n * Allows attaching event listeners before calling start().\n */\n readonly discoveryTracker: DiscoveryTracker;\n /**\n * Channel client for payment channel operations.\n * Null if the connector does not expose openChannel()/getChannelState().\n * Available when using @toon-protocol/connector >=1.2.0.\n */\n readonly channelClient: ConnectorChannelClient | null;\n /**\n * Read-only access to the ILP client for sending packets.\n * Used by ServiceNode.publishEvent() to send outbound events.\n */\n readonly ilpClient: IlpClient;\n /**\n * Initiate peering with a discovered peer.\n * The peer must have been discovered by the discovery tracker first.\n * Registers the peer with the connector and attempts settlement.\n */\n peerWith(pubkey: string): Promise<void>;\n}\n\n/**\n * Create a TOON Node with integrated bootstrap and discovery tracking.\n *\n * This composition function wires ConnectorNode ↔ DirectRuntimeClient ↔\n * DirectConnectorAdmin ↔ BootstrapService ↔ DiscoveryTracker into a single\n * object with start() / stop() lifecycle, enabling zero-latency embedded mode\n * without manually wiring each component.\n *\n * @param config - Configuration for the node\n * @returns ToonNode instance with start() / stop() methods\n *\n * @example\n * ```typescript\n * import { ConnectorNode } from '@toon-protocol/connector';\n * import { createToonNode } from '@toon-protocol/core/compose';\n * import { encodeEvent, decodeEvent } from '@toon-protocol/relay';\n *\n * const connector = new ConnectorNode({ ... });\n *\n * const node = createToonNode({\n * connector,\n * handlePacket: async (req) => { ... },\n * secretKey: new Uint8Array(32),\n * ilpInfo: { ilpAddress: 'g.example', ... },\n * toonEncoder: encodeEvent,\n * toonDecoder: decodeEvent,\n * });\n *\n * // Attach event listeners before start\n * node.bootstrapService.on((event) => console.log('bootstrap:', event));\n * node.discoveryTracker.on((event) => console.log('discovery:', event));\n *\n * // Start the node\n * const result = await node.start();\n * console.log(`Bootstrapped ${result.peerCount} peers`);\n *\n * // Clean up\n * await node.stop();\n * ```\n */\nexport function createToonNode(config: ToonNodeConfig): ToonNode {\n // Create direct clients for zero-latency embedded mode\n const directIlpClient = createDirectIlpClient(config.connector, {\n toonDecoder: config.toonDecoder,\n });\n\n const directAdminClient = createDirectConnectorAdmin(config.connector);\n\n // Create direct channel client if connector supports channel methods\n const channelClient: ConnectorChannelClient | null =\n config.connector.openChannel && config.connector.getChannelState\n ? createDirectChannelClient(\n config.connector as Required<\n Pick<EmbeddableConnectorLike, 'openChannel' | 'getChannelState'>\n >\n )\n : null;\n\n // Create BootstrapService with mapped config\n const bootstrapService = new BootstrapService(\n {\n knownPeers: config.knownPeers ?? [],\n ardriveEnabled: config.ardriveEnabled ?? true,\n defaultRelayUrl: config.defaultRelayUrl ?? '',\n queryTimeout: config.queryTimeout ?? 5000,\n settlementInfo: config.settlementInfo,\n ownIlpAddress: config.ilpInfo.ilpAddress,\n toonEncoder: config.toonEncoder,\n toonDecoder: config.toonDecoder,\n basePricePerByte: config.basePricePerByte ?? 10n,\n },\n config.secretKey,\n config.ilpInfo\n );\n\n // Wire clients to bootstrap service\n bootstrapService.setIlpClient(directIlpClient);\n bootstrapService.setConnectorAdmin(directAdminClient);\n if (channelClient) {\n bootstrapService.setChannelClient(channelClient);\n }\n\n // Create DiscoveryTracker\n const discoveryTracker = createDiscoveryTracker({\n secretKey: config.secretKey,\n settlementInfo: config.settlementInfo,\n });\n\n // Wire clients to discovery tracker\n discoveryTracker.setConnectorAdmin(directAdminClient);\n if (channelClient) {\n discoveryTracker.setChannelClient(channelClient);\n }\n\n // Track lifecycle state\n let started = false;\n\n return {\n bootstrapService,\n discoveryTracker,\n channelClient,\n ilpClient: directIlpClient,\n\n peerWith(pubkey: string): Promise<void> {\n return discoveryTracker.peerWith(pubkey);\n },\n\n async start(): Promise<ToonNodeStartResult> {\n // Guard against double-start\n if (started) {\n throw new BootstrapError('ToonNode already started');\n }\n\n try {\n // Wire the handlePacket callback to the connector (if supported)\n // HTTP mode uses local delivery instead of callback.\n // Adapter translates HandlePacketResponse (flat code/message) to\n // connector PaymentResponse (rejectReason: { code, message }).\n //\n // Connector v3.3.2 contract: the connector's payment-handler adapter\n // feeds `rejectReason.code` through `mapRejectCode()` — a\n // SEMANTIC-reason → wire-code lookup (e.g. `'internal_error'` →\n // `'T00'`). Handlers in this codebase reject with raw ILP wire\n // codes (`'T00'`, `'F00'`, ...), which are NOT keys in the\n // connector's `REJECT_CODE_MAP`. Without translation, every reject\n // collapses to `'F99'` (the connector's fallback). Translate the\n // wire code back to the semantic reason here. See\n // `utils/reject-code.ts` for the mapping rationale.\n if (config.connector.setPacketHandler) {\n const adaptedHandler = async (\n request: HandlePacketRequest\n ): Promise<HandlePacketResponse> => {\n const result = await config.handlePacket(request);\n if (result.accept) {\n return result;\n }\n // Attach rejectReason for connector compatibility while preserving\n // the HandlePacketResponse shape for type safety\n const adapted = result as HandlePacketRejectResponse & {\n rejectReason?: { code: string; message: string };\n };\n adapted.rejectReason = {\n code: ilpCodeToSemantic(result.code),\n message: result.message,\n };\n return adapted;\n };\n config.connector.setPacketHandler(adaptedHandler);\n }\n\n // Run bootstrap to discover and register peers\n const results = await bootstrapService.bootstrap(\n config.additionalPeersJson\n );\n\n // Extract bootstrapped peer pubkeys for discovery tracker exclusion\n const bootstrapPeerPubkeys = results.map((r) => r.knownPeer.pubkey);\n\n // Exclude already-bootstrapped peers from discovery\n discoveryTracker.addExcludedPubkeys(bootstrapPeerPubkeys);\n\n started = true;\n\n // Return bootstrap results summary\n const channelCount = results.filter((r) => r.channelId).length;\n return {\n bootstrapResults: results,\n peerCount: results.length,\n channelCount,\n };\n } catch (error) {\n throw new BootstrapError(\n `Failed to start ToonNode: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n async stop(): Promise<void> {\n if (!started) {\n return; // No-op if not started\n }\n\n started = false;\n },\n };\n}\n","/**\n * Mock USDC token configuration for local development (Anvil).\n *\n * In production, USDC is the native Circle USD Coin on Arbitrum One:\n * 0xaf88d065e77c8cC2239327C5EDb3A432268e5831\n *\n * For local development on Anvil, a mock ERC-20 is deployed at a\n * deterministic address by the DeployLocal.s.sol script in the connector\n * repo. This contract serves as the mock USDC for payment channel testing.\n *\n * **On-chain decimal discrepancy (Anvil only):**\n * The legacy on-chain mock contract on Anvil uses 18 decimals (inherited\n * from the original ERC-20 deploy script in the connector repo). The\n * constants below reflect production USDC semantics (6 decimals). When\n * interacting with the Anvil mock contract directly (e.g., via the\n * faucet), use 18 decimals for on-chain amounts. The pricing pipeline\n * (basePricePerByte * toonLength) is denomination-agnostic (bigint math)\n * and works correctly regardless of on-chain decimals.\n *\n * **FiatTokenV2_2-compatible mock (Epic 5 prep):**\n * For DVM compute settlement and x402 testing with proper 6-decimal\n * semantics and EIP-3009 `transferWithAuthorization` support, deploy\n * the FiatTokenV2_2-compatible mock:\n * `./scripts/deploy-mock-usdc.sh`\n * This deploys a contract with 6 decimals, EIP-3009, and EIP-712\n * domain matching production USDC (\"USD Coin\", version \"2\").\n *\n * USDC uses 6 decimals (not 18 like most ERC-20 tokens):\n * 1 USDC = 1,000,000 micro-USDC (10^6)\n *\n * @module\n */\n\n/**\n * Mock USDC contract address on Anvil (deterministic from deployment).\n *\n * This is the first contract deployed by DeployLocal.s.sol using\n * Anvil Account #0 at nonce 0, giving it a deterministic address.\n */\nexport const MOCK_USDC_ADDRESS =\n '0x5FbDB2315678afecb367f032d93F642f64180aa3' as const;\n\n/**\n * USDC uses 6 decimals (1 USDC = 1,000,000 micro-units).\n *\n * This differs from most ERC-20 tokens which use 18 decimals.\n * All pricing amounts in the TOON protocol are denominated\n * in USDC micro-units when using USDC as the settlement token.\n */\nexport const USDC_DECIMALS = 6 as const;\n\n/** USDC token symbol. */\nexport const USDC_SYMBOL = 'USDC' as const;\n\n/** USDC token name. */\nexport const USDC_NAME = 'USD Coin' as const;\n\n/**\n * Configuration for the mock USDC contract deployment.\n */\nexport interface MockUsdcConfig {\n /** Contract address on the target chain */\n address: string;\n /** Number of decimal places (6 for USDC) */\n decimals: number;\n /** Token symbol */\n symbol: string;\n /** Token name */\n name: string;\n}\n\n/**\n * Default mock USDC configuration for Anvil local development.\n */\nexport const MOCK_USDC_CONFIG: MockUsdcConfig = {\n address: MOCK_USDC_ADDRESS,\n decimals: USDC_DECIMALS,\n symbol: USDC_SYMBOL,\n name: USDC_NAME,\n};\n","/**\n * Multi-environment chain configuration for TOON relay nodes.\n *\n * Provides chain presets for three blockchain families:\n * - **EVM**: Anvil (local dev), Arbitrum Sepolia (staging), Arbitrum One (production)\n * - **Solana**: solana-devnet (local dev)\n * - **Mina**: mina-devnet (local dev)\n *\n * Environment variable overrides:\n * - `TOON_CHAIN` overrides the config-level chain parameter\n * - `TOON_RPC_URL` overrides the preset RPC endpoint\n * - `TOON_TOKEN_NETWORK` overrides the preset TokenNetwork address\n *\n * @module\n */\n\nimport { ToonError } from '../errors.js';\nimport { MOCK_USDC_ADDRESS } from './usdc.js';\n\n// ---------- Types ----------\n\n/**\n * Blockchain family type. Matches the connector's BlockchainType.\n */\nexport type ChainType = 'evm' | 'solana' | 'mina';\n\n/**\n * Supported EVM chain preset names (backward-compatible).\n *\n * `anvil` is the local-dev chain. `arbitrum-*` and `base-*` are the public\n * Arbitrum and Base networks used by the network-mode resolver\n * (see network-profile.ts). Base is the primary EVM chain for single-EVM\n * nodes; the apex connector and Mill can hold providers for both families.\n */\nexport type ChainName =\n | 'anvil'\n | 'arbitrum-sepolia'\n | 'arbitrum-one'\n | 'base-sepolia'\n | 'base-mainnet';\n\n/**\n * Supported Solana chain preset names.\n */\nexport type SolanaChainName = 'solana-devnet';\n\n/**\n * Supported Mina chain preset names.\n */\nexport type MinaChainName = 'mina-devnet';\n\n/**\n * All supported chain preset names across all chain types.\n */\nexport type MultiChainName = ChainName | SolanaChainName | MinaChainName;\n\n/**\n * Resolved EVM chain configuration with all fields populated.\n */\nexport interface ChainPreset {\n /** Preset identifier ('anvil' | 'arbitrum-sepolia' | 'arbitrum-one'). */\n name: string;\n /** EVM chain ID. */\n chainId: number;\n /** Default RPC endpoint URL. */\n rpcUrl: string;\n /** USDC token contract address on this chain. */\n usdcAddress: string;\n /** TokenNetwork contract address for USDC on this chain. */\n tokenNetworkAddress: string;\n /** TokenNetworkRegistry contract address on this chain. */\n registryAddress: string;\n}\n\n/**\n * Resolved Solana chain configuration.\n */\nexport interface SolanaChainPreset {\n /** Preset identifier (e.g., 'solana-devnet'). */\n name: string;\n /** Chain type discriminator. */\n chainType: 'solana';\n /** Solana cluster RPC endpoint (HTTP). */\n rpcUrl: string;\n /** Payment channel program ID (base58-encoded). TBD until deployed. */\n programId: string;\n /** Solana cluster name for chain ID namespacing (e.g., 'devnet'). */\n cluster: string;\n /** Optional SPL token mint address. */\n tokenMint?: string;\n}\n\n/**\n * Resolved Mina chain configuration.\n */\nexport interface MinaChainPreset {\n /** Preset identifier (e.g., 'mina-devnet'). */\n name: string;\n /** Chain type discriminator. */\n chainType: 'mina';\n /** Mina GraphQL endpoint. */\n graphqlUrl: string;\n /** zkApp address for the payment channel contract. TBD until deployed. */\n zkAppAddress: string;\n /** Mina network name (e.g., 'devnet'). */\n network: string;\n /** Optional Mina token ID. */\n tokenId?: string;\n}\n\n// ---------- Chain Provider Config Entry ----------\n\n/**\n * EVM-specific provider configuration entry.\n */\nexport interface EVMProviderConfigEntry {\n chainType: 'evm';\n chainId: string;\n rpcUrl: string;\n registryAddress: string;\n keyId: string;\n tokenAddress: string;\n privateKey?: string;\n /**\n * Settlement tuning knobs. The connector reads its GLOBAL settlement\n * threshold from the first EVM provider carrying `settlementOptions` and\n * applies that single `threshold` across all chains (EVM/Solana/Mina).\n * Mirrors the connector's `EVMProviderConfig.settlementOptions` so a custom\n * provider entry can pass it straight through.\n */\n settlementOptions?: {\n threshold?: string;\n settlementTimeoutSecs?: number;\n initialDepositMultiplier?: number;\n pollingIntervalMs?: number;\n };\n}\n\n/**\n * Solana-specific provider configuration entry.\n */\nexport interface SolanaProviderConfigEntry {\n chainType: 'solana';\n chainId: string;\n rpcUrl: string;\n programId: string;\n keyId: string;\n wsUrl?: string;\n cluster?: string;\n tokenMint?: string;\n}\n\n/**\n * Mina-specific provider configuration entry.\n */\nexport interface MinaProviderConfigEntry {\n chainType: 'mina';\n chainId: string;\n graphqlUrl: string;\n zkAppAddress: string;\n keyId?: string;\n tokenId?: string;\n network?: string;\n}\n\n/**\n * Discriminated union of all chain provider config entries.\n * Mirrors the connector's ChainProviderConfigEntry type for passthrough.\n */\nexport type ChainProviderConfigEntry =\n | EVMProviderConfigEntry\n | SolanaProviderConfigEntry\n | MinaProviderConfigEntry;\n\n// ---------- Presets ----------\n\n/**\n * Built-in EVM chain presets for supported deployment environments.\n *\n * Each preset provides the chainId, RPC URL, USDC address, and\n * TokenNetwork address for its environment. Fields can be overridden\n * at runtime via environment variables.\n */\nexport const CHAIN_PRESETS: Record<ChainName, ChainPreset> = {\n anvil: {\n name: 'anvil',\n chainId: 31337,\n rpcUrl: 'http://localhost:8545',\n usdcAddress: MOCK_USDC_ADDRESS,\n tokenNetworkAddress: '0xCafac3dD18aC6c6e92c921884f9E4176737C052c',\n registryAddress: '0xe7f1725e7734ce288f8367e1bb143e90bb3f0512',\n },\n 'arbitrum-sepolia': {\n name: 'arbitrum-sepolia',\n chainId: 421614,\n rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc',\n usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',\n tokenNetworkAddress: '0x91d62b1F7C5d1129A64EE3915c480DBF288B1cBa',\n registryAddress: '',\n },\n 'arbitrum-one': {\n name: 'arbitrum-one',\n chainId: 42161,\n rpcUrl: 'https://arb1.arbitrum.io/rpc',\n usdcAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n tokenNetworkAddress: '',\n registryAddress: '',\n },\n // Base Sepolia (public testnet) — TOON's deployed public-testnet settlement\n // environment (source of truth: e2e/testnets.json). USDC is the deployed test\n // token the TokenNetwork was opened against (NOT Circle's native testnet USDC,\n // which the channel would not recognise). Registry + TokenNetwork are live, so\n // this preset is settlement-complete: `--network testnet|devnet` settles here.\n 'base-sepolia': {\n name: 'base-sepolia',\n chainId: 84532,\n rpcUrl: 'https://sepolia.base.org',\n usdcAddress: '0xac80670b86db1eeb5c18c82e18a6bda98fcb4504',\n tokenNetworkAddress: '0x47616F4b9cF4dA25F74FD727Cd85E9CA0C70Ec5C',\n registryAddress: '0xb9516c6c53c016c43f3671b1e5eb6096c83ec2c7',\n },\n // Base mainnet (public). USDC is Circle's native USDC on Base.\n // TOON TokenNetwork/registry not deployed yet → settlement relay-only.\n 'base-mainnet': {\n name: 'base-mainnet',\n chainId: 8453,\n rpcUrl: 'https://mainnet.base.org',\n usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n tokenNetworkAddress: '',\n registryAddress: '',\n },\n};\n\n// ---------- Functions ----------\n\n/**\n * Resolve chain configuration from a chain name, with environment\n * variable overrides applied.\n *\n * Resolution order:\n * 1. `TOON_CHAIN` env var overrides the `chain` parameter\n * 2. Defaults to `'anvil'` if neither is provided\n * 3. Looks up the chain name in `CHAIN_PRESETS`\n * 4. `TOON_RPC_URL` env var overrides the preset's `rpcUrl`\n * 5. `TOON_TOKEN_NETWORK` env var overrides the preset's `tokenNetworkAddress`\n * 6. `TOON_REGISTRY_ADDRESS` env var overrides the preset's `registryAddress`\n *\n * Returns a defensive copy -- callers can mutate the result without\n * affecting the shared preset objects.\n *\n * @param chain - Chain name to resolve (default: 'anvil')\n * @returns Resolved chain preset with env var overrides applied\n * @throws ToonError if the chain name is not recognized\n */\nexport function resolveChainConfig(chain?: ChainName | string): ChainPreset {\n // 1. TOON_CHAIN env var overrides the parameter\n const envChain = process.env['TOON_CHAIN'];\n const name = envChain || chain || 'anvil';\n\n // 2. Look up the chain name in presets\n const preset = CHAIN_PRESETS[name as ChainName];\n if (!preset) {\n // Enumerate the ACTUAL allow-list from CHAIN_PRESETS rather than a\n // hard-coded literal. A stale literal here (it previously read\n // \"anvil, arbitrum-sepolia, arbitrum-one\") masks newer presets such as\n // base-sepolia/base-mainnet and produces a misleading diagnostic when an\n // image bundles older core (see issue #196).\n const validNames = Object.keys(CHAIN_PRESETS).join(', ');\n throw new ToonError(\n `Unknown chain \"${name}\". Valid chains: ${validNames}`,\n 'INVALID_CHAIN'\n );\n }\n\n // 3. Create defensive copy\n const resolved: ChainPreset = { ...preset };\n\n // 4. TOON_RPC_URL env var overrides preset rpcUrl\n const envRpcUrl = process.env['TOON_RPC_URL'];\n if (envRpcUrl) {\n resolved.rpcUrl = envRpcUrl;\n }\n\n // 5. TOON_TOKEN_NETWORK env var overrides preset tokenNetworkAddress\n const envTokenNetwork = process.env['TOON_TOKEN_NETWORK'];\n if (envTokenNetwork) {\n resolved.tokenNetworkAddress = envTokenNetwork;\n }\n\n // 6. TOON_REGISTRY_ADDRESS env var overrides preset registryAddress\n const envRegistryAddress = process.env['TOON_REGISTRY_ADDRESS'];\n if (envRegistryAddress) {\n resolved.registryAddress = envRegistryAddress;\n }\n\n return resolved;\n}\n\n/**\n * Build an EIP-712 domain separator from a resolved chain configuration.\n *\n * The returned domain matches the structure used by\n * `getBalanceProofDomain()` in `packages/client/src/signing/evm-signer.ts`:\n *\n * ```typescript\n * { name: 'TokenNetwork', version: '1', chainId, verifyingContract }\n * ```\n *\n * Since `@toon-protocol/core` does not depend on viem, the `verifyingContract`\n * field is typed as `string` (not viem's `Hex`). Consumers in the client\n * package can cast to `Hex` if needed.\n *\n * @param config - Resolved chain preset\n * @returns EIP-712 domain separator object\n */\nexport function buildEip712Domain(config: ChainPreset): {\n name: string;\n version: string;\n chainId: number;\n verifyingContract: string;\n} {\n return {\n name: 'TokenNetwork',\n version: '1',\n chainId: config.chainId,\n verifyingContract: config.tokenNetworkAddress,\n };\n}\n\n// ---------- Solana Presets ----------\n\n/**\n * Built-in Solana chain presets.\n */\nexport const SOLANA_CHAIN_PRESETS: Record<SolanaChainName, SolanaChainPreset> =\n {\n 'solana-devnet': {\n name: 'solana-devnet',\n chainType: 'solana',\n rpcUrl: 'http://localhost:19899',\n programId: '', // TBD: set after program deployment\n cluster: 'devnet',\n },\n };\n\n// ---------- Mina Presets ----------\n\n/**\n * Built-in Mina chain presets.\n */\nexport const MINA_CHAIN_PRESETS: Record<MinaChainName, MinaChainPreset> = {\n 'mina-devnet': {\n name: 'mina-devnet',\n chainType: 'mina',\n graphqlUrl: 'http://localhost:19085/graphql',\n zkAppAddress: '', // TBD: set after zkApp deployment\n network: 'devnet',\n },\n};\n\n// ---------- Multi-Chain Resolution ----------\n\n/**\n * Resolve a Solana chain preset by name, with env var overrides.\n *\n * Environment variable overrides:\n * - `SOLANA_RPC_URL` overrides the preset's rpcUrl\n * - `SOLANA_PROGRAM_ID` overrides the preset's programId\n *\n * @param name - Solana chain preset name\n * @returns Resolved Solana chain preset\n * @throws ToonError if the name is not recognized\n */\nexport function resolveSolanaChainConfig(\n name: SolanaChainName\n): SolanaChainPreset {\n const preset = SOLANA_CHAIN_PRESETS[name];\n if (!preset) {\n const validNames = Object.keys(SOLANA_CHAIN_PRESETS).join(', ');\n throw new ToonError(\n `Unknown Solana chain \"${name}\". Valid Solana chains: ${validNames}`,\n 'INVALID_CHAIN'\n );\n }\n\n const resolved: SolanaChainPreset = { ...preset };\n\n const envRpcUrl = process.env['SOLANA_RPC_URL'];\n if (envRpcUrl) {\n resolved.rpcUrl = envRpcUrl;\n }\n\n const envProgramId = process.env['SOLANA_PROGRAM_ID'];\n if (envProgramId) {\n resolved.programId = envProgramId;\n }\n\n return resolved;\n}\n\n/**\n * Resolve a Mina chain preset by name, with env var overrides.\n *\n * Environment variable overrides:\n * - `MINA_GRAPHQL_URL` overrides the preset's graphqlUrl\n * - `MINA_ZKAPP_ADDRESS` overrides the preset's zkAppAddress\n *\n * @param name - Mina chain preset name\n * @returns Resolved Mina chain preset\n * @throws ToonError if the name is not recognized\n */\nexport function resolveMinaChainConfig(name: MinaChainName): MinaChainPreset {\n const preset = MINA_CHAIN_PRESETS[name];\n if (!preset) {\n const validNames = Object.keys(MINA_CHAIN_PRESETS).join(', ');\n throw new ToonError(\n `Unknown Mina chain \"${name}\". Valid Mina chains: ${validNames}`,\n 'INVALID_CHAIN'\n );\n }\n\n const resolved: MinaChainPreset = { ...preset };\n\n const envGraphqlUrl = process.env['MINA_GRAPHQL_URL'];\n if (envGraphqlUrl) {\n resolved.graphqlUrl = envGraphqlUrl;\n }\n\n const envZkAppAddress = process.env['MINA_ZKAPP_ADDRESS'];\n if (envZkAppAddress) {\n resolved.zkAppAddress = envZkAppAddress;\n }\n\n return resolved;\n}\n\n/**\n * Build a ChainProviderConfigEntry from a resolved EVM chain preset.\n *\n * Converts the TOON-specific ChainPreset into the connector's\n * ChainProviderConfigEntry format for the `chainProviders` array.\n *\n * @param config - Resolved EVM chain preset\n * @param keyId - Key identifier for signing operations\n * @returns EVM chain provider config entry\n */\nexport function buildEvmProviderEntry(\n config: ChainPreset,\n keyId: string\n): EVMProviderConfigEntry {\n return {\n chainType: 'evm',\n chainId: `evm:${config.chainId}`,\n rpcUrl: config.rpcUrl,\n registryAddress: config.registryAddress,\n tokenAddress: config.usdcAddress,\n keyId,\n };\n}\n\n/**\n * Build a ChainProviderConfigEntry from a resolved Solana chain preset.\n *\n * @param config - Resolved Solana chain preset\n * @param keyId - Key identifier for Ed25519 signing operations\n * @returns Solana chain provider config entry\n */\nexport function buildSolanaProviderEntry(\n config: SolanaChainPreset,\n keyId: string\n): SolanaProviderConfigEntry {\n return {\n chainType: 'solana',\n chainId: `solana:${config.cluster}`,\n rpcUrl: config.rpcUrl,\n programId: config.programId,\n keyId,\n cluster: config.cluster,\n ...(config.tokenMint && { tokenMint: config.tokenMint }),\n };\n}\n\n/**\n * Build a ChainProviderConfigEntry from a resolved Mina chain preset.\n *\n * @param config - Resolved Mina chain preset\n * @param keyId - Optional key identifier for signing operations\n * @returns Mina chain provider config entry\n */\nexport function buildMinaProviderEntry(\n config: MinaChainPreset,\n keyId?: string\n): MinaProviderConfigEntry {\n return {\n chainType: 'mina',\n chainId: `mina:${config.network}`,\n graphqlUrl: config.graphqlUrl,\n zkAppAddress: config.zkAppAddress,\n ...(keyId && { keyId }),\n ...(config.tokenId && { tokenId: config.tokenId }),\n network: config.network,\n };\n}\n","/**\n * Network-mode resolution for the Townhouse `network` flag.\n *\n * A single operator-facing selector — `mainnet | testnet | devnet | custom` —\n * resolves a coherent multi-chain configuration that is consumed by BOTH:\n * (`custom` additionally accepts operator RPC URLs via `--evm-url`/`--sol-url`\n * to point at the project's dev chains — e.g. the Akash-hosted anvil + solana.)\n *\n * - the **apex** standalone connector (its `chainProviders` settlement array), and\n * - the **children** node containers (town/mill), via a small set of env vars the\n * HS compose template interpolates (`EVM_CHAIN`, `EVM_RPC_URL`, `EVM_CHAIN_ID`,\n * `EVM_USDC_ADDRESS`, `SOLANA_RPC_URL`, `SOLANA_USDC_MINT`).\n *\n * Design notes:\n * - **All tiers are public.** No tier resolves to a local chain (anvil/lightnet),\n * so a node never points at an unreachable `localhost` RPC. This is what fixes\n * the \"JsonRpcProvider failed to detect network\" boot-loop that left town nodes\n * permanently disconnected (an empty RPC fell back to the `anvil` preset whose\n * `localhost:8545` does not exist in the HS network).\n * - **EVM = Base (primary) + Arbitrum.** The single-EVM town node uses Base; the\n * apex connector and Mill can hold providers for both families.\n * - **Settlement status.** TOON's settlement contracts are deployed for the\n * public **testnet/devnet** tiers (EVM Base Sepolia registry + TokenNetwork,\n * Solana devnet program, Mina devnet zkApp — source of truth: e2e/testnets.json),\n * so those families resolve `configured` and the apex builds real\n * `chainProviders` for them. **Mainnet remains unconfigured** (contracts not\n * deployed there yet) → relay-only. The resolver only builds a connector\n * `chainProviders` entry for a family once its on-chain addresses are present.\n * No addresses are invented; filling the remaining (mainnet) presets later\n * requires no change here. The deployed testnet/devnet addresses are\n * maintained in sync with e2e/testnets.json (the one-time public deploy).\n *\n * The low-level local `solana-devnet` / `mina-devnet` presets in chain-config.ts\n * (used by the dev/e2e stack) are intentionally NOT reused — this module defines\n * the public Solana/Mina endpoints itself.\n *\n * @module\n */\n\nimport {\n CHAIN_PRESETS,\n buildEvmProviderEntry,\n buildSolanaProviderEntry,\n buildMinaProviderEntry,\n type ChainName,\n type ChainPreset,\n type ChainProviderConfigEntry,\n} from './chain-config.js';\n\n/** Operator-facing network selector. */\nexport type NetworkMode = 'mainnet' | 'testnet' | 'devnet' | 'custom';\n\n/** The three preset-derivable public tiers (everything except custom). */\ntype DerivableTier = Exclude<NetworkMode, 'custom'>;\n\n/**\n * Operator-supplied RPC URLs for `network: 'custom'`\n * (`--evm-url` / `--sol-url`). Use this to point the apex + nodes at the\n * project's dev chains hosted anywhere — e.g. the anvil + solana that\n * scripts/akash-deploy.sh deploys to Akash (whose ingress hostnames rotate per\n * redeploy, so the operator passes the current URLs). The EVM chain is assumed\n * to be the chain-id 31338 `akash-anvil` deploy (deterministic TOON settlement\n * contracts → settlement-complete); Solana is RPC + Mock-USDC (relay-only, no\n * program). For arbitrary real chains with their own contracts, use the full\n * `customProviders` (chains editor) path instead.\n */\nexport interface CustomEndpoints {\n /** EVM JSON-RPC URL (the project's anvil deploy). */\n evmUrl?: string;\n /** Solana JSON-RPC URL. */\n solUrl?: string;\n}\n\n/**\n * Dev-chain templates for the URL-only custom path. The EVM chain is the\n * `anvil` preset — chain-id **31337** with the deterministic Foundry TOON\n * contracts (settlement-complete). This matches what scripts/akash-deploy.sh\n * actually deploys: the SDL pins `CHAIN_ID=31337` so the Akash anvil lines up\n * with the `anvil` preset (verified live: eth_chainId → 0x7a69, registry +\n * tokenNetwork have code). The operator's `--evm-url` overrides the preset's\n * localhost rpcUrl. The Solana node bootstraps a known Mock-USDC mint but the\n * payment-channel program is not deployed → Solana relay-only.\n */\nconst DEV_EVM_PRESET = 'anvil' as const;\nconst DEV_SOLANA = {\n usdcMint: '6GbdrVghwNKTz9raga7y3Y4qqX5Zgg3AC4d48Kt7C59Q',\n programId: '', // TOON payment-channel program not deployed on the dev Solana node\n};\n\n/** Per-family settlement readiness, for honest UX. */\nexport interface NetworkFamilyStatus {\n /** `configured` once the family has on-chain settlement addresses for this tier. */\n evm: 'configured' | 'unconfigured';\n solana: 'configured' | 'unconfigured';\n mina: 'configured' | 'unconfigured';\n}\n\n/**\n * Env vars the HS compose template interpolates into the node containers.\n * Only keys with real values are present (absent ⇒ compose `${VAR:-}` default).\n */\nexport interface NetworkNodeEnv {\n /** Primary EVM chain preset name → town `TOON_CHAIN` (`'none'` ⇒ relay-only). */\n EVM_CHAIN?: string;\n EVM_RPC_URL?: string;\n EVM_CHAIN_ID?: string;\n EVM_USDC_ADDRESS?: string;\n SOLANA_RPC_URL?: string;\n SOLANA_USDC_MINT?: string;\n /** Solana payment-channel program id (filled per-deploy; empty in presets). */\n SOLANA_PROGRAM_ID?: string;\n /** Mina payment-channel zkApp address (filled per-deploy; empty in presets). */\n MINA_ZKAPP_ADDRESS?: string;\n}\n\n/** Resolved network configuration for apex + children. */\nexport interface NetworkProfile {\n network: NetworkMode;\n /**\n * Connector `chainProviders` for the apex. Contains only settlement-complete\n * families (omitted when on-chain addresses are absent — the caller falls back\n * to its own default in that case).\n */\n chainProviders: ChainProviderConfigEntry[];\n /** Env overlay for the node containers. */\n nodeEnv: NetworkNodeEnv;\n /** Per-family settlement readiness. */\n status: NetworkFamilyStatus;\n}\n\n/** Sentinel for the town node meaning \"no EVM settlement chain — run relay-only\". */\nexport const RELAY_ONLY_CHAIN = 'none';\n\n/** Primary + secondary EVM presets per derivable tier (Base is primary). */\nconst EVM_TIER: Record<\n DerivableTier,\n { primary: ChainName; also: ChainName[] }\n> = {\n mainnet: { primary: 'base-mainnet', also: ['arbitrum-one'] },\n testnet: { primary: 'base-sepolia', also: ['arbitrum-sepolia'] },\n // EVM has no public devnet → the public Sepolia testnets serve the devnet tier.\n devnet: { primary: 'base-sepolia', also: ['arbitrum-sepolia'] },\n};\n\ninterface SolanaTierCfg {\n rpcUrl: string;\n cluster: string;\n usdcMint: string;\n programId: string;\n}\n\n/**\n * TOON's deployed public-testnet Solana settlement (Solana devnet). Source of\n * truth: e2e/testnets.json. The TokenNetwork program + its mint are live, so\n * this is settlement-complete. The `testnet` tier reuses the devnet cluster\n * because TOON has no deployment on Solana's `testnet` cluster — there is one\n * public deployment and both operator-facing tiers resolve to it.\n */\nconst SOLANA_DEPLOYED_DEVNET: SolanaTierCfg = {\n rpcUrl: 'https://api.devnet.solana.com',\n cluster: 'devnet',\n usdcMint: '9FtYCXjNiGDn17jSGvZuB5P4dZAKgVxUsDiQpLc8rbWy',\n programId: 'EdJxYPDxGvaJuu57DSUptf4soLv8enpdyQJJhHDLiydG',\n};\n\n/** Public Solana endpoints per tier (the low-level presets are local-only). */\nconst SOLANA_TIER: Record<DerivableTier, SolanaTierCfg> = {\n mainnet: {\n rpcUrl: 'https://api.mainnet-beta.solana.com',\n cluster: 'mainnet-beta',\n usdcMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n programId: '', // TOON payment-channel program not deployed on mainnet\n },\n testnet: SOLANA_DEPLOYED_DEVNET,\n devnet: SOLANA_DEPLOYED_DEVNET,\n};\n\ninterface MinaTierCfg {\n graphqlUrl: string;\n network: string;\n zkAppAddress: string;\n}\n\n/**\n * TOON's deployed public-testnet Mina settlement (Mina devnet). Source of truth:\n * e2e/testnets.json. Mina has no separate `testnet` network → both the testnet\n * and devnet tiers resolve to the one deployed devnet zkApp.\n */\nconst MINA_DEPLOYED_DEVNET: MinaTierCfg = {\n graphqlUrl: 'https://api.minascan.io/node/devnet/v1/graphql',\n network: 'devnet',\n zkAppAddress: 'B62qjFgXZWDWVE4P6h63JSzdMRzXpqJEgMM3Gt6PvWzzrSCawBZ4hE3',\n};\n\n/** Public Mina endpoints per tier (Mina has no separate testnet → uses devnet). */\nconst MINA_TIER: Record<DerivableTier, MinaTierCfg> = {\n mainnet: {\n graphqlUrl: 'https://api.minascan.io/node/mainnet/v1/graphql',\n network: 'mainnet',\n zkAppAddress: '', // TOON payment-channel zkApp not deployed on mainnet\n },\n testnet: MINA_DEPLOYED_DEVNET,\n devnet: MINA_DEPLOYED_DEVNET,\n};\n\n/** An EVM preset is settlement-complete when registry + tokenNetwork are deployed. */\nfunction evmSettlementComplete(p: ChainPreset): boolean {\n return p.registryAddress !== '' && p.tokenNetworkAddress !== '';\n}\n\n/**\n * Resolve a {@link NetworkProfile} from a network mode.\n *\n * @param network - The operator-selected network mode.\n * @param opts.keyId - Settlement signing key for connector `chainProviders`\n * entries. Required to emit settlement-complete providers; omit for the common\n * relay-only case (no providers are built without it).\n * @param opts.customProviders - For `network: 'custom'`, the operator-supplied\n * `chainProviders` to pass through verbatim (and to derive node env from).\n * @param opts.endpoints - For `network: 'custom'`, operator-supplied RPC URLs\n * (`--evm-url` / `--sol-url`) pointing at the project's dev chains. Used when\n * `customProviders` is empty (the lightweight URL-only path).\n */\nexport function resolveNetworkProfile(\n network: NetworkMode,\n opts: {\n keyId?: string;\n customProviders?: ChainProviderConfigEntry[];\n endpoints?: CustomEndpoints;\n } = {}\n): NetworkProfile {\n if (network === 'custom') {\n return resolveCustom(\n opts.customProviders ?? [],\n opts.endpoints ?? {},\n opts.keyId\n );\n }\n\n const tier = network as DerivableTier;\n const chainProviders: ChainProviderConfigEntry[] = [];\n const status: NetworkFamilyStatus = {\n evm: 'unconfigured',\n solana: 'unconfigured',\n mina: 'unconfigured',\n };\n\n // ── EVM (primary Base + secondary Arbitrum) ──\n const primary = CHAIN_PRESETS[EVM_TIER[tier].primary];\n const nodeEnv: NetworkNodeEnv = {\n EVM_CHAIN: primary.name,\n EVM_RPC_URL: primary.rpcUrl,\n EVM_CHAIN_ID: String(primary.chainId),\n EVM_USDC_ADDRESS: primary.usdcAddress,\n };\n\n if (opts.keyId) {\n for (const name of [EVM_TIER[tier].primary, ...EVM_TIER[tier].also]) {\n const preset = CHAIN_PRESETS[name];\n if (evmSettlementComplete(preset)) {\n chainProviders.push(buildEvmProviderEntry(preset, opts.keyId));\n status.evm = 'configured';\n }\n }\n }\n\n // ── Solana ──\n const sol = SOLANA_TIER[tier];\n nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;\n if (sol.usdcMint) nodeEnv.SOLANA_USDC_MINT = sol.usdcMint;\n if (opts.keyId && sol.programId) {\n chainProviders.push(\n buildSolanaProviderEntry(\n {\n name: `solana-${sol.cluster}`,\n chainType: 'solana',\n rpcUrl: sol.rpcUrl,\n programId: sol.programId,\n cluster: sol.cluster,\n ...(sol.usdcMint && { tokenMint: sol.usdcMint }),\n },\n opts.keyId\n )\n );\n status.solana = 'configured';\n }\n\n // ── Mina ──\n // Gated on keyId for parity with EVM/Solana: a connector settlement provider\n // is only useful with a signing key, and `status` tracks apex settlement\n // readiness (the apex always resolves with a keyId; child node-env never does).\n const mina = MINA_TIER[tier];\n if (opts.keyId && mina.zkAppAddress) {\n chainProviders.push(\n buildMinaProviderEntry(\n {\n name: `mina-${mina.network}`,\n chainType: 'mina',\n graphqlUrl: mina.graphqlUrl,\n zkAppAddress: mina.zkAppAddress,\n network: mina.network,\n },\n opts.keyId\n )\n );\n status.mina = 'configured';\n }\n\n return { network, chainProviders, nodeEnv, status };\n}\n\n/**\n * Client-facing per-chain settlement presets resolved from a network mode.\n *\n * Where {@link NetworkProfile} targets the apex connector + node containers\n * (env overlay + `chainProviders`), this targets the @toon-protocol/client\n * `ToonClientConfig` shape: identifier-keyed maps (`evm:<name>:<chainId>`,\n * `solana:<cluster>`, `mina:<network>`) plus the Solana/Mina channel params.\n * It draws from the SAME presets (`CHAIN_PRESETS`, `SOLANA_TIER`, `MINA_TIER`),\n * so node and client default to the identical live contracts — no duplicated\n * address tables in the client package.\n *\n * Only `mainnet | testnet | devnet` are resolvable here; `custom` is the\n * client's fully-manual path and is intentionally not handled.\n */\nexport interface ClientNetworkPresets {\n /** Chain identifiers (`evm:base:84532`, `solana:devnet`, `mina:devnet`). */\n supportedChains: string[];\n /** identifier → JSON-RPC / GraphQL URL. */\n chainRpcUrls: Record<string, string>;\n /** identifier → preferred token (USDC / SPL mint) address. */\n preferredTokens: Record<string, string>;\n /** identifier → EVM TokenNetwork contract address (EVM only). */\n tokenNetworks: Record<string, string>;\n /** Solana channel params (rpcUrl + programId + tokenMint), if deployed. */\n solanaChannel?: { rpcUrl: string; programId: string; tokenMint?: string };\n /** Mina channel params (graphqlUrl + zkAppAddress + networkId), if deployed. */\n minaChannel?: {\n graphqlUrl: string;\n zkAppAddress: string;\n networkId: 'devnet' | 'mainnet';\n };\n /** Per-family settlement readiness (mirrors the node). */\n status: NetworkFamilyStatus;\n}\n\n/** EVM client identifier: `evm:<family>:<chainId>` (family = base/arbitrum/…). */\nfunction evmClientId(preset: ChainPreset): string {\n const family = preset.name.split('-')[0] ?? preset.name;\n return `evm:${family}:${preset.chainId}`;\n}\n\n/**\n * Resolve {@link ClientNetworkPresets} for a derivable network tier.\n *\n * Mirrors {@link resolveNetworkProfile}'s address sourcing but emits the\n * client config shape. The EVM family is the tier's primary chain (Base);\n * Solana/Mina are the public tier endpoints. Only families with deployed TOON\n * contracts contribute settlement maps + channel params (others stay relay-only\n * and are reported `unconfigured`).\n */\nexport function resolveClientNetwork(\n network: DerivableTier\n): ClientNetworkPresets {\n const supportedChains: string[] = [];\n const chainRpcUrls: Record<string, string> = {};\n const preferredTokens: Record<string, string> = {};\n const tokenNetworks: Record<string, string> = {};\n const status: NetworkFamilyStatus = {\n evm: 'unconfigured',\n solana: 'unconfigured',\n mina: 'unconfigured',\n };\n\n // ── EVM (primary Base) ──\n const evm = CHAIN_PRESETS[EVM_TIER[network].primary];\n const evmId = evmClientId(evm);\n supportedChains.push(evmId);\n chainRpcUrls[evmId] = evm.rpcUrl;\n if (evm.usdcAddress) preferredTokens[evmId] = evm.usdcAddress;\n if (evmSettlementComplete(evm)) {\n tokenNetworks[evmId] = evm.tokenNetworkAddress;\n status.evm = 'configured';\n }\n\n // ── Solana ──\n const sol = SOLANA_TIER[network];\n const solId = `solana:${sol.cluster}`;\n supportedChains.push(solId);\n chainRpcUrls[solId] = sol.rpcUrl;\n if (sol.usdcMint) preferredTokens[solId] = sol.usdcMint;\n let solanaChannel: ClientNetworkPresets['solanaChannel'];\n if (sol.programId) {\n solanaChannel = {\n rpcUrl: sol.rpcUrl,\n programId: sol.programId,\n ...(sol.usdcMint && { tokenMint: sol.usdcMint }),\n };\n status.solana = 'configured';\n }\n\n // ── Mina ──\n const mina = MINA_TIER[network];\n const minaId = `mina:${mina.network}`;\n supportedChains.push(minaId);\n chainRpcUrls[minaId] = mina.graphqlUrl;\n let minaChannel: ClientNetworkPresets['minaChannel'];\n if (mina.zkAppAddress) {\n minaChannel = {\n graphqlUrl: mina.graphqlUrl,\n zkAppAddress: mina.zkAppAddress,\n networkId: mina.network === 'mainnet' ? 'mainnet' : 'devnet',\n };\n status.mina = 'configured';\n }\n\n return {\n supportedChains,\n chainRpcUrls,\n preferredTokens,\n tokenNetworks,\n ...(solanaChannel && { solanaChannel }),\n ...(minaChannel && { minaChannel }),\n status,\n };\n}\n\n/**\n * Resolve the `custom` mode. Two operator paths:\n * 1. explicit `providers` (the chains editor / `chains add`) → used verbatim;\n * the apex settles on them, the town node runs relay-only with their RPC.\n * 2. URL-only (`endpoints` from `--evm-url`/`--sol-url`) → point the apex +\n * nodes at the project's dev chains (chain-id 31338 `akash-anvil`, which is\n * settlement-complete; Solana RPC + Mock-USDC, relay-only).\n * `providers` takes precedence; with neither, the node runs relay-only.\n */\nfunction resolveCustom(\n providers: ChainProviderConfigEntry[],\n endpoints: CustomEndpoints,\n keyId?: string\n): NetworkProfile {\n if (providers.length === 0 && (endpoints.evmUrl || endpoints.solUrl)) {\n return resolveCustomEndpoints(endpoints, keyId);\n }\n\n const status: NetworkFamilyStatus = {\n evm: 'unconfigured',\n solana: 'unconfigured',\n mina: 'unconfigured',\n };\n const nodeEnv: NetworkNodeEnv = {};\n\n const evm = providers.find((p) => p.chainType === 'evm');\n if (evm && evm.chainType === 'evm') {\n nodeEnv.EVM_RPC_URL = evm.rpcUrl;\n // chainId arrives as `evm:<numeric>` — strip the namespace for the node env.\n nodeEnv.EVM_CHAIN_ID = evm.chainId.replace(/^evm:/, '');\n nodeEnv.EVM_USDC_ADDRESS = evm.tokenAddress;\n // Custom EVM carries explicit addresses → let the node resolve via RPC override\n // rather than a named preset.\n nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;\n if (evm.registryAddress) status.evm = 'configured';\n } else {\n // No EVM in a custom config → town runs relay-only.\n nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;\n }\n\n const sol = providers.find((p) => p.chainType === 'solana');\n if (sol && sol.chainType === 'solana') {\n nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;\n if (sol.tokenMint) nodeEnv.SOLANA_USDC_MINT = sol.tokenMint;\n if (sol.programId) status.solana = 'configured';\n }\n\n const mina = providers.find((p) => p.chainType === 'mina');\n if (mina && mina.chainType === 'mina' && mina.zkAppAddress) {\n status.mina = 'configured';\n }\n\n return { network: 'custom', chainProviders: providers, nodeEnv, status };\n}\n\n/**\n * URL-only custom path (`--evm-url` / `--sol-url`): point the apex + nodes at the\n * project's dev chains. EVM is the chain-id 31338 `akash-anvil` deploy\n * (settlement-complete via baked deterministic addresses); Solana is RPC +\n * Mock-USDC (relay-only). A family with no URL degrades to relay-only.\n */\nfunction resolveCustomEndpoints(\n endpoints: CustomEndpoints,\n keyId?: string\n): NetworkProfile {\n const evm = CHAIN_PRESETS[DEV_EVM_PRESET];\n const status: NetworkFamilyStatus = {\n evm: 'unconfigured',\n solana: 'unconfigured',\n mina: 'unconfigured',\n };\n const chainProviders: ChainProviderConfigEntry[] = [];\n\n // EVM_CHAIN points the town node at the akash-anvil preset (chain-id 31338 +\n // deterministic TOON contract addresses); EVM_RPC_URL supplies the operator's\n // URL, which the town overlays via TOON_RPC_URL.\n const nodeEnv: NetworkNodeEnv = {\n EVM_CHAIN: DEV_EVM_PRESET,\n EVM_CHAIN_ID: String(evm.chainId),\n EVM_USDC_ADDRESS: evm.usdcAddress,\n };\n\n if (endpoints.evmUrl) {\n nodeEnv.EVM_RPC_URL = endpoints.evmUrl;\n // akash-anvil carries registry + tokenNetwork → settlement-complete with a URL.\n status.evm = 'configured';\n if (keyId) {\n chainProviders.push(\n buildEvmProviderEntry({ ...evm, rpcUrl: endpoints.evmUrl }, keyId)\n );\n }\n } else {\n // No URL → akash-anvil preset rpcUrl is '' → town runs relay-only.\n nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;\n }\n\n if (endpoints.solUrl) {\n nodeEnv.SOLANA_RPC_URL = endpoints.solUrl;\n nodeEnv.SOLANA_USDC_MINT = DEV_SOLANA.usdcMint;\n // programId is empty → Solana settlement relay-only; no connector provider.\n }\n\n return { network: 'custom', chainProviders, nodeEnv, status };\n}\n","/**\n * Shared ILP PREPARE packet construction for the TOON protocol.\n *\n * This function is the **single point of truth** for constructing ILP PREPARE\n * packet parameters. Both the x402 `/publish` handler and the existing\n * `publishEvent()` in the SDK must use it (or produce equivalent output).\n *\n * This ensures packet equivalence: the destination relay cannot distinguish\n * between packets sent via the x402 HTTP on-ramp and the ILP-native rail.\n *\n * @module\n */\n\n/**\n * Parameters for constructing an ILP PREPARE packet.\n */\nexport interface BuildIlpPrepareParams {\n /** ILP destination address (e.g., \"g.toon.target-relay\"). */\n destination: string;\n /** Payment amount in ILP units (bigint). */\n amount: bigint;\n /** TOON-encoded event as raw bytes. */\n data: Uint8Array;\n /** Packet expiry. Default: 30 seconds from now. */\n expiresAt?: Date;\n}\n\n/**\n * Result of building an ILP PREPARE packet.\n *\n * This matches the shape expected by `IlpClient.sendIlpPacket()`:\n * `{ destination, amount, data }` where amount is a string and data\n * is base64-encoded.\n */\nexport interface IlpPreparePacket {\n /** ILP destination address. */\n destination: string;\n /** Payment amount as a string (BigInt.toString()). */\n amount: string;\n /** TOON-encoded event as base64 string. */\n data: string;\n}\n\n/**\n * Build an ILP PREPARE packet from the given parameters.\n *\n * Converts the bigint amount to a string, encodes the TOON data to base64,\n * and passes through the destination. This is deliberately simple -- the\n * value is in having ONE function both the x402 and ILP paths call, not\n * in complex logic.\n *\n * @param params - Packet construction parameters.\n * @returns ILP PREPARE packet fields ready for `sendIlpPacket()`.\n */\nexport function buildIlpPrepare(\n params: BuildIlpPrepareParams\n): IlpPreparePacket {\n return {\n destination: params.destination,\n amount: String(params.amount),\n data: Buffer.from(params.data).toString('base64'),\n };\n}\n","/**\n * KMS identity derivation for TEE enclave-bound keypairs.\n *\n * Derives a Nostr-compatible secp256k1 keypair from a raw 32-byte Nautilus\n * KMS seed using the NIP-06 derivation path (m/44'/1237'/0'/0/{index}).\n *\n * The KMS seed is only accessible when the enclave's attestation is valid\n * (correct PCR values). If the relay code changes, PCR values change,\n * attestation fails, KMS seed becomes inaccessible, and the relay loses its\n * identity -- creating a cryptographic binding: identity proves code integrity.\n *\n * This module lives in @toon-protocol/core (not SDK) because Docker entrypoints\n * import from core. It does NOT include EVM address derivation (SDK concern).\n */\n\nimport { validateMnemonic, mnemonicToSeedSync } from '@scure/bip39';\nimport { wordlist } from '@scure/bip39/wordlists/english.js';\nimport { HDKey } from '@scure/bip32';\nimport { getPublicKey } from 'nostr-tools/pure';\nimport { ToonError } from '../errors.js';\n\n// ---------- Error Class ----------\n\n/**\n * Error thrown when KMS identity derivation fails.\n * Signals that the enclave cannot derive its identity -- this is a\n * security-critical condition that must NEVER fall back to random keys.\n */\nexport class KmsIdentityError extends ToonError {\n constructor(message: string, cause?: Error) {\n super(message, 'KMS_IDENTITY_ERROR', cause);\n this.name = 'KmsIdentityError';\n }\n}\n\n// ---------- Types ----------\n\n/** A Nostr keypair derived from a KMS seed. */\nexport interface KmsKeypair {\n /** The 32-byte secp256k1 secret key derived from the KMS seed. */\n secretKey: Uint8Array;\n /** The x-only Schnorr public key (32 bytes, 64 lowercase hex characters). */\n pubkey: string;\n}\n\n/** Options for `deriveFromKmsSeed()`. */\nexport interface DeriveFromKmsSeedOptions {\n /** BIP-39 mnemonic -- when provided, takes precedence over raw seed for derivation. */\n mnemonic?: string;\n /** Key index in the NIP-06 derivation path. Defaults to 0. */\n accountIndex?: number;\n}\n\n// ---------- Constants ----------\n\n/**\n * Maximum valid BIP-32 non-hardened child index (2^31 - 1).\n * Values at or above 2^31 are reserved for hardened derivation.\n */\nconst MAX_BIP32_INDEX = 0x7fffffff;\n\n// ---------- Implementation ----------\n\n/**\n * Derives a Nostr keypair from a raw 32-byte KMS seed or BIP-39 mnemonic\n * using the NIP-06 derivation path.\n *\n * When `options.mnemonic` is provided it takes precedence over the raw seed.\n * The raw seed is still validated even when a mnemonic is supplied (caller\n * must always provide a valid seed to prove KMS reachability).\n *\n * @param seed - A 32-byte Uint8Array from Nautilus KMS.\n * @param options - Optional derivation overrides (mnemonic, accountIndex).\n * @returns A `KmsKeypair` with `secretKey` and `pubkey`.\n * @throws {KmsIdentityError} If the seed is invalid or derivation fails.\n */\nexport function deriveFromKmsSeed(\n seed: Uint8Array,\n options?: DeriveFromKmsSeedOptions\n): KmsKeypair {\n // -- Validate seed --\n if (!(seed instanceof Uint8Array)) {\n throw new KmsIdentityError(\n `KMS seed unavailable: expected Uint8Array, got ${seed === null ? 'null' : typeof seed}`\n );\n }\n if (seed.length !== 32) {\n throw new KmsIdentityError(\n `KMS seed invalid: expected 32 bytes, got ${seed.length} bytes`\n );\n }\n\n // -- Validate accountIndex --\n const accountIndex = options?.accountIndex ?? 0;\n if (\n !Number.isInteger(accountIndex) ||\n accountIndex < 0 ||\n accountIndex > MAX_BIP32_INDEX\n ) {\n throw new KmsIdentityError(\n `Invalid accountIndex: expected a non-negative integer (0 to ${MAX_BIP32_INDEX}), got ${String(accountIndex)}`\n );\n }\n\n const path = `m/44'/1237'/0'/0/${accountIndex}`;\n let derivationSeed: Uint8Array | undefined;\n let masterKey: HDKey | undefined;\n let childKey: HDKey | undefined;\n\n try {\n if (options?.mnemonic !== undefined) {\n // -- Mnemonic path (takes precedence) --\n if (!validateMnemonic(options.mnemonic, wordlist)) {\n throw new KmsIdentityError(\n 'Invalid BIP-39 mnemonic: the provided words do not form a valid mnemonic phrase'\n );\n }\n derivationSeed = mnemonicToSeedSync(options.mnemonic);\n } else {\n // -- Raw seed path --\n derivationSeed = seed;\n }\n\n masterKey = HDKey.fromMasterSeed(derivationSeed);\n childKey = masterKey.derive(path);\n\n if (!childKey.privateKey) {\n throw new KmsIdentityError(\n `Failed to derive private key at path ${path}`\n );\n }\n\n const secretKey = childKey.privateKey;\n const pubkey = getPublicKey(secretKey);\n\n // Defensive copy of the private key to prevent external mutation\n return { secretKey: new Uint8Array(secretKey), pubkey };\n } catch (error: unknown) {\n if (error instanceof KmsIdentityError) {\n throw error;\n }\n throw new KmsIdentityError(\n `KMS key derivation failed at path ${path}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Best-effort zeroing of intermediate key material.\n // HDKey.wipePrivateData() zeros the internal private key buffer and\n // chain code, limiting the window during which secrets remain in memory.\n masterKey?.wipePrivateData();\n childKey?.wipePrivateData();\n\n // Only zero the mnemonic-derived seed (64 bytes), not the raw KMS seed\n // which the caller owns.\n if (options?.mnemonic !== undefined && derivationSeed) {\n derivationSeed.fill(0);\n }\n }\n}\n","/**\n * Nix Build Orchestration for Reproducible Docker Images\n *\n * The NixBuilder class wraps the `nix build` CLI invocation to produce\n * deterministic Docker images for TEE (Trusted Execution Environment)\n * deployment. Each build returns a NixBuildResult containing the image\n * hash, PCR (Platform Configuration Register) values, and Nix store path.\n *\n * PCR values are SHA-384 hashes measured by the TEE hardware from the\n * loaded image. If the Docker image is deterministic (identical content\n * hash across builds), then PCR values will also be identical -- enabling\n * remote attestation verification (Story 4.3 AttestationVerifier).\n *\n * IMPORTANT: The NixBuilder shells out to `nix build` -- it requires\n * the Nix package manager to be installed on the build machine. Tests\n * that exercise actual Nix builds should be conditionally skipped when\n * Nix is not available. Unit tests use mocked child_process.\n *\n * @module\n */\n\nimport { execFile as execFileCb } from 'node:child_process';\nimport { readFile, mkdtemp, cp, writeFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\n\n/**\n * Promise wrapper for child_process.execFile. Uses a manual wrapper rather\n * than util.promisify so that the function can be reliably mocked in tests\n * (vi.mock of node:child_process replaces execFile with a vi.fn(), and\n * promisify at module scope captures the original before mock replacement).\n */\nfunction execFileAsync(\n cmd: string,\n args: string[],\n options: { cwd?: string; timeout?: number }\n): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n execFileCb(cmd, args, options, (error, stdout, stderr) => {\n if (error) {\n reject(error);\n } else {\n resolve({ stdout: stdout.toString(), stderr: stderr.toString() });\n }\n });\n });\n}\n\n/**\n * Result of a Nix build invocation.\n * Contains the Docker image content hash, PCR values derived from the image,\n * the Nix store path, and the build timestamp.\n */\nexport interface NixBuildResult {\n /** Docker image content hash (sha256:... -- 64 lowercase hex chars after prefix) */\n imageHash: string;\n /** PCR0 -- SHA-384 hash of the enclave image (96 lowercase hex chars) */\n pcr0: string;\n /** PCR1 -- SHA-384 hash of the kernel (96 lowercase hex chars) */\n pcr1: string;\n /** PCR2 -- SHA-384 hash of the application (96 lowercase hex chars) */\n pcr2: string;\n /** Path to the image in the Nix store (/nix/store/...) */\n imagePath: string;\n /** Unix timestamp (seconds since epoch) of the build */\n buildTimestamp: number;\n}\n\n/**\n * Configuration for the NixBuilder.\n */\nexport interface NixBuilderConfig {\n /** Root directory of the project (containing flake.nix) */\n projectRoot: string;\n /** Path to Dockerfile.nix relative to projectRoot */\n dockerfilePath: string;\n /**\n * Optional source overrides for testing. Keys are relative paths from\n * projectRoot, values are the file content to write. When set, NixBuilder\n * creates a temporary copy of the source tree with the overrides applied\n * before running the build. This allows testing that source changes\n * produce different PCR values.\n */\n sourceOverride?: Record<string, string>;\n}\n\n/**\n * Orchestrates Nix-based Docker image builds for reproducible TEE deployment.\n *\n * The build() method shells out to `nix build .#docker-image` and parses the\n * output to extract the image hash, Nix store path, and PCR values. PCR values\n * are computed from the image content using SHA-384 to simulate the measurement\n * that would be performed by the TEE hardware (AWS Nitro hypervisor).\n *\n * @example\n * ```typescript\n * const builder = new NixBuilder({\n * projectRoot: '/path/to/toon',\n * dockerfilePath: 'docker/Dockerfile.nix',\n * });\n * const result = await builder.build();\n * console.log(result.imageHash); // sha256:abc123...\n * console.log(result.pcr0); // 96-char hex string\n * ```\n */\nexport class NixBuilder {\n private readonly config: NixBuilderConfig;\n\n constructor(config: NixBuilderConfig) {\n this.config = config;\n }\n\n /**\n * Execute a Nix build and return the result.\n *\n * Shells out to `nix build .#docker-image` in the project root directory.\n * If sourceOverride is configured, creates a temporary modified source tree.\n *\n * @throws Error if Nix is not installed or the build fails\n */\n async build(): Promise<NixBuildResult> {\n let buildDir = this.config.projectRoot;\n let tempDir: string | undefined;\n\n try {\n // If source overrides are configured, create a temporary modified source tree\n if (this.config.sourceOverride) {\n tempDir = await mkdtemp(path.join(tmpdir(), 'toon-nix-'));\n await cp(this.config.projectRoot, tempDir, { recursive: true });\n\n for (const [relativePath, content] of Object.entries(\n this.config.sourceOverride\n )) {\n const fullPath = path.resolve(tempDir, relativePath);\n // Defense-in-depth: prevent path traversal outside the temp dir.\n // Use tempDir + path.sep to prevent prefix collision attacks where\n // a sibling directory shares the same prefix (e.g., tempDir is\n // /tmp/toon-nix-abc and path resolves to /tmp/toon-nix-abc123/).\n if (\n !fullPath.startsWith(tempDir + path.sep) &&\n fullPath !== tempDir\n ) {\n throw new Error(\n `sourceOverride path traversal detected: \"${relativePath}\" resolves outside temp directory`\n );\n }\n await writeFile(fullPath, content, 'utf-8');\n }\n\n buildDir = tempDir;\n }\n\n // Run nix build -- this produces a `result` symlink pointing to\n // the image in the Nix store\n const { stdout } = await execFileAsync(\n 'nix',\n ['build', '.#docker-image', '--print-out-paths'],\n {\n cwd: buildDir,\n timeout: 600_000, // 10 minutes -- Nix builds can be slow on first run\n }\n );\n\n const imagePath = stdout.trim();\n\n // Validate the store path looks correct\n if (!imagePath.startsWith('/nix/store/')) {\n throw new Error(\n `Unexpected Nix build output path: ${imagePath} (expected /nix/store/...)`\n );\n }\n\n // Read the image file to compute hashes\n const imageData = await readFile(imagePath);\n\n // Compute Docker image content hash (SHA-256)\n const sha256 = createHash('sha256').update(imageData).digest('hex');\n const imageHash = `sha256:${sha256}`;\n\n // Compute PCR values (SHA-384) from the image content.\n // In the real Oyster CVM environment, PCR values are measured by the\n // AWS Nitro hypervisor from the loaded image. Here we simulate this\n // by computing SHA-384 of different aspects of the image:\n // PCR0: SHA-384 of the entire image file (enclave image measurement)\n // PCR1: SHA-384 of the first 1MB (kernel/boot region approximation)\n // PCR2: SHA-384 of bytes after the first 1MB (application measurement)\n //\n // Note: For images <= 1MB, PCR1 === PCR0 (entire image fits in kernel\n // region) and PCR2 is computed over imageData with a domain separator\n // prefix to ensure PCR2 !== PCR0 even when the app region is empty.\n const pcr0 = createHash('sha384').update(imageData).digest('hex');\n\n const KERNEL_REGION_SIZE = 1024 * 1024;\n const kernelRegion = imageData.subarray(\n 0,\n Math.min(KERNEL_REGION_SIZE, imageData.length)\n );\n const pcr1 = createHash('sha384').update(kernelRegion).digest('hex');\n\n const appRegion = imageData.subarray(\n Math.min(KERNEL_REGION_SIZE, imageData.length)\n );\n // When the image is <= 1MB the app region is empty. Use a domain\n // separator prefix ('pcr2:') so PCR2 is always distinct from PCR0.\n const pcr2 =\n appRegion.length > 0\n ? createHash('sha384').update(appRegion).digest('hex')\n : createHash('sha384')\n .update('pcr2:')\n .update(imageData)\n .digest('hex');\n\n return {\n imageHash,\n pcr0,\n pcr1,\n pcr2,\n imagePath,\n buildTimestamp: Math.floor(Date.now() / 1000),\n };\n } finally {\n // Clean up temporary directory if created\n if (tempDir) {\n await rm(tempDir, { recursive: true, force: true }).catch(() => {\n // Best-effort cleanup -- ignore errors\n });\n }\n }\n }\n}\n","/**\n * PCR Validation and Dockerfile Determinism Analysis\n *\n * Provides utilities for verifying PCR (Platform Configuration Register)\n * reproducibility across independent Nix builds, and for statically\n * analyzing Dockerfile.nix expressions for non-deterministic patterns.\n *\n * PCR values are SHA-384 hashes (96 lowercase hex characters) measured\n * by the TEE hardware. If two builds of the same source tree produce\n * different PCR values, the attestation verification model collapses\n * (R-E4-002, Score 6) because the AttestationVerifier cannot compare\n * observed PCR values against a known-good registry.\n *\n * @module\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { ToonError } from '../errors.js';\nimport type { NixBuildResult } from './nix-builder.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A forbidden pattern that must not appear in a deterministic Dockerfile/Nix\n * expression. Each entry includes a regex, a human-readable name, and an\n * explanation of why the pattern breaks reproducibility.\n */\nexport interface ForbiddenPattern {\n /** Regex to match against each line of the Dockerfile */\n pattern: RegExp;\n /** Human-readable name of the pattern (e.g., 'apt-get update') */\n name: string;\n /** Explanation of why this pattern breaks reproducibility */\n reason: string;\n}\n\n/**\n * A single violation found during determinism analysis. Includes the line\n * number (1-indexed), the name of the matched pattern, and the text that\n * triggered the match.\n */\nexport interface Violation {\n /** Line number (1-indexed) where the violation was found */\n line: number;\n /** Name of the forbidden pattern that matched */\n patternName: string;\n /** The text from the line that triggered the match */\n matchedText: string;\n}\n\n/**\n * Result of analyzing a Dockerfile for non-deterministic patterns.\n */\nexport interface DeterminismReport {\n /** True if no forbidden patterns were found */\n deterministic: boolean;\n /** List of violations found (empty if deterministic) */\n violations: Violation[];\n /** Number of lines scanned */\n scannedLines: number;\n}\n\n/**\n * Result of comparing two NixBuildResults for PCR reproducibility.\n */\nexport interface PcrReproducibilityResult {\n /** True if all PCR values and image hash match */\n reproducible: boolean;\n /** Whether PCR0 values match */\n pcr0Match: boolean;\n /** Whether PCR1 values match */\n pcr1Match: boolean;\n /** Whether PCR2 values match */\n pcr2Match: boolean;\n /** Whether Docker image content hashes match */\n imageHashMatch: boolean;\n /** The actual PCR and hash values from both builds for debugging */\n details: {\n buildA: { pcr0: string; pcr1: string; pcr2: string; imageHash: string };\n buildB: { pcr0: string; pcr1: string; pcr2: string; imageHash: string };\n };\n /** Human-readable summary suitable for CI log output */\n summary: string;\n}\n\n/**\n * Options for verifyPcrReproducibility().\n */\nexport interface VerifyOptions {\n /** If true, throws PcrReproducibilityError when builds diverge */\n throwOnMismatch?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Error class\n// ---------------------------------------------------------------------------\n\n/**\n * Error thrown when PCR reproducibility verification fails (builds diverge).\n * Contains both build results in the error message for CI debugging.\n */\nexport class PcrReproducibilityError extends ToonError {\n constructor(buildA: NixBuildResult, buildB: NixBuildResult) {\n super(\n `PCR reproducibility check failed: ` +\n `pcr0 buildA=${buildA.pcr0} buildB=${buildB.pcr0}, ` +\n `pcr1 buildA=${buildA.pcr1} buildB=${buildB.pcr1}, ` +\n `pcr2 buildA=${buildA.pcr2} buildB=${buildB.pcr2}`,\n 'PCR_REPRODUCIBILITY_ERROR'\n );\n this.name = 'PcrReproducibilityError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dockerfile analysis\n// ---------------------------------------------------------------------------\n\n/**\n * Reads the content of a Dockerfile.nix file from disk.\n *\n * This is a thin I/O wrapper separated from the analysis function to keep\n * `analyzeDockerfileForNonDeterminism()` a pure function (no side effects).\n *\n * @param filePath - Absolute path to the Dockerfile.nix file\n * @returns The file content as a UTF-8 string\n */\nexport async function readDockerfileNix(filePath: string): Promise<string> {\n return readFile(filePath, 'utf-8');\n}\n\n/**\n * Analyzes a Dockerfile/Nix expression for non-deterministic patterns.\n *\n * This is a pure function -- it takes the file content as a string and an\n * array of forbidden patterns, and returns a structured report. No I/O is\n * performed; file reading is handled by `readDockerfileNix()`.\n *\n * Each line of the content is tested against every forbidden pattern. Lines\n * that start with '#' (comments) are skipped since they don't affect the\n * build output.\n *\n * @param content - The Dockerfile/Nix expression content as a string\n * @param forbiddenPatterns - Array of patterns that indicate non-determinism\n * @returns A DeterminismReport with violations and line numbers\n *\n * @example\n * ```typescript\n * const report = analyzeDockerfileForNonDeterminism(content, [\n * { pattern: /apt-get\\s+update/, name: 'apt-get update', reason: '...' },\n * ]);\n * if (!report.deterministic) {\n * console.error('Violations:', report.violations);\n * }\n * ```\n */\nexport function analyzeDockerfileForNonDeterminism(\n content: string,\n forbiddenPatterns: ForbiddenPattern[]\n): DeterminismReport {\n const lines = content.split('\\n');\n const violations: Violation[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n\n // Skip comment-only lines (Nix uses # for comments, Dockerfiles use # too)\n const trimmed = line.trim();\n if (trimmed.startsWith('#')) continue;\n\n for (const fp of forbiddenPatterns) {\n // Reset lastIndex to avoid stateful regex issues when callers\n // pass patterns with the global (g) flag set\n fp.pattern.lastIndex = 0;\n const match = fp.pattern.exec(line);\n if (match) {\n violations.push({\n line: i + 1, // 1-indexed line numbers\n patternName: fp.name,\n matchedText: match[0],\n });\n }\n }\n }\n\n return {\n deterministic: violations.length === 0,\n violations,\n scannedLines: lines.length,\n };\n}\n\n// ---------------------------------------------------------------------------\n// PCR reproducibility verification\n// ---------------------------------------------------------------------------\n\n/**\n * Verifies that two NixBuildResults are reproducible -- i.e., they produce\n * identical PCR values and Docker image content hashes.\n *\n * This function is intended for CI pipelines: build the Docker image twice\n * from the same source tree, then call this function to verify the outputs\n * match. If they don't, the build is non-deterministic and PCR-based\n * attestation verification will fail.\n *\n * PCR values are normalized to lowercase before comparison to avoid\n * case-sensitivity issues.\n *\n * @param buildA - Result from the first build\n * @param buildB - Result from the second build\n * @param options - Optional: throwOnMismatch to throw PcrReproducibilityError\n * @returns A structured PcrReproducibilityResult with match details and summary\n *\n * Note: This function is async by API contract (Story 4.5 AC #4) to allow\n * future implementations to perform I/O (e.g., fetching PCR values from a\n * remote registry). The current implementation is synchronous.\n *\n * @example\n * ```typescript\n * const buildA = await builder.build();\n * const buildB = await builder.build();\n * const result = await verifyPcrReproducibility(buildA, buildB, {\n * throwOnMismatch: true,\n * });\n * console.log(result.summary); // \"PCR reproducibility: PASS ...\"\n * ```\n */\nexport async function verifyPcrReproducibility(\n buildA: NixBuildResult,\n buildB: NixBuildResult,\n options?: VerifyOptions\n): Promise<PcrReproducibilityResult> {\n // Normalize PCR values to lowercase for comparison\n const pcr0A = buildA.pcr0.toLowerCase();\n const pcr0B = buildB.pcr0.toLowerCase();\n const pcr1A = buildA.pcr1.toLowerCase();\n const pcr1B = buildB.pcr1.toLowerCase();\n const pcr2A = buildA.pcr2.toLowerCase();\n const pcr2B = buildB.pcr2.toLowerCase();\n const hashA = buildA.imageHash.toLowerCase();\n const hashB = buildB.imageHash.toLowerCase();\n\n const pcr0Match = pcr0A === pcr0B;\n const pcr1Match = pcr1A === pcr1B;\n const pcr2Match = pcr2A === pcr2B;\n const imageHashMatch = hashA === hashB;\n\n const reproducible = pcr0Match && pcr1Match && pcr2Match && imageHashMatch;\n\n // Build human-readable summary for CI logs\n const status = reproducible ? 'PASS' : 'FAIL';\n const summaryLines = [\n `PCR reproducibility: ${status}`,\n ` PCR0: ${pcr0Match ? 'match' : 'MISMATCH'} (${pcr0A})`,\n ` PCR1: ${pcr1Match ? 'match' : 'MISMATCH'} (${pcr1A})`,\n ` PCR2: ${pcr2Match ? 'match' : 'MISMATCH'} (${pcr2A})`,\n ` Image hash: ${imageHashMatch ? 'match' : 'MISMATCH'} (${hashA})`,\n ];\n\n if (!reproducible) {\n if (!pcr0Match) {\n summaryLines.push(` PCR0 buildB: ${pcr0B}`);\n }\n if (!pcr1Match) {\n summaryLines.push(` PCR1 buildB: ${pcr1B}`);\n }\n if (!pcr2Match) {\n summaryLines.push(` PCR2 buildB: ${pcr2B}`);\n }\n if (!imageHashMatch) {\n summaryLines.push(` Image hash buildB: ${hashB}`);\n }\n }\n\n const result: PcrReproducibilityResult = {\n reproducible,\n pcr0Match,\n pcr1Match,\n pcr2Match,\n imageHashMatch,\n details: {\n buildA: {\n pcr0: buildA.pcr0,\n pcr1: buildA.pcr1,\n pcr2: buildA.pcr2,\n imageHash: buildA.imageHash,\n },\n buildB: {\n pcr0: buildB.pcr0,\n pcr1: buildB.pcr1,\n pcr2: buildB.pcr2,\n imageHash: buildB.imageHash,\n },\n },\n summary: summaryLines.join('\\n'),\n };\n\n if (!reproducible && options?.throwOnMismatch) {\n throw new PcrReproducibilityError(buildA, buildB);\n }\n\n return result;\n}\n","/**\n * Structured logging for the TOON protocol.\n *\n * Provides JSON-formatted log output with contextual fields for:\n * - TEE enclave observability (component, enclaveType)\n * - DVM job log correlation (correlationId)\n * - Multi-node debugging (nodeId, pubkey)\n *\n * Zero runtime dependencies. Uses console.log/console.error as the\n * transport layer. Output is structured JSON when `json: true` (default\n * in production), or human-readable when `json: false` (development).\n *\n * @module\n */\n\n/**\n * Log levels ordered by severity.\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Configuration for creating a Logger instance.\n */\nexport interface LoggerConfig {\n /** Component name (e.g., 'bootstrap', 'x402', 'attestation') */\n component: string;\n /** Minimum log level to emit. Default: 'info' */\n level?: LogLevel;\n /** Output as JSON. Default: true */\n json?: boolean;\n /** Static context fields merged into every log entry */\n context?: Record<string, unknown>;\n}\n\n/**\n * A structured log entry.\n */\nexport interface LogEntry {\n /** ISO 8601 timestamp */\n timestamp: string;\n /** Log level */\n level: LogLevel;\n /** Component that produced the log */\n component: string;\n /** Log message */\n msg: string;\n /** Additional structured fields */\n [key: string]: unknown;\n}\n\n/**\n * Structured logger with contextual fields.\n *\n * Usage:\n * ```typescript\n * const log = createLogger({ component: 'bootstrap' });\n * log.info('Peer registered', { peerId: 'abc', ilpAddress: 'g.toon.peer' });\n * log.error('Bootstrap failed', { error: err.message });\n *\n * // Child logger with additional context\n * const jobLog = log.child({ correlationId: 'job-123' });\n * jobLog.info('DVM job started');\n * ```\n */\nexport interface Logger {\n debug(msg: string, fields?: Record<string, unknown>): void;\n info(msg: string, fields?: Record<string, unknown>): void;\n warn(msg: string, fields?: Record<string, unknown>): void;\n error(msg: string, fields?: Record<string, unknown>): void;\n\n /**\n * Create a child logger with additional context fields.\n * The child inherits all parent context and adds its own.\n */\n child(context: Record<string, unknown>): Logger;\n}\n\n/**\n * Create a structured logger.\n *\n * @param config - Logger configuration\n * @returns A Logger instance\n */\nexport function createLogger(config: LoggerConfig): Logger {\n const minLevel = config.level ?? 'info';\n const jsonOutput = config.json ?? true;\n const baseContext = config.context ?? {};\n const component = config.component;\n\n function shouldLog(level: LogLevel): boolean {\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[minLevel];\n }\n\n function formatEntry(\n level: LogLevel,\n msg: string,\n fields?: Record<string, unknown>\n ): LogEntry {\n return {\n timestamp: new Date().toISOString(),\n level,\n component,\n msg,\n ...baseContext,\n ...fields,\n };\n }\n\n function emit(\n level: LogLevel,\n msg: string,\n fields?: Record<string, unknown>\n ): void {\n if (!shouldLog(level)) return;\n\n const entry = formatEntry(level, msg, fields);\n\n if (jsonOutput) {\n const output = JSON.stringify(entry);\n if (level === 'error') {\n console.error(output);\n } else if (level === 'warn') {\n console.warn(output);\n } else {\n console.log(output);\n }\n } else {\n // Human-readable format for development\n const contextStr =\n Object.keys({ ...baseContext, ...fields }).length > 0\n ? ' ' + JSON.stringify({ ...baseContext, ...fields })\n : '';\n const prefix = `[${level.toUpperCase()}] [${component}]`;\n const output = `${prefix} ${msg}${contextStr}`;\n if (level === 'error') {\n console.error(output);\n } else if (level === 'warn') {\n console.warn(output);\n } else {\n console.log(output);\n }\n }\n }\n\n const logger: Logger = {\n debug: (msg, fields) => emit('debug', msg, fields),\n info: (msg, fields) => emit('info', msg, fields),\n warn: (msg, fields) => emit('warn', msg, fields),\n error: (msg, fields) => emit('error', msg, fields),\n child: (context) =>\n createLogger({\n component,\n level: minLevel,\n json: jsonOutput,\n context: { ...baseContext, ...context },\n }),\n };\n\n return logger;\n}\n","/**\n * @toon-protocol/core\n *\n * Core library for Nostr-based ILP peer discovery.\n */\n\nexport const VERSION = '0.1.0';\n\n// Event kind constants\nexport {\n ILP_PEER_INFO_KIND,\n SERVICE_DISCOVERY_KIND,\n SEED_RELAY_LIST_KIND,\n TEE_ATTESTATION_KIND,\n JOB_REQUEST_KIND_BASE,\n JOB_RESULT_KIND_BASE,\n JOB_FEEDBACK_KIND,\n TEXT_GENERATION_KIND,\n IMAGE_GENERATION_KIND,\n TEXT_TO_SPEECH_KIND,\n TRANSLATION_KIND,\n BLOB_STORAGE_REQUEST_KIND,\n BLOB_STORAGE_RESULT_KIND,\n ILP_ROOT_PREFIX,\n PREFIX_CLAIM_KIND,\n PREFIX_GRANT_KIND,\n PET_INTERACTION_REQUEST_KIND,\n PET_INTERACTION_RESULT_KIND,\n PET_INTERACTION_EVENT_KIND,\n} from './constants.js';\n\n// ILP address derivation and BTP prefix exchange\nexport { deriveChildAddress } from './address/index.js';\nexport {\n isValidIlpAddressStructure,\n validateIlpAddress,\n extractPrefixFromHandshake,\n buildPrefixHandshakeData,\n validatePrefixConsistency,\n checkAddressCollision,\n assignAddressFromHandshake,\n isGenesisNode,\n AddressRegistry,\n validatePrefix,\n} from './address/index.js';\nexport type {\n BtpHandshakeExtension,\n PrefixValidationResult,\n} from './address/index.js';\n\n// TypeScript interfaces\nexport type {\n IlpPeerInfo,\n SwapPair,\n TeeAttestation,\n Subscription,\n OpenChannelParams,\n OpenChannelResult,\n ChannelState,\n ConnectorChannelClient,\n} from './types.js';\n\n// Error classes\nexport { ToonError, InvalidEventError, PeerDiscoveryError } from './errors.js';\n\n// Event parsers and builders\nexport {\n parseIlpPeerInfo,\n validateChainId,\n buildIlpPeerInfoEvent,\n buildSeedRelayListEvent,\n parseSeedRelayList,\n type SeedRelayEntry,\n buildServiceDiscoveryEvent,\n parseServiceDiscovery,\n type ServiceDiscoveryContent,\n type SkillDescriptor,\n buildAttestationEvent,\n parseAttestation,\n type AttestationEventOptions,\n type ParsedAttestation,\n buildJobRequestEvent,\n buildJobResultEvent,\n buildJobFeedbackEvent,\n parseJobRequest,\n parseJobResult,\n parseJobFeedback,\n type DvmJobStatus,\n type JobRequestParams,\n type JobResultParams,\n type JobFeedbackParams,\n type ParsedJobRequest,\n type ParsedJobResult,\n type ParsedJobFeedback,\n buildWorkflowDefinitionEvent,\n parseWorkflowDefinition,\n WORKFLOW_CHAIN_KIND,\n type WorkflowStep,\n type WorkflowDefinitionParams,\n type ParsedWorkflowDefinition,\n buildSwarmRequestEvent,\n buildSwarmSelectionEvent,\n parseSwarmRequest,\n parseSwarmSelection,\n type SwarmRequestParams,\n type SwarmSelectionParams,\n type ParsedSwarmRequest,\n type ParsedSwarmSelection,\n buildPrefixClaimEvent,\n parsePrefixClaimEvent,\n buildPrefixGrantEvent,\n parsePrefixGrantEvent,\n type PrefixClaimContent,\n type PrefixGrantContent,\n buildBlobStorageRequest,\n parseBlobStorageRequest,\n type BlobStorageRequestParams,\n type ParsedBlobStorageRequest,\n AttestedResultVerifier,\n hasRequireAttestation,\n type AttestedResultVerificationOptions,\n type AttestedResultVerificationResult,\n buildJobReviewEvent,\n parseJobReview,\n buildWotDeclarationEvent,\n parseWotDeclaration,\n ReputationScoreCalculator,\n hasMinReputation,\n JOB_REVIEW_KIND,\n WEB_OF_TRUST_KIND,\n type JobReviewParams,\n type ParsedJobReview,\n type WotDeclarationParams,\n type ParsedWotDeclaration,\n type ReputationSignals,\n type ReputationScore,\n} from './events/index.js';\n\n// Peer discovery\nexport {\n NostrPeerDiscovery,\n GenesisPeerLoader,\n type GenesisPeer,\n ArDrivePeerRegistry,\n SocialPeerDiscovery,\n type SocialPeerDiscoveryConfig,\n type SocialDiscoveryEvent,\n type SocialDiscoveryEventListener,\n SeedRelayDiscovery,\n publishSeedRelayEntry,\n type SeedRelayDiscoveryConfig,\n type SeedRelayDiscoveryResult,\n type PublishSeedRelayConfig,\n} from './discovery/index.js';\n\n// Fee calculation utilities\nexport {\n calculateRouteAmount,\n type CalculateRouteAmountParams,\n resolveRouteFees,\n type ResolveRouteFeesParams,\n type ResolveRouteFeesResult,\n} from './fee/index.js';\n\n// Settlement utilities\nexport {\n negotiateSettlementChain,\n resolveTokenForChain,\n // Canonical balance-proof hash/field layouts (single source of truth) + the\n // base58 / Mina-key helpers used to build chain-specific claims.\n hexToBytes,\n bigintToBytes32BE,\n concatBytes,\n balanceProofHashEvm,\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n base58Encode,\n base58Decode,\n hexToMinaBase58PrivateKey,\n} from './settlement/index.js';\n\n// Bootstrap service\nexport {\n BootstrapService,\n BootstrapError,\n createDiscoveryTracker,\n type DiscoveryTracker,\n type DiscoveryTrackerConfig,\n createHttpIlpClient,\n createHttpRuntimeClient,\n createAgentRuntimeClient,\n createDirectIlpClient,\n createDirectRuntimeClient,\n createDirectConnectorAdmin,\n type KnownPeer,\n type BootstrapConfig,\n type BootstrapServiceConfig,\n type BootstrapResult,\n type ConnectorAdminClient,\n type BootstrapPhase,\n type BootstrapEvent,\n type BootstrapEventListener,\n type IlpClient,\n type AgentRuntimeClient,\n type IlpSendResult,\n type ConnectorNodeLike,\n type SendPacketParams,\n type SendPacketResult,\n type DirectRuntimeClientConfig,\n type ConnectorAdminLike,\n type RegisterPeerParams,\n createDirectChannelClient,\n type ConnectorChannelLike,\n type DiscoveredPeer,\n type SettlementConfig,\n createHttpConnectorAdmin,\n createHttpRuntimeClientV2,\n createHttpChannelClient,\n AttestationVerifier,\n AttestationState,\n type VerificationResult,\n type PeerDescriptor,\n type AttestationVerifierConfig,\n AttestationBootstrap,\n type AttestationBootstrapConfig,\n type AttestationBootstrapResult,\n type AttestationBootstrapEvent,\n type AttestationBootstrapEventListener,\n} from './bootstrap/index.js';\n\n// Compose - embedded connector orchestration\nexport {\n createToonNode,\n type ToonNodeConfig,\n type ToonNode,\n type ToonNodeStartResult,\n type EmbeddableConnectorLike,\n type PacketHandler,\n type HandlePacketRequest,\n type HandlePacketAcceptResponse,\n type HandlePacketRejectResponse,\n type HandlePacketResponse,\n} from './compose.js';\n\n// Connector reject-code translation (ILP wire code → semantic reason)\nexport { ILP_TO_SEMANTIC, ilpCodeToSemantic } from './utils/reject-code.js';\n\n// TOON codec\nexport {\n encodeEventToToon,\n encodeEventToToonString,\n ToonEncodeError,\n decodeEventFromToon,\n ToonDecodeError,\n shallowParseToon,\n type ToonRoutingMeta,\n} from './toon/index.js';\n\n// Chain configuration\nexport {\n MOCK_USDC_ADDRESS,\n USDC_DECIMALS,\n USDC_SYMBOL,\n USDC_NAME,\n MOCK_USDC_CONFIG,\n type MockUsdcConfig,\n} from './chain/usdc.js';\n\n// Chain presets and multi-environment configuration\nexport {\n resolveChainConfig,\n resolveSolanaChainConfig,\n resolveMinaChainConfig,\n buildEip712Domain,\n buildEvmProviderEntry,\n buildSolanaProviderEntry,\n buildMinaProviderEntry,\n CHAIN_PRESETS,\n SOLANA_CHAIN_PRESETS,\n MINA_CHAIN_PRESETS,\n type ChainPreset,\n type ChainName,\n type ChainType,\n type SolanaChainName,\n type MinaChainName,\n type MultiChainName,\n type SolanaChainPreset,\n type MinaChainPreset,\n type ChainProviderConfigEntry,\n type EVMProviderConfigEntry,\n type SolanaProviderConfigEntry,\n type MinaProviderConfigEntry,\n} from './chain/chain-config.js';\n\n// Network-mode resolution (mainnet/testnet/devnet/custom → apex + node config)\nexport {\n resolveNetworkProfile,\n resolveClientNetwork,\n RELAY_ONLY_CHAIN,\n type NetworkMode,\n type NetworkProfile,\n type NetworkNodeEnv,\n type NetworkFamilyStatus,\n type CustomEndpoints,\n type ClientNetworkPresets,\n} from './chain/network-profile.js';\n\n// x402 protocol support (shared ILP PREPARE construction)\nexport {\n buildIlpPrepare,\n type BuildIlpPrepareParams,\n type IlpPreparePacket,\n} from './x402/index.js';\n\n// KMS Identity (TEE enclave-bound key derivation)\nexport {\n deriveFromKmsSeed,\n KmsIdentityError,\n type KmsKeypair,\n type DeriveFromKmsSeedOptions,\n} from './identity/index.js';\n\n// Nix reproducible builds (TEE deployment)\nexport {\n NixBuilder,\n type NixBuildResult,\n type NixBuilderConfig,\n verifyPcrReproducibility,\n readDockerfileNix,\n analyzeDockerfileForNonDeterminism,\n PcrReproducibilityError,\n type PcrReproducibilityResult,\n type VerifyOptions,\n type DeterminismReport,\n type Violation,\n type ForbiddenPattern,\n} from './build/index.js';\n\n// Structured logging\nexport {\n createLogger,\n type Logger,\n type LoggerConfig,\n type LogLevel,\n type LogEntry,\n} from './logger.js';\n\n// NIP-34: Git stuff\n// NOTE: Import from '@toon-protocol/core/nip34' to use Git integration\n// This avoids loading simple-git dependency when not needed\n","/**\n * PBKDF (RFC 2898). Can be used to create a key from password and salt.\n * @module\n */\nimport { hmac } from './hmac.ts';\n// prettier-ignore\nimport {\n ahash, anumber,\n asyncLoop, checkOpts, clean, createView, Hash, kdfInputToBytes,\n type CHash,\n type KDFInput\n} from './utils.ts';\n\nexport type Pbkdf2Opt = {\n c: number; // Iterations\n dkLen?: number; // Desired key length in bytes (Intended output length in octets of the derived key\n asyncTick?: number; // Maximum time in ms for which async function can block execution\n};\n// Common prologue and epilogue for sync/async functions\nfunction pbkdf2Init(hash: CHash, _password: KDFInput, _salt: KDFInput, _opts: Pbkdf2Opt) {\n ahash(hash);\n const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts);\n const { c, dkLen, asyncTick } = opts;\n anumber(c);\n anumber(dkLen);\n anumber(asyncTick);\n if (c < 1) throw new Error('iterations (c) should be >= 1');\n const password = kdfInputToBytes(_password);\n const salt = kdfInputToBytes(_salt);\n // DK = PBKDF2(PRF, Password, Salt, c, dkLen);\n const DK = new Uint8Array(dkLen);\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n const PRF = hmac.create(hash, password);\n const PRFSalt = PRF._cloneInto().update(salt);\n return { c, dkLen, asyncTick, DK, PRF, PRFSalt };\n}\n\nfunction pbkdf2Output<T extends Hash<T>>(\n PRF: Hash<T>,\n PRFSalt: Hash<T>,\n DK: Uint8Array,\n prfW: Hash<T>,\n u: Uint8Array\n) {\n PRF.destroy();\n PRFSalt.destroy();\n if (prfW) prfW.destroy();\n clean(u);\n return DK;\n}\n\n/**\n * PBKDF2-HMAC: RFC 2898 key derivation function\n * @param hash - hash function that would be used e.g. sha256\n * @param password - password from which a derived key is generated\n * @param salt - cryptographic salt\n * @param opts - {c, dkLen} where c is work factor and dkLen is output message size\n * @example\n * const key = pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) });\n */\nexport function pbkdf2(\n hash: CHash,\n password: KDFInput,\n salt: KDFInput,\n opts: Pbkdf2Opt\n): Uint8Array {\n const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);\n let prfW: any; // Working copy\n const arr = new Uint8Array(4);\n const view = createView(arr);\n const u = new Uint8Array(PRF.outputLen);\n // DK = T1 + T2 + ⋯ + Tdklen/hlen\n for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {\n // Ti = F(Password, Salt, c, i)\n const Ti = DK.subarray(pos, pos + PRF.outputLen);\n view.setInt32(0, ti, false);\n // F(Password, Salt, c, i) = U1 ^ U2 ^ ⋯ ^ Uc\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);\n Ti.set(u.subarray(0, Ti.length));\n for (let ui = 1; ui < c; ui++) {\n // Uc = PRF(Password, Uc−1)\n PRF._cloneInto(prfW).update(u).digestInto(u);\n for (let i = 0; i < Ti.length; i++) Ti[i] ^= u[i];\n }\n }\n return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);\n}\n\n/**\n * PBKDF2-HMAC: RFC 2898 key derivation function. Async version.\n * @example\n * await pbkdf2Async(sha256, 'password', 'salt', { dkLen: 32, c: 500_000 });\n */\nexport async function pbkdf2Async(\n hash: CHash,\n password: KDFInput,\n salt: KDFInput,\n opts: Pbkdf2Opt\n): Promise<Uint8Array> {\n const { c, dkLen, asyncTick, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);\n let prfW: any; // Working copy\n const arr = new Uint8Array(4);\n const view = createView(arr);\n const u = new Uint8Array(PRF.outputLen);\n // DK = T1 + T2 + ⋯ + Tdklen/hlen\n for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {\n // Ti = F(Password, Salt, c, i)\n const Ti = DK.subarray(pos, pos + PRF.outputLen);\n view.setInt32(0, ti, false);\n // F(Password, Salt, c, i) = U1 ^ U2 ^ ⋯ ^ Uc\n // U1 = PRF(Password, Salt + INT_32_BE(i))\n (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);\n Ti.set(u.subarray(0, Ti.length));\n await asyncLoop(c - 1, asyncTick, () => {\n // Uc = PRF(Password, Uc−1)\n PRF._cloneInto(prfW).update(u).digestInto(u);\n for (let i = 0; i < Ti.length; i++) Ti[i] ^= u[i];\n });\n }\n return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);\n}\n","/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */\n\nexport interface Coder<F, T> {\n encode(from: F): T;\n decode(to: T): F;\n}\n\nexport interface BytesCoder extends Coder<Uint8Array, string> {\n encode: (data: Uint8Array) => string;\n decode: (str: string) => Uint8Array;\n}\n\nfunction isBytes(a: unknown): a is Uint8Array {\n return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');\n}\n/** Asserts something is Uint8Array. */\nfunction abytes(b: Uint8Array | undefined, ...lengths: number[]): void {\n if (!isBytes(b)) throw new Error('Uint8Array expected');\n if (lengths.length > 0 && !lengths.includes(b.length))\n throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);\n}\n\nfunction isArrayOf(isString: boolean, arr: any[]) {\n if (!Array.isArray(arr)) return false;\n if (arr.length === 0) return true;\n if (isString) {\n return arr.every((item) => typeof item === 'string');\n } else {\n return arr.every((item) => Number.isSafeInteger(item));\n }\n}\n\n// no abytes: seems to have 10% slowdown. Why?!\n\nfunction afn(input: Function): input is Function {\n if (typeof input !== 'function') throw new Error('function expected');\n return true;\n}\n\nfunction astr(label: string, input: unknown): input is string {\n if (typeof input !== 'string') throw new Error(`${label}: string expected`);\n return true;\n}\n\nfunction anumber(n: number): void {\n if (!Number.isSafeInteger(n)) throw new Error(`invalid integer: ${n}`);\n}\n\nfunction aArr(input: any[]) {\n if (!Array.isArray(input)) throw new Error('array expected');\n}\nfunction astrArr(label: string, input: string[]) {\n if (!isArrayOf(true, input)) throw new Error(`${label}: array of strings expected`);\n}\nfunction anumArr(label: string, input: number[]) {\n if (!isArrayOf(false, input)) throw new Error(`${label}: array of numbers expected`);\n}\n\n// TODO: some recusive type inference so it would check correct order of input/output inside rest?\n// like <string, number>, <number, bytes>, <bytes, float>\ntype Chain = [Coder<any, any>, ...Coder<any, any>[]];\n// Extract info from Coder type\ntype Input<F> = F extends Coder<infer T, any> ? T : never;\ntype Output<F> = F extends Coder<any, infer T> ? T : never;\n// Generic function for arrays\ntype First<T> = T extends [infer U, ...any[]] ? U : never;\ntype Last<T> = T extends [...any[], infer U] ? U : never;\ntype Tail<T> = T extends [any, ...infer U] ? U : never;\n\ntype AsChain<C extends Chain, Rest = Tail<C>> = {\n // C[K] = Coder<Input<C[K]>, Input<Rest[k]>>\n [K in keyof C]: Coder<Input<C[K]>, Input<K extends keyof Rest ? Rest[K] : any>>;\n};\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction chain<T extends Chain & AsChain<T>>(...args: T): Coder<Input<First<T>>, Output<Last<T>>> {\n const id = (a: any) => a;\n // Wrap call in closure so JIT can inline calls\n const wrap = (a: any, b: any) => (c: any) => a(b(c));\n // Construct chain of args[-1].encode(args[-2].encode([...]))\n const encode = args.map((x) => x.encode).reduceRight(wrap, id);\n // Construct chain of args[0].decode(args[1].decode(...))\n const decode = args.map((x) => x.decode).reduce(wrap, id);\n return { encode, decode };\n}\n\n/**\n * Encodes integer radix representation to array of strings using alphabet and back.\n * Could also be array of strings.\n * @__NO_SIDE_EFFECTS__\n */\nfunction alphabet(letters: string | string[]): Coder<number[], string[]> {\n // mapping 1 to \"b\"\n const lettersA = typeof letters === 'string' ? letters.split('') : letters;\n const len = lettersA.length;\n astrArr('alphabet', lettersA);\n\n // mapping \"b\" to 1\n const indexes = new Map(lettersA.map((l, i) => [l, i]));\n return {\n encode: (digits: number[]) => {\n aArr(digits);\n return digits.map((i) => {\n if (!Number.isSafeInteger(i) || i < 0 || i >= len)\n throw new Error(\n `alphabet.encode: digit index outside alphabet \"${i}\". Allowed: ${letters}`\n );\n return lettersA[i]!;\n });\n },\n decode: (input: string[]): number[] => {\n aArr(input);\n return input.map((letter) => {\n astr('alphabet.decode', letter);\n const i = indexes.get(letter);\n if (i === undefined) throw new Error(`Unknown letter: \"${letter}\". Allowed: ${letters}`);\n return i;\n });\n },\n };\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction join(separator = ''): Coder<string[], string> {\n astr('join', separator);\n return {\n encode: (from) => {\n astrArr('join.decode', from);\n return from.join(separator);\n },\n decode: (to) => {\n astr('join.decode', to);\n return to.split(separator);\n },\n };\n}\n\n/**\n * Pad strings array so it has integer number of bits\n * @__NO_SIDE_EFFECTS__\n */\nfunction padding(bits: number, chr = '='): Coder<string[], string[]> {\n anumber(bits);\n astr('padding', chr);\n return {\n encode(data: string[]): string[] {\n astrArr('padding.encode', data);\n while ((data.length * bits) % 8) data.push(chr);\n return data;\n },\n decode(input: string[]): string[] {\n astrArr('padding.decode', input);\n let end = input.length;\n if ((end * bits) % 8)\n throw new Error('padding: invalid, string should have whole number of bytes');\n for (; end > 0 && input[end - 1] === chr; end--) {\n const last = end - 1;\n const byte = last * bits;\n if (byte % 8 === 0) throw new Error('padding: invalid, string has too much padding');\n }\n return input.slice(0, end);\n },\n };\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction normalize<T>(fn: (val: T) => T): Coder<T, T> {\n afn(fn);\n return { encode: (from: T) => from, decode: (to: T) => fn(to) };\n}\n\n/**\n * Slow: O(n^2) time complexity\n */\nfunction convertRadix(data: number[], from: number, to: number): number[] {\n // base 1 is impossible\n if (from < 2) throw new Error(`convertRadix: invalid from=${from}, base cannot be less than 2`);\n if (to < 2) throw new Error(`convertRadix: invalid to=${to}, base cannot be less than 2`);\n aArr(data);\n if (!data.length) return [];\n let pos = 0;\n const res = [];\n const digits = Array.from(data, (d) => {\n anumber(d);\n if (d < 0 || d >= from) throw new Error(`invalid integer: ${d}`);\n return d;\n });\n const dlen = digits.length;\n while (true) {\n let carry = 0;\n let done = true;\n for (let i = pos; i < dlen; i++) {\n const digit = digits[i]!;\n const fromCarry = from * carry;\n const digitBase = fromCarry + digit;\n if (\n !Number.isSafeInteger(digitBase) ||\n fromCarry / from !== carry ||\n digitBase - digit !== fromCarry\n ) {\n throw new Error('convertRadix: carry overflow');\n }\n const div = digitBase / to;\n carry = digitBase % to;\n const rounded = Math.floor(div);\n digits[i] = rounded;\n if (!Number.isSafeInteger(rounded) || rounded * to + carry !== digitBase)\n throw new Error('convertRadix: carry overflow');\n if (!done) continue;\n else if (!rounded) pos = i;\n else done = false;\n }\n res.push(carry);\n if (done) break;\n }\n for (let i = 0; i < data.length - 1 && data[i] === 0; i++) res.push(0);\n return res.reverse();\n}\n\nconst gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\nconst radix2carry = /* @__NO_SIDE_EFFECTS__ */ (from: number, to: number) =>\n from + (to - gcd(from, to));\nconst powers: number[] = /* @__PURE__ */ (() => {\n let res = [];\n for (let i = 0; i < 40; i++) res.push(2 ** i);\n return res;\n})();\n/**\n * Implemented with numbers, because BigInt is 5x slower\n */\nfunction convertRadix2(data: number[], from: number, to: number, padding: boolean): number[] {\n aArr(data);\n if (from <= 0 || from > 32) throw new Error(`convertRadix2: wrong from=${from}`);\n if (to <= 0 || to > 32) throw new Error(`convertRadix2: wrong to=${to}`);\n if (radix2carry(from, to) > 32) {\n throw new Error(\n `convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`\n );\n }\n let carry = 0;\n let pos = 0; // bitwise position in current element\n const max = powers[from]!;\n const mask = powers[to]! - 1;\n const res: number[] = [];\n for (const n of data) {\n anumber(n);\n if (n >= max) throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);\n carry = (carry << from) | n;\n if (pos + from > 32) throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);\n pos += from;\n for (; pos >= to; pos -= to) res.push(((carry >> (pos - to)) & mask) >>> 0);\n const pow = powers[pos];\n if (pow === undefined) throw new Error('invalid carry');\n carry &= pow - 1; // clean carry, otherwise it will cause overflow\n }\n carry = (carry << (to - pos)) & mask;\n if (!padding && pos >= from) throw new Error('Excess padding');\n if (!padding && carry > 0) throw new Error(`Non-zero padding: ${carry}`);\n if (padding && pos > 0) res.push(carry >>> 0);\n return res;\n}\n\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction radix(num: number): Coder<Uint8Array, number[]> {\n anumber(num);\n const _256 = 2 ** 8;\n return {\n encode: (bytes: Uint8Array) => {\n if (!isBytes(bytes)) throw new Error('radix.encode input should be Uint8Array');\n return convertRadix(Array.from(bytes), _256, num);\n },\n decode: (digits: number[]) => {\n anumArr('radix.decode', digits);\n return Uint8Array.from(convertRadix(digits, num, _256));\n },\n };\n}\n\n/**\n * If both bases are power of same number (like `2**8 <-> 2**64`),\n * there is a linear algorithm. For now we have implementation for power-of-two bases only.\n * @__NO_SIDE_EFFECTS__\n */\nfunction radix2(bits: number, revPadding = false): Coder<Uint8Array, number[]> {\n anumber(bits);\n if (bits <= 0 || bits > 32) throw new Error('radix2: bits should be in (0..32]');\n if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)\n throw new Error('radix2: carry overflow');\n return {\n encode: (bytes: Uint8Array) => {\n if (!isBytes(bytes)) throw new Error('radix2.encode input should be Uint8Array');\n return convertRadix2(Array.from(bytes), 8, bits, !revPadding);\n },\n decode: (digits: number[]) => {\n anumArr('radix2.decode', digits);\n return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));\n },\n };\n}\n\ntype ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;\nfunction unsafeWrapper<T extends (...args: any) => any>(fn: T) {\n afn(fn);\n return function (...args: ArgumentTypes<T>): ReturnType<T> | void {\n try {\n return fn.apply(null, args);\n } catch (e) {}\n };\n}\n\nfunction checksum(\n len: number,\n fn: (data: Uint8Array) => Uint8Array\n): Coder<Uint8Array, Uint8Array> {\n anumber(len);\n afn(fn);\n return {\n encode(data: Uint8Array) {\n if (!isBytes(data)) throw new Error('checksum.encode: input should be Uint8Array');\n const sum = fn(data).slice(0, len);\n const res = new Uint8Array(data.length + len);\n res.set(data);\n res.set(sum, data.length);\n return res;\n },\n decode(data: Uint8Array) {\n if (!isBytes(data)) throw new Error('checksum.decode: input should be Uint8Array');\n const payload = data.slice(0, -len);\n const oldChecksum = data.slice(-len);\n const newChecksum = fn(payload).slice(0, len);\n for (let i = 0; i < len; i++)\n if (newChecksum[i] !== oldChecksum[i]) throw new Error('Invalid checksum');\n return payload;\n },\n };\n}\n\n// prettier-ignore\nexport const utils: { alphabet: typeof alphabet; chain: typeof chain; checksum: typeof checksum; convertRadix: typeof convertRadix; convertRadix2: typeof convertRadix2; radix: typeof radix; radix2: typeof radix2; join: typeof join; padding: typeof padding; } = {\n alphabet, chain, checksum, convertRadix, convertRadix2, radix, radix2, join, padding,\n};\n\n// RFC 4648 aka RFC 3548\n// ---------------------\n\n/**\n * base16 encoding from RFC 4648.\n * @example\n * ```js\n * base16.encode(Uint8Array.from([0x12, 0xab]));\n * // => '12AB'\n * ```\n */\nexport const base16: BytesCoder = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));\n\n/**\n * base32 encoding from RFC 4648. Has padding.\n * Use `base32nopad` for unpadded version.\n * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.\n * @example\n * ```js\n * base32.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'CKVQ===='\n * base32.decode('CKVQ====');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32: BytesCoder = chain(\n radix2(5),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),\n padding(5),\n join('')\n);\n\n/**\n * base32 encoding from RFC 4648. No padding.\n * Use `base32` for padded version.\n * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.\n * @example\n * ```js\n * base32nopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'CKVQ'\n * base32nopad.decode('CKVQ');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32nopad: BytesCoder = chain(\n radix2(5),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),\n join('')\n);\n/**\n * base32 encoding from RFC 4648. Padded. Compared to ordinary `base32`, slightly different alphabet.\n * Use `base32hexnopad` for unpadded version.\n * @example\n * ```js\n * base32hex.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ALG===='\n * base32hex.decode('2ALG====');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32hex: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'),\n padding(5),\n join('')\n);\n\n/**\n * base32 encoding from RFC 4648. No padding. Compared to ordinary `base32`, slightly different alphabet.\n * Use `base32hex` for padded version.\n * @example\n * ```js\n * base32hexnopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ALG'\n * base32hexnopad.decode('2ALG');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32hexnopad: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'),\n join('')\n);\n/**\n * base32 encoding from RFC 4648. Doug Crockford's version.\n * https://www.crockford.com/base32.html\n * @example\n * ```js\n * base32crockford.encode(Uint8Array.from([0x12, 0xab]));\n * // => '2ANG'\n * base32crockford.decode('2ANG');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base32crockford: BytesCoder = chain(\n radix2(5),\n alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'),\n join(''),\n normalize((s: string) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1'))\n);\n\n// Built-in base64 conversion https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64\n// prettier-ignore\nconst hasBase64Builtin: boolean = /* @__PURE__ */ (() =>\n typeof (Uint8Array as any).from([]).toBase64 === 'function' &&\n typeof (Uint8Array as any).fromBase64 === 'function')();\n\nconst decodeBase64Builtin = (s: string, isUrl: boolean) => {\n astr('base64', s);\n const re = isUrl ? /^[A-Za-z0-9=_-]+$/ : /^[A-Za-z0-9=+/]+$/;\n const alphabet = isUrl ? 'base64url' : 'base64';\n if (s.length > 0 && !re.test(s)) throw new Error('invalid base64');\n return (Uint8Array as any).fromBase64(s, { alphabet, lastChunkHandling: 'strict' });\n};\n\n/**\n * base64 from RFC 4648. Padded.\n * Use `base64nopad` for unpadded version.\n * Also check out `base64url`, `base64urlnopad`.\n * Falls back to built-in function, when available.\n * @example\n * ```js\n * base64.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs='\n * base64.decode('Eqs=');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\n// prettier-ignore\nexport const base64: BytesCoder = hasBase64Builtin ? {\n encode(b) { abytes(b); return (b as any).toBase64(); },\n decode(s) { return decodeBase64Builtin(s, false); },\n} : chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'),\n padding(6),\n join('')\n);\n/**\n * base64 from RFC 4648. No padding.\n * Use `base64` for padded version.\n * @example\n * ```js\n * base64nopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs'\n * base64nopad.decode('Eqs');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base64nopad: BytesCoder = chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'),\n join('')\n);\n\n/**\n * base64 from RFC 4648, using URL-safe alphabet. Padded.\n * Use `base64urlnopad` for unpadded version.\n * Falls back to built-in function, when available.\n * @example\n * ```js\n * base64url.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs='\n * base64url.decode('Eqs=');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\n// prettier-ignore\nexport const base64url: BytesCoder = hasBase64Builtin ? {\n encode(b) { abytes(b); return (b as any).toBase64({ alphabet: 'base64url' }); },\n decode(s) { return decodeBase64Builtin(s, true); },\n} : chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'),\n padding(6),\n join('')\n);\n\n/**\n * base64 from RFC 4648, using URL-safe alphabet. No padding.\n * Use `base64url` for padded version.\n * @example\n * ```js\n * base64urlnopad.encode(Uint8Array.from([0x12, 0xab]));\n * // => 'Eqs'\n * base64urlnopad.decode('Eqs');\n * // => Uint8Array.from([0x12, 0xab])\n * ```\n */\nexport const base64urlnopad: BytesCoder = chain(\n radix2(6),\n alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'),\n join('')\n);\n\n// base58 code\n// -----------\nconst genBase58 = /* @__NO_SIDE_EFFECTS__ */ (abc: string) =>\n chain(radix(58), alphabet(abc), join(''));\n\n/**\n * base58: base64 without ambigous characters +, /, 0, O, I, l.\n * Quadratic (O(n^2)) - so, can't be used on large inputs.\n * @example\n * ```js\n * base58.decode('01abcdef');\n * // => '3UhJW'\n * ```\n */\nexport const base58: BytesCoder = genBase58(\n '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'\n);\n/**\n * base58: flickr version. Check out `base58`.\n */\nexport const base58flickr: BytesCoder = genBase58(\n '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'\n);\n/**\n * base58: XRP version. Check out `base58`.\n */\nexport const base58xrp: BytesCoder = genBase58(\n 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'\n);\n\n// Data len (index) -> encoded block len\nconst XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];\n\n/**\n * base58: XMR version. Check out `base58`.\n * Done in 8-byte blocks (which equals 11 chars in decoding). Last (non-full) block padded with '1' to size in XMR_BLOCK_LEN.\n * Block encoding significantly reduces quadratic complexity of base58.\n */\nexport const base58xmr: BytesCoder = {\n encode(data: Uint8Array) {\n let res = '';\n for (let i = 0; i < data.length; i += 8) {\n const block = data.subarray(i, i + 8);\n res += base58.encode(block).padStart(XMR_BLOCK_LEN[block.length]!, '1');\n }\n return res;\n },\n decode(str: string) {\n let res: number[] = [];\n for (let i = 0; i < str.length; i += 11) {\n const slice = str.slice(i, i + 11);\n const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);\n const block = base58.decode(slice);\n for (let j = 0; j < block.length - blockLen; j++) {\n if (block[j] !== 0) throw new Error('base58xmr: wrong padding');\n }\n res = res.concat(Array.from(block.slice(block.length - blockLen)));\n }\n return Uint8Array.from(res);\n },\n};\n\n/**\n * Method, which creates base58check encoder.\n * Requires function, calculating sha256.\n */\nexport const createBase58check = (sha256: (data: Uint8Array) => Uint8Array): BytesCoder =>\n chain(\n checksum(4, (data) => sha256(sha256(data))),\n base58\n );\n\n/**\n * Use `createBase58check` instead.\n * @deprecated\n */\nexport const base58check: (sha256: (data: Uint8Array) => Uint8Array) => BytesCoder =\n createBase58check;\n\n// Bech32 code\n// -----------\nexport interface Bech32Decoded<Prefix extends string = string> {\n prefix: Prefix;\n words: number[];\n}\nexport interface Bech32DecodedWithArray<Prefix extends string = string> {\n prefix: Prefix;\n words: number[];\n bytes: Uint8Array;\n}\n\nconst BECH_ALPHABET: Coder<number[], string> = chain(\n alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'),\n join('')\n);\n\nconst POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\nfunction bech32Polymod(pre: number): number {\n const b = pre >> 25;\n let chk = (pre & 0x1ffffff) << 5;\n for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {\n if (((b >> i) & 1) === 1) chk ^= POLYMOD_GENERATORS[i]!;\n }\n return chk;\n}\n\nfunction bechChecksum(prefix: string, words: number[], encodingConst = 1): string {\n const len = prefix.length;\n let chk = 1;\n for (let i = 0; i < len; i++) {\n const c = prefix.charCodeAt(i);\n if (c < 33 || c > 126) throw new Error(`Invalid prefix (${prefix})`);\n chk = bech32Polymod(chk) ^ (c >> 5);\n }\n chk = bech32Polymod(chk);\n for (let i = 0; i < len; i++) chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);\n for (let v of words) chk = bech32Polymod(chk) ^ v;\n for (let i = 0; i < 6; i++) chk = bech32Polymod(chk);\n chk ^= encodingConst;\n return BECH_ALPHABET.encode(convertRadix2([chk % powers[30]!], 30, 5, false));\n}\n\nexport interface Bech32 {\n encode<Prefix extends string>(\n prefix: Prefix,\n words: number[] | Uint8Array,\n limit?: number | false\n ): `${Lowercase<Prefix>}1${string}`;\n decode<Prefix extends string>(\n str: `${Prefix}1${string}`,\n limit?: number | false\n ): Bech32Decoded<Prefix>;\n encodeFromBytes(prefix: string, bytes: Uint8Array): string;\n decodeToBytes(str: string): Bech32DecodedWithArray;\n decodeUnsafe(str: string, limit?: number | false): void | Bech32Decoded<string>;\n fromWords(to: number[]): Uint8Array;\n fromWordsUnsafe(to: number[]): void | Uint8Array;\n toWords(from: Uint8Array): number[];\n}\n/**\n * @__NO_SIDE_EFFECTS__\n */\nfunction genBech32(encoding: 'bech32' | 'bech32m'): Bech32 {\n const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;\n const _words = radix2(5);\n const fromWords = _words.decode;\n const toWords = _words.encode;\n const fromWordsUnsafe = unsafeWrapper(fromWords);\n\n function encode<Prefix extends string>(\n prefix: Prefix,\n words: number[] | Uint8Array,\n limit: number | false = 90\n ): `${Lowercase<Prefix>}1${string}` {\n astr('bech32.encode prefix', prefix);\n if (isBytes(words)) words = Array.from(words);\n anumArr('bech32.encode', words);\n const plen = prefix.length;\n if (plen === 0) throw new TypeError(`Invalid prefix length ${plen}`);\n const actualLength = plen + 7 + words.length;\n if (limit !== false && actualLength > limit)\n throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);\n const lowered = prefix.toLowerCase();\n const sum = bechChecksum(lowered, words, ENCODING_CONST);\n return `${lowered}1${BECH_ALPHABET.encode(words)}${sum}` as `${Lowercase<Prefix>}1${string}`;\n }\n\n function decode<Prefix extends string>(\n str: `${Prefix}1${string}`,\n limit?: number | false\n ): Bech32Decoded<Prefix>;\n function decode(str: string, limit?: number | false): Bech32Decoded;\n function decode(str: string, limit: number | false = 90): Bech32Decoded {\n astr('bech32.decode input', str);\n const slen = str.length;\n if (slen < 8 || (limit !== false && slen > limit))\n throw new TypeError(`invalid string length: ${slen} (${str}). Expected (8..${limit})`);\n // don't allow mixed case\n const lowered = str.toLowerCase();\n if (str !== lowered && str !== str.toUpperCase())\n throw new Error(`String must be lowercase or uppercase`);\n const sepIndex = lowered.lastIndexOf('1');\n if (sepIndex === 0 || sepIndex === -1)\n throw new Error(`Letter \"1\" must be present between prefix and data only`);\n const prefix = lowered.slice(0, sepIndex);\n const data = lowered.slice(sepIndex + 1);\n if (data.length < 6) throw new Error('Data must be at least 6 characters long');\n const words = BECH_ALPHABET.decode(data).slice(0, -6);\n const sum = bechChecksum(prefix, words, ENCODING_CONST);\n if (!data.endsWith(sum)) throw new Error(`Invalid checksum in ${str}: expected \"${sum}\"`);\n return { prefix, words };\n }\n\n const decodeUnsafe = unsafeWrapper(decode);\n\n function decodeToBytes(str: string): Bech32DecodedWithArray {\n const { prefix, words } = decode(str, false);\n return { prefix, words, bytes: fromWords(words) };\n }\n\n function encodeFromBytes(prefix: string, bytes: Uint8Array) {\n return encode(prefix, toWords(bytes));\n }\n\n return {\n encode,\n decode,\n encodeFromBytes,\n decodeToBytes,\n decodeUnsafe,\n fromWords,\n fromWordsUnsafe,\n toWords,\n };\n}\n\n/**\n * bech32 from BIP 173. Operates on words.\n * For high-level, check out scure-btc-signer:\n * https://github.com/paulmillr/scure-btc-signer.\n */\nexport const bech32: Bech32 = genBech32('bech32');\n\n/**\n * bech32m from BIP 350. Operates on words.\n * It was to mitigate `bech32` weaknesses.\n * For high-level, check out scure-btc-signer:\n * https://github.com/paulmillr/scure-btc-signer.\n */\nexport const bech32m: Bech32 = genBech32('bech32m');\n\ndeclare const TextEncoder: any;\ndeclare const TextDecoder: any;\n\n/**\n * UTF-8-to-byte decoder. Uses built-in TextDecoder / TextEncoder.\n * @example\n * ```js\n * const b = utf8.decode(\"hey\"); // => new Uint8Array([ 104, 101, 121 ])\n * const str = utf8.encode(b); // \"hey\"\n * ```\n */\nexport const utf8: BytesCoder = {\n encode: (data) => new TextDecoder().decode(data),\n decode: (str) => new TextEncoder().encode(str),\n};\n\n// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex\n// prettier-ignore\nconst hasHexBuiltin: boolean = /* @__PURE__ */ (() =>\n typeof (Uint8Array as any).from([]).toHex === 'function' &&\n typeof (Uint8Array as any).fromHex === 'function')();\n// prettier-ignore\nconst hexBuiltin: BytesCoder = {\n encode(data) { abytes(data); return (data as any).toHex(); },\n decode(s) { astr('hex', s); return (Uint8Array as any).fromHex(s); },\n};\n/**\n * hex string decoder. Uses built-in function, when available.\n * @example\n * ```js\n * const b = hex.decode(\"0102ff\"); // => new Uint8Array([ 1, 2, 255 ])\n * const str = hex.encode(b); // \"0102ff\"\n * ```\n */\nexport const hex: BytesCoder = hasHexBuiltin\n ? hexBuiltin\n : chain(\n radix2(4),\n alphabet('0123456789abcdef'),\n join(''),\n normalize((s: string) => {\n if (typeof s !== 'string' || s.length % 2 !== 0)\n throw new TypeError(\n `hex.decode: expected string, got ${typeof s} with length ${s.length}`\n );\n return s.toLowerCase();\n })\n );\n\nexport type SomeCoders = {\n utf8: BytesCoder;\n hex: BytesCoder;\n base16: BytesCoder;\n base32: BytesCoder;\n base64: BytesCoder;\n base64url: BytesCoder;\n base58: BytesCoder;\n base58xmr: BytesCoder;\n};\n// prettier-ignore\nconst CODERS: SomeCoders = {\n utf8, hex, base16, base32, base64, base64url, base58, base58xmr\n};\ntype CoderType = keyof SomeCoders;\nconst coderTypeError =\n 'Invalid encoding type. Available types: utf8, hex, base16, base32, base64, base64url, base58, base58xmr';\n\n/** @deprecated */\nexport const bytesToString = (type: CoderType, bytes: Uint8Array): string => {\n if (typeof type !== 'string' || !CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError);\n if (!isBytes(bytes)) throw new TypeError('bytesToString() expects Uint8Array');\n return CODERS[type].encode(bytes);\n};\n\n/** @deprecated */\nexport const str: (type: CoderType, bytes: Uint8Array) => string = bytesToString; // as in python, but for bytes only\n\n/** @deprecated */\nexport const stringToBytes = (type: CoderType, str: string): Uint8Array => {\n if (!CODERS.hasOwnProperty(type)) throw new TypeError(coderTypeError);\n if (typeof str !== 'string') throw new TypeError('stringToBytes() expects string');\n return CODERS[type].decode(str);\n};\n/** @deprecated */\nexport const bytes: (type: CoderType, str: string) => Uint8Array = stringToBytes;\n","/**\n * Audited & minimal JS implementation of\n * [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).\n * @module\n * @example\n```js\nimport * as bip39 from '@scure/bip39';\nimport { wordlist } from '@scure/bip39/wordlists/english';\nconst mn = bip39.generateMnemonic(wordlist);\nconsole.log(mn);\nconst ent = bip39.mnemonicToEntropy(mn, wordlist)\nbip39.entropyToMnemonic(ent, wordlist);\nbip39.validateMnemonic(mn, wordlist);\nawait bip39.mnemonicToSeed(mn, 'password');\nbip39.mnemonicToSeedSync(mn, 'password');\n\n// Wordlists\nimport { wordlist as czech } from '@scure/bip39/wordlists/czech';\nimport { wordlist as english } from '@scure/bip39/wordlists/english';\nimport { wordlist as french } from '@scure/bip39/wordlists/french';\nimport { wordlist as italian } from '@scure/bip39/wordlists/italian';\nimport { wordlist as japanese } from '@scure/bip39/wordlists/japanese';\nimport { wordlist as korean } from '@scure/bip39/wordlists/korean';\nimport { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese';\nimport { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese';\nimport { wordlist as spanish } from '@scure/bip39/wordlists/spanish';\nimport { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese';\n```\n */\n/*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */\nimport { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2';\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport { abytes, anumber, randomBytes } from '@noble/hashes/utils';\nimport { utils as baseUtils } from '@scure/base';\n// Japanese wordlist\nconst isJapanese = (wordlist) => wordlist[0] === '\\u3042\\u3044\\u3053\\u304f\\u3057\\u3093';\n// Normalization replaces equivalent sequences of characters\n// so that any two texts that are equivalent will be reduced\n// to the same sequence of code points, called the normal form of the original text.\n// https://tonsky.me/blog/unicode/#why-is-a----\nfunction nfkd(str) {\n if (typeof str !== 'string')\n throw new TypeError('invalid mnemonic type: ' + typeof str);\n return str.normalize('NFKD');\n}\nfunction normalize(str) {\n const norm = nfkd(str);\n const words = norm.split(' ');\n if (![12, 15, 18, 21, 24].includes(words.length))\n throw new Error('Invalid mnemonic');\n return { nfkd: norm, words };\n}\nfunction aentropy(ent) {\n abytes(ent, 16, 20, 24, 28, 32);\n}\n/**\n * Generate x random words. Uses Cryptographically-Secure Random Number Generator.\n * @param wordlist imported wordlist for specific language\n * @param strength mnemonic strength 128-256 bits\n * @example\n * generateMnemonic(wordlist, 128)\n * // 'legal winner thank year wave sausage worth useful legal winner thank yellow'\n */\nexport function generateMnemonic(wordlist, strength = 128) {\n anumber(strength);\n if (strength % 32 !== 0 || strength > 256)\n throw new TypeError('Invalid entropy');\n return entropyToMnemonic(randomBytes(strength / 8), wordlist);\n}\nconst calcChecksum = (entropy) => {\n // Checksum is ent.length/4 bits long\n const bitsLeft = 8 - entropy.length / 4;\n // Zero rightmost \"bitsLeft\" bits in byte\n // For example: bitsLeft=4 val=10111101 -> 10110000\n return new Uint8Array([(sha256(entropy)[0] >> bitsLeft) << bitsLeft]);\n};\nfunction getCoder(wordlist) {\n if (!Array.isArray(wordlist) || wordlist.length !== 2048 || typeof wordlist[0] !== 'string')\n throw new Error('Wordlist: expected array of 2048 strings');\n wordlist.forEach((i) => {\n if (typeof i !== 'string')\n throw new Error('wordlist: non-string element: ' + i);\n });\n return baseUtils.chain(baseUtils.checksum(1, calcChecksum), baseUtils.radix2(11, true), baseUtils.alphabet(wordlist));\n}\n/**\n * Reversible: Converts mnemonic string to raw entropy in form of byte array.\n * @param mnemonic 12-24 words\n * @param wordlist imported wordlist for specific language\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * mnemonicToEntropy(mnem, wordlist)\n * // Produces\n * new Uint8Array([\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f\n * ])\n */\nexport function mnemonicToEntropy(mnemonic, wordlist) {\n const { words } = normalize(mnemonic);\n const entropy = getCoder(wordlist).decode(words);\n aentropy(entropy);\n return entropy;\n}\n/**\n * Reversible: Converts raw entropy in form of byte array to mnemonic string.\n * @param entropy byte array\n * @param wordlist imported wordlist for specific language\n * @returns 12-24 words\n * @example\n * const ent = new Uint8Array([\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,\n * 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f\n * ]);\n * entropyToMnemonic(ent, wordlist);\n * // 'legal winner thank year wave sausage worth useful legal winner thank yellow'\n */\nexport function entropyToMnemonic(entropy, wordlist) {\n aentropy(entropy);\n const words = getCoder(wordlist).encode(entropy);\n return words.join(isJapanese(wordlist) ? '\\u3000' : ' ');\n}\n/**\n * Validates mnemonic for being 12-24 words contained in `wordlist`.\n */\nexport function validateMnemonic(mnemonic, wordlist) {\n try {\n mnemonicToEntropy(mnemonic, wordlist);\n }\n catch (e) {\n return false;\n }\n return true;\n}\nconst psalt = (passphrase) => nfkd('mnemonic' + passphrase);\n/**\n * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.\n * @param mnemonic 12-24 words\n * @param passphrase string that will additionally protect the key\n * @returns 64 bytes of key data\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * await mnemonicToSeed(mnem, 'password');\n * // new Uint8Array([...64 bytes])\n */\nexport function mnemonicToSeed(mnemonic, passphrase = '') {\n return pbkdf2Async(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });\n}\n/**\n * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.\n * @param mnemonic 12-24 words\n * @param passphrase string that will additionally protect the key\n * @returns 64 bytes of key data\n * @example\n * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';\n * mnemonicToSeedSync(mnem, 'password');\n * // new Uint8Array([...64 bytes])\n */\nexport function mnemonicToSeedSync(mnemonic, passphrase = '') {\n return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });\n}\n","export const wordlist = `abandon\nability\nable\nabout\nabove\nabsent\nabsorb\nabstract\nabsurd\nabuse\naccess\naccident\naccount\naccuse\nachieve\nacid\nacoustic\nacquire\nacross\nact\naction\nactor\nactress\nactual\nadapt\nadd\naddict\naddress\nadjust\nadmit\nadult\nadvance\nadvice\naerobic\naffair\nafford\nafraid\nagain\nage\nagent\nagree\nahead\naim\nair\nairport\naisle\nalarm\nalbum\nalcohol\nalert\nalien\nall\nalley\nallow\nalmost\nalone\nalpha\nalready\nalso\nalter\nalways\namateur\namazing\namong\namount\namused\nanalyst\nanchor\nancient\nanger\nangle\nangry\nanimal\nankle\nannounce\nannual\nanother\nanswer\nantenna\nantique\nanxiety\nany\napart\napology\nappear\napple\napprove\napril\narch\narctic\narea\narena\nargue\narm\narmed\narmor\narmy\naround\narrange\narrest\narrive\narrow\nart\nartefact\nartist\nartwork\nask\naspect\nassault\nasset\nassist\nassume\nasthma\nathlete\natom\nattack\nattend\nattitude\nattract\nauction\naudit\naugust\naunt\nauthor\nauto\nautumn\naverage\navocado\navoid\nawake\naware\naway\nawesome\nawful\nawkward\naxis\nbaby\nbachelor\nbacon\nbadge\nbag\nbalance\nbalcony\nball\nbamboo\nbanana\nbanner\nbar\nbarely\nbargain\nbarrel\nbase\nbasic\nbasket\nbattle\nbeach\nbean\nbeauty\nbecause\nbecome\nbeef\nbefore\nbegin\nbehave\nbehind\nbelieve\nbelow\nbelt\nbench\nbenefit\nbest\nbetray\nbetter\nbetween\nbeyond\nbicycle\nbid\nbike\nbind\nbiology\nbird\nbirth\nbitter\nblack\nblade\nblame\nblanket\nblast\nbleak\nbless\nblind\nblood\nblossom\nblouse\nblue\nblur\nblush\nboard\nboat\nbody\nboil\nbomb\nbone\nbonus\nbook\nboost\nborder\nboring\nborrow\nboss\nbottom\nbounce\nbox\nboy\nbracket\nbrain\nbrand\nbrass\nbrave\nbread\nbreeze\nbrick\nbridge\nbrief\nbright\nbring\nbrisk\nbroccoli\nbroken\nbronze\nbroom\nbrother\nbrown\nbrush\nbubble\nbuddy\nbudget\nbuffalo\nbuild\nbulb\nbulk\nbullet\nbundle\nbunker\nburden\nburger\nburst\nbus\nbusiness\nbusy\nbutter\nbuyer\nbuzz\ncabbage\ncabin\ncable\ncactus\ncage\ncake\ncall\ncalm\ncamera\ncamp\ncan\ncanal\ncancel\ncandy\ncannon\ncanoe\ncanvas\ncanyon\ncapable\ncapital\ncaptain\ncar\ncarbon\ncard\ncargo\ncarpet\ncarry\ncart\ncase\ncash\ncasino\ncastle\ncasual\ncat\ncatalog\ncatch\ncategory\ncattle\ncaught\ncause\ncaution\ncave\nceiling\ncelery\ncement\ncensus\ncentury\ncereal\ncertain\nchair\nchalk\nchampion\nchange\nchaos\nchapter\ncharge\nchase\nchat\ncheap\ncheck\ncheese\nchef\ncherry\nchest\nchicken\nchief\nchild\nchimney\nchoice\nchoose\nchronic\nchuckle\nchunk\nchurn\ncigar\ncinnamon\ncircle\ncitizen\ncity\ncivil\nclaim\nclap\nclarify\nclaw\nclay\nclean\nclerk\nclever\nclick\nclient\ncliff\nclimb\nclinic\nclip\nclock\nclog\nclose\ncloth\ncloud\nclown\nclub\nclump\ncluster\nclutch\ncoach\ncoast\ncoconut\ncode\ncoffee\ncoil\ncoin\ncollect\ncolor\ncolumn\ncombine\ncome\ncomfort\ncomic\ncommon\ncompany\nconcert\nconduct\nconfirm\ncongress\nconnect\nconsider\ncontrol\nconvince\ncook\ncool\ncopper\ncopy\ncoral\ncore\ncorn\ncorrect\ncost\ncotton\ncouch\ncountry\ncouple\ncourse\ncousin\ncover\ncoyote\ncrack\ncradle\ncraft\ncram\ncrane\ncrash\ncrater\ncrawl\ncrazy\ncream\ncredit\ncreek\ncrew\ncricket\ncrime\ncrisp\ncritic\ncrop\ncross\ncrouch\ncrowd\ncrucial\ncruel\ncruise\ncrumble\ncrunch\ncrush\ncry\ncrystal\ncube\nculture\ncup\ncupboard\ncurious\ncurrent\ncurtain\ncurve\ncushion\ncustom\ncute\ncycle\ndad\ndamage\ndamp\ndance\ndanger\ndaring\ndash\ndaughter\ndawn\nday\ndeal\ndebate\ndebris\ndecade\ndecember\ndecide\ndecline\ndecorate\ndecrease\ndeer\ndefense\ndefine\ndefy\ndegree\ndelay\ndeliver\ndemand\ndemise\ndenial\ndentist\ndeny\ndepart\ndepend\ndeposit\ndepth\ndeputy\nderive\ndescribe\ndesert\ndesign\ndesk\ndespair\ndestroy\ndetail\ndetect\ndevelop\ndevice\ndevote\ndiagram\ndial\ndiamond\ndiary\ndice\ndiesel\ndiet\ndiffer\ndigital\ndignity\ndilemma\ndinner\ndinosaur\ndirect\ndirt\ndisagree\ndiscover\ndisease\ndish\ndismiss\ndisorder\ndisplay\ndistance\ndivert\ndivide\ndivorce\ndizzy\ndoctor\ndocument\ndog\ndoll\ndolphin\ndomain\ndonate\ndonkey\ndonor\ndoor\ndose\ndouble\ndove\ndraft\ndragon\ndrama\ndrastic\ndraw\ndream\ndress\ndrift\ndrill\ndrink\ndrip\ndrive\ndrop\ndrum\ndry\nduck\ndumb\ndune\nduring\ndust\ndutch\nduty\ndwarf\ndynamic\neager\neagle\nearly\nearn\nearth\neasily\neast\neasy\necho\necology\neconomy\nedge\nedit\neducate\neffort\negg\neight\neither\nelbow\nelder\nelectric\nelegant\nelement\nelephant\nelevator\nelite\nelse\nembark\nembody\nembrace\nemerge\nemotion\nemploy\nempower\nempty\nenable\nenact\nend\nendless\nendorse\nenemy\nenergy\nenforce\nengage\nengine\nenhance\nenjoy\nenlist\nenough\nenrich\nenroll\nensure\nenter\nentire\nentry\nenvelope\nepisode\nequal\nequip\nera\nerase\nerode\nerosion\nerror\nerupt\nescape\nessay\nessence\nestate\neternal\nethics\nevidence\nevil\nevoke\nevolve\nexact\nexample\nexcess\nexchange\nexcite\nexclude\nexcuse\nexecute\nexercise\nexhaust\nexhibit\nexile\nexist\nexit\nexotic\nexpand\nexpect\nexpire\nexplain\nexpose\nexpress\nextend\nextra\neye\neyebrow\nfabric\nface\nfaculty\nfade\nfaint\nfaith\nfall\nfalse\nfame\nfamily\nfamous\nfan\nfancy\nfantasy\nfarm\nfashion\nfat\nfatal\nfather\nfatigue\nfault\nfavorite\nfeature\nfebruary\nfederal\nfee\nfeed\nfeel\nfemale\nfence\nfestival\nfetch\nfever\nfew\nfiber\nfiction\nfield\nfigure\nfile\nfilm\nfilter\nfinal\nfind\nfine\nfinger\nfinish\nfire\nfirm\nfirst\nfiscal\nfish\nfit\nfitness\nfix\nflag\nflame\nflash\nflat\nflavor\nflee\nflight\nflip\nfloat\nflock\nfloor\nflower\nfluid\nflush\nfly\nfoam\nfocus\nfog\nfoil\nfold\nfollow\nfood\nfoot\nforce\nforest\nforget\nfork\nfortune\nforum\nforward\nfossil\nfoster\nfound\nfox\nfragile\nframe\nfrequent\nfresh\nfriend\nfringe\nfrog\nfront\nfrost\nfrown\nfrozen\nfruit\nfuel\nfun\nfunny\nfurnace\nfury\nfuture\ngadget\ngain\ngalaxy\ngallery\ngame\ngap\ngarage\ngarbage\ngarden\ngarlic\ngarment\ngas\ngasp\ngate\ngather\ngauge\ngaze\ngeneral\ngenius\ngenre\ngentle\ngenuine\ngesture\nghost\ngiant\ngift\ngiggle\nginger\ngiraffe\ngirl\ngive\nglad\nglance\nglare\nglass\nglide\nglimpse\nglobe\ngloom\nglory\nglove\nglow\nglue\ngoat\ngoddess\ngold\ngood\ngoose\ngorilla\ngospel\ngossip\ngovern\ngown\ngrab\ngrace\ngrain\ngrant\ngrape\ngrass\ngravity\ngreat\ngreen\ngrid\ngrief\ngrit\ngrocery\ngroup\ngrow\ngrunt\nguard\nguess\nguide\nguilt\nguitar\ngun\ngym\nhabit\nhair\nhalf\nhammer\nhamster\nhand\nhappy\nharbor\nhard\nharsh\nharvest\nhat\nhave\nhawk\nhazard\nhead\nhealth\nheart\nheavy\nhedgehog\nheight\nhello\nhelmet\nhelp\nhen\nhero\nhidden\nhigh\nhill\nhint\nhip\nhire\nhistory\nhobby\nhockey\nhold\nhole\nholiday\nhollow\nhome\nhoney\nhood\nhope\nhorn\nhorror\nhorse\nhospital\nhost\nhotel\nhour\nhover\nhub\nhuge\nhuman\nhumble\nhumor\nhundred\nhungry\nhunt\nhurdle\nhurry\nhurt\nhusband\nhybrid\nice\nicon\nidea\nidentify\nidle\nignore\nill\nillegal\nillness\nimage\nimitate\nimmense\nimmune\nimpact\nimpose\nimprove\nimpulse\ninch\ninclude\nincome\nincrease\nindex\nindicate\nindoor\nindustry\ninfant\ninflict\ninform\ninhale\ninherit\ninitial\ninject\ninjury\ninmate\ninner\ninnocent\ninput\ninquiry\ninsane\ninsect\ninside\ninspire\ninstall\nintact\ninterest\ninto\ninvest\ninvite\ninvolve\niron\nisland\nisolate\nissue\nitem\nivory\njacket\njaguar\njar\njazz\njealous\njeans\njelly\njewel\njob\njoin\njoke\njourney\njoy\njudge\njuice\njump\njungle\njunior\njunk\njust\nkangaroo\nkeen\nkeep\nketchup\nkey\nkick\nkid\nkidney\nkind\nkingdom\nkiss\nkit\nkitchen\nkite\nkitten\nkiwi\nknee\nknife\nknock\nknow\nlab\nlabel\nlabor\nladder\nlady\nlake\nlamp\nlanguage\nlaptop\nlarge\nlater\nlatin\nlaugh\nlaundry\nlava\nlaw\nlawn\nlawsuit\nlayer\nlazy\nleader\nleaf\nlearn\nleave\nlecture\nleft\nleg\nlegal\nlegend\nleisure\nlemon\nlend\nlength\nlens\nleopard\nlesson\nletter\nlevel\nliar\nliberty\nlibrary\nlicense\nlife\nlift\nlight\nlike\nlimb\nlimit\nlink\nlion\nliquid\nlist\nlittle\nlive\nlizard\nload\nloan\nlobster\nlocal\nlock\nlogic\nlonely\nlong\nloop\nlottery\nloud\nlounge\nlove\nloyal\nlucky\nluggage\nlumber\nlunar\nlunch\nluxury\nlyrics\nmachine\nmad\nmagic\nmagnet\nmaid\nmail\nmain\nmajor\nmake\nmammal\nman\nmanage\nmandate\nmango\nmansion\nmanual\nmaple\nmarble\nmarch\nmargin\nmarine\nmarket\nmarriage\nmask\nmass\nmaster\nmatch\nmaterial\nmath\nmatrix\nmatter\nmaximum\nmaze\nmeadow\nmean\nmeasure\nmeat\nmechanic\nmedal\nmedia\nmelody\nmelt\nmember\nmemory\nmention\nmenu\nmercy\nmerge\nmerit\nmerry\nmesh\nmessage\nmetal\nmethod\nmiddle\nmidnight\nmilk\nmillion\nmimic\nmind\nminimum\nminor\nminute\nmiracle\nmirror\nmisery\nmiss\nmistake\nmix\nmixed\nmixture\nmobile\nmodel\nmodify\nmom\nmoment\nmonitor\nmonkey\nmonster\nmonth\nmoon\nmoral\nmore\nmorning\nmosquito\nmother\nmotion\nmotor\nmountain\nmouse\nmove\nmovie\nmuch\nmuffin\nmule\nmultiply\nmuscle\nmuseum\nmushroom\nmusic\nmust\nmutual\nmyself\nmystery\nmyth\nnaive\nname\nnapkin\nnarrow\nnasty\nnation\nnature\nnear\nneck\nneed\nnegative\nneglect\nneither\nnephew\nnerve\nnest\nnet\nnetwork\nneutral\nnever\nnews\nnext\nnice\nnight\nnoble\nnoise\nnominee\nnoodle\nnormal\nnorth\nnose\nnotable\nnote\nnothing\nnotice\nnovel\nnow\nnuclear\nnumber\nnurse\nnut\noak\nobey\nobject\noblige\nobscure\nobserve\nobtain\nobvious\noccur\nocean\noctober\nodor\noff\noffer\noffice\noften\noil\nokay\nold\nolive\nolympic\nomit\nonce\none\nonion\nonline\nonly\nopen\nopera\nopinion\noppose\noption\norange\norbit\norchard\norder\nordinary\norgan\norient\noriginal\norphan\nostrich\nother\noutdoor\nouter\noutput\noutside\noval\noven\nover\nown\nowner\noxygen\noyster\nozone\npact\npaddle\npage\npair\npalace\npalm\npanda\npanel\npanic\npanther\npaper\nparade\nparent\npark\nparrot\nparty\npass\npatch\npath\npatient\npatrol\npattern\npause\npave\npayment\npeace\npeanut\npear\npeasant\npelican\npen\npenalty\npencil\npeople\npepper\nperfect\npermit\nperson\npet\nphone\nphoto\nphrase\nphysical\npiano\npicnic\npicture\npiece\npig\npigeon\npill\npilot\npink\npioneer\npipe\npistol\npitch\npizza\nplace\nplanet\nplastic\nplate\nplay\nplease\npledge\npluck\nplug\nplunge\npoem\npoet\npoint\npolar\npole\npolice\npond\npony\npool\npopular\nportion\nposition\npossible\npost\npotato\npottery\npoverty\npowder\npower\npractice\npraise\npredict\nprefer\nprepare\npresent\npretty\nprevent\nprice\npride\nprimary\nprint\npriority\nprison\nprivate\nprize\nproblem\nprocess\nproduce\nprofit\nprogram\nproject\npromote\nproof\nproperty\nprosper\nprotect\nproud\nprovide\npublic\npudding\npull\npulp\npulse\npumpkin\npunch\npupil\npuppy\npurchase\npurity\npurpose\npurse\npush\nput\npuzzle\npyramid\nquality\nquantum\nquarter\nquestion\nquick\nquit\nquiz\nquote\nrabbit\nraccoon\nrace\nrack\nradar\nradio\nrail\nrain\nraise\nrally\nramp\nranch\nrandom\nrange\nrapid\nrare\nrate\nrather\nraven\nraw\nrazor\nready\nreal\nreason\nrebel\nrebuild\nrecall\nreceive\nrecipe\nrecord\nrecycle\nreduce\nreflect\nreform\nrefuse\nregion\nregret\nregular\nreject\nrelax\nrelease\nrelief\nrely\nremain\nremember\nremind\nremove\nrender\nrenew\nrent\nreopen\nrepair\nrepeat\nreplace\nreport\nrequire\nrescue\nresemble\nresist\nresource\nresponse\nresult\nretire\nretreat\nreturn\nreunion\nreveal\nreview\nreward\nrhythm\nrib\nribbon\nrice\nrich\nride\nridge\nrifle\nright\nrigid\nring\nriot\nripple\nrisk\nritual\nrival\nriver\nroad\nroast\nrobot\nrobust\nrocket\nromance\nroof\nrookie\nroom\nrose\nrotate\nrough\nround\nroute\nroyal\nrubber\nrude\nrug\nrule\nrun\nrunway\nrural\nsad\nsaddle\nsadness\nsafe\nsail\nsalad\nsalmon\nsalon\nsalt\nsalute\nsame\nsample\nsand\nsatisfy\nsatoshi\nsauce\nsausage\nsave\nsay\nscale\nscan\nscare\nscatter\nscene\nscheme\nschool\nscience\nscissors\nscorpion\nscout\nscrap\nscreen\nscript\nscrub\nsea\nsearch\nseason\nseat\nsecond\nsecret\nsection\nsecurity\nseed\nseek\nsegment\nselect\nsell\nseminar\nsenior\nsense\nsentence\nseries\nservice\nsession\nsettle\nsetup\nseven\nshadow\nshaft\nshallow\nshare\nshed\nshell\nsheriff\nshield\nshift\nshine\nship\nshiver\nshock\nshoe\nshoot\nshop\nshort\nshoulder\nshove\nshrimp\nshrug\nshuffle\nshy\nsibling\nsick\nside\nsiege\nsight\nsign\nsilent\nsilk\nsilly\nsilver\nsimilar\nsimple\nsince\nsing\nsiren\nsister\nsituate\nsix\nsize\nskate\nsketch\nski\nskill\nskin\nskirt\nskull\nslab\nslam\nsleep\nslender\nslice\nslide\nslight\nslim\nslogan\nslot\nslow\nslush\nsmall\nsmart\nsmile\nsmoke\nsmooth\nsnack\nsnake\nsnap\nsniff\nsnow\nsoap\nsoccer\nsocial\nsock\nsoda\nsoft\nsolar\nsoldier\nsolid\nsolution\nsolve\nsomeone\nsong\nsoon\nsorry\nsort\nsoul\nsound\nsoup\nsource\nsouth\nspace\nspare\nspatial\nspawn\nspeak\nspecial\nspeed\nspell\nspend\nsphere\nspice\nspider\nspike\nspin\nspirit\nsplit\nspoil\nsponsor\nspoon\nsport\nspot\nspray\nspread\nspring\nspy\nsquare\nsqueeze\nsquirrel\nstable\nstadium\nstaff\nstage\nstairs\nstamp\nstand\nstart\nstate\nstay\nsteak\nsteel\nstem\nstep\nstereo\nstick\nstill\nsting\nstock\nstomach\nstone\nstool\nstory\nstove\nstrategy\nstreet\nstrike\nstrong\nstruggle\nstudent\nstuff\nstumble\nstyle\nsubject\nsubmit\nsubway\nsuccess\nsuch\nsudden\nsuffer\nsugar\nsuggest\nsuit\nsummer\nsun\nsunny\nsunset\nsuper\nsupply\nsupreme\nsure\nsurface\nsurge\nsurprise\nsurround\nsurvey\nsuspect\nsustain\nswallow\nswamp\nswap\nswarm\nswear\nsweet\nswift\nswim\nswing\nswitch\nsword\nsymbol\nsymptom\nsyrup\nsystem\ntable\ntackle\ntag\ntail\ntalent\ntalk\ntank\ntape\ntarget\ntask\ntaste\ntattoo\ntaxi\nteach\nteam\ntell\nten\ntenant\ntennis\ntent\nterm\ntest\ntext\nthank\nthat\ntheme\nthen\ntheory\nthere\nthey\nthing\nthis\nthought\nthree\nthrive\nthrow\nthumb\nthunder\nticket\ntide\ntiger\ntilt\ntimber\ntime\ntiny\ntip\ntired\ntissue\ntitle\ntoast\ntobacco\ntoday\ntoddler\ntoe\ntogether\ntoilet\ntoken\ntomato\ntomorrow\ntone\ntongue\ntonight\ntool\ntooth\ntop\ntopic\ntopple\ntorch\ntornado\ntortoise\ntoss\ntotal\ntourist\ntoward\ntower\ntown\ntoy\ntrack\ntrade\ntraffic\ntragic\ntrain\ntransfer\ntrap\ntrash\ntravel\ntray\ntreat\ntree\ntrend\ntrial\ntribe\ntrick\ntrigger\ntrim\ntrip\ntrophy\ntrouble\ntruck\ntrue\ntruly\ntrumpet\ntrust\ntruth\ntry\ntube\ntuition\ntumble\ntuna\ntunnel\nturkey\nturn\nturtle\ntwelve\ntwenty\ntwice\ntwin\ntwist\ntwo\ntype\ntypical\nugly\numbrella\nunable\nunaware\nuncle\nuncover\nunder\nundo\nunfair\nunfold\nunhappy\nuniform\nunique\nunit\nuniverse\nunknown\nunlock\nuntil\nunusual\nunveil\nupdate\nupgrade\nuphold\nupon\nupper\nupset\nurban\nurge\nusage\nuse\nused\nuseful\nuseless\nusual\nutility\nvacant\nvacuum\nvague\nvalid\nvalley\nvalve\nvan\nvanish\nvapor\nvarious\nvast\nvault\nvehicle\nvelvet\nvendor\nventure\nvenue\nverb\nverify\nversion\nvery\nvessel\nveteran\nviable\nvibrant\nvicious\nvictory\nvideo\nview\nvillage\nvintage\nviolin\nvirtual\nvirus\nvisa\nvisit\nvisual\nvital\nvivid\nvocal\nvoice\nvoid\nvolcano\nvolume\nvote\nvoyage\nwage\nwagon\nwait\nwalk\nwall\nwalnut\nwant\nwarfare\nwarm\nwarrior\nwash\nwasp\nwaste\nwater\nwave\nway\nwealth\nweapon\nwear\nweasel\nweather\nweb\nwedding\nweekend\nweird\nwelcome\nwest\nwet\nwhale\nwhat\nwheat\nwheel\nwhen\nwhere\nwhip\nwhisper\nwide\nwidth\nwife\nwild\nwill\nwin\nwindow\nwine\nwing\nwink\nwinner\nwinter\nwire\nwisdom\nwise\nwish\nwitness\nwolf\nwoman\nwonder\nwood\nwool\nword\nwork\nworld\nworry\nworth\nwrap\nwreck\nwrestle\nwrist\nwrite\nwrong\nyard\nyear\nyellow\nyou\nyoung\nyouth\nzebra\nzero\nzone\nzoo`.split('\\n');\n","/**\n * Hex, bytes and number utilities.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n abytes as abytes_,\n bytesToHex as bytesToHex_,\n concatBytes as concatBytes_,\n hexToBytes as hexToBytes_,\n isBytes as isBytes_,\n} from '@noble/hashes/utils.js';\nexport {\n abytes,\n anumber,\n bytesToHex,\n bytesToUtf8,\n concatBytes,\n hexToBytes,\n isBytes,\n randomBytes,\n utf8ToBytes,\n} from '@noble/hashes/utils.js';\nconst _0n = /* @__PURE__ */ BigInt(0);\nconst _1n = /* @__PURE__ */ BigInt(1);\nexport type Hex = Uint8Array | string; // hex strings are accepted for simplicity\nexport type PrivKey = Hex | bigint; // bigints are accepted to ease learning curve\nexport type CHash = {\n (message: Uint8Array | string): Uint8Array;\n blockLen: number;\n outputLen: number;\n create(opts?: { dkLen?: number }): any; // For shake\n};\nexport type FHash = (message: Uint8Array | string) => Uint8Array;\n\nexport function abool(title: string, value: boolean): void {\n if (typeof value !== 'boolean') throw new Error(title + ' boolean expected, got ' + value);\n}\n\n// tmp name until v2\nexport function _abool2(value: boolean, title: string = ''): boolean {\n if (typeof value !== 'boolean') {\n const prefix = title && `\"${title}\"`;\n throw new Error(prefix + 'expected boolean, got type=' + typeof value);\n }\n return value;\n}\n\n// tmp name until v2\n/** Asserts something is Uint8Array. */\nexport function _abytes2(value: Uint8Array, length?: number, title: string = ''): Uint8Array {\n const bytes = isBytes_(value);\n const len = value?.length;\n const needsLen = length !== undefined;\n if (!bytes || (needsLen && len !== length)) {\n const prefix = title && `\"${title}\" `;\n const ofLen = needsLen ? ` of length ${length}` : '';\n const got = bytes ? `length=${len}` : `type=${typeof value}`;\n throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);\n }\n return value;\n}\n\n// Used in weierstrass, der\nexport function numberToHexUnpadded(num: number | bigint): string {\n const hex = num.toString(16);\n return hex.length & 1 ? '0' + hex : hex;\n}\n\nexport function hexToNumber(hex: string): bigint {\n if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);\n return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian\n}\n\n// BE: Big Endian, LE: Little Endian\nexport function bytesToNumberBE(bytes: Uint8Array): bigint {\n return hexToNumber(bytesToHex_(bytes));\n}\nexport function bytesToNumberLE(bytes: Uint8Array): bigint {\n abytes_(bytes);\n return hexToNumber(bytesToHex_(Uint8Array.from(bytes).reverse()));\n}\n\nexport function numberToBytesBE(n: number | bigint, len: number): Uint8Array {\n return hexToBytes_(n.toString(16).padStart(len * 2, '0'));\n}\nexport function numberToBytesLE(n: number | bigint, len: number): Uint8Array {\n return numberToBytesBE(n, len).reverse();\n}\n// Unpadded, rarely used\nexport function numberToVarBytesBE(n: number | bigint): Uint8Array {\n return hexToBytes_(numberToHexUnpadded(n));\n}\n\n/**\n * Takes hex string or Uint8Array, converts to Uint8Array.\n * Validates output length.\n * Will throw error for other types.\n * @param title descriptive title for an error e.g. 'secret key'\n * @param hex hex string or Uint8Array\n * @param expectedLength optional, will compare to result array's length\n * @returns\n */\nexport function ensureBytes(title: string, hex: Hex, expectedLength?: number): Uint8Array {\n let res: Uint8Array;\n if (typeof hex === 'string') {\n try {\n res = hexToBytes_(hex);\n } catch (e) {\n throw new Error(title + ' must be hex string or Uint8Array, cause: ' + e);\n }\n } else if (isBytes_(hex)) {\n // Uint8Array.from() instead of hash.slice() because node.js Buffer\n // is instance of Uint8Array, and its slice() creates **mutable** copy\n res = Uint8Array.from(hex);\n } else {\n throw new Error(title + ' must be hex string or Uint8Array');\n }\n const len = res.length;\n if (typeof expectedLength === 'number' && len !== expectedLength)\n throw new Error(title + ' of length ' + expectedLength + ' expected, got ' + len);\n return res;\n}\n\n// Compares 2 u8a-s in kinda constant time\nexport function equalBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];\n return diff === 0;\n}\n/**\n * Copies Uint8Array. We can't use u8a.slice(), because u8a can be Buffer,\n * and Buffer#slice creates mutable copy. Never use Buffers!\n */\nexport function copyBytes(bytes: Uint8Array): Uint8Array {\n return Uint8Array.from(bytes);\n}\n\n/**\n * Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols\n * Should be safe to use for things expected to be ASCII.\n * Returns exact same result as utf8ToBytes for ASCII or throws.\n */\nexport function asciiToBytes(ascii: string): Uint8Array {\n return Uint8Array.from(ascii, (c, i) => {\n const charCode = c.charCodeAt(0);\n if (c.length !== 1 || charCode > 127) {\n throw new Error(\n `string contains non-ASCII character \"${ascii[i]}\" with code ${charCode} at position ${i}`\n );\n }\n return charCode;\n });\n}\n\n/**\n * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])\n */\n// export const utf8ToBytes: typeof utf8ToBytes_ = utf8ToBytes_;\n/**\n * Converts bytes to string using UTF8 encoding.\n * @example bytesToUtf8(Uint8Array.from([97, 98, 99])) // 'abc'\n */\n// export const bytesToUtf8: typeof bytesToUtf8_ = bytesToUtf8_;\n\n// Is positive bigint\nconst isPosBig = (n: bigint) => typeof n === 'bigint' && _0n <= n;\n\nexport function inRange(n: bigint, min: bigint, max: bigint): boolean {\n return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;\n}\n\n/**\n * Asserts min <= n < max. NOTE: It's < max and not <= max.\n * @example\n * aInRange('x', x, 1n, 256n); // would assume x is in (1n..255n)\n */\nexport function aInRange(title: string, n: bigint, min: bigint, max: bigint): void {\n // Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)?\n // consider P=256n, min=0n, max=P\n // - a for min=0 would require -1: `inRange('x', x, -1n, P)`\n // - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)`\n // - our way is the cleanest: `inRange('x', x, 0n, P)\n if (!inRange(n, min, max))\n throw new Error('expected valid ' + title + ': ' + min + ' <= n < ' + max + ', got ' + n);\n}\n\n// Bit operations\n\n/**\n * Calculates amount of bits in a bigint.\n * Same as `n.toString(2).length`\n * TODO: merge with nLength in modular\n */\nexport function bitLen(n: bigint): number {\n let len;\n for (len = 0; n > _0n; n >>= _1n, len += 1);\n return len;\n}\n\n/**\n * Gets single bit at position.\n * NOTE: first bit position is 0 (same as arrays)\n * Same as `!!+Array.from(n.toString(2)).reverse()[pos]`\n */\nexport function bitGet(n: bigint, pos: number): bigint {\n return (n >> BigInt(pos)) & _1n;\n}\n\n/**\n * Sets single bit at position.\n */\nexport function bitSet(n: bigint, pos: number, value: boolean): bigint {\n return n | ((value ? _1n : _0n) << BigInt(pos));\n}\n\n/**\n * Calculate mask for N bits. Not using ** operator with bigints because of old engines.\n * Same as BigInt(`0b${Array(i).fill('1').join('')}`)\n */\nexport const bitMask = (n: number): bigint => (_1n << BigInt(n)) - _1n;\n\n// DRBG\n\ntype Pred<T> = (v: Uint8Array) => T | undefined;\n/**\n * Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n * @returns function that will call DRBG until 2nd arg returns something meaningful\n * @example\n * const drbg = createHmacDRBG<Key>(32, 32, hmac);\n * drbg(seed, bytesToKey); // bytesToKey must return Key or undefined\n */\nexport function createHmacDrbg<T>(\n hashLen: number,\n qByteLen: number,\n hmacFn: (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array\n): (seed: Uint8Array, predicate: Pred<T>) => T {\n if (typeof hashLen !== 'number' || hashLen < 2) throw new Error('hashLen must be a number');\n if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');\n if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');\n // Step B, Step C: set hashLen to 8*ceil(hlen/8)\n const u8n = (len: number) => new Uint8Array(len); // creates Uint8Array\n const u8of = (byte: number) => Uint8Array.of(byte); // another shortcut\n let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.\n let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same\n let i = 0; // Iterations counter, will throw when over 1000\n const reset = () => {\n v.fill(1);\n k.fill(0);\n i = 0;\n };\n const h = (...b: Uint8Array[]) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)\n const reseed = (seed = u8n(0)) => {\n // HMAC-DRBG reseed() function. Steps D-G\n k = h(u8of(0x00), seed); // k = hmac(k || v || 0x00 || seed)\n v = h(); // v = hmac(k || v)\n if (seed.length === 0) return;\n k = h(u8of(0x01), seed); // k = hmac(k || v || 0x01 || seed)\n v = h(); // v = hmac(k || v)\n };\n const gen = () => {\n // HMAC-DRBG generate() function\n if (i++ >= 1000) throw new Error('drbg: tried 1000 values');\n let len = 0;\n const out: Uint8Array[] = [];\n while (len < qByteLen) {\n v = h();\n const sl = v.slice();\n out.push(sl);\n len += v.length;\n }\n return concatBytes_(...out);\n };\n const genUntil = (seed: Uint8Array, pred: Pred<T>): T => {\n reset();\n reseed(seed); // Steps D-G\n let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]\n while (!(res = pred(gen()))) reseed();\n reset();\n return res;\n };\n return genUntil;\n}\n\n// Validating curves and fields\n\nconst validatorFns = {\n bigint: (val: any): boolean => typeof val === 'bigint',\n function: (val: any): boolean => typeof val === 'function',\n boolean: (val: any): boolean => typeof val === 'boolean',\n string: (val: any): boolean => typeof val === 'string',\n stringOrUint8Array: (val: any): boolean => typeof val === 'string' || isBytes_(val),\n isSafeInteger: (val: any): boolean => Number.isSafeInteger(val),\n array: (val: any): boolean => Array.isArray(val),\n field: (val: any, object: any): any => (object as any).Fp.isValid(val),\n hash: (val: any): boolean => typeof val === 'function' && Number.isSafeInteger(val.outputLen),\n} as const;\ntype Validator = keyof typeof validatorFns;\ntype ValMap<T extends Record<string, any>> = { [K in keyof T]?: Validator };\n// type Record<K extends string | number | symbol, T> = { [P in K]: T; }\n\nexport function validateObject<T extends Record<string, any>>(\n object: T,\n validators: ValMap<T>,\n optValidators: ValMap<T> = {}\n): T {\n const checkField = (fieldName: keyof T, type: Validator, isOptional: boolean) => {\n const checkVal = validatorFns[type];\n if (typeof checkVal !== 'function') throw new Error('invalid validator function');\n\n const val = object[fieldName as keyof typeof object];\n if (isOptional && val === undefined) return;\n if (!checkVal(val, object)) {\n throw new Error(\n 'param ' + String(fieldName) + ' is invalid. Expected ' + type + ', got ' + val\n );\n }\n };\n for (const [fieldName, type] of Object.entries(validators)) checkField(fieldName, type!, false);\n for (const [fieldName, type] of Object.entries(optValidators)) checkField(fieldName, type!, true);\n return object;\n}\n// validate type tests\n// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };\n// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!\n// // Should fail type-check\n// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });\n// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });\n// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });\n// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });\n\nexport function isHash(val: CHash): boolean {\n return typeof val === 'function' && Number.isSafeInteger(val.outputLen);\n}\nexport function _validateObject(\n object: Record<string, any>,\n fields: Record<string, string>,\n optFields: Record<string, string> = {}\n): void {\n if (!object || typeof object !== 'object') throw new Error('expected valid options object');\n type Item = keyof typeof object;\n function checkField(fieldName: Item, expectedType: string, isOpt: boolean) {\n const val = object[fieldName];\n if (isOpt && val === undefined) return;\n const current = typeof val;\n if (current !== expectedType || val === null)\n throw new Error(`param \"${fieldName}\" is invalid: expected ${expectedType}, got ${current}`);\n }\n Object.entries(fields).forEach(([k, v]) => checkField(k, v, false));\n Object.entries(optFields).forEach(([k, v]) => checkField(k, v, true));\n}\n\n/**\n * throws not implemented error\n */\nexport const notImplemented = (): never => {\n throw new Error('not implemented');\n};\n\n/**\n * Memoizes (caches) computation result.\n * Uses WeakMap: the value is going auto-cleaned by GC after last reference is removed.\n */\nexport function memoized<T extends object, R, O extends any[]>(\n fn: (arg: T, ...args: O) => R\n): (arg: T, ...args: O) => R {\n const map = new WeakMap<T, R>();\n return (arg: T, ...args: O): R => {\n const val = map.get(arg);\n if (val !== undefined) return val;\n const computed = fn(arg, ...args);\n map.set(arg, computed);\n return computed;\n };\n}\n","/**\n * Utils for modular division and fields.\n * Field over 11 is a finite (Galois) field is integer number operations `mod 11`.\n * There is no division: it is replaced by modular multiplicative inverse.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport {\n _validateObject,\n anumber,\n bitMask,\n bytesToNumberBE,\n bytesToNumberLE,\n ensureBytes,\n numberToBytesBE,\n numberToBytesLE,\n} from '../utils.ts';\n\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3);\n// prettier-ignore\nconst _4n = /* @__PURE__ */ BigInt(4), _5n = /* @__PURE__ */ BigInt(5), _7n = /* @__PURE__ */ BigInt(7);\n// prettier-ignore\nconst _8n = /* @__PURE__ */ BigInt(8), _9n = /* @__PURE__ */ BigInt(9), _16n = /* @__PURE__ */ BigInt(16);\n\n// Calculates a modulo b\nexport function mod(a: bigint, b: bigint): bigint {\n const result = a % b;\n return result >= _0n ? result : b + result;\n}\n/**\n * Efficiently raise num to power and do modular division.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n * @example\n * pow(2n, 6n, 11n) // 64n % 11n == 9n\n */\nexport function pow(num: bigint, power: bigint, modulo: bigint): bigint {\n return FpPow(Field(modulo), num, power);\n}\n\n/** Does `x^(2^power)` mod p. `pow2(30, 4)` == `30^(2^4)` */\nexport function pow2(x: bigint, power: bigint, modulo: bigint): bigint {\n let res = x;\n while (power-- > _0n) {\n res *= res;\n res %= modulo;\n }\n return res;\n}\n\n/**\n * Inverses number over modulo.\n * Implemented using [Euclidean GCD](https://brilliant.org/wiki/extended-euclidean-algorithm/).\n */\nexport function invert(number: bigint, modulo: bigint): bigint {\n if (number === _0n) throw new Error('invert: expected non-zero number');\n if (modulo <= _0n) throw new Error('invert: expected positive modulus, got ' + modulo);\n // Fermat's little theorem \"CT-like\" version inv(n) = n^(m-2) mod m is 30x slower.\n let a = mod(number, modulo);\n let b = modulo;\n // prettier-ignore\n let x = _0n, y = _1n, u = _1n, v = _0n;\n while (a !== _0n) {\n // JIT applies optimization if those two lines follow each other\n const q = b / a;\n const r = b % a;\n const m = x - u * q;\n const n = y - v * q;\n // prettier-ignore\n b = a, a = r, x = u, y = v, u = m, v = n;\n }\n const gcd = b;\n if (gcd !== _1n) throw new Error('invert: does not exist');\n return mod(x, modulo);\n}\n\nfunction assertIsSquare<T>(Fp: IField<T>, root: T, n: T): void {\n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root');\n}\n\n// Not all roots are possible! Example which will throw:\n// const NUM =\n// n = 72057594037927816n;\n// Fp = Field(BigInt('0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab'));\nfunction sqrt3mod4<T>(Fp: IField<T>, n: T) {\n const p1div4 = (Fp.ORDER + _1n) / _4n;\n const root = Fp.pow(n, p1div4);\n assertIsSquare(Fp, root, n);\n return root;\n}\n\nfunction sqrt5mod8<T>(Fp: IField<T>, n: T) {\n const p5div8 = (Fp.ORDER - _5n) / _8n;\n const n2 = Fp.mul(n, _2n);\n const v = Fp.pow(n2, p5div8);\n const nv = Fp.mul(n, v);\n const i = Fp.mul(Fp.mul(nv, _2n), v);\n const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));\n assertIsSquare(Fp, root, n);\n return root;\n}\n\n// Based on RFC9380, Kong algorithm\n// prettier-ignore\nfunction sqrt9mod16(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n const Fp_ = Field(P);\n const tn = tonelliShanks(P);\n const c1 = tn(Fp_, Fp_.neg(Fp_.ONE));// 1. c1 = sqrt(-1) in F, i.e., (c1^2) == -1 in F\n const c2 = tn(Fp_, c1); // 2. c2 = sqrt(c1) in F, i.e., (c2^2) == c1 in F\n const c3 = tn(Fp_, Fp_.neg(c1)); // 3. c3 = sqrt(-c1) in F, i.e., (c3^2) == -c1 in F\n const c4 = (P + _7n) / _16n; // 4. c4 = (q + 7) / 16 # Integer arithmetic\n return <T>(Fp: IField<T>, n: T) => {\n let tv1 = Fp.pow(n, c4); // 1. tv1 = x^c4\n let tv2 = Fp.mul(tv1, c1); // 2. tv2 = c1 * tv1\n const tv3 = Fp.mul(tv1, c2); // 3. tv3 = c2 * tv1\n const tv4 = Fp.mul(tv1, c3); // 4. tv4 = c3 * tv1\n const e1 = Fp.eql(Fp.sqr(tv2), n); // 5. e1 = (tv2^2) == x\n const e2 = Fp.eql(Fp.sqr(tv3), n); // 6. e2 = (tv3^2) == x\n tv1 = Fp.cmov(tv1, tv2, e1); // 7. tv1 = CMOV(tv1, tv2, e1) # Select tv2 if (tv2^2) == x\n tv2 = Fp.cmov(tv4, tv3, e2); // 8. tv2 = CMOV(tv4, tv3, e2) # Select tv3 if (tv3^2) == x\n const e3 = Fp.eql(Fp.sqr(tv2), n); // 9. e3 = (tv2^2) == x\n const root = Fp.cmov(tv1, tv2, e3);// 10. z = CMOV(tv1, tv2, e3) # Select sqrt from tv1 & tv2\n assertIsSquare(Fp, root, n);\n return root;\n };\n}\n\n/**\n * Tonelli-Shanks square root search algorithm.\n * 1. https://eprint.iacr.org/2012/685.pdf (page 12)\n * 2. Square Roots from 1; 24, 51, 10 to Dan Shanks\n * @param P field order\n * @returns function that takes field Fp (created from P) and number n\n */\nexport function tonelliShanks(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n // Initialization (precomputation).\n // Caching initialization could boost perf by 7%.\n if (P < _3n) throw new Error('sqrt is not defined for small field');\n // Factor P - 1 = Q * 2^S, where Q is odd\n let Q = P - _1n;\n let S = 0;\n while (Q % _2n === _0n) {\n Q /= _2n;\n S++;\n }\n\n // Find the first quadratic non-residue Z >= 2\n let Z = _2n;\n const _Fp = Field(P);\n while (FpLegendre(_Fp, Z) === 1) {\n // Basic primality test for P. After x iterations, chance of\n // not finding quadratic non-residue is 2^x, so 2^1000.\n if (Z++ > 1000) throw new Error('Cannot find square root: probably non-prime P');\n }\n // Fast-path; usually done before Z, but we do \"primality test\".\n if (S === 1) return sqrt3mod4;\n\n // Slow-path\n // TODO: test on Fp2 and others\n let cc = _Fp.pow(Z, Q); // c = z^Q\n const Q1div2 = (Q + _1n) / _2n;\n return function tonelliSlow<T>(Fp: IField<T>, n: T): T {\n if (Fp.is0(n)) return n;\n // Check if n is a quadratic residue using Legendre symbol\n if (FpLegendre(Fp, n) !== 1) throw new Error('Cannot find square root');\n\n // Initialize variables for the main loop\n let M = S;\n let c = Fp.mul(Fp.ONE, cc); // c = z^Q, move cc from field _Fp into field Fp\n let t = Fp.pow(n, Q); // t = n^Q, first guess at the fudge factor\n let R = Fp.pow(n, Q1div2); // R = n^((Q+1)/2), first guess at the square root\n\n // Main loop\n // while t != 1\n while (!Fp.eql(t, Fp.ONE)) {\n if (Fp.is0(t)) return Fp.ZERO; // if t=0 return R=0\n let i = 1;\n\n // Find the smallest i >= 1 such that t^(2^i) ≡ 1 (mod P)\n let t_tmp = Fp.sqr(t); // t^(2^1)\n while (!Fp.eql(t_tmp, Fp.ONE)) {\n i++;\n t_tmp = Fp.sqr(t_tmp); // t^(2^2)...\n if (i === M) throw new Error('Cannot find square root');\n }\n\n // Calculate the exponent for b: 2^(M - i - 1)\n const exponent = _1n << BigInt(M - i - 1); // bigint is important\n const b = Fp.pow(c, exponent); // b = 2^(M - i - 1)\n\n // Update variables\n M = i;\n c = Fp.sqr(b); // c = b^2\n t = Fp.mul(t, c); // t = (t * b^2)\n R = Fp.mul(R, b); // R = R*b\n }\n return R;\n };\n}\n\n/**\n * Square root for a finite field. Will try optimized versions first:\n *\n * 1. P ≡ 3 (mod 4)\n * 2. P ≡ 5 (mod 8)\n * 3. P ≡ 9 (mod 16)\n * 4. Tonelli-Shanks algorithm\n *\n * Different algorithms can give different roots, it is up to user to decide which one they want.\n * For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve).\n */\nexport function FpSqrt(P: bigint): <T>(Fp: IField<T>, n: T) => T {\n // P ≡ 3 (mod 4) => √n = n^((P+1)/4)\n if (P % _4n === _3n) return sqrt3mod4;\n // P ≡ 5 (mod 8) => Atkin algorithm, page 10 of https://eprint.iacr.org/2012/685.pdf\n if (P % _8n === _5n) return sqrt5mod8;\n // P ≡ 9 (mod 16) => Kong algorithm, page 11 of https://eprint.iacr.org/2012/685.pdf (algorithm 4)\n if (P % _16n === _9n) return sqrt9mod16(P);\n // Tonelli-Shanks algorithm\n return tonelliShanks(P);\n}\n\n// Little-endian check for first LE bit (last BE bit);\nexport const isNegativeLE = (num: bigint, modulo: bigint): boolean =>\n (mod(num, modulo) & _1n) === _1n;\n\n/** Field is not always over prime: for example, Fp2 has ORDER(q)=p^m. */\nexport interface IField<T> {\n ORDER: bigint;\n isLE: boolean;\n BYTES: number;\n BITS: number;\n MASK: bigint;\n ZERO: T;\n ONE: T;\n // 1-arg\n create: (num: T) => T;\n isValid: (num: T) => boolean;\n is0: (num: T) => boolean;\n isValidNot0: (num: T) => boolean;\n neg(num: T): T;\n inv(num: T): T;\n sqrt(num: T): T;\n sqr(num: T): T;\n // 2-args\n eql(lhs: T, rhs: T): boolean;\n add(lhs: T, rhs: T): T;\n sub(lhs: T, rhs: T): T;\n mul(lhs: T, rhs: T | bigint): T;\n pow(lhs: T, power: bigint): T;\n div(lhs: T, rhs: T | bigint): T;\n // N for NonNormalized (for now)\n addN(lhs: T, rhs: T): T;\n subN(lhs: T, rhs: T): T;\n mulN(lhs: T, rhs: T | bigint): T;\n sqrN(num: T): T;\n\n // Optional\n // Should be same as sgn0 function in\n // [RFC9380](https://www.rfc-editor.org/rfc/rfc9380#section-4.1).\n // NOTE: sgn0 is 'negative in LE', which is same as odd. And negative in LE is kinda strange definition anyway.\n isOdd?(num: T): boolean; // Odd instead of even since we have it for Fp2\n allowedLengths?: number[];\n // legendre?(num: T): T;\n invertBatch: (lst: T[]) => T[];\n toBytes(num: T): Uint8Array;\n fromBytes(bytes: Uint8Array, skipValidation?: boolean): T;\n // If c is False, CMOV returns a, otherwise it returns b.\n cmov(a: T, b: T, c: boolean): T;\n}\n// prettier-ignore\nconst FIELD_FIELDS = [\n 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr',\n 'eql', 'add', 'sub', 'mul', 'pow', 'div',\n 'addN', 'subN', 'mulN', 'sqrN'\n] as const;\nexport function validateField<T>(field: IField<T>): IField<T> {\n const initial = {\n ORDER: 'bigint',\n MASK: 'bigint',\n BYTES: 'number',\n BITS: 'number',\n } as Record<string, string>;\n const opts = FIELD_FIELDS.reduce((map, val: string) => {\n map[val] = 'function';\n return map;\n }, initial);\n _validateObject(field, opts);\n // const max = 16384;\n // if (field.BYTES < 1 || field.BYTES > max) throw new Error('invalid field');\n // if (field.BITS < 1 || field.BITS > 8 * max) throw new Error('invalid field');\n return field;\n}\n\n// Generic field functions\n\n/**\n * Same as `pow` but for Fp: non-constant-time.\n * Unsafe in some contexts: uses ladder, so can expose bigint bits.\n */\nexport function FpPow<T>(Fp: IField<T>, num: T, power: bigint): T {\n if (power < _0n) throw new Error('invalid exponent, negatives unsupported');\n if (power === _0n) return Fp.ONE;\n if (power === _1n) return num;\n let p = Fp.ONE;\n let d = num;\n while (power > _0n) {\n if (power & _1n) p = Fp.mul(p, d);\n d = Fp.sqr(d);\n power >>= _1n;\n }\n return p;\n}\n\n/**\n * Efficiently invert an array of Field elements.\n * Exception-free. Will return `undefined` for 0 elements.\n * @param passZero map 0 to 0 (instead of undefined)\n */\nexport function FpInvertBatch<T>(Fp: IField<T>, nums: T[], passZero = false): T[] {\n const inverted = new Array(nums.length).fill(passZero ? Fp.ZERO : undefined);\n // Walk from first to last, multiply them by each other MOD p\n const multipliedAcc = nums.reduce((acc, num, i) => {\n if (Fp.is0(num)) return acc;\n inverted[i] = acc;\n return Fp.mul(acc, num);\n }, Fp.ONE);\n // Invert last element\n const invertedAcc = Fp.inv(multipliedAcc);\n // Walk from last to first, multiply them by inverted each other MOD p\n nums.reduceRight((acc, num, i) => {\n if (Fp.is0(num)) return acc;\n inverted[i] = Fp.mul(acc, inverted[i]);\n return Fp.mul(acc, num);\n }, invertedAcc);\n return inverted;\n}\n\n// TODO: remove\nexport function FpDiv<T>(Fp: IField<T>, lhs: T, rhs: T | bigint): T {\n return Fp.mul(lhs, typeof rhs === 'bigint' ? invert(rhs, Fp.ORDER) : Fp.inv(rhs));\n}\n\n/**\n * Legendre symbol.\n * Legendre constant is used to calculate Legendre symbol (a | p)\n * which denotes the value of a^((p-1)/2) (mod p).\n *\n * * (a | p) ≡ 1 if a is a square (mod p), quadratic residue\n * * (a | p) ≡ -1 if a is not a square (mod p), quadratic non residue\n * * (a | p) ≡ 0 if a ≡ 0 (mod p)\n */\nexport function FpLegendre<T>(Fp: IField<T>, n: T): -1 | 0 | 1 {\n // We can use 3rd argument as optional cache of this value\n // but seems unneeded for now. The operation is very fast.\n const p1mod2 = (Fp.ORDER - _1n) / _2n;\n const powered = Fp.pow(n, p1mod2);\n const yes = Fp.eql(powered, Fp.ONE);\n const zero = Fp.eql(powered, Fp.ZERO);\n const no = Fp.eql(powered, Fp.neg(Fp.ONE));\n if (!yes && !zero && !no) throw new Error('invalid Legendre symbol result');\n return yes ? 1 : zero ? 0 : -1;\n}\n\n// This function returns True whenever the value x is a square in the field F.\nexport function FpIsSquare<T>(Fp: IField<T>, n: T): boolean {\n const l = FpLegendre(Fp, n);\n return l === 1;\n}\n\nexport type NLength = { nByteLength: number; nBitLength: number };\n// CURVE.n lengths\nexport function nLength(n: bigint, nBitLength?: number): NLength {\n // Bit size, byte size of CURVE.n\n if (nBitLength !== undefined) anumber(nBitLength);\n const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;\n const nByteLength = Math.ceil(_nBitLength / 8);\n return { nBitLength: _nBitLength, nByteLength };\n}\n\ntype FpField = IField<bigint> & Required<Pick<IField<bigint>, 'isOdd'>>;\ntype SqrtFn = (n: bigint) => bigint;\ntype FieldOpts = Partial<{\n sqrt: SqrtFn;\n isLE: boolean;\n BITS: number;\n modFromBytes: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n\n allowedLengths?: readonly number[]; // for P521 (adds padding for smaller sizes)\n}>;\n/**\n * Creates a finite field. Major performance optimizations:\n * * 1. Denormalized operations like mulN instead of mul.\n * * 2. Identical object shape: never add or remove keys.\n * * 3. `Object.freeze`.\n * Fragile: always run a benchmark on a change.\n * Security note: operations don't check 'isValid' for all elements for performance reasons,\n * it is caller responsibility to check this.\n * This is low-level code, please make sure you know what you're doing.\n *\n * Note about field properties:\n * * CHARACTERISTIC p = prime number, number of elements in main subgroup.\n * * ORDER q = similar to cofactor in curves, may be composite `q = p^m`.\n *\n * @param ORDER field order, probably prime, or could be composite\n * @param bitLen how many bits the field consumes\n * @param isLE (default: false) if encoding / decoding should be in little-endian\n * @param redef optional faster redefinitions of sqrt and other methods\n */\nexport function Field(\n ORDER: bigint,\n bitLenOrOpts?: number | FieldOpts, // TODO: use opts only in v2?\n isLE = false,\n opts: { sqrt?: SqrtFn } = {}\n): Readonly<FpField> {\n if (ORDER <= _0n) throw new Error('invalid field: expected ORDER > 0, got ' + ORDER);\n let _nbitLength: number | undefined = undefined;\n let _sqrt: SqrtFn | undefined = undefined;\n let modFromBytes: boolean = false;\n let allowedLengths: undefined | readonly number[] = undefined;\n if (typeof bitLenOrOpts === 'object' && bitLenOrOpts != null) {\n if (opts.sqrt || isLE) throw new Error('cannot specify opts in two arguments');\n const _opts = bitLenOrOpts;\n if (_opts.BITS) _nbitLength = _opts.BITS;\n if (_opts.sqrt) _sqrt = _opts.sqrt;\n if (typeof _opts.isLE === 'boolean') isLE = _opts.isLE;\n if (typeof _opts.modFromBytes === 'boolean') modFromBytes = _opts.modFromBytes;\n allowedLengths = _opts.allowedLengths;\n } else {\n if (typeof bitLenOrOpts === 'number') _nbitLength = bitLenOrOpts;\n if (opts.sqrt) _sqrt = opts.sqrt;\n }\n const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, _nbitLength);\n if (BYTES > 2048) throw new Error('invalid field: expected ORDER of <= 2048 bytes');\n let sqrtP: ReturnType<typeof FpSqrt>; // cached sqrtP\n const f: Readonly<FpField> = Object.freeze({\n ORDER,\n isLE,\n BITS,\n BYTES,\n MASK: bitMask(BITS),\n ZERO: _0n,\n ONE: _1n,\n allowedLengths: allowedLengths,\n create: (num) => mod(num, ORDER),\n isValid: (num) => {\n if (typeof num !== 'bigint')\n throw new Error('invalid field element: expected bigint, got ' + typeof num);\n return _0n <= num && num < ORDER; // 0 is valid element, but it's not invertible\n },\n is0: (num) => num === _0n,\n // is valid and invertible\n isValidNot0: (num: bigint) => !f.is0(num) && f.isValid(num),\n isOdd: (num) => (num & _1n) === _1n,\n neg: (num) => mod(-num, ORDER),\n eql: (lhs, rhs) => lhs === rhs,\n\n sqr: (num) => mod(num * num, ORDER),\n add: (lhs, rhs) => mod(lhs + rhs, ORDER),\n sub: (lhs, rhs) => mod(lhs - rhs, ORDER),\n mul: (lhs, rhs) => mod(lhs * rhs, ORDER),\n pow: (num, power) => FpPow(f, num, power),\n div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),\n\n // Same as above, but doesn't normalize\n sqrN: (num) => num * num,\n addN: (lhs, rhs) => lhs + rhs,\n subN: (lhs, rhs) => lhs - rhs,\n mulN: (lhs, rhs) => lhs * rhs,\n\n inv: (num) => invert(num, ORDER),\n sqrt:\n _sqrt ||\n ((n) => {\n if (!sqrtP) sqrtP = FpSqrt(ORDER);\n return sqrtP(f, n);\n }),\n toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)),\n fromBytes: (bytes, skipValidation = true) => {\n if (allowedLengths) {\n if (!allowedLengths.includes(bytes.length) || bytes.length > BYTES) {\n throw new Error(\n 'Field.fromBytes: expected ' + allowedLengths + ' bytes, got ' + bytes.length\n );\n }\n const padded = new Uint8Array(BYTES);\n // isLE add 0 to right, !isLE to the left.\n padded.set(bytes, isLE ? 0 : padded.length - bytes.length);\n bytes = padded;\n }\n if (bytes.length !== BYTES)\n throw new Error('Field.fromBytes: expected ' + BYTES + ' bytes, got ' + bytes.length);\n let scalar = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);\n if (modFromBytes) scalar = mod(scalar, ORDER);\n if (!skipValidation)\n if (!f.isValid(scalar)) throw new Error('invalid field element: outside of range 0..ORDER');\n // NOTE: we don't validate scalar here, please use isValid. This done such way because some\n // protocol may allow non-reduced scalar that reduced later or changed some other way.\n return scalar;\n },\n // TODO: we don't need it here, move out to separate fn\n invertBatch: (lst) => FpInvertBatch(f, lst),\n // We can't move this out because Fp6, Fp12 implement it\n // and it's unclear what to return in there.\n cmov: (a, b, c) => (c ? b : a),\n } as FpField);\n return Object.freeze(f);\n}\n\n// Generic random scalar, we can do same for other fields if via Fp2.mul(Fp2.ONE, Fp2.random)?\n// This allows unsafe methods like ignore bias or zero. These unsafe, but often used in different protocols (if deterministic RNG).\n// which mean we cannot force this via opts.\n// Not sure what to do with randomBytes, we can accept it inside opts if wanted.\n// Probably need to export getMinHashLength somewhere?\n// random(bytes?: Uint8Array, unsafeAllowZero = false, unsafeAllowBias = false) {\n// const LEN = !unsafeAllowBias ? getMinHashLength(ORDER) : BYTES;\n// if (bytes === undefined) bytes = randomBytes(LEN); // _opts.randomBytes?\n// const num = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);\n// // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0\n// const reduced = unsafeAllowZero ? mod(num, ORDER) : mod(num, ORDER - _1n) + _1n;\n// return reduced;\n// },\n\nexport function FpSqrtOdd<T>(Fp: IField<T>, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? root : Fp.neg(root);\n}\n\nexport function FpSqrtEven<T>(Fp: IField<T>, elm: T): T {\n if (!Fp.isOdd) throw new Error(\"Field doesn't have isOdd\");\n const root = Fp.sqrt(elm);\n return Fp.isOdd(root) ? Fp.neg(root) : root;\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Same as mapKeyToField, but accepts less bytes (40 instead of 48 for 32-byte field).\n * Which makes it slightly more biased, less secure.\n * @deprecated use `mapKeyToField` instead\n */\nexport function hashToPrivateScalar(\n hash: string | Uint8Array,\n groupOrder: bigint,\n isLE = false\n): bigint {\n hash = ensureBytes('privateHash', hash);\n const hashLen = hash.length;\n const minLen = nLength(groupOrder).nByteLength + 8;\n if (minLen < 24 || hashLen < minLen || hashLen > 1024)\n throw new Error(\n 'hashToPrivateScalar: expected ' + minLen + '-1024 bytes of input, got ' + hashLen\n );\n const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);\n return mod(num, groupOrder - _1n) + _1n;\n}\n\n/**\n * Returns total number of bytes consumed by the field element.\n * For example, 32 bytes for usual 256-bit weierstrass curve.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of field\n */\nexport function getFieldBytesLength(fieldOrder: bigint): number {\n if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint');\n const bitLength = fieldOrder.toString(2).length;\n return Math.ceil(bitLength / 8);\n}\n\n/**\n * Returns minimal amount of bytes that can be safely reduced\n * by field order.\n * Should be 2^-128 for 128-bit curve such as P256.\n * @param fieldOrder number of field elements, usually CURVE.n\n * @returns byte length of target hash\n */\nexport function getMinHashLength(fieldOrder: bigint): number {\n const length = getFieldBytesLength(fieldOrder);\n return length + Math.ceil(length / 2);\n}\n\n/**\n * \"Constant-time\" private key generation utility.\n * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF\n * and convert them into private scalar, with the modulo bias being negligible.\n * Needs at least 48 bytes of input for 32-byte private key.\n * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/\n * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final\n * RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5\n * @param hash hash output from SHA3 or a similar function\n * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n)\n * @param isLE interpret hash bytes as LE num\n * @returns valid private scalar\n */\nexport function mapHashToField(key: Uint8Array, fieldOrder: bigint, isLE = false): Uint8Array {\n const len = key.length;\n const fieldLen = getFieldBytesLength(fieldOrder);\n const minLen = getMinHashLength(fieldOrder);\n // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings.\n if (len < 16 || len < minLen || len > 1024)\n throw new Error('expected ' + minLen + '-1024 bytes of input, got ' + len);\n const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);\n // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0\n const reduced = mod(num, fieldOrder - _1n) + _1n;\n return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);\n}\n","/**\n * Methods for elliptic curve multiplication by scalars.\n * Contains wNAF, pippenger.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { bitLen, bitMask, validateObject } from '../utils.ts';\nimport { Field, FpInvertBatch, nLength, validateField, type IField } from './modular.ts';\n\nconst _0n = BigInt(0);\nconst _1n = BigInt(1);\n\nexport type AffinePoint<T> = {\n x: T;\n y: T;\n} & { Z?: never };\n\n// This was initialy do this way to re-use montgomery ladder in field (add->mul,double->sqr), but\n// that didn't happen and there is probably not much reason to have separate Group like this?\nexport interface Group<T extends Group<T>> {\n double(): T;\n negate(): T;\n add(other: T): T;\n subtract(other: T): T;\n equals(other: T): boolean;\n multiply(scalar: bigint): T;\n toAffine?(invertedZ?: any): AffinePoint<any>;\n}\n\n// We can't \"abstract out\" coordinates (X, Y, Z; and T in Edwards): argument names of constructor\n// are not accessible. See Typescript gh-56093, gh-41594.\n//\n// We have to use recursive types, so it will return actual point, not constained `CurvePoint`.\n// If, at any point, P is `any`, it will erase all types and replace it\n// with `any`, because of recursion, `any implements CurvePoint`,\n// but we lose all constrains on methods.\n\n/** Base interface for all elliptic curve Points. */\nexport interface CurvePoint<F, P extends CurvePoint<F, P>> extends Group<P> {\n /** Affine x coordinate. Different from projective / extended X coordinate. */\n x: F;\n /** Affine y coordinate. Different from projective / extended Y coordinate. */\n y: F;\n Z?: F;\n double(): P;\n negate(): P;\n add(other: P): P;\n subtract(other: P): P;\n equals(other: P): boolean;\n multiply(scalar: bigint): P;\n assertValidity(): void;\n clearCofactor(): P;\n is0(): boolean;\n isTorsionFree(): boolean;\n isSmallOrder(): boolean;\n multiplyUnsafe(scalar: bigint): P;\n /**\n * Massively speeds up `p.multiply(n)` by using precompute tables (caching). See {@link wNAF}.\n * @param isLazy calculate cache now. Default (true) ensures it's deferred to first `multiply()`\n */\n precompute(windowSize?: number, isLazy?: boolean): P;\n /** Converts point to 2D xy affine coordinates */\n toAffine(invertedZ?: F): AffinePoint<F>;\n toBytes(): Uint8Array;\n toHex(): string;\n}\n\n/** Base interface for all elliptic curve Point constructors. */\nexport interface CurvePointCons<P extends CurvePoint<any, P>> {\n [Symbol.hasInstance]: (item: unknown) => boolean;\n BASE: P;\n ZERO: P;\n /** Field for basic curve math */\n Fp: IField<P_F<P>>;\n /** Scalar field, for scalars in multiply and others */\n Fn: IField<bigint>;\n /** Creates point from x, y. Does NOT validate if the point is valid. Use `.assertValidity()`. */\n fromAffine(p: AffinePoint<P_F<P>>): P;\n fromBytes(bytes: Uint8Array): P;\n fromHex(hex: Uint8Array | string): P;\n}\n\n// Type inference helpers: PC - PointConstructor, P - Point, Fp - Field element\n// Short names, because we use them a lot in result types:\n// * we can't do 'P = GetCurvePoint<PC>': this is default value and doesn't constrain anything\n// * we can't do 'type X = GetCurvePoint<PC>': it won't be accesible for arguments/return types\n// * `CurvePointCons<P extends CurvePoint<any, P>>` constraints from interface definition\n// won't propagate, if `PC extends CurvePointCons<any>`: the P would be 'any', which is incorrect\n// * PC could be super specific with super specific P, which implements CurvePoint<any, P>.\n// this means we need to do stuff like\n// `function test<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(`\n// if we want type safety around P, otherwise PC_P<PC> will be any\n\n/** Returns Fp type from Point (P_F<P> == P.F) */\nexport type P_F<P extends CurvePoint<any, P>> = P extends CurvePoint<infer F, P> ? F : never;\n/** Returns Fp type from PointCons (PC_F<PC> == PC.P.F) */\nexport type PC_F<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['Fp']['ZERO'];\n/** Returns Point type from PointCons (PC_P<PC> == PC.P) */\nexport type PC_P<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['ZERO'];\n\n// Ugly hack to get proper type inference, because in typescript fails to infer resursively.\n// The hack allows to do up to 10 chained operations without applying type erasure.\n//\n// Types which won't work:\n// * `CurvePointCons<CurvePoint<any, any>>`, will return `any` after 1 operation\n// * `CurvePointCons<any>: WeierstrassPointCons<bigint> extends CurvePointCons<any> = false`\n// * `P extends CurvePoint, PC extends CurvePointCons<P>`\n// * It can't infer P from PC alone\n// * Too many relations between F, P & PC\n// * It will infer P/F if `arg: CurvePointCons<F, P>`, but will fail if PC is generic\n// * It will work correctly if there is an additional argument of type P\n// * But generally, we don't want to parametrize `CurvePointCons` over `F`: it will complicate\n// types, making them un-inferable\n// prettier-ignore\nexport type PC_ANY = CurvePointCons<\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any,\n CurvePoint<any, any>\n >>>>>>>>>\n>;\n\nexport interface CurveLengths {\n secretKey?: number;\n publicKey?: number;\n publicKeyUncompressed?: number;\n publicKeyHasPrefix?: boolean;\n signature?: number;\n seed?: number;\n}\nexport type GroupConstructor<T> = {\n BASE: T;\n ZERO: T;\n};\n/** @deprecated */\nexport type ExtendedGroupConstructor<T> = GroupConstructor<T> & {\n Fp: IField<any>;\n Fn: IField<bigint>;\n fromAffine(ap: AffinePoint<any>): T;\n};\nexport type Mapper<T> = (i: T[]) => T[];\n\nexport function negateCt<T extends { negate: () => T }>(condition: boolean, item: T): T {\n const neg = item.negate();\n return condition ? neg : item;\n}\n\n/**\n * Takes a bunch of Projective Points but executes only one\n * inversion on all of them. Inversion is very slow operation,\n * so this improves performance massively.\n * Optimization: converts a list of projective points to a list of identical points with Z=1.\n */\nexport function normalizeZ<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n points: P[]\n): P[] {\n const invertedZs = FpInvertBatch(\n c.Fp,\n points.map((p) => p.Z!)\n );\n return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));\n}\n\nfunction validateW(W: number, bits: number) {\n if (!Number.isSafeInteger(W) || W <= 0 || W > bits)\n throw new Error('invalid window size, expected [1..' + bits + '], got W=' + W);\n}\n\n/** Internal wNAF opts for specific W and scalarBits */\nexport type WOpts = {\n windows: number;\n windowSize: number;\n mask: bigint;\n maxNumber: number;\n shiftBy: bigint;\n};\n\nfunction calcWOpts(W: number, scalarBits: number): WOpts {\n validateW(W, scalarBits);\n const windows = Math.ceil(scalarBits / W) + 1; // W=8 33. Not 32, because we skip zero\n const windowSize = 2 ** (W - 1); // W=8 128. Not 256, because we skip zero\n const maxNumber = 2 ** W; // W=8 256\n const mask = bitMask(W); // W=8 255 == mask 0b11111111\n const shiftBy = BigInt(W); // W=8 8\n return { windows, windowSize, mask, maxNumber, shiftBy };\n}\n\nfunction calcOffsets(n: bigint, window: number, wOpts: WOpts) {\n const { windowSize, mask, maxNumber, shiftBy } = wOpts;\n let wbits = Number(n & mask); // extract W bits.\n let nextN = n >> shiftBy; // shift number by W bits.\n\n // What actually happens here:\n // const highestBit = Number(mask ^ (mask >> 1n));\n // let wbits2 = wbits - 1; // skip zero\n // if (wbits2 & highestBit) { wbits2 ^= Number(mask); // (~);\n\n // split if bits > max: +224 => 256-32\n if (wbits > windowSize) {\n // we skip zero, which means instead of `>= size-1`, we do `> size`\n wbits -= maxNumber; // -32, can be maxNumber - wbits, but then we need to set isNeg here.\n nextN += _1n; // +256 (carry)\n }\n const offsetStart = window * windowSize;\n const offset = offsetStart + Math.abs(wbits) - 1; // -1 because we skip zero\n const isZero = wbits === 0; // is current window slice a 0?\n const isNeg = wbits < 0; // is current window slice negative?\n const isNegF = window % 2 !== 0; // fake random statement for noise\n const offsetF = offsetStart; // fake offset for noise\n return { nextN, offset, isZero, isNeg, isNegF, offsetF };\n}\n\nfunction validateMSMPoints(points: any[], c: any) {\n if (!Array.isArray(points)) throw new Error('array expected');\n points.forEach((p, i) => {\n if (!(p instanceof c)) throw new Error('invalid point at index ' + i);\n });\n}\nfunction validateMSMScalars(scalars: any[], field: any) {\n if (!Array.isArray(scalars)) throw new Error('array of scalars expected');\n scalars.forEach((s, i) => {\n if (!field.isValid(s)) throw new Error('invalid scalar at index ' + i);\n });\n}\n\n// Since points in different groups cannot be equal (different object constructor),\n// we can have single place to store precomputes.\n// Allows to make points frozen / immutable.\nconst pointPrecomputes = new WeakMap<any, any[]>();\nconst pointWindowSizes = new WeakMap<any, number>();\n\nfunction getW(P: any): number {\n // To disable precomputes:\n // return 1;\n return pointWindowSizes.get(P) || 1;\n}\n\nfunction assert0(n: bigint): void {\n if (n !== _0n) throw new Error('invalid wNAF');\n}\n\n/**\n * Elliptic curve multiplication of Point by scalar. Fragile.\n * Table generation takes **30MB of ram and 10ms on high-end CPU**,\n * but may take much longer on slow devices. Actual generation will happen on\n * first call of `multiply()`. By default, `BASE` point is precomputed.\n *\n * Scalars should always be less than curve order: this should be checked inside of a curve itself.\n * Creates precomputation tables for fast multiplication:\n * - private scalar is split by fixed size windows of W bits\n * - every window point is collected from window's table & added to accumulator\n * - since windows are different, same point inside tables won't be accessed more than once per calc\n * - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)\n * - +1 window is neccessary for wNAF\n * - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication\n *\n * @todo Research returning 2d JS array of windows, instead of a single window.\n * This would allow windows to be in different memory locations\n */\nexport class wNAF<PC extends PC_ANY> {\n private readonly BASE: PC_P<PC>;\n private readonly ZERO: PC_P<PC>;\n private readonly Fn: PC['Fn'];\n readonly bits: number;\n\n // Parametrized with a given Point class (not individual point)\n constructor(Point: PC, bits: number) {\n this.BASE = Point.BASE;\n this.ZERO = Point.ZERO;\n this.Fn = Point.Fn;\n this.bits = bits;\n }\n\n // non-const time multiplication ladder\n _unsafeLadder(elm: PC_P<PC>, n: bigint, p: PC_P<PC> = this.ZERO): PC_P<PC> {\n let d: PC_P<PC> = elm;\n while (n > _0n) {\n if (n & _1n) p = p.add(d);\n d = d.double();\n n >>= _1n;\n }\n return p;\n }\n\n /**\n * Creates a wNAF precomputation window. Used for caching.\n * Default window size is set by `utils.precompute()` and is equal to 8.\n * Number of precomputed points depends on the curve size:\n * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:\n * - 𝑊 is the window size\n * - 𝑛 is the bitlength of the curve order.\n * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.\n * @param point Point instance\n * @param W window size\n * @returns precomputed point tables flattened to a single array\n */\n private precomputeWindow(point: PC_P<PC>, W: number): PC_P<PC>[] {\n const { windows, windowSize } = calcWOpts(W, this.bits);\n const points: PC_P<PC>[] = [];\n let p: PC_P<PC> = point;\n let base = p;\n for (let window = 0; window < windows; window++) {\n base = p;\n points.push(base);\n // i=1, bc we skip 0\n for (let i = 1; i < windowSize; i++) {\n base = base.add(p);\n points.push(base);\n }\n p = base.double();\n }\n return points;\n }\n\n /**\n * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.\n * More compact implementation:\n * https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541\n * @returns real and fake (for const-time) points\n */\n private wNAF(W: number, precomputes: PC_P<PC>[], n: bigint): { p: PC_P<PC>; f: PC_P<PC> } {\n // Scalar should be smaller than field order\n if (!this.Fn.isValid(n)) throw new Error('invalid scalar');\n // Accumulators\n let p = this.ZERO;\n let f = this.BASE;\n // This code was first written with assumption that 'f' and 'p' will never be infinity point:\n // since each addition is multiplied by 2 ** W, it cannot cancel each other. However,\n // there is negate now: it is possible that negated element from low value\n // would be the same as high element, which will create carry into next window.\n // It's not obvious how this can fail, but still worth investigating later.\n const wo = calcWOpts(W, this.bits);\n for (let window = 0; window < wo.windows; window++) {\n // (n === _0n) is handled and not early-exited. isEven and offsetF are used for noise\n const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window, wo);\n n = nextN;\n if (isZero) {\n // bits are 0: add garbage to fake point\n // Important part for const-time getPublicKey: add random \"noise\" point to f.\n f = f.add(negateCt(isNegF, precomputes[offsetF]));\n } else {\n // bits are 1: add to result point\n p = p.add(negateCt(isNeg, precomputes[offset]));\n }\n }\n assert0(n);\n // Return both real and fake points: JIT won't eliminate f.\n // At this point there is a way to F be infinity-point even if p is not,\n // which makes it less const-time: around 1 bigint multiply.\n return { p, f };\n }\n\n /**\n * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.\n * @param acc accumulator point to add result of multiplication\n * @returns point\n */\n private wNAFUnsafe(\n W: number,\n precomputes: PC_P<PC>[],\n n: bigint,\n acc: PC_P<PC> = this.ZERO\n ): PC_P<PC> {\n const wo = calcWOpts(W, this.bits);\n for (let window = 0; window < wo.windows; window++) {\n if (n === _0n) break; // Early-exit, skip 0 value\n const { nextN, offset, isZero, isNeg } = calcOffsets(n, window, wo);\n n = nextN;\n if (isZero) {\n // Window bits are 0: skip processing.\n // Move to next window.\n continue;\n } else {\n const item = precomputes[offset];\n acc = acc.add(isNeg ? item.negate() : item); // Re-using acc allows to save adds in MSM\n }\n }\n assert0(n);\n return acc;\n }\n\n private getPrecomputes(W: number, point: PC_P<PC>, transform?: Mapper<PC_P<PC>>): PC_P<PC>[] {\n // Calculate precomputes on a first run, reuse them after\n let comp = pointPrecomputes.get(point);\n if (!comp) {\n comp = this.precomputeWindow(point, W) as PC_P<PC>[];\n if (W !== 1) {\n // Doing transform outside of if brings 15% perf hit\n if (typeof transform === 'function') comp = transform(comp);\n pointPrecomputes.set(point, comp);\n }\n }\n return comp;\n }\n\n cached(\n point: PC_P<PC>,\n scalar: bigint,\n transform?: Mapper<PC_P<PC>>\n ): { p: PC_P<PC>; f: PC_P<PC> } {\n const W = getW(point);\n return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);\n }\n\n unsafe(point: PC_P<PC>, scalar: bigint, transform?: Mapper<PC_P<PC>>, prev?: PC_P<PC>): PC_P<PC> {\n const W = getW(point);\n if (W === 1) return this._unsafeLadder(point, scalar, prev); // For W=1 ladder is ~x2 faster\n return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);\n }\n\n // We calculate precomputes for elliptic curve point multiplication\n // using windowed method. This specifies window size and\n // stores precomputed values. Usually only base point would be precomputed.\n createCache(P: PC_P<PC>, W: number): void {\n validateW(W, this.bits);\n pointWindowSizes.set(P, W);\n pointPrecomputes.delete(P);\n }\n\n hasCache(elm: PC_P<PC>): boolean {\n return getW(elm) !== 1;\n }\n}\n\n/**\n * Endomorphism-specific multiplication for Koblitz curves.\n * Cost: 128 dbl, 0-256 adds.\n */\nexport function mulEndoUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n Point: PC,\n point: P,\n k1: bigint,\n k2: bigint\n): { p1: P; p2: P } {\n let acc = point;\n let p1 = Point.ZERO;\n let p2 = Point.ZERO;\n while (k1 > _0n || k2 > _0n) {\n if (k1 & _1n) p1 = p1.add(acc);\n if (k2 & _1n) p2 = p2.add(acc);\n acc = acc.double();\n k1 >>= _1n;\n k2 >>= _1n;\n }\n return { p1, p2 };\n}\n\n/**\n * Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * 30x faster vs naive addition on L=4096, 10x faster than precomputes.\n * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.\n * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @param scalars array of L scalars (aka secret keys / bigints)\n */\nexport function pippenger<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n fieldN: IField<bigint>,\n points: P[],\n scalars: bigint[]\n): P {\n // If we split scalars by some window (let's say 8 bits), every chunk will only\n // take 256 buckets even if there are 4096 scalars, also re-uses double.\n // TODO:\n // - https://eprint.iacr.org/2024/750.pdf\n // - https://tches.iacr.org/index.php/TCHES/article/view/10287\n // 0 is accepted in scalars\n validateMSMPoints(points, c);\n validateMSMScalars(scalars, fieldN);\n const plength = points.length;\n const slength = scalars.length;\n if (plength !== slength) throw new Error('arrays of points and scalars must have equal length');\n // if (plength === 0) throw new Error('array must be of length >= 2');\n const zero = c.ZERO;\n const wbits = bitLen(BigInt(plength));\n let windowSize = 1; // bits\n if (wbits > 12) windowSize = wbits - 3;\n else if (wbits > 4) windowSize = wbits - 2;\n else if (wbits > 0) windowSize = 2;\n const MASK = bitMask(windowSize);\n const buckets = new Array(Number(MASK) + 1).fill(zero); // +1 for zero array\n const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;\n let sum = zero;\n for (let i = lastBits; i >= 0; i -= windowSize) {\n buckets.fill(zero);\n for (let j = 0; j < slength; j++) {\n const scalar = scalars[j];\n const wbits = Number((scalar >> BigInt(i)) & MASK);\n buckets[wbits] = buckets[wbits].add(points[j]);\n }\n let resI = zero; // not using this will do small speed-up, but will lose ct\n // Skip first bucket, because it is zero\n for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {\n sumI = sumI.add(buckets[j]);\n resI = resI.add(sumI);\n }\n sum = sum.add(resI);\n if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double();\n }\n return sum as P;\n}\n/**\n * Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).\n * @param c Curve Point constructor\n * @param fieldN field over CURVE.N - important that it's not over CURVE.P\n * @param points array of L curve points\n * @returns function which multiplies points with scaars\n */\nexport function precomputeMSMUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(\n c: PC,\n fieldN: IField<bigint>,\n points: P[],\n windowSize: number\n): (scalars: bigint[]) => P {\n /**\n * Performance Analysis of Window-based Precomputation\n *\n * Base Case (256-bit scalar, 8-bit window):\n * - Standard precomputation requires:\n * - 31 additions per scalar × 256 scalars = 7,936 ops\n * - Plus 255 summary additions = 8,191 total ops\n * Note: Summary additions can be optimized via accumulator\n *\n * Chunked Precomputation Analysis:\n * - Using 32 chunks requires:\n * - 255 additions per chunk\n * - 256 doublings\n * - Total: (255 × 32) + 256 = 8,416 ops\n *\n * Memory Usage Comparison:\n * Window Size | Standard Points | Chunked Points\n * ------------|-----------------|---------------\n * 4-bit | 520 | 15\n * 8-bit | 4,224 | 255\n * 10-bit | 13,824 | 1,023\n * 16-bit | 557,056 | 65,535\n *\n * Key Advantages:\n * 1. Enables larger window sizes due to reduced memory overhead\n * 2. More efficient for smaller scalar counts:\n * - 16 chunks: (16 × 255) + 256 = 4,336 ops\n * - ~2x faster than standard 8,191 ops\n *\n * Limitations:\n * - Not suitable for plain precomputes (requires 256 constant doublings)\n * - Performance degrades with larger scalar counts:\n * - Optimal for ~256 scalars\n * - Less efficient for 4096+ scalars (Pippenger preferred)\n */\n validateW(windowSize, fieldN.BITS);\n validateMSMPoints(points, c);\n const zero = c.ZERO;\n const tableSize = 2 ** windowSize - 1; // table size (without zero)\n const chunks = Math.ceil(fieldN.BITS / windowSize); // chunks of item\n const MASK = bitMask(windowSize);\n const tables = points.map((p: P) => {\n const res = [];\n for (let i = 0, acc = p; i < tableSize; i++) {\n res.push(acc);\n acc = acc.add(p);\n }\n return res;\n });\n return (scalars: bigint[]): P => {\n validateMSMScalars(scalars, fieldN);\n if (scalars.length > points.length)\n throw new Error('array of scalars must be smaller than array of points');\n let res = zero;\n for (let i = 0; i < chunks; i++) {\n // No need to double if accumulator is still zero.\n if (res !== zero) for (let j = 0; j < windowSize; j++) res = res.double();\n const shiftBy = BigInt(chunks * windowSize - (i + 1) * windowSize);\n for (let j = 0; j < scalars.length; j++) {\n const n = scalars[j];\n const curr = Number((n >> shiftBy) & MASK);\n if (!curr) continue; // skip zero scalars chunks\n res = res.add(tables[j][curr - 1]);\n }\n }\n return res;\n };\n}\n\n// TODO: remove\n/**\n * Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.\n * Though generator can be different (Fp2 / Fp6 for BLS).\n */\nexport type BasicCurve<T> = {\n Fp: IField<T>; // Field over which we'll do calculations (Fp)\n n: bigint; // Curve order, total count of valid points in the field\n nBitLength?: number; // bit length of curve order\n nByteLength?: number; // byte length of curve order\n h: bigint; // cofactor. we can assign default=1, but users will just ignore it w/o validation\n hEff?: bigint; // Number to multiply to clear cofactor\n Gx: T; // base point X coordinate\n Gy: T; // base point Y coordinate\n allowInfinityPoint?: boolean; // bls12-381 requires it. ZERO point is valid, but invalid pubkey\n};\n\n// TODO: remove\n/** @deprecated */\nexport function validateBasic<FP, T>(\n curve: BasicCurve<FP> & T\n): Readonly<\n {\n readonly nBitLength: number;\n readonly nByteLength: number;\n } & BasicCurve<FP> &\n T & {\n p: bigint;\n }\n> {\n validateField(curve.Fp);\n validateObject(\n curve,\n {\n n: 'bigint',\n h: 'bigint',\n Gx: 'field',\n Gy: 'field',\n },\n {\n nBitLength: 'isSafeInteger',\n nByteLength: 'isSafeInteger',\n }\n );\n // Set defaults\n return Object.freeze({\n ...nLength(curve.n, curve.nBitLength),\n ...curve,\n ...{ p: curve.Fp.ORDER },\n } as const);\n}\n\nexport type ValidCurveParams<T> = {\n p: bigint;\n n: bigint;\n h: bigint;\n a: T;\n b?: T;\n d?: T;\n Gx: T;\n Gy: T;\n};\n\nfunction createField<T>(order: bigint, field?: IField<T>, isLE?: boolean): IField<T> {\n if (field) {\n if (field.ORDER !== order) throw new Error('Field.ORDER must match order: Fp == p, Fn == n');\n validateField(field);\n return field;\n } else {\n return Field(order, { isLE }) as unknown as IField<T>;\n }\n}\nexport type FpFn<T> = { Fp: IField<T>; Fn: IField<bigint> };\n\n/** Validates CURVE opts and creates fields */\nexport function _createCurveFields<T>(\n type: 'weierstrass' | 'edwards',\n CURVE: ValidCurveParams<T>,\n curveOpts: Partial<FpFn<T>> = {},\n FpFnLE?: boolean\n): FpFn<T> & { CURVE: ValidCurveParams<T> } {\n if (FpFnLE === undefined) FpFnLE = type === 'edwards';\n if (!CURVE || typeof CURVE !== 'object') throw new Error(`expected valid ${type} CURVE object`);\n for (const p of ['p', 'n', 'h'] as const) {\n const val = CURVE[p];\n if (!(typeof val === 'bigint' && val > _0n))\n throw new Error(`CURVE.${p} must be positive bigint`);\n }\n const Fp = createField(CURVE.p, curveOpts.Fp, FpFnLE);\n const Fn = createField(CURVE.n, curveOpts.Fn, FpFnLE);\n const _b: 'b' | 'd' = type === 'weierstrass' ? 'b' : 'd';\n const params = ['Gx', 'Gy', 'a', _b] as const;\n for (const p of params) {\n // @ts-ignore\n if (!Fp.isValid(CURVE[p]))\n throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);\n }\n CURVE = Object.freeze(Object.assign({}, CURVE));\n return { CURVE, Fp, Fn };\n}\n","/**\n * Short Weierstrass curve methods. The formula is: y² = x³ + ax + b.\n *\n * ### Design rationale for types\n *\n * * Interaction between classes from different curves should fail:\n * `k256.Point.BASE.add(p256.Point.BASE)`\n * * For this purpose we want to use `instanceof` operator, which is fast and works during runtime\n * * Different calls of `curve()` would return different classes -\n * `curve(params) !== curve(params)`: if somebody decided to monkey-patch their curve,\n * it won't affect others\n *\n * TypeScript can't infer types for classes created inside a function. Classes is one instance\n * of nominative types in TypeScript and interfaces only check for shape, so it's hard to create\n * unique type for every function call.\n *\n * We can use generic types via some param, like curve opts, but that would:\n * 1. Enable interaction between `curve(params)` and `curve(params)` (curves of same params)\n * which is hard to debug.\n * 2. Params can be generic and we can't enforce them to be constant value:\n * if somebody creates curve from non-constant params,\n * it would be allowed to interact with other curves with non-constant params\n *\n * @todo https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { hmac as nobleHmac } from '@noble/hashes/hmac.js';\nimport { ahash } from '@noble/hashes/utils';\nimport {\n _validateObject,\n _abool2 as abool,\n _abytes2 as abytes,\n aInRange,\n bitLen,\n bitMask,\n bytesToHex,\n bytesToNumberBE,\n concatBytes,\n createHmacDrbg,\n ensureBytes,\n hexToBytes,\n inRange,\n isBytes,\n memoized,\n numberToHexUnpadded,\n randomBytes as randomBytesWeb,\n type CHash,\n type Hex,\n type PrivKey,\n} from '../utils.ts';\nimport {\n _createCurveFields,\n mulEndoUnsafe,\n negateCt,\n normalizeZ,\n pippenger,\n wNAF,\n type AffinePoint,\n type BasicCurve,\n type CurveLengths,\n type CurvePoint,\n type CurvePointCons,\n} from './curve.ts';\nimport {\n Field,\n FpInvertBatch,\n getMinHashLength,\n mapHashToField,\n nLength,\n validateField,\n type IField,\n type NLength,\n} from './modular.ts';\n\nexport type { AffinePoint };\nexport type HmacFnSync = (key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array;\n\ntype EndoBasis = [[bigint, bigint], [bigint, bigint]];\n/**\n * When Weierstrass curve has `a=0`, it becomes Koblitz curve.\n * Koblitz curves allow using **efficiently-computable GLV endomorphism ψ**.\n * Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.\n * For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.\n *\n * Endomorphism consists of beta, lambda and splitScalar:\n *\n * 1. GLV endomorphism ψ transforms a point: `P = (x, y) ↦ ψ(P) = (β·x mod p, y)`\n * 2. GLV scalar decomposition transforms a scalar: `k ≡ k₁ + k₂·λ (mod n)`\n * 3. Then these are combined: `k·P = k₁·P + k₂·ψ(P)`\n * 4. Two 128-bit point-by-scalar multiplications + one point addition is faster than\n * one 256-bit multiplication.\n *\n * where\n * * beta: β ∈ Fₚ with β³ = 1, β ≠ 1\n * * lambda: λ ∈ Fₙ with λ³ = 1, λ ≠ 1\n * * splitScalar decomposes k ↦ k₁, k₂, by using reduced basis vectors.\n * Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`\n *\n * Check out `test/misc/endomorphism.js` and\n * [gist](https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066).\n */\nexport type EndomorphismOpts = {\n beta: bigint;\n basises?: EndoBasis;\n splitScalar?: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };\n};\n\n// We construct basis in such way that den is always positive and equals n, but num sign depends on basis (not on secret value)\nconst divNearest = (num: bigint, den: bigint) => (num + (num >= 0 ? den : -den) / _2n) / den;\n\nexport type ScalarEndoParts = { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };\n\n/**\n * Splits scalar for GLV endomorphism.\n */\nexport function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): ScalarEndoParts {\n // Split scalar into two such that part is ~half bits: `abs(part) < sqrt(N)`\n // Since part can be negative, we need to do this on point.\n // TODO: verifyScalar function which consumes lambda\n const [[a1, b1], [a2, b2]] = basis;\n const c1 = divNearest(b2 * k, n);\n const c2 = divNearest(-b1 * k, n);\n // |k1|/|k2| is < sqrt(N), but can be negative.\n // If we do `k1 mod N`, we'll get big scalar (`> sqrt(N)`): so, we do cheaper negation instead.\n let k1 = k - c1 * a1 - c2 * a2;\n let k2 = -c1 * b1 - c2 * b2;\n const k1neg = k1 < _0n;\n const k2neg = k2 < _0n;\n if (k1neg) k1 = -k1;\n if (k2neg) k2 = -k2;\n // Double check that resulting scalar less than half bits of N: otherwise wNAF will fail.\n // This should only happen on wrong basises. Also, math inside is too complex and I don't trust it.\n const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n; // Half bits of N\n if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) {\n throw new Error('splitScalar (endomorphism): failed, k=' + k);\n }\n return { k1neg, k1, k2neg, k2 };\n}\n\nexport type ECDSASigFormat = 'compact' | 'recovered' | 'der';\nexport type ECDSARecoverOpts = {\n prehash?: boolean;\n};\nexport type ECDSAVerifyOpts = {\n prehash?: boolean;\n lowS?: boolean;\n format?: ECDSASigFormat;\n};\nexport type ECDSASignOpts = {\n prehash?: boolean;\n lowS?: boolean;\n format?: ECDSASigFormat;\n extraEntropy?: Uint8Array | boolean;\n};\n\nfunction validateSigFormat(format: string): ECDSASigFormat {\n if (!['compact', 'recovered', 'der'].includes(format))\n throw new Error('Signature format must be \"compact\", \"recovered\", or \"der\"');\n return format as ECDSASigFormat;\n}\n\nfunction validateSigOpts<T extends ECDSASignOpts, D extends Required<ECDSASignOpts>>(\n opts: T,\n def: D\n): Required<ECDSASignOpts> {\n const optsn: ECDSASignOpts = {};\n for (let optName of Object.keys(def)) {\n // @ts-ignore\n optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];\n }\n abool(optsn.lowS!, 'lowS');\n abool(optsn.prehash!, 'prehash');\n if (optsn.format !== undefined) validateSigFormat(optsn.format);\n return optsn as Required<ECDSASignOpts>;\n}\n\n/** Instance methods for 3D XYZ projective points. */\nexport interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>> {\n /** projective X coordinate. Different from affine x. */\n readonly X: T;\n /** projective Y coordinate. Different from affine y. */\n readonly Y: T;\n /** projective z coordinate */\n readonly Z: T;\n /** affine x coordinate. Different from projective X. */\n get x(): T;\n /** affine y coordinate. Different from projective Y. */\n get y(): T;\n /** Encodes point using IEEE P1363 (DER) encoding. First byte is 2/3/4. Default = isCompressed. */\n toBytes(isCompressed?: boolean): Uint8Array;\n toHex(isCompressed?: boolean): string;\n\n /** @deprecated use `.X` */\n readonly px: T;\n /** @deprecated use `.Y` */\n readonly py: T;\n /** @deprecated use `.Z` */\n readonly pz: T;\n /** @deprecated use `toBytes` */\n toRawBytes(isCompressed?: boolean): Uint8Array;\n /** @deprecated use `multiplyUnsafe` */\n multiplyAndAddUnsafe(\n Q: WeierstrassPoint<T>,\n a: bigint,\n b: bigint\n ): WeierstrassPoint<T> | undefined;\n /** @deprecated use `p.y % 2n === 0n` */\n hasEvenY(): boolean;\n /** @deprecated use `p.precompute(windowSize)` */\n _setWindowSize(windowSize: number): void;\n}\n\n/** Static methods for 3D XYZ projective points. */\nexport interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint<T>> {\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n new (X: T, Y: T, Z: T): WeierstrassPoint<T>;\n CURVE(): WeierstrassOpts<T>;\n /** @deprecated use `Point.BASE.multiply(Point.Fn.fromBytes(privateKey))` */\n fromPrivateKey(privateKey: PrivKey): WeierstrassPoint<T>;\n /** @deprecated use `import { normalizeZ } from '@noble/curves/abstract/curve.js';` */\n normalizeZ(points: WeierstrassPoint<T>[]): WeierstrassPoint<T>[];\n /** @deprecated use `import { pippenger } from '@noble/curves/abstract/curve.js';` */\n msm(points: WeierstrassPoint<T>[], scalars: bigint[]): WeierstrassPoint<T>;\n}\n\n/**\n * Weierstrass curve options.\n *\n * * p: prime characteristic (order) of finite field, in which arithmetics is done\n * * n: order of prime subgroup a.k.a total amount of valid curve points\n * * h: cofactor, usually 1. h*n is group order; n is subgroup order\n * * a: formula param, must be in field of p\n * * b: formula param, must be in field of p\n * * Gx: x coordinate of generator point a.k.a. base point\n * * Gy: y coordinate of generator point\n */\nexport type WeierstrassOpts<T> = Readonly<{\n p: bigint;\n n: bigint;\n h: bigint;\n a: T;\n b: T;\n Gx: T;\n Gy: T;\n}>;\n\n// When a cofactor != 1, there can be an effective methods to:\n// 1. Determine whether a point is torsion-free\n// 2. Clear torsion component\n// wrapPrivateKey: bls12-381 requires mod(n) instead of rejecting keys >= n\nexport type WeierstrassExtraOpts<T> = Partial<{\n Fp: IField<T>;\n Fn: IField<bigint>;\n allowInfinityPoint: boolean;\n endo: EndomorphismOpts;\n isTorsionFree: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;\n clearCofactor: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;\n fromBytes: (bytes: Uint8Array) => AffinePoint<T>;\n toBytes: (\n c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ) => Uint8Array;\n}>;\n\n/**\n * Options for ECDSA signatures over a Weierstrass curve.\n *\n * * lowS: (default: true) whether produced / verified signatures occupy low half of ecdsaOpts.p. Prevents malleability.\n * * hmac: (default: noble-hashes hmac) function, would be used to init hmac-drbg for k generation.\n * * randomBytes: (default: webcrypto os-level CSPRNG) custom method for fetching secure randomness.\n * * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves\n */\nexport type ECDSAOpts = Partial<{\n lowS: boolean;\n hmac: HmacFnSync;\n randomBytes: (bytesLength?: number) => Uint8Array;\n bits2int: (bytes: Uint8Array) => bigint;\n bits2int_modN: (bytes: Uint8Array) => bigint;\n}>;\n\n/**\n * Elliptic Curve Diffie-Hellman interface.\n * Provides keygen, secret-to-public conversion, calculating shared secrets.\n */\nexport interface ECDH {\n keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };\n getPublicKey: (secretKey: PrivKey, isCompressed?: boolean) => Uint8Array;\n getSharedSecret: (secretKeyA: PrivKey, publicKeyB: Hex, isCompressed?: boolean) => Uint8Array;\n Point: WeierstrassPointCons<bigint>;\n utils: {\n isValidSecretKey: (secretKey: PrivKey) => boolean;\n isValidPublicKey: (publicKey: Uint8Array, isCompressed?: boolean) => boolean;\n randomSecretKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `randomSecretKey` */\n randomPrivateKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `isValidSecretKey` */\n isValidPrivateKey: (secretKey: PrivKey) => boolean;\n /** @deprecated use `Point.Fn.fromBytes()` */\n normPrivateKeyToScalar: (key: PrivKey) => bigint;\n /** @deprecated use `point.precompute()` */\n precompute: (windowSize?: number, point?: WeierstrassPoint<bigint>) => WeierstrassPoint<bigint>;\n };\n lengths: CurveLengths;\n}\n\n/**\n * ECDSA interface.\n * Only supported for prime fields, not Fp2 (extension fields).\n */\nexport interface ECDSA extends ECDH {\n sign: (message: Hex, secretKey: PrivKey, opts?: ECDSASignOpts) => ECDSASigRecovered;\n verify: (\n signature: Uint8Array,\n message: Uint8Array,\n publicKey: Uint8Array,\n opts?: ECDSAVerifyOpts\n ) => boolean;\n recoverPublicKey(signature: Uint8Array, message: Uint8Array, opts?: ECDSARecoverOpts): Uint8Array;\n Signature: ECDSASignatureCons;\n}\nexport class DERErr extends Error {\n constructor(m = '') {\n super(m);\n }\n}\nexport type IDER = {\n // asn.1 DER encoding utils\n Err: typeof DERErr;\n // Basic building block is TLV (Tag-Length-Value)\n _tlv: {\n encode: (tag: number, data: string) => string;\n // v - value, l - left bytes (unparsed)\n decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array };\n };\n // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,\n // since we always use positive integers here. It must always be empty:\n // - add zero byte if exists\n // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)\n _int: {\n encode(num: bigint): string;\n decode(data: Uint8Array): bigint;\n };\n toSig(hex: string | Uint8Array): { r: bigint; s: bigint };\n hexFromSig(sig: { r: bigint; s: bigint }): string;\n};\n/**\n * ASN.1 DER encoding utilities. ASN is very complex & fragile. Format:\n *\n * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]\n *\n * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html\n */\nexport const DER: IDER = {\n // asn.1 DER encoding utils\n Err: DERErr,\n // Basic building block is TLV (Tag-Length-Value)\n _tlv: {\n encode: (tag: number, data: string): string => {\n const { Err: E } = DER;\n if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');\n if (data.length & 1) throw new E('tlv.encode: unpadded data');\n const dataLen = data.length / 2;\n const len = numberToHexUnpadded(dataLen);\n if ((len.length / 2) & 0b1000_0000) throw new E('tlv.encode: long form length too big');\n // length of length with long form flag\n const lenLen = dataLen > 127 ? numberToHexUnpadded((len.length / 2) | 0b1000_0000) : '';\n const t = numberToHexUnpadded(tag);\n return t + lenLen + len + data;\n },\n // v - value, l - left bytes (unparsed)\n decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array } {\n const { Err: E } = DER;\n let pos = 0;\n if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');\n if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv');\n const first = data[pos++];\n const isLong = !!(first & 0b1000_0000); // First bit of first length byte is flag for short/long form\n let length = 0;\n if (!isLong) length = first;\n else {\n // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)]\n const lenLen = first & 0b0111_1111;\n if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported');\n if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js\n const lengthBytes = data.subarray(pos, pos + lenLen);\n if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete');\n if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte');\n for (const b of lengthBytes) length = (length << 8) | b;\n pos += lenLen;\n if (length < 128) throw new E('tlv.decode(long): not minimal encoding');\n }\n const v = data.subarray(pos, pos + length);\n if (v.length !== length) throw new E('tlv.decode: wrong value length');\n return { v, l: data.subarray(pos + length) };\n },\n },\n // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,\n // since we always use positive integers here. It must always be empty:\n // - add zero byte if exists\n // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)\n _int: {\n encode(num: bigint): string {\n const { Err: E } = DER;\n if (num < _0n) throw new E('integer: negative integers are not allowed');\n let hex = numberToHexUnpadded(num);\n // Pad with zero byte if negative flag is present\n if (Number.parseInt(hex[0], 16) & 0b1000) hex = '00' + hex;\n if (hex.length & 1) throw new E('unexpected DER parsing assertion: unpadded hex');\n return hex;\n },\n decode(data: Uint8Array): bigint {\n const { Err: E } = DER;\n if (data[0] & 0b1000_0000) throw new E('invalid signature integer: negative');\n if (data[0] === 0x00 && !(data[1] & 0b1000_0000))\n throw new E('invalid signature integer: unnecessary leading zero');\n return bytesToNumberBE(data);\n },\n },\n toSig(hex: string | Uint8Array): { r: bigint; s: bigint } {\n // parse DER signature\n const { Err: E, _int: int, _tlv: tlv } = DER;\n const data = ensureBytes('signature', hex);\n const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data);\n if (seqLeftBytes.length) throw new E('invalid signature: left bytes after parsing');\n const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes);\n const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes);\n if (sLeftBytes.length) throw new E('invalid signature: left bytes after parsing');\n return { r: int.decode(rBytes), s: int.decode(sBytes) };\n },\n hexFromSig(sig: { r: bigint; s: bigint }): string {\n const { _tlv: tlv, _int: int } = DER;\n const rs = tlv.encode(0x02, int.encode(sig.r));\n const ss = tlv.encode(0x02, int.encode(sig.s));\n const seq = rs + ss;\n return tlv.encode(0x30, seq);\n },\n};\n\n// Be friendly to bad ECMAScript parsers by not using bigint literals\n// prettier-ignore\nconst _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);\n\nexport function _normFnElement(Fn: IField<bigint>, key: PrivKey): bigint {\n const { BYTES: expected } = Fn;\n let num: bigint;\n if (typeof key === 'bigint') {\n num = key;\n } else {\n let bytes = ensureBytes('private key', key);\n try {\n num = Fn.fromBytes(bytes);\n } catch (error) {\n throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`);\n }\n }\n if (!Fn.isValidNot0(num)) throw new Error('invalid private key: out of range [1..N-1]');\n return num;\n}\n\n/**\n * Creates weierstrass Point constructor, based on specified curve options.\n *\n * @example\n```js\nconst opts = {\n p: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),\n n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),\n h: BigInt(1),\n a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),\n b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),\n Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),\n Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),\n};\nconst p256_Point = weierstrass(opts);\n```\n */\nexport function weierstrassN<T>(\n params: WeierstrassOpts<T>,\n extraOpts: WeierstrassExtraOpts<T> = {}\n): WeierstrassPointCons<T> {\n const validated = _createCurveFields('weierstrass', params, extraOpts);\n const { Fp, Fn } = validated;\n let CURVE = validated.CURVE as WeierstrassOpts<T>;\n const { h: cofactor, n: CURVE_ORDER } = CURVE;\n _validateObject(\n extraOpts,\n {},\n {\n allowInfinityPoint: 'boolean',\n clearCofactor: 'function',\n isTorsionFree: 'function',\n fromBytes: 'function',\n toBytes: 'function',\n endo: 'object',\n wrapPrivateKey: 'boolean',\n }\n );\n\n const { endo } = extraOpts;\n if (endo) {\n // validateObject(endo, { beta: 'bigint', splitScalar: 'function' });\n if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) {\n throw new Error('invalid endo: expected \"beta\": bigint and \"basises\": array');\n }\n }\n\n const lengths = getWLengths(Fp, Fn);\n\n function assertCompressionIsSupported() {\n if (!Fp.isOdd) throw new Error('compression is not supported: Field does not have .isOdd()');\n }\n\n // Implements IEEE P1363 point encoding\n function pointToBytes(\n _c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ): Uint8Array {\n const { x, y } = point.toAffine();\n const bx = Fp.toBytes(x);\n abool(isCompressed, 'isCompressed');\n if (isCompressed) {\n assertCompressionIsSupported();\n const hasEvenY = !Fp.isOdd!(y);\n return concatBytes(pprefix(hasEvenY), bx);\n } else {\n return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y));\n }\n }\n function pointFromBytes(bytes: Uint8Array) {\n abytes(bytes, undefined, 'Point');\n const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65\n const length = bytes.length;\n const head = bytes[0];\n const tail = bytes.subarray(1);\n // No actual validation is done here: use .assertValidity()\n if (length === comp && (head === 0x02 || head === 0x03)) {\n const x = Fp.fromBytes(tail);\n if (!Fp.isValid(x)) throw new Error('bad point: is not on curve, wrong x');\n const y2 = weierstrassEquation(x); // y² = x³ + ax + b\n let y: T;\n try {\n y = Fp.sqrt(y2); // y = y² ^ (p+1)/4\n } catch (sqrtError) {\n const err = sqrtError instanceof Error ? ': ' + sqrtError.message : '';\n throw new Error('bad point: is not on curve, sqrt error' + err);\n }\n assertCompressionIsSupported();\n const isYOdd = Fp.isOdd!(y); // (y & _1n) === _1n;\n const isHeadOdd = (head & 1) === 1; // ECDSA-specific\n if (isHeadOdd !== isYOdd) y = Fp.neg(y);\n return { x, y };\n } else if (length === uncomp && head === 0x04) {\n // TODO: more checks\n const L = Fp.BYTES;\n const x = Fp.fromBytes(tail.subarray(0, L));\n const y = Fp.fromBytes(tail.subarray(L, L * 2));\n if (!isValidXY(x, y)) throw new Error('bad point: is not on curve');\n return { x, y };\n } else {\n throw new Error(\n `bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`\n );\n }\n }\n\n const encodePoint = extraOpts.toBytes || pointToBytes;\n const decodePoint = extraOpts.fromBytes || pointFromBytes;\n function weierstrassEquation(x: T): T {\n const x2 = Fp.sqr(x); // x * x\n const x3 = Fp.mul(x2, x); // x² * x\n return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b); // x³ + a * x + b\n }\n\n // TODO: move top-level\n /** Checks whether equation holds for given x, y: y² == x³ + ax + b */\n function isValidXY(x: T, y: T): boolean {\n const left = Fp.sqr(y); // y²\n const right = weierstrassEquation(x); // x³ + ax + b\n return Fp.eql(left, right);\n }\n\n // Validate whether the passed curve params are valid.\n // Test 1: equation y² = x³ + ax + b should work for generator point.\n if (!isValidXY(CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point');\n\n // Test 2: discriminant Δ part should be non-zero: 4a³ + 27b² != 0.\n // Guarantees curve is genus-1, smooth (non-singular).\n const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n);\n const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));\n if (Fp.is0(Fp.add(_4a3, _27b2))) throw new Error('bad curve params: a or b');\n\n /** Asserts coordinate is valid: 0 <= n < Fp.ORDER. */\n function acoord(title: string, n: T, banZero = false) {\n if (!Fp.isValid(n) || (banZero && Fp.is0(n))) throw new Error(`bad point coordinate ${title}`);\n return n;\n }\n\n function aprjpoint(other: unknown) {\n if (!(other instanceof Point)) throw new Error('ProjectivePoint expected');\n }\n\n function splitEndoScalarN(k: bigint) {\n if (!endo || !endo.basises) throw new Error('no endo');\n return _splitEndoScalar(k, endo.basises, Fn.ORDER);\n }\n\n // Memoized toAffine / validity check. They are heavy. Points are immutable.\n\n // Converts Projective point to affine (x, y) coordinates.\n // Can accept precomputed Z^-1 - for example, from invertBatch.\n // (X, Y, Z) ∋ (x=X/Z, y=Y/Z)\n const toAffineMemo = memoized((p: Point, iz?: T): AffinePoint<T> => {\n const { X, Y, Z } = p;\n // Fast-path for normalized points\n if (Fp.eql(Z, Fp.ONE)) return { x: X, y: Y };\n const is0 = p.is0();\n // If invZ was 0, we return zero point. However we still want to execute\n // all operations, so we replace invZ with a random number, 1.\n if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(Z);\n const x = Fp.mul(X, iz);\n const y = Fp.mul(Y, iz);\n const zz = Fp.mul(Z, iz);\n if (is0) return { x: Fp.ZERO, y: Fp.ZERO };\n if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid');\n return { x, y };\n });\n // NOTE: on exception this will crash 'cached' and no value will be set.\n // Otherwise true will be return\n const assertValidMemo = memoized((p: Point) => {\n if (p.is0()) {\n // (0, 1, 0) aka ZERO is invalid in most contexts.\n // In BLS, ZERO can be serialized, so we allow it.\n // (0, 0, 0) is invalid representation of ZERO.\n if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y)) return;\n throw new Error('bad point: ZERO');\n }\n // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`\n const { x, y } = p.toAffine();\n if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements');\n if (!isValidXY(x, y)) throw new Error('bad point: equation left != right');\n if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');\n return true;\n });\n\n function finishEndo(\n endoBeta: EndomorphismOpts['beta'],\n k1p: Point,\n k2p: Point,\n k1neg: boolean,\n k2neg: boolean\n ) {\n k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);\n k1p = negateCt(k1neg, k1p);\n k2p = negateCt(k2neg, k2p);\n return k1p.add(k2p);\n }\n\n /**\n * Projective Point works in 3d / projective (homogeneous) coordinates:(X, Y, Z) ∋ (x=X/Z, y=Y/Z).\n * Default Point works in 2d / affine coordinates: (x, y).\n * We're doing calculations in projective, because its operations don't require costly inversion.\n */\n class Point implements WeierstrassPoint<T> {\n // base / generator point\n static readonly BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);\n // zero / infinity / identity point\n static readonly ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0\n // math field\n static readonly Fp = Fp;\n // scalar field\n static readonly Fn = Fn;\n\n readonly X: T;\n readonly Y: T;\n readonly Z: T;\n\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n constructor(X: T, Y: T, Z: T) {\n this.X = acoord('x', X);\n this.Y = acoord('y', Y, true);\n this.Z = acoord('z', Z);\n Object.freeze(this);\n }\n\n static CURVE(): WeierstrassOpts<T> {\n return CURVE;\n }\n\n /** Does NOT validate if the point is valid. Use `.assertValidity()`. */\n static fromAffine(p: AffinePoint<T>): Point {\n const { x, y } = p || {};\n if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point');\n if (p instanceof Point) throw new Error('projective point not allowed');\n // (0, 0) would've produced (0, 0, 1) - instead, we need (0, 1, 0)\n if (Fp.is0(x) && Fp.is0(y)) return Point.ZERO;\n return new Point(x, y, Fp.ONE);\n }\n\n static fromBytes(bytes: Uint8Array): Point {\n const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));\n P.assertValidity();\n return P;\n }\n static fromHex(hex: Hex): Point {\n return Point.fromBytes(ensureBytes('pointHex', hex));\n }\n\n get x(): T {\n return this.toAffine().x;\n }\n get y(): T {\n return this.toAffine().y;\n }\n\n /**\n *\n * @param windowSize\n * @param isLazy true will defer table computation until the first multiplication\n * @returns\n */\n precompute(windowSize: number = 8, isLazy = true): Point {\n wnaf.createCache(this, windowSize);\n if (!isLazy) this.multiply(_3n); // random number\n return this;\n }\n\n // TODO: return `this`\n /** A point on curve is valid if it conforms to equation. */\n assertValidity(): void {\n assertValidMemo(this);\n }\n\n hasEvenY(): boolean {\n const { y } = this.toAffine();\n if (!Fp.isOdd) throw new Error(\"Field doesn't support isOdd\");\n return !Fp.isOdd(y);\n }\n\n /** Compare one point to another. */\n equals(other: Point): boolean {\n aprjpoint(other);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n const { X: X2, Y: Y2, Z: Z2 } = other;\n const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));\n const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));\n return U1 && U2;\n }\n\n /** Flips point to one corresponding to (x, -y) in Affine coordinates. */\n negate(): Point {\n return new Point(this.X, Fp.neg(this.Y), this.Z);\n }\n\n // Renes-Costello-Batina exception-free doubling formula.\n // There is 30% faster Jacobian formula, but it is not complete.\n // https://eprint.iacr.org/2015/1060, algorithm 3\n // Cost: 8M + 3S + 3*a + 2*b3 + 15add.\n double() {\n const { a, b } = CURVE;\n const b3 = Fp.mul(b, _3n);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore\n let t0 = Fp.mul(X1, X1); // step 1\n let t1 = Fp.mul(Y1, Y1);\n let t2 = Fp.mul(Z1, Z1);\n let t3 = Fp.mul(X1, Y1);\n t3 = Fp.add(t3, t3); // step 5\n Z3 = Fp.mul(X1, Z1);\n Z3 = Fp.add(Z3, Z3);\n X3 = Fp.mul(a, Z3);\n Y3 = Fp.mul(b3, t2);\n Y3 = Fp.add(X3, Y3); // step 10\n X3 = Fp.sub(t1, Y3);\n Y3 = Fp.add(t1, Y3);\n Y3 = Fp.mul(X3, Y3);\n X3 = Fp.mul(t3, X3);\n Z3 = Fp.mul(b3, Z3); // step 15\n t2 = Fp.mul(a, t2);\n t3 = Fp.sub(t0, t2);\n t3 = Fp.mul(a, t3);\n t3 = Fp.add(t3, Z3);\n Z3 = Fp.add(t0, t0); // step 20\n t0 = Fp.add(Z3, t0);\n t0 = Fp.add(t0, t2);\n t0 = Fp.mul(t0, t3);\n Y3 = Fp.add(Y3, t0);\n t2 = Fp.mul(Y1, Z1); // step 25\n t2 = Fp.add(t2, t2);\n t0 = Fp.mul(t2, t3);\n X3 = Fp.sub(X3, t0);\n Z3 = Fp.mul(t2, t1);\n Z3 = Fp.add(Z3, Z3); // step 30\n Z3 = Fp.add(Z3, Z3);\n return new Point(X3, Y3, Z3);\n }\n\n // Renes-Costello-Batina exception-free addition formula.\n // There is 30% faster Jacobian formula, but it is not complete.\n // https://eprint.iacr.org/2015/1060, algorithm 1\n // Cost: 12M + 0S + 3*a + 3*b3 + 23add.\n add(other: Point): Point {\n aprjpoint(other);\n const { X: X1, Y: Y1, Z: Z1 } = this;\n const { X: X2, Y: Y2, Z: Z2 } = other;\n let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore\n const a = CURVE.a;\n const b3 = Fp.mul(CURVE.b, _3n);\n let t0 = Fp.mul(X1, X2); // step 1\n let t1 = Fp.mul(Y1, Y2);\n let t2 = Fp.mul(Z1, Z2);\n let t3 = Fp.add(X1, Y1);\n let t4 = Fp.add(X2, Y2); // step 5\n t3 = Fp.mul(t3, t4);\n t4 = Fp.add(t0, t1);\n t3 = Fp.sub(t3, t4);\n t4 = Fp.add(X1, Z1);\n let t5 = Fp.add(X2, Z2); // step 10\n t4 = Fp.mul(t4, t5);\n t5 = Fp.add(t0, t2);\n t4 = Fp.sub(t4, t5);\n t5 = Fp.add(Y1, Z1);\n X3 = Fp.add(Y2, Z2); // step 15\n t5 = Fp.mul(t5, X3);\n X3 = Fp.add(t1, t2);\n t5 = Fp.sub(t5, X3);\n Z3 = Fp.mul(a, t4);\n X3 = Fp.mul(b3, t2); // step 20\n Z3 = Fp.add(X3, Z3);\n X3 = Fp.sub(t1, Z3);\n Z3 = Fp.add(t1, Z3);\n Y3 = Fp.mul(X3, Z3);\n t1 = Fp.add(t0, t0); // step 25\n t1 = Fp.add(t1, t0);\n t2 = Fp.mul(a, t2);\n t4 = Fp.mul(b3, t4);\n t1 = Fp.add(t1, t2);\n t2 = Fp.sub(t0, t2); // step 30\n t2 = Fp.mul(a, t2);\n t4 = Fp.add(t4, t2);\n t0 = Fp.mul(t1, t4);\n Y3 = Fp.add(Y3, t0);\n t0 = Fp.mul(t5, t4); // step 35\n X3 = Fp.mul(t3, X3);\n X3 = Fp.sub(X3, t0);\n t0 = Fp.mul(t3, t1);\n Z3 = Fp.mul(t5, Z3);\n Z3 = Fp.add(Z3, t0); // step 40\n return new Point(X3, Y3, Z3);\n }\n\n subtract(other: Point) {\n return this.add(other.negate());\n }\n\n is0(): boolean {\n return this.equals(Point.ZERO);\n }\n\n /**\n * Constant time multiplication.\n * Uses wNAF method. Windowed method may be 10% faster,\n * but takes 2x longer to generate and consumes 2x memory.\n * Uses precomputes when available.\n * Uses endomorphism for Koblitz curves.\n * @param scalar by which the point would be multiplied\n * @returns New point\n */\n multiply(scalar: bigint): Point {\n const { endo } = extraOpts;\n if (!Fn.isValidNot0(scalar)) throw new Error('invalid scalar: out of range'); // 0 is invalid\n let point: Point, fake: Point; // Fake point is used to const-time mult\n const mul = (n: bigint) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));\n /** See docs for {@link EndomorphismOpts} */\n if (endo) {\n const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);\n const { p: k1p, f: k1f } = mul(k1);\n const { p: k2p, f: k2f } = mul(k2);\n fake = k1f.add(k2f);\n point = finishEndo(endo.beta, k1p, k2p, k1neg, k2neg);\n } else {\n const { p, f } = mul(scalar);\n point = p;\n fake = f;\n }\n // Normalize `z` for both points, but return only real one\n return normalizeZ(Point, [point, fake])[0];\n }\n\n /**\n * Non-constant-time multiplication. Uses double-and-add algorithm.\n * It's faster, but should only be used when you don't care about\n * an exposed secret key e.g. sig verification, which works over *public* keys.\n */\n multiplyUnsafe(sc: bigint): Point {\n const { endo } = extraOpts;\n const p = this as Point;\n if (!Fn.isValid(sc)) throw new Error('invalid scalar: out of range'); // 0 is valid\n if (sc === _0n || p.is0()) return Point.ZERO;\n if (sc === _1n) return p; // fast-path\n if (wnaf.hasCache(this)) return this.multiply(sc);\n if (endo) {\n const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);\n const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2); // 30% faster vs wnaf.unsafe\n return finishEndo(endo.beta, p1, p2, k1neg, k2neg);\n } else {\n return wnaf.unsafe(p, sc);\n }\n }\n\n multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined {\n const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));\n return sum.is0() ? undefined : sum;\n }\n\n /**\n * Converts Projective point to affine (x, y) coordinates.\n * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch\n */\n toAffine(invertedZ?: T): AffinePoint<T> {\n return toAffineMemo(this, invertedZ);\n }\n\n /**\n * Checks whether Point is free of torsion elements (is in prime subgroup).\n * Always torsion-free for cofactor=1 curves.\n */\n isTorsionFree(): boolean {\n const { isTorsionFree } = extraOpts;\n if (cofactor === _1n) return true;\n if (isTorsionFree) return isTorsionFree(Point, this);\n return wnaf.unsafe(this, CURVE_ORDER).is0();\n }\n\n clearCofactor(): Point {\n const { clearCofactor } = extraOpts;\n if (cofactor === _1n) return this; // Fast-path\n if (clearCofactor) return clearCofactor(Point, this) as Point;\n return this.multiplyUnsafe(cofactor);\n }\n\n isSmallOrder(): boolean {\n // can we use this.clearCofactor()?\n return this.multiplyUnsafe(cofactor).is0();\n }\n\n toBytes(isCompressed = true): Uint8Array {\n abool(isCompressed, 'isCompressed');\n this.assertValidity();\n return encodePoint(Point, this, isCompressed);\n }\n\n toHex(isCompressed = true): string {\n return bytesToHex(this.toBytes(isCompressed));\n }\n\n toString() {\n return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;\n }\n\n // TODO: remove\n get px(): T {\n return this.X;\n }\n get py(): T {\n return this.X;\n }\n get pz(): T {\n return this.Z;\n }\n toRawBytes(isCompressed = true): Uint8Array {\n return this.toBytes(isCompressed);\n }\n _setWindowSize(windowSize: number) {\n this.precompute(windowSize);\n }\n static normalizeZ(points: Point[]): Point[] {\n return normalizeZ(Point, points);\n }\n static msm(points: Point[], scalars: bigint[]): Point {\n return pippenger(Point, Fn, points, scalars);\n }\n static fromPrivateKey(privateKey: PrivKey) {\n return Point.BASE.multiply(_normFnElement(Fn, privateKey));\n }\n }\n const bits = Fn.BITS;\n const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);\n Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.\n return Point;\n}\n\n/** Methods of ECDSA signature instance. */\nexport interface ECDSASignature {\n readonly r: bigint;\n readonly s: bigint;\n readonly recovery?: number;\n addRecoveryBit(recovery: number): ECDSASigRecovered;\n hasHighS(): boolean;\n toBytes(format?: string): Uint8Array;\n toHex(format?: string): string;\n\n /** @deprecated */\n assertValidity(): void;\n /** @deprecated */\n normalizeS(): ECDSASignature;\n /** @deprecated use standalone method `curve.recoverPublicKey(sig.toBytes('recovered'), msg)` */\n recoverPublicKey(msgHash: Hex): WeierstrassPoint<bigint>;\n /** @deprecated use `.toBytes('compact')` */\n toCompactRawBytes(): Uint8Array;\n /** @deprecated use `.toBytes('compact')` */\n toCompactHex(): string;\n /** @deprecated use `.toBytes('der')` */\n toDERRawBytes(): Uint8Array;\n /** @deprecated use `.toBytes('der')` */\n toDERHex(): string;\n}\nexport type ECDSASigRecovered = ECDSASignature & {\n readonly recovery: number;\n};\n/** Methods of ECDSA signature constructor. */\nexport type ECDSASignatureCons = {\n new (r: bigint, s: bigint, recovery?: number): ECDSASignature;\n fromBytes(bytes: Uint8Array, format?: ECDSASigFormat): ECDSASignature;\n fromHex(hex: string, format?: ECDSASigFormat): ECDSASignature;\n\n /** @deprecated use `.fromBytes(bytes, 'compact')` */\n fromCompact(hex: Hex): ECDSASignature;\n /** @deprecated use `.fromBytes(bytes, 'der')` */\n fromDER(hex: Hex): ECDSASignature;\n};\n\n// Points start with byte 0x02 when y is even; otherwise 0x03\nfunction pprefix(hasEvenY: boolean): Uint8Array {\n return Uint8Array.of(hasEvenY ? 0x02 : 0x03);\n}\n\n/**\n * Implementation of the Shallue and van de Woestijne method for any weierstrass curve.\n * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.\n * b = True and y = sqrt(u / v) if (u / v) is square in F, and\n * b = False and y = sqrt(Z * (u / v)) otherwise.\n * @param Fp\n * @param Z\n * @returns\n */\nexport function SWUFpSqrtRatio<T>(\n Fp: IField<T>,\n Z: T\n): (u: T, v: T) => { isValid: boolean; value: T } {\n // Generic implementation\n const q = Fp.ORDER;\n let l = _0n;\n for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;\n const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.\n // We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<.\n // 2n ** c1 == 2n << (c1-1)\n const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n);\n const _2n_pow_c1 = _2n_pow_c1_1 * _2n;\n const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic\n const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic\n const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic\n const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic\n const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2\n const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)\n let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {\n let tv1 = c6; // 1. tv1 = c6\n let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4\n let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2\n tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v\n let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3\n tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3\n tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2\n tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v\n tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u\n let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2\n tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5\n let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1\n tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7\n tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1\n tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)\n tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)\n // 17. for i in (c1, c1 - 1, ..., 2):\n for (let i = c1; i > _1n; i--) {\n let tv5 = i - _2n; // 18. tv5 = i - 2\n tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5\n let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5\n const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1\n tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1\n tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1\n tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1\n tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)\n tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)\n }\n return { isValid: isQR, value: tv3 };\n };\n if (Fp.ORDER % _4n === _3n) {\n // sqrt_ratio_3mod4(u, v)\n const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic\n const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)\n sqrtRatio = (u: T, v: T) => {\n let tv1 = Fp.sqr(v); // 1. tv1 = v^2\n const tv2 = Fp.mul(u, v); // 2. tv2 = u * v\n tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2\n let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1\n y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2\n const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2\n const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v\n const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u\n let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)\n return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2\n };\n }\n // No curves uses that\n // if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8\n return sqrtRatio;\n}\n/**\n * Simplified Shallue-van de Woestijne-Ulas Method\n * https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2\n */\nexport function mapToCurveSimpleSWU<T>(\n Fp: IField<T>,\n opts: {\n A: T;\n B: T;\n Z: T;\n }\n): (u: T) => { x: T; y: T } {\n validateField(Fp);\n const { A, B, Z } = opts;\n if (!Fp.isValid(A) || !Fp.isValid(B) || !Fp.isValid(Z))\n throw new Error('mapToCurveSimpleSWU: invalid opts');\n const sqrtRatio = SWUFpSqrtRatio(Fp, Z);\n if (!Fp.isOdd) throw new Error('Field does not have .isOdd()');\n // Input: u, an element of F.\n // Output: (x, y), a point on E.\n return (u: T): { x: T; y: T } => {\n // prettier-ignore\n let tv1, tv2, tv3, tv4, tv5, tv6, x, y;\n tv1 = Fp.sqr(u); // 1. tv1 = u^2\n tv1 = Fp.mul(tv1, Z); // 2. tv1 = Z * tv1\n tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2\n tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1\n tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1\n tv3 = Fp.mul(tv3, B); // 6. tv3 = B * tv3\n tv4 = Fp.cmov(Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)\n tv4 = Fp.mul(tv4, A); // 8. tv4 = A * tv4\n tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2\n tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2\n tv5 = Fp.mul(tv6, A); // 11. tv5 = A * tv6\n tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5\n tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3\n tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4\n tv5 = Fp.mul(tv6, B); // 15. tv5 = B * tv6\n tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5\n x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3\n const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)\n y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1\n y = Fp.mul(y, value); // 20. y = y * y1\n x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)\n y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)\n const e1 = Fp.isOdd!(u) === Fp.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)\n y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)\n const tv4_inv = FpInvertBatch(Fp, [tv4], true)[0];\n x = Fp.mul(x, tv4_inv); // 25. x = x / tv4\n return { x, y };\n };\n}\n\nfunction getWLengths<T>(Fp: IField<T>, Fn: IField<bigint>) {\n return {\n secretKey: Fn.BYTES,\n publicKey: 1 + Fp.BYTES,\n publicKeyUncompressed: 1 + 2 * Fp.BYTES,\n publicKeyHasPrefix: true,\n signature: 2 * Fn.BYTES,\n };\n}\n\n/**\n * Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.\n * This helper ensures no signature functionality is present. Less code, smaller bundle size.\n */\nexport function ecdh(\n Point: WeierstrassPointCons<bigint>,\n ecdhOpts: { randomBytes?: (bytesLength?: number) => Uint8Array } = {}\n): ECDH {\n const { Fn } = Point;\n const randomBytes_ = ecdhOpts.randomBytes || randomBytesWeb;\n const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });\n\n function isValidSecretKey(secretKey: PrivKey) {\n try {\n return !!_normFnElement(Fn, secretKey);\n } catch (error) {\n return false;\n }\n }\n\n function isValidPublicKey(publicKey: Uint8Array, isCompressed?: boolean): boolean {\n const { publicKey: comp, publicKeyUncompressed } = lengths;\n try {\n const l = publicKey.length;\n if (isCompressed === true && l !== comp) return false;\n if (isCompressed === false && l !== publicKeyUncompressed) return false;\n return !!Point.fromBytes(publicKey);\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Produces cryptographically secure secret key from random of size\n * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.\n */\n function randomSecretKey(seed = randomBytes_(lengths.seed)): Uint8Array {\n return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER);\n }\n\n /**\n * Computes public key for a secret key. Checks for validity of the secret key.\n * @param isCompressed whether to return compact (default), or full key\n * @returns Public key, full when isCompressed=false; short when isCompressed=true\n */\n function getPublicKey(secretKey: PrivKey, isCompressed = true): Uint8Array {\n return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed);\n }\n\n function keygen(seed?: Uint8Array) {\n const secretKey = randomSecretKey(seed);\n return { secretKey, publicKey: getPublicKey(secretKey) };\n }\n\n /**\n * Quick and dirty check for item being public key. Does not validate hex, or being on-curve.\n */\n function isProbPub(item: PrivKey | PubKey): boolean | undefined {\n if (typeof item === 'bigint') return false;\n if (item instanceof Point) return true;\n const { secretKey, publicKey, publicKeyUncompressed } = lengths;\n if (Fn.allowedLengths || secretKey === publicKey) return undefined;\n const l = ensureBytes('key', item).length;\n return l === publicKey || l === publicKeyUncompressed;\n }\n\n /**\n * ECDH (Elliptic Curve Diffie Hellman).\n * Computes shared public key from secret key A and public key B.\n * Checks: 1) secret key validity 2) shared key is on-curve.\n * Does NOT hash the result.\n * @param isCompressed whether to return compact (default), or full key\n * @returns shared public key\n */\n function getSharedSecret(secretKeyA: PrivKey, publicKeyB: Hex, isCompressed = true): Uint8Array {\n if (isProbPub(secretKeyA) === true) throw new Error('first arg must be private key');\n if (isProbPub(publicKeyB) === false) throw new Error('second arg must be public key');\n const s = _normFnElement(Fn, secretKeyA);\n const b = Point.fromHex(publicKeyB); // checks for being on-curve\n return b.multiply(s).toBytes(isCompressed);\n }\n\n const utils = {\n isValidSecretKey,\n isValidPublicKey,\n randomSecretKey,\n\n // TODO: remove\n isValidPrivateKey: isValidSecretKey,\n randomPrivateKey: randomSecretKey,\n normPrivateKeyToScalar: (key: PrivKey) => _normFnElement(Fn, key),\n precompute(windowSize = 8, point = Point.BASE): WeierstrassPoint<bigint> {\n return point.precompute(windowSize, false);\n },\n };\n\n return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });\n}\n\n/**\n * Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.\n * We need `hash` for 2 features:\n * 1. Message prehash-ing. NOT used if `sign` / `verify` are called with `prehash: false`\n * 2. k generation in `sign`, using HMAC-drbg(hash)\n *\n * ECDSAOpts are only rarely needed.\n *\n * @example\n * ```js\n * const p256_Point = weierstrass(...);\n * const p256_sha256 = ecdsa(p256_Point, sha256);\n * const p256_sha224 = ecdsa(p256_Point, sha224);\n * const p256_sha224_r = ecdsa(p256_Point, sha224, { randomBytes: (length) => { ... } });\n * ```\n */\nexport function ecdsa(\n Point: WeierstrassPointCons<bigint>,\n hash: CHash,\n ecdsaOpts: ECDSAOpts = {}\n): ECDSA {\n ahash(hash);\n _validateObject(\n ecdsaOpts,\n {},\n {\n hmac: 'function',\n lowS: 'boolean',\n randomBytes: 'function',\n bits2int: 'function',\n bits2int_modN: 'function',\n }\n );\n\n const randomBytes = ecdsaOpts.randomBytes || randomBytesWeb;\n const hmac: HmacFnSync =\n ecdsaOpts.hmac ||\n (((key, ...msgs) => nobleHmac(hash, key, concatBytes(...msgs))) satisfies HmacFnSync);\n\n const { Fp, Fn } = Point;\n const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;\n const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);\n const defaultSigOpts: Required<ECDSASignOpts> = {\n prehash: false,\n lowS: typeof ecdsaOpts.lowS === 'boolean' ? ecdsaOpts.lowS : false,\n format: undefined as any, //'compact' as ECDSASigFormat,\n extraEntropy: false,\n };\n const defaultSigOpts_format = 'compact';\n\n function isBiggerThanHalfOrder(number: bigint) {\n const HALF = CURVE_ORDER >> _1n;\n return number > HALF;\n }\n function validateRS(title: string, num: bigint): bigint {\n if (!Fn.isValidNot0(num))\n throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);\n return num;\n }\n function validateSigLength(bytes: Uint8Array, format: ECDSASigFormat) {\n validateSigFormat(format);\n const size = lengths.signature!;\n const sizer = format === 'compact' ? size : format === 'recovered' ? size + 1 : undefined;\n return abytes(bytes, sizer, `${format} signature`);\n }\n\n /**\n * ECDSA signature with its (r, s) properties. Supports compact, recovered & DER representations.\n */\n class Signature implements ECDSASignature {\n readonly r: bigint;\n readonly s: bigint;\n readonly recovery?: number;\n constructor(r: bigint, s: bigint, recovery?: number) {\n this.r = validateRS('r', r); // r in [1..N-1];\n this.s = validateRS('s', s); // s in [1..N-1];\n if (recovery != null) this.recovery = recovery;\n Object.freeze(this);\n }\n\n static fromBytes(bytes: Uint8Array, format: ECDSASigFormat = defaultSigOpts_format): Signature {\n validateSigLength(bytes, format);\n let recid: number | undefined;\n if (format === 'der') {\n const { r, s } = DER.toSig(abytes(bytes));\n return new Signature(r, s);\n }\n if (format === 'recovered') {\n recid = bytes[0];\n format = 'compact';\n bytes = bytes.subarray(1);\n }\n const L = Fn.BYTES;\n const r = bytes.subarray(0, L);\n const s = bytes.subarray(L, L * 2);\n return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);\n }\n\n static fromHex(hex: string, format?: ECDSASigFormat) {\n return this.fromBytes(hexToBytes(hex), format);\n }\n\n addRecoveryBit(recovery: number): RecoveredSignature {\n return new Signature(this.r, this.s, recovery) as RecoveredSignature;\n }\n\n recoverPublicKey(messageHash: Hex): WeierstrassPoint<bigint> {\n const FIELD_ORDER = Fp.ORDER;\n const { r, s, recovery: rec } = this;\n if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid');\n\n // ECDSA recovery is hard for cofactor > 1 curves.\n // In sign, `r = q.x mod n`, and here we recover q.x from r.\n // While recovering q.x >= n, we need to add r+n for cofactor=1 curves.\n // However, for cofactor>1, r+n may not get q.x:\n // r+n*i would need to be done instead where i is unknown.\n // To easily get i, we either need to:\n // a. increase amount of valid recid values (4, 5...); OR\n // b. prohibit non-prime-order signatures (recid > 1).\n const hasCofactor = CURVE_ORDER * _2n < FIELD_ORDER;\n if (hasCofactor && rec > 1) throw new Error('recovery id is ambiguous for h>1 curve');\n\n const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;\n if (!Fp.isValid(radj)) throw new Error('recovery id 2 or 3 invalid');\n const x = Fp.toBytes(radj);\n const R = Point.fromBytes(concatBytes(pprefix((rec & 1) === 0), x));\n const ir = Fn.inv(radj); // r^-1\n const h = bits2int_modN(ensureBytes('msgHash', messageHash)); // Truncate hash\n const u1 = Fn.create(-h * ir); // -hr^-1\n const u2 = Fn.create(s * ir); // sr^-1\n // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1). unsafe is fine: there is no private data.\n const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));\n if (Q.is0()) throw new Error('point at infinify');\n Q.assertValidity();\n return Q;\n }\n\n // Signatures should be low-s, to prevent malleability.\n hasHighS(): boolean {\n return isBiggerThanHalfOrder(this.s);\n }\n\n toBytes(format: ECDSASigFormat = defaultSigOpts_format) {\n validateSigFormat(format);\n if (format === 'der') return hexToBytes(DER.hexFromSig(this));\n const r = Fn.toBytes(this.r);\n const s = Fn.toBytes(this.s);\n if (format === 'recovered') {\n if (this.recovery == null) throw new Error('recovery bit must be present');\n return concatBytes(Uint8Array.of(this.recovery), r, s);\n }\n return concatBytes(r, s);\n }\n\n toHex(format?: ECDSASigFormat) {\n return bytesToHex(this.toBytes(format));\n }\n\n // TODO: remove\n assertValidity(): void {}\n static fromCompact(hex: Hex) {\n return Signature.fromBytes(ensureBytes('sig', hex), 'compact');\n }\n static fromDER(hex: Hex) {\n return Signature.fromBytes(ensureBytes('sig', hex), 'der');\n }\n normalizeS() {\n return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this;\n }\n toDERRawBytes() {\n return this.toBytes('der');\n }\n toDERHex() {\n return bytesToHex(this.toBytes('der'));\n }\n toCompactRawBytes() {\n return this.toBytes('compact');\n }\n toCompactHex() {\n return bytesToHex(this.toBytes('compact'));\n }\n }\n type RecoveredSignature = Signature & { recovery: number };\n\n // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.\n // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int.\n // bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.\n // int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors\n const bits2int =\n ecdsaOpts.bits2int ||\n function bits2int_def(bytes: Uint8Array): bigint {\n // Our custom check \"just in case\", for protection against DoS\n if (bytes.length > 8192) throw new Error('input is too large');\n // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)\n // for some cases, since bytes.length * 8 is not actual bitLength.\n const num = bytesToNumberBE(bytes); // check for == u8 done here\n const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits\n return delta > 0 ? num >> BigInt(delta) : num;\n };\n const bits2int_modN =\n ecdsaOpts.bits2int_modN ||\n function bits2int_modN_def(bytes: Uint8Array): bigint {\n return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here\n };\n // Pads output with zero as per spec\n const ORDER_MASK = bitMask(fnBits);\n /** Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. */\n function int2octets(num: bigint): Uint8Array {\n // IMPORTANT: the check ensures working for case `Fn.BYTES != Fn.BITS * 8`\n aInRange('num < 2^' + fnBits, num, _0n, ORDER_MASK);\n return Fn.toBytes(num);\n }\n\n function validateMsgAndHash(message: Uint8Array, prehash: boolean) {\n abytes(message, undefined, 'message');\n return prehash ? abytes(hash(message), undefined, 'prehashed message') : message;\n }\n\n /**\n * Steps A, D of RFC6979 3.2.\n * Creates RFC6979 seed; converts msg/privKey to numbers.\n * Used only in sign, not in verify.\n *\n * Warning: we cannot assume here that message has same amount of bytes as curve order,\n * this will be invalid at least for P521. Also it can be bigger for P224 + SHA256.\n */\n function prepSig(message: Uint8Array, privateKey: PrivKey, opts: ECDSASignOpts) {\n if (['recovered', 'canonical'].some((k) => k in opts))\n throw new Error('sign() legacy options not supported');\n const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);\n message = validateMsgAndHash(message, prehash); // RFC6979 3.2 A: h1 = H(m)\n // We can't later call bits2octets, since nested bits2int is broken for curves\n // with fnBits % 8 !== 0. Because of that, we unwrap it here as int2octets call.\n // const bits2octets = (bits) => int2octets(bits2int_modN(bits))\n const h1int = bits2int_modN(message);\n const d = _normFnElement(Fn, privateKey); // validate secret key, convert to bigint\n const seedArgs = [int2octets(d), int2octets(h1int)];\n // extraEntropy. RFC6979 3.6: additional k' (optional).\n if (extraEntropy != null && extraEntropy !== false) {\n // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')\n // gen random bytes OR pass as-is\n const e = extraEntropy === true ? randomBytes(lengths.secretKey) : extraEntropy;\n seedArgs.push(ensureBytes('extraEntropy', e)); // check for being bytes\n }\n const seed = concatBytes(...seedArgs); // Step D of RFC6979 3.2\n const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash!\n // Converts signature params into point w r/s, checks result for validity.\n // To transform k => Signature:\n // q = k⋅G\n // r = q.x mod n\n // s = k^-1(m + rd) mod n\n // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to\n // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:\n // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT\n function k2sig(kBytes: Uint8Array): RecoveredSignature | undefined {\n // RFC 6979 Section 3.2, step 3: k = bits2int(T)\n // Important: all mod() calls here must be done over N\n const k = bits2int(kBytes); // mod n, not mod p\n if (!Fn.isValidNot0(k)) return; // Valid scalars (including k) must be in 1..N-1\n const ik = Fn.inv(k); // k^-1 mod n\n const q = Point.BASE.multiply(k).toAffine(); // q = k⋅G\n const r = Fn.create(q.x); // r = q.x mod n\n if (r === _0n) return;\n const s = Fn.create(ik * Fn.create(m + r * d)); // Not using blinding here, see comment above\n if (s === _0n) return;\n let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); // recovery bit (2 or 3, when q.x > n)\n let normS = s;\n if (lowS && isBiggerThanHalfOrder(s)) {\n normS = Fn.neg(s); // if lowS was passed, ensure s is always\n recovery ^= 1; // // in the bottom half of N\n }\n return new Signature(r, normS, recovery) as RecoveredSignature; // use normS, not s\n }\n return { seed, k2sig };\n }\n\n /**\n * Signs message hash with a secret key.\n *\n * ```\n * sign(m, d) where\n * k = rfc6979_hmac_drbg(m, d)\n * (x, y) = G × k\n * r = x mod n\n * s = (m + dr) / k mod n\n * ```\n */\n function sign(message: Hex, secretKey: PrivKey, opts: ECDSASignOpts = {}): RecoveredSignature {\n message = ensureBytes('message', message);\n const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2.\n const drbg = createHmacDrbg<RecoveredSignature>(hash.outputLen, Fn.BYTES, hmac);\n const sig = drbg(seed, k2sig); // Steps B, C, D, E, F, G\n return sig;\n }\n\n function tryParsingSig(sg: Hex | SignatureLike) {\n // Try to deduce format\n let sig: Signature | undefined = undefined;\n const isHex = typeof sg === 'string' || isBytes(sg);\n const isObj =\n !isHex &&\n sg !== null &&\n typeof sg === 'object' &&\n typeof sg.r === 'bigint' &&\n typeof sg.s === 'bigint';\n if (!isHex && !isObj)\n throw new Error('invalid signature, expected Uint8Array, hex string or Signature instance');\n if (isObj) {\n sig = new Signature(sg.r, sg.s);\n } else if (isHex) {\n try {\n sig = Signature.fromBytes(ensureBytes('sig', sg), 'der');\n } catch (derError) {\n if (!(derError instanceof DER.Err)) throw derError;\n }\n if (!sig) {\n try {\n sig = Signature.fromBytes(ensureBytes('sig', sg), 'compact');\n } catch (error) {\n return false;\n }\n }\n }\n if (!sig) return false;\n return sig;\n }\n\n /**\n * Verifies a signature against message and public key.\n * Rejects lowS signatures by default: see {@link ECDSAVerifyOpts}.\n * Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf:\n *\n * ```\n * verify(r, s, h, P) where\n * u1 = hs^-1 mod n\n * u2 = rs^-1 mod n\n * R = u1⋅G + u2⋅P\n * mod(R.x, n) == r\n * ```\n */\n function verify(\n signature: Hex | SignatureLike,\n message: Hex,\n publicKey: Hex,\n opts: ECDSAVerifyOpts = {}\n ): boolean {\n const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);\n publicKey = ensureBytes('publicKey', publicKey);\n message = validateMsgAndHash(ensureBytes('message', message), prehash);\n if ('strict' in opts) throw new Error('options.strict was renamed to lowS');\n const sig =\n format === undefined\n ? tryParsingSig(signature)\n : Signature.fromBytes(ensureBytes('sig', signature as Hex), format);\n if (sig === false) return false;\n try {\n const P = Point.fromBytes(publicKey);\n if (lowS && sig.hasHighS()) return false;\n const { r, s } = sig;\n const h = bits2int_modN(message); // mod n, not mod p\n const is = Fn.inv(s); // s^-1 mod n\n const u1 = Fn.create(h * is); // u1 = hs^-1 mod n\n const u2 = Fn.create(r * is); // u2 = rs^-1 mod n\n const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2)); // u1⋅G + u2⋅P\n if (R.is0()) return false;\n const v = Fn.create(R.x); // v = r.x mod n\n return v === r;\n } catch (e) {\n return false;\n }\n }\n\n function recoverPublicKey(\n signature: Uint8Array,\n message: Uint8Array,\n opts: ECDSARecoverOpts = {}\n ): Uint8Array {\n const { prehash } = validateSigOpts(opts, defaultSigOpts);\n message = validateMsgAndHash(message, prehash);\n return Signature.fromBytes(signature, 'recovered').recoverPublicKey(message).toBytes();\n }\n\n return Object.freeze({\n keygen,\n getPublicKey,\n getSharedSecret,\n utils,\n lengths,\n Point,\n sign,\n verify,\n recoverPublicKey,\n Signature,\n hash,\n });\n}\n\n// TODO: remove everything below\n/** @deprecated use ECDSASignature */\nexport type SignatureType = ECDSASignature;\n/** @deprecated use ECDSASigRecovered */\nexport type RecoveredSignatureType = ECDSASigRecovered;\n/** @deprecated switch to Uint8Array signatures in format 'compact' */\nexport type SignatureLike = { r: bigint; s: bigint };\nexport type ECDSAExtraEntropy = Hex | boolean;\n/** @deprecated use `ECDSAExtraEntropy` */\nexport type Entropy = Hex | boolean;\nexport type BasicWCurve<T> = BasicCurve<T> & {\n // Params: a, b\n a: T;\n b: T;\n\n // Optional params\n allowedPrivateKeyLengths?: readonly number[]; // for P521\n wrapPrivateKey?: boolean; // bls12-381 requires mod(n) instead of rejecting keys >= n\n endo?: EndomorphismOpts;\n // When a cofactor != 1, there can be an effective methods to:\n // 1. Determine whether a point is torsion-free\n isTorsionFree?: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;\n // 2. Clear torsion component\n clearCofactor?: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;\n};\n/** @deprecated use ECDSASignOpts */\nexport type SignOpts = ECDSASignOpts;\n/** @deprecated use ECDSASignOpts */\nexport type VerOpts = ECDSAVerifyOpts;\n\n/** @deprecated use WeierstrassPoint */\nexport type ProjPointType<T> = WeierstrassPoint<T>;\n/** @deprecated use WeierstrassPointCons */\nexport type ProjConstructor<T> = WeierstrassPointCons<T>;\n/** @deprecated use ECDSASignatureCons */\nexport type SignatureConstructor = ECDSASignatureCons;\n\n// TODO: remove\nexport type CurvePointsType<T> = BasicWCurve<T> & {\n fromBytes?: (bytes: Uint8Array) => AffinePoint<T>;\n toBytes?: (\n c: WeierstrassPointCons<T>,\n point: WeierstrassPoint<T>,\n isCompressed: boolean\n ) => Uint8Array;\n};\n\n// LegacyWeierstrassOpts\nexport type CurvePointsTypeWithLength<T> = Readonly<CurvePointsType<T> & Partial<NLength>>;\n\n// LegacyWeierstrass\nexport type CurvePointsRes<T> = {\n Point: WeierstrassPointCons<T>;\n\n /** @deprecated use `Point.CURVE()` */\n CURVE: CurvePointsType<T>;\n /** @deprecated use `Point` */\n ProjectivePoint: WeierstrassPointCons<T>;\n /** @deprecated use `Point.Fn.fromBytes(privateKey)` */\n normPrivateKeyToScalar: (key: PrivKey) => bigint;\n /** @deprecated */\n weierstrassEquation: (x: T) => T;\n /** @deprecated use `Point.Fn.isValidNot0(num)` */\n isWithinCurveOrder: (num: bigint) => boolean;\n};\n\n// Aliases to legacy types\n// export type CurveType = LegacyECDSAOpts;\n// export type CurveFn = LegacyECDSA;\n// export type CurvePointsRes<T> = LegacyWeierstrass<T>;\n// export type CurvePointsType<T> = LegacyWeierstrassOpts<T>;\n// export type CurvePointsTypeWithLength<T> = LegacyWeierstrassOpts<T>;\n// export type BasicWCurve<T> = LegacyWeierstrassOpts<T>;\n\n/** @deprecated use `Uint8Array` */\nexport type PubKey = Hex | WeierstrassPoint<bigint>;\nexport type CurveType = BasicWCurve<bigint> & {\n hash: CHash; // CHash not FHash because we need outputLen for DRBG\n hmac?: HmacFnSync;\n randomBytes?: (bytesLength?: number) => Uint8Array;\n lowS?: boolean;\n bits2int?: (bytes: Uint8Array) => bigint;\n bits2int_modN?: (bytes: Uint8Array) => bigint;\n};\nexport type CurveFn = {\n /** @deprecated use `Point.CURVE()` */\n CURVE: CurvePointsType<bigint>;\n keygen: ECDSA['keygen'];\n getPublicKey: ECDSA['getPublicKey'];\n getSharedSecret: ECDSA['getSharedSecret'];\n sign: ECDSA['sign'];\n verify: ECDSA['verify'];\n Point: WeierstrassPointCons<bigint>;\n /** @deprecated use `Point` */\n ProjectivePoint: WeierstrassPointCons<bigint>;\n Signature: ECDSASignatureCons;\n utils: ECDSA['utils'];\n lengths: ECDSA['lengths'];\n};\n/** @deprecated use `weierstrass` in newer releases */\nexport function weierstrassPoints<T>(c: CurvePointsTypeWithLength<T>): CurvePointsRes<T> {\n const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);\n const Point = weierstrassN(CURVE, curveOpts);\n return _weierstrass_new_output_to_legacy(c, Point);\n}\nexport type WsPointComposed<T> = {\n CURVE: WeierstrassOpts<T>;\n curveOpts: WeierstrassExtraOpts<T>;\n};\nexport type WsComposed = {\n /** @deprecated use `Point.CURVE()` */\n CURVE: WeierstrassOpts<bigint>;\n hash: CHash;\n curveOpts: WeierstrassExtraOpts<bigint>;\n ecdsaOpts: ECDSAOpts;\n};\nfunction _weierstrass_legacy_opts_to_new<T>(c: CurvePointsType<T>): WsPointComposed<T> {\n const CURVE: WeierstrassOpts<T> = {\n a: c.a,\n b: c.b,\n p: c.Fp.ORDER,\n n: c.n,\n h: c.h,\n Gx: c.Gx,\n Gy: c.Gy,\n };\n const Fp = c.Fp;\n let allowedLengths = c.allowedPrivateKeyLengths\n ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2))))\n : undefined;\n const Fn = Field(CURVE.n, {\n BITS: c.nBitLength,\n allowedLengths: allowedLengths,\n modFromBytes: c.wrapPrivateKey,\n });\n const curveOpts: WeierstrassExtraOpts<T> = {\n Fp,\n Fn,\n allowInfinityPoint: c.allowInfinityPoint,\n endo: c.endo,\n isTorsionFree: c.isTorsionFree,\n clearCofactor: c.clearCofactor,\n fromBytes: c.fromBytes,\n toBytes: c.toBytes,\n };\n return { CURVE, curveOpts };\n}\nfunction _ecdsa_legacy_opts_to_new(c: CurveType): WsComposed {\n const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);\n const ecdsaOpts: ECDSAOpts = {\n hmac: c.hmac,\n randomBytes: c.randomBytes,\n lowS: c.lowS,\n bits2int: c.bits2int,\n bits2int_modN: c.bits2int_modN,\n };\n return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };\n}\nexport function _legacyHelperEquat<T>(Fp: IField<T>, a: T, b: T): (x: T) => T {\n /**\n * y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y².\n * @returns y²\n */\n function weierstrassEquation(x: T): T {\n const x2 = Fp.sqr(x); // x * x\n const x3 = Fp.mul(x2, x); // x² * x\n return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b\n }\n return weierstrassEquation;\n}\nfunction _weierstrass_new_output_to_legacy<T>(\n c: CurvePointsType<T>,\n Point: WeierstrassPointCons<T>\n): CurvePointsRes<T> {\n const { Fp, Fn } = Point;\n function isWithinCurveOrder(num: bigint): boolean {\n return inRange(num, _1n, Fn.ORDER);\n }\n const weierstrassEquation = _legacyHelperEquat(Fp, c.a, c.b);\n return Object.assign(\n {},\n {\n CURVE: c,\n Point: Point,\n ProjectivePoint: Point,\n normPrivateKeyToScalar: (key: PrivKey) => _normFnElement(Fn, key),\n weierstrassEquation,\n isWithinCurveOrder,\n }\n );\n}\nfunction _ecdsa_new_output_to_legacy(c: CurveType, _ecdsa: ECDSA): CurveFn {\n const Point = _ecdsa.Point;\n return Object.assign({}, _ecdsa, {\n ProjectivePoint: Point,\n CURVE: Object.assign({}, c, nLength(Point.Fn.ORDER, Point.Fn.BITS)),\n });\n}\n\n// _ecdsa_legacy\nexport function weierstrass(c: CurveType): CurveFn {\n const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);\n const Point = weierstrassN(CURVE, curveOpts);\n const signs = ecdsa(Point, hash, ecdsaOpts);\n return _ecdsa_new_output_to_legacy(c, signs);\n}\n","/**\n * Utilities for short weierstrass curves, combined with noble-hashes.\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { type CurveFn, type CurveType, weierstrass } from './abstract/weierstrass.ts';\nimport type { CHash } from './utils.ts';\n\n/** connects noble-curves to noble-hashes */\nexport function getHash(hash: CHash): { hash: CHash } {\n return { hash };\n}\n/** Same API as @noble/hashes, with ability to create curve with custom hash */\nexport type CurveDef = Readonly<Omit<CurveType, 'hash'>>;\nexport type CurveFnWithCreate = CurveFn & { create: (hash: CHash) => CurveFn };\n\n/** @deprecated use new `weierstrass()` and `ecdsa()` methods */\nexport function createCurve(curveDef: CurveDef, defHash: CHash): CurveFnWithCreate {\n const create = (hash: CHash): CurveFn => weierstrass({ ...curveDef, hash: hash });\n return { ...create(defHash), create };\n}\n","/**\n * SECG secp256k1. See [pdf](https://www.secg.org/sec2-v2.pdf).\n *\n * Belongs to Koblitz curves: it has efficiently-computable GLV endomorphism ψ,\n * check out {@link EndomorphismOpts}. Seems to be rigid (not backdoored).\n * @module\n */\n/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { randomBytes } from '@noble/hashes/utils.js';\nimport { createCurve, type CurveFnWithCreate } from './_shortw_utils.ts';\nimport type { CurveLengths } from './abstract/curve.ts';\nimport {\n createHasher,\n type H2CHasher,\n type H2CMethod,\n isogenyMap,\n} from './abstract/hash-to-curve.ts';\nimport { Field, mapHashToField, mod, pow2 } from './abstract/modular.ts';\nimport {\n _normFnElement,\n type EndomorphismOpts,\n mapToCurveSimpleSWU,\n type WeierstrassPoint as PointType,\n type WeierstrassOpts,\n type WeierstrassPointCons,\n} from './abstract/weierstrass.ts';\nimport type { Hex, PrivKey } from './utils.ts';\nimport {\n bytesToNumberBE,\n concatBytes,\n ensureBytes,\n inRange,\n numberToBytesBE,\n utf8ToBytes,\n} from './utils.ts';\n\n// Seems like generator was produced from some seed:\n// `Point.BASE.multiply(Point.Fn.inv(2n, N)).toAffine().x`\n// // gives short x 0x3b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63n\nconst secp256k1_CURVE: WeierstrassOpts<bigint> = {\n p: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),\n n: BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'),\n h: BigInt(1),\n a: BigInt(0),\n b: BigInt(7),\n Gx: BigInt('0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),\n Gy: BigInt('0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'),\n};\n\nconst secp256k1_ENDO: EndomorphismOpts = {\n beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),\n basises: [\n [BigInt('0x3086d221a7d46bcde86c90e49284eb15'), -BigInt('0xe4437ed6010e88286f547fa90abfe4c3')],\n [BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8'), BigInt('0x3086d221a7d46bcde86c90e49284eb15')],\n ],\n};\n\nconst _0n = /* @__PURE__ */ BigInt(0);\nconst _1n = /* @__PURE__ */ BigInt(1);\nconst _2n = /* @__PURE__ */ BigInt(2);\n\n/**\n * √n = n^((p+1)/4) for fields p = 3 mod 4. We unwrap the loop and multiply bit-by-bit.\n * (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]\n */\nfunction sqrtMod(y: bigint): bigint {\n const P = secp256k1_CURVE.p;\n // prettier-ignore\n const _3n = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);\n // prettier-ignore\n const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);\n const b2 = (y * y * y) % P; // x^3, 11\n const b3 = (b2 * b2 * y) % P; // x^7\n const b6 = (pow2(b3, _3n, P) * b3) % P;\n const b9 = (pow2(b6, _3n, P) * b3) % P;\n const b11 = (pow2(b9, _2n, P) * b2) % P;\n const b22 = (pow2(b11, _11n, P) * b11) % P;\n const b44 = (pow2(b22, _22n, P) * b22) % P;\n const b88 = (pow2(b44, _44n, P) * b44) % P;\n const b176 = (pow2(b88, _88n, P) * b88) % P;\n const b220 = (pow2(b176, _44n, P) * b44) % P;\n const b223 = (pow2(b220, _3n, P) * b3) % P;\n const t1 = (pow2(b223, _23n, P) * b22) % P;\n const t2 = (pow2(t1, _6n, P) * b2) % P;\n const root = pow2(t2, _2n, P);\n if (!Fpk1.eql(Fpk1.sqr(root), y)) throw new Error('Cannot find square root');\n return root;\n}\n\nconst Fpk1 = Field(secp256k1_CURVE.p, { sqrt: sqrtMod });\n\n/**\n * secp256k1 curve, ECDSA and ECDH methods.\n *\n * Field: `2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n`\n *\n * @example\n * ```js\n * import { secp256k1 } from '@noble/curves/secp256k1';\n * const { secretKey, publicKey } = secp256k1.keygen();\n * const msg = new TextEncoder().encode('hello');\n * const sig = secp256k1.sign(msg, secretKey);\n * const isValid = secp256k1.verify(sig, msg, publicKey) === true;\n * ```\n */\nexport const secp256k1: CurveFnWithCreate = createCurve(\n { ...secp256k1_CURVE, Fp: Fpk1, lowS: true, endo: secp256k1_ENDO },\n sha256\n);\n\n// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.\n// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki\n/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */\nconst TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};\nfunction taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {\n let tagP = TAGGED_HASH_PREFIXES[tag];\n if (tagP === undefined) {\n const tagH = sha256(utf8ToBytes(tag));\n tagP = concatBytes(tagH, tagH);\n TAGGED_HASH_PREFIXES[tag] = tagP;\n }\n return sha256(concatBytes(tagP, ...messages));\n}\n\n// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03\nconst pointToBytes = (point: PointType<bigint>) => point.toBytes(true).slice(1);\nconst Pointk1 = /* @__PURE__ */ (() => secp256k1.Point)();\nconst hasEven = (y: bigint) => y % _2n === _0n;\n\n// Calculate point, scalar and bytes\nfunction schnorrGetExtPubKey(priv: PrivKey) {\n const { Fn, BASE } = Pointk1;\n const d_ = _normFnElement(Fn, priv);\n const p = BASE.multiply(d_); // P = d'⋅G; 0 < d' < n check is done inside\n const scalar = hasEven(p.y) ? d_ : Fn.neg(d_);\n return { scalar, bytes: pointToBytes(p) };\n}\n/**\n * lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.\n * @returns valid point checked for being on-curve\n */\nfunction lift_x(x: bigint): PointType<bigint> {\n const Fp = Fpk1;\n if (!Fp.isValidNot0(x)) throw new Error('invalid x: Fail if x ≥ p');\n const xx = Fp.create(x * x);\n const c = Fp.create(xx * x + BigInt(7)); // Let c = x³ + 7 mod p.\n let y = Fp.sqrt(c); // Let y = c^(p+1)/4 mod p. Same as sqrt().\n // Return the unique point P such that x(P) = x and\n // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.\n if (!hasEven(y)) y = Fp.neg(y);\n const p = Pointk1.fromAffine({ x, y });\n p.assertValidity();\n return p;\n}\nconst num = bytesToNumberBE;\n/**\n * Create tagged hash, convert it to bigint, reduce modulo-n.\n */\nfunction challenge(...args: Uint8Array[]): bigint {\n return Pointk1.Fn.create(num(taggedHash('BIP0340/challenge', ...args)));\n}\n\n/**\n * Schnorr public key is just `x` coordinate of Point as per BIP340.\n */\nfunction schnorrGetPublicKey(secretKey: Hex): Uint8Array {\n return schnorrGetExtPubKey(secretKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)\n}\n\n/**\n * Creates Schnorr signature as per BIP340. Verifies itself before returning anything.\n * auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.\n */\nfunction schnorrSign(message: Hex, secretKey: PrivKey, auxRand: Hex = randomBytes(32)): Uint8Array {\n const { Fn } = Pointk1;\n const m = ensureBytes('message', message);\n const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey); // checks for isWithinCurveOrder\n const a = ensureBytes('auxRand', auxRand, 32); // Auxiliary random data a: a 32-byte array\n const t = Fn.toBytes(d ^ num(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)\n const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)\n // Let k' = int(rand) mod n. Fail if k' = 0. Let R = k'⋅G\n const { bytes: rx, scalar: k } = schnorrGetExtPubKey(rand);\n const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.\n const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).\n sig.set(rx, 0);\n sig.set(Fn.toBytes(Fn.create(k + e * d)), 32);\n // If Verify(bytes(P), m, sig) (see below) returns failure, abort\n if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');\n return sig;\n}\n\n/**\n * Verifies Schnorr signature.\n * Will swallow errors & return false except for initial type validation of arguments.\n */\nfunction schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {\n const { Fn, BASE } = Pointk1;\n const sig = ensureBytes('signature', signature, 64);\n const m = ensureBytes('message', message);\n const pub = ensureBytes('publicKey', publicKey, 32);\n try {\n const P = lift_x(num(pub)); // P = lift_x(int(pk)); fail if that fails\n const r = num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.\n if (!inRange(r, _1n, secp256k1_CURVE.p)) return false;\n const s = num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.\n if (!inRange(s, _1n, secp256k1_CURVE.n)) return false;\n // int(challenge(bytes(r)||bytes(P)||m))%n\n const e = challenge(Fn.toBytes(r), pointToBytes(P), m);\n // R = s⋅G - e⋅P, where -eP == (n-e)P\n const R = BASE.multiplyUnsafe(s).add(P.multiplyUnsafe(Fn.neg(e)));\n const { x, y } = R.toAffine();\n // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.\n if (R.is0() || !hasEven(y) || x !== r) return false;\n return true;\n } catch (error) {\n return false;\n }\n}\n\nexport type SecpSchnorr = {\n keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };\n getPublicKey: typeof schnorrGetPublicKey;\n sign: typeof schnorrSign;\n verify: typeof schnorrVerify;\n Point: WeierstrassPointCons<bigint>;\n utils: {\n randomSecretKey: (seed?: Uint8Array) => Uint8Array;\n pointToBytes: (point: PointType<bigint>) => Uint8Array;\n lift_x: typeof lift_x;\n taggedHash: typeof taggedHash;\n\n /** @deprecated use `randomSecretKey` */\n randomPrivateKey: (seed?: Uint8Array) => Uint8Array;\n /** @deprecated use `utils` */\n numberToBytesBE: typeof numberToBytesBE;\n /** @deprecated use `utils` */\n bytesToNumberBE: typeof bytesToNumberBE;\n /** @deprecated use `modular` */\n mod: typeof mod;\n };\n lengths: CurveLengths;\n};\n/**\n * Schnorr signatures over secp256k1.\n * https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki\n * @example\n * ```js\n * import { schnorr } from '@noble/curves/secp256k1';\n * const { secretKey, publicKey } = schnorr.keygen();\n * // const publicKey = schnorr.getPublicKey(secretKey);\n * const msg = new TextEncoder().encode('hello');\n * const sig = schnorr.sign(msg, secretKey);\n * const isValid = schnorr.verify(sig, msg, publicKey);\n * ```\n */\nexport const schnorr: SecpSchnorr = /* @__PURE__ */ (() => {\n const size = 32;\n const seedLength = 48;\n const randomSecretKey = (seed = randomBytes(seedLength)): Uint8Array => {\n return mapHashToField(seed, secp256k1_CURVE.n);\n };\n // TODO: remove\n secp256k1.utils.randomSecretKey;\n function keygen(seed?: Uint8Array) {\n const secretKey = randomSecretKey(seed);\n return { secretKey, publicKey: schnorrGetPublicKey(secretKey) };\n }\n return {\n keygen,\n getPublicKey: schnorrGetPublicKey,\n sign: schnorrSign,\n verify: schnorrVerify,\n Point: Pointk1,\n utils: {\n randomSecretKey: randomSecretKey,\n randomPrivateKey: randomSecretKey,\n taggedHash,\n\n // TODO: remove\n lift_x,\n pointToBytes,\n numberToBytesBE,\n bytesToNumberBE,\n mod,\n },\n lengths: {\n secretKey: size,\n publicKey: size,\n publicKeyHasPrefix: false,\n signature: size * 2,\n seed: seedLength,\n },\n };\n})();\n\nconst isoMap = /* @__PURE__ */ (() =>\n isogenyMap(\n Fpk1,\n [\n // xNum\n [\n '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7',\n '0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581',\n '0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262',\n '0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c',\n ],\n // xDen\n [\n '0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b',\n '0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14',\n '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1\n ],\n // yNum\n [\n '0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c',\n '0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3',\n '0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931',\n '0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84',\n ],\n // yDen\n [\n '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b',\n '0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573',\n '0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f',\n '0x0000000000000000000000000000000000000000000000000000000000000001', // LAST 1\n ],\n ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]\n ))();\nconst mapSWU = /* @__PURE__ */ (() =>\n mapToCurveSimpleSWU(Fpk1, {\n A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),\n B: BigInt('1771'),\n Z: Fpk1.create(BigInt('-11')),\n }))();\n\n/** Hashing / encoding to secp256k1 points / field. RFC 9380 methods. */\nexport const secp256k1_hasher: H2CHasher<bigint> = /* @__PURE__ */ (() =>\n createHasher(\n secp256k1.Point,\n (scalars: bigint[]) => {\n const { x, y } = mapSWU(Fpk1.create(scalars[0]));\n return isoMap(x, y);\n },\n {\n DST: 'secp256k1_XMD:SHA-256_SSWU_RO_',\n encodeDST: 'secp256k1_XMD:SHA-256_SSWU_NU_',\n p: Fpk1.ORDER,\n m: 1,\n k: 128,\n expand: 'xmd',\n hash: sha256,\n }\n ))();\n\n/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */\nexport const hashToCurve: H2CMethod<bigint> = /* @__PURE__ */ (() =>\n secp256k1_hasher.hashToCurve)();\n\n/** @deprecated use `import { secp256k1_hasher } from '@noble/curves/secp256k1.js';` */\nexport const encodeToCurve: H2CMethod<bigint> = /* @__PURE__ */ (() =>\n secp256k1_hasher.encodeToCurve)();\n","/**\n\nSHA1 (RFC 3174), MD5 (RFC 1321) and RIPEMD160 (RFC 2286) legacy, weak hash functions.\nDon't use them in a new protocol. What \"weak\" means:\n\n- Collisions can be made with 2^18 effort in MD5, 2^60 in SHA1, 2^80 in RIPEMD160.\n- No practical pre-image attacks (only theoretical, 2^123.4)\n- HMAC seems kinda ok: https://datatracker.ietf.org/doc/html/rfc6151\n * @module\n */\nimport { Chi, HashMD, Maj } from './_md.ts';\nimport { type CHash, clean, createHasher, rotl } from './utils.ts';\n\n/** Initial SHA1 state */\nconst SHA1_IV = /* @__PURE__ */ Uint32Array.from([\n 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0,\n]);\n\n// Reusable temporary buffer\nconst SHA1_W = /* @__PURE__ */ new Uint32Array(80);\n\n/** SHA1 legacy hash class. */\nexport class SHA1 extends HashMD<SHA1> {\n private A = SHA1_IV[0] | 0;\n private B = SHA1_IV[1] | 0;\n private C = SHA1_IV[2] | 0;\n private D = SHA1_IV[3] | 0;\n private E = SHA1_IV[4] | 0;\n\n constructor() {\n super(64, 20, 8, false);\n }\n protected get(): [number, number, number, number, number] {\n const { A, B, C, D, E } = this;\n return [A, B, C, D, E];\n }\n protected set(A: number, B: number, C: number, D: number, E: number): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n this.E = E | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) SHA1_W[i] = view.getUint32(offset, false);\n for (let i = 16; i < 80; i++)\n SHA1_W[i] = rotl(SHA1_W[i - 3] ^ SHA1_W[i - 8] ^ SHA1_W[i - 14] ^ SHA1_W[i - 16], 1);\n // Compression function main loop, 80 rounds\n let { A, B, C, D, E } = this;\n for (let i = 0; i < 80; i++) {\n let F, K;\n if (i < 20) {\n F = Chi(B, C, D);\n K = 0x5a827999;\n } else if (i < 40) {\n F = B ^ C ^ D;\n K = 0x6ed9eba1;\n } else if (i < 60) {\n F = Maj(B, C, D);\n K = 0x8f1bbcdc;\n } else {\n F = B ^ C ^ D;\n K = 0xca62c1d6;\n }\n const T = (rotl(A, 5) + F + E + K + SHA1_W[i]) | 0;\n E = D;\n D = C;\n C = rotl(B, 30);\n B = A;\n A = T;\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n E = (E + this.E) | 0;\n this.set(A, B, C, D, E);\n }\n protected roundClean(): void {\n clean(SHA1_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/** SHA1 (RFC 3174) legacy hash function. It was cryptographically broken. */\nexport const sha1: CHash = /* @__PURE__ */ createHasher(() => new SHA1());\n\n/** Per-round constants */\nconst p32 = /* @__PURE__ */ Math.pow(2, 32);\nconst K = /* @__PURE__ */ Array.from({ length: 64 }, (_, i) =>\n Math.floor(p32 * Math.abs(Math.sin(i + 1)))\n);\n\n/** md5 initial state: same as sha1, but 4 u32 instead of 5. */\nconst MD5_IV = /* @__PURE__ */ SHA1_IV.slice(0, 4);\n\n// Reusable temporary buffer\nconst MD5_W = /* @__PURE__ */ new Uint32Array(16);\n/** MD5 legacy hash class. */\nexport class MD5 extends HashMD<MD5> {\n private A = MD5_IV[0] | 0;\n private B = MD5_IV[1] | 0;\n private C = MD5_IV[2] | 0;\n private D = MD5_IV[3] | 0;\n\n constructor() {\n super(64, 16, 8, true);\n }\n protected get(): [number, number, number, number] {\n const { A, B, C, D } = this;\n return [A, B, C, D];\n }\n protected set(A: number, B: number, C: number, D: number): void {\n this.A = A | 0;\n this.B = B | 0;\n this.C = C | 0;\n this.D = D | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) MD5_W[i] = view.getUint32(offset, true);\n // Compression function main loop, 64 rounds\n let { A, B, C, D } = this;\n for (let i = 0; i < 64; i++) {\n let F, g, s;\n if (i < 16) {\n F = Chi(B, C, D);\n g = i;\n s = [7, 12, 17, 22];\n } else if (i < 32) {\n F = Chi(D, B, C);\n g = (5 * i + 1) % 16;\n s = [5, 9, 14, 20];\n } else if (i < 48) {\n F = B ^ C ^ D;\n g = (3 * i + 5) % 16;\n s = [4, 11, 16, 23];\n } else {\n F = C ^ (B | ~D);\n g = (7 * i) % 16;\n s = [6, 10, 15, 21];\n }\n F = F + A + K[i] + MD5_W[g];\n A = D;\n D = C;\n C = B;\n B = B + rotl(F, s[i % 4]);\n }\n // Add the compressed chunk to the current hash value\n A = (A + this.A) | 0;\n B = (B + this.B) | 0;\n C = (C + this.C) | 0;\n D = (D + this.D) | 0;\n this.set(A, B, C, D);\n }\n protected roundClean(): void {\n clean(MD5_W);\n }\n destroy(): void {\n this.set(0, 0, 0, 0);\n clean(this.buffer);\n }\n}\n\n/**\n * MD5 (RFC 1321) legacy hash function. It was cryptographically broken.\n * MD5 architecture is similar to SHA1, with some differences:\n * - Reduced output length: 16 bytes (128 bit) instead of 20\n * - 64 rounds, instead of 80\n * - Little-endian: could be faster, but will require more code\n * - Non-linear index selection: huge speed-up for unroll\n * - Per round constants: more memory accesses, additional speed-up for unroll\n */\nexport const md5: CHash = /* @__PURE__ */ createHasher(() => new MD5());\n\n// RIPEMD-160\n\nconst Rho160 = /* @__PURE__ */ Uint8Array.from([\n 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,\n]);\nconst Id160 = /* @__PURE__ */ (() => Uint8Array.from(new Array(16).fill(0).map((_, i) => i)))();\nconst Pi160 = /* @__PURE__ */ (() => Id160.map((i) => (9 * i + 5) % 16))();\nconst idxLR = /* @__PURE__ */ (() => {\n const L = [Id160];\n const R = [Pi160];\n const res = [L, R];\n for (let i = 0; i < 4; i++) for (let j of res) j.push(j[i].map((k) => Rho160[k]));\n return res;\n})();\nconst idxL = /* @__PURE__ */ (() => idxLR[0])();\nconst idxR = /* @__PURE__ */ (() => idxLR[1])();\n// const [idxL, idxR] = idxLR;\n\nconst shifts160 = /* @__PURE__ */ [\n [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8],\n [12, 13, 11, 15, 6, 9, 9, 7, 12, 15, 11, 13, 7, 8, 7, 7],\n [13, 15, 14, 11, 7, 7, 6, 8, 13, 14, 13, 12, 5, 5, 6, 9],\n [14, 11, 12, 14, 8, 6, 5, 5, 15, 12, 15, 14, 9, 9, 8, 6],\n [15, 12, 13, 13, 9, 5, 8, 6, 14, 11, 12, 11, 8, 6, 5, 5],\n].map((i) => Uint8Array.from(i));\nconst shiftsL160 = /* @__PURE__ */ idxL.map((idx, i) => idx.map((j) => shifts160[i][j]));\nconst shiftsR160 = /* @__PURE__ */ idxR.map((idx, i) => idx.map((j) => shifts160[i][j]));\nconst Kl160 = /* @__PURE__ */ Uint32Array.from([\n 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e,\n]);\nconst Kr160 = /* @__PURE__ */ Uint32Array.from([\n 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000,\n]);\n// It's called f() in spec.\nfunction ripemd_f(group: number, x: number, y: number, z: number): number {\n if (group === 0) return x ^ y ^ z;\n if (group === 1) return (x & y) | (~x & z);\n if (group === 2) return (x | ~y) ^ z;\n if (group === 3) return (x & z) | (y & ~z);\n return x ^ (y | ~z);\n}\n// Reusable temporary buffer\nconst BUF_160 = /* @__PURE__ */ new Uint32Array(16);\nexport class RIPEMD160 extends HashMD<RIPEMD160> {\n private h0 = 0x67452301 | 0;\n private h1 = 0xefcdab89 | 0;\n private h2 = 0x98badcfe | 0;\n private h3 = 0x10325476 | 0;\n private h4 = 0xc3d2e1f0 | 0;\n\n constructor() {\n super(64, 20, 8, true);\n }\n protected get(): [number, number, number, number, number] {\n const { h0, h1, h2, h3, h4 } = this;\n return [h0, h1, h2, h3, h4];\n }\n protected set(h0: number, h1: number, h2: number, h3: number, h4: number): void {\n this.h0 = h0 | 0;\n this.h1 = h1 | 0;\n this.h2 = h2 | 0;\n this.h3 = h3 | 0;\n this.h4 = h4 | 0;\n }\n protected process(view: DataView, offset: number): void {\n for (let i = 0; i < 16; i++, offset += 4) BUF_160[i] = view.getUint32(offset, true);\n // prettier-ignore\n let al = this.h0 | 0, ar = al,\n bl = this.h1 | 0, br = bl,\n cl = this.h2 | 0, cr = cl,\n dl = this.h3 | 0, dr = dl,\n el = this.h4 | 0, er = el;\n\n // Instead of iterating 0 to 80, we split it into 5 groups\n // And use the groups in constants, functions, etc. Much simpler\n for (let group = 0; group < 5; group++) {\n const rGroup = 4 - group;\n const hbl = Kl160[group], hbr = Kr160[group]; // prettier-ignore\n const rl = idxL[group], rr = idxR[group]; // prettier-ignore\n const sl = shiftsL160[group], sr = shiftsR160[group]; // prettier-ignore\n for (let i = 0; i < 16; i++) {\n const tl = (rotl(al + ripemd_f(group, bl, cl, dl) + BUF_160[rl[i]] + hbl, sl[i]) + el) | 0;\n al = el, el = dl, dl = rotl(cl, 10) | 0, cl = bl, bl = tl; // prettier-ignore\n }\n // 2 loops are 10% faster\n for (let i = 0; i < 16; i++) {\n const tr = (rotl(ar + ripemd_f(rGroup, br, cr, dr) + BUF_160[rr[i]] + hbr, sr[i]) + er) | 0;\n ar = er, er = dr, dr = rotl(cr, 10) | 0, cr = br, br = tr; // prettier-ignore\n }\n }\n // Add the compressed chunk to the current hash value\n this.set(\n (this.h1 + cl + dr) | 0,\n (this.h2 + dl + er) | 0,\n (this.h3 + el + ar) | 0,\n (this.h4 + al + br) | 0,\n (this.h0 + bl + cr) | 0\n );\n }\n protected roundClean(): void {\n clean(BUF_160);\n }\n destroy(): void {\n this.destroyed = true;\n clean(this.buffer);\n this.set(0, 0, 0, 0, 0);\n }\n}\n\n/**\n * RIPEMD-160 - a legacy hash function from 1990s.\n * * https://homes.esat.kuleuven.be/~bosselae/ripemd160.html\n * * https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf\n */\nexport const ripemd160: CHash = /* @__PURE__ */ createHasher(() => new RIPEMD160());\n","/**\n * @module BIP32 hierarchical deterministic (HD) wallets over secp256k1.\n * @example\n * ```js\n * import { HDKey } from \"@scure/bip32\";\n * const hdkey1 = HDKey.fromMasterSeed(seed);\n * const hdkey2 = HDKey.fromExtendedKey(base58key);\n * const hdkey3 = HDKey.fromJSON({ xpriv: string });\n *\n * // props\n * [hdkey1.depth, hdkey1.index, hdkey1.chainCode];\n * console.log(hdkey2.privateKey, hdkey2.publicKey);\n * console.log(hdkey3.derive(\"m/0/2147483647'/1\"));\n * const sig = hdkey3.sign(hash);\n * hdkey3.verify(hash, sig);\n * ```\n */\n/*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */\nimport { mod } from '@noble/curves/abstract/modular';\nimport { secp256k1 as secp } from '@noble/curves/secp256k1';\nimport { hmac } from '@noble/hashes/hmac';\nimport { ripemd160 } from '@noble/hashes/legacy';\nimport { sha256, sha512 } from '@noble/hashes/sha2';\nimport {\n abytes,\n bytesToHex,\n concatBytes,\n createView,\n hexToBytes,\n utf8ToBytes,\n} from '@noble/hashes/utils';\nimport { createBase58check } from '@scure/base';\n\nconst Point = secp.ProjectivePoint;\nconst base58check = createBase58check(sha256);\n\nfunction bytesToNumber(bytes: Uint8Array): bigint {\n abytes(bytes);\n const h = bytes.length === 0 ? '0' : bytesToHex(bytes);\n return BigInt('0x' + h);\n}\n\nfunction numberToBytes(num: bigint): Uint8Array {\n if (typeof num !== 'bigint') throw new Error('bigint expected');\n return hexToBytes(num.toString(16).padStart(64, '0'));\n}\n\nconst MASTER_SECRET = utf8ToBytes('Bitcoin seed');\n// Bitcoin hardcoded by default\nconst BITCOIN_VERSIONS: Versions = { private: 0x0488ade4, public: 0x0488b21e };\nexport const HARDENED_OFFSET: number = 0x80000000;\n\nexport interface Versions {\n private: number;\n public: number;\n}\n\nconst hash160 = (data: Uint8Array) => ripemd160(sha256(data));\nconst fromU32 = (data: Uint8Array) => createView(data).getUint32(0, false);\nconst toU32 = (n: number) => {\n if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {\n throw new Error('invalid number, should be from 0 to 2**32-1, got ' + n);\n }\n const buf = new Uint8Array(4);\n createView(buf).setUint32(0, n, false);\n return buf;\n};\n\ninterface HDKeyOpt {\n versions?: Versions;\n depth?: number;\n index?: number;\n parentFingerprint?: number;\n chainCode?: Uint8Array;\n publicKey?: Uint8Array;\n privateKey?: Uint8Array | bigint;\n}\n\nexport class HDKey {\n get fingerprint(): number {\n if (!this.pubHash) {\n throw new Error('No publicKey set!');\n }\n return fromU32(this.pubHash);\n }\n get identifier(): Uint8Array | undefined {\n return this.pubHash;\n }\n get pubKeyHash(): Uint8Array | undefined {\n return this.pubHash;\n }\n get privateKey(): Uint8Array | null {\n return this.privKeyBytes || null;\n }\n get publicKey(): Uint8Array | null {\n return this.pubKey || null;\n }\n get privateExtendedKey(): string {\n const priv = this.privateKey;\n if (!priv) {\n throw new Error('No private key');\n }\n return base58check.encode(\n this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv))\n );\n }\n get publicExtendedKey(): string {\n if (!this.pubKey) {\n throw new Error('No public key');\n }\n return base58check.encode(this.serialize(this.versions.public, this.pubKey));\n }\n\n public static fromMasterSeed(seed: Uint8Array, versions: Versions = BITCOIN_VERSIONS): HDKey {\n abytes(seed);\n if (8 * seed.length < 128 || 8 * seed.length > 512) {\n throw new Error(\n 'HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ' +\n seed.length\n );\n }\n const I = hmac(sha512, MASTER_SECRET, seed);\n return new HDKey({\n versions,\n chainCode: I.slice(32),\n privateKey: I.slice(0, 32),\n });\n }\n\n public static fromExtendedKey(base58key: string, versions: Versions = BITCOIN_VERSIONS): HDKey {\n // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)\n const keyBuffer: Uint8Array = base58check.decode(base58key);\n const keyView = createView(keyBuffer);\n const version = keyView.getUint32(0, false);\n const opt = {\n versions,\n depth: keyBuffer[4],\n parentFingerprint: keyView.getUint32(5, false),\n index: keyView.getUint32(9, false),\n chainCode: keyBuffer.slice(13, 45),\n };\n const key = keyBuffer.slice(45);\n const isPriv = key[0] === 0;\n if (version !== versions[isPriv ? 'private' : 'public']) {\n throw new Error('Version mismatch');\n }\n if (isPriv) {\n return new HDKey({ ...opt, privateKey: key.slice(1) });\n } else {\n return new HDKey({ ...opt, publicKey: key });\n }\n }\n\n public static fromJSON(json: { xpriv: string }): HDKey {\n return HDKey.fromExtendedKey(json.xpriv);\n }\n public readonly versions: Versions;\n public readonly depth: number = 0;\n public readonly index: number = 0;\n public readonly chainCode: Uint8Array | null = null;\n public readonly parentFingerprint: number = 0;\n private privKey?: bigint;\n private privKeyBytes?: Uint8Array;\n private pubKey?: Uint8Array;\n private pubHash: Uint8Array | undefined;\n\n constructor(opt: HDKeyOpt) {\n if (!opt || typeof opt !== 'object') {\n throw new Error('HDKey.constructor must not be called directly');\n }\n this.versions = opt.versions || BITCOIN_VERSIONS;\n this.depth = opt.depth || 0;\n this.chainCode = opt.chainCode || null;\n this.index = opt.index || 0;\n this.parentFingerprint = opt.parentFingerprint || 0;\n if (!this.depth) {\n if (this.parentFingerprint || this.index) {\n throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');\n }\n }\n if (opt.publicKey && opt.privateKey) {\n throw new Error('HDKey: publicKey and privateKey at same time.');\n }\n if (opt.privateKey) {\n if (!secp.utils.isValidPrivateKey(opt.privateKey)) {\n throw new Error('Invalid private key');\n }\n this.privKey =\n typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);\n this.privKeyBytes = numberToBytes(this.privKey);\n this.pubKey = secp.getPublicKey(opt.privateKey, true);\n } else if (opt.publicKey) {\n this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true); // force compressed point\n } else {\n throw new Error('HDKey: no public or private key provided');\n }\n this.pubHash = hash160(this.pubKey);\n }\n\n public derive(path: string): HDKey {\n if (!/^[mM]'?/.test(path)) {\n throw new Error('Path must start with \"m\" or \"M\"');\n }\n if (/^[mM]'?$/.test(path)) {\n return this;\n }\n const parts = path.replace(/^[mM]'?\\//, '').split('/');\n // tslint:disable-next-line\n let child: HDKey = this;\n for (const c of parts) {\n const m = /^(\\d+)('?)$/.exec(c);\n const m1 = m && m[1];\n if (!m || m.length !== 3 || typeof m1 !== 'string')\n throw new Error('invalid child index: ' + c);\n let idx = +m1;\n if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {\n throw new Error('Invalid index');\n }\n // hardened key\n if (m[2] === \"'\") {\n idx += HARDENED_OFFSET;\n }\n child = child.deriveChild(idx);\n }\n return child;\n }\n\n public deriveChild(index: number): HDKey {\n if (!this.pubKey || !this.chainCode) {\n throw new Error('No publicKey or chainCode set');\n }\n let data = toU32(index);\n if (index >= HARDENED_OFFSET) {\n // Hardened\n const priv = this.privateKey;\n if (!priv) {\n throw new Error('Could not derive hardened child key');\n }\n // Hardened child: 0x00 || ser256(kpar) || ser32(index)\n data = concatBytes(new Uint8Array([0]), priv, data);\n } else {\n // Normal child: serP(point(kpar)) || ser32(index)\n data = concatBytes(this.pubKey, data);\n }\n const I = hmac(sha512, this.chainCode, data);\n const childTweak = bytesToNumber(I.slice(0, 32));\n const chainCode = I.slice(32);\n if (!secp.utils.isValidPrivateKey(childTweak)) {\n throw new Error('Tweak bigger than curve order');\n }\n const opt: HDKeyOpt = {\n versions: this.versions,\n chainCode,\n depth: this.depth + 1,\n parentFingerprint: this.fingerprint,\n index,\n };\n try {\n // Private parent key -> private child key\n if (this.privateKey) {\n const added = mod(this.privKey! + childTweak, secp.CURVE.n);\n if (!secp.utils.isValidPrivateKey(added)) {\n throw new Error('The tweak was out of range or the resulted private key is invalid');\n }\n opt.privateKey = added;\n } else {\n const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));\n // Cryptographically impossible: hmac-sha512 preimage would need to be found\n if (added.equals(Point.ZERO)) {\n throw new Error('The tweak was equal to negative P, which made the result key invalid');\n }\n opt.publicKey = added.toRawBytes(true);\n }\n return new HDKey(opt);\n } catch (err) {\n return this.deriveChild(index + 1);\n }\n }\n\n public sign(hash: Uint8Array): Uint8Array {\n if (!this.privateKey) {\n throw new Error('No privateKey set!');\n }\n abytes(hash, 32);\n return secp.sign(hash, this.privKey!).toCompactRawBytes();\n }\n\n public verify(hash: Uint8Array, signature: Uint8Array): boolean {\n abytes(hash, 32);\n abytes(signature, 64);\n if (!this.publicKey) {\n throw new Error('No publicKey set!');\n }\n let sig;\n try {\n sig = secp.Signature.fromCompact(signature);\n } catch (error) {\n return false;\n }\n return secp.verify(sig, hash, this.publicKey);\n }\n\n public wipePrivateData(): this {\n this.privKey = undefined;\n if (this.privKeyBytes) {\n this.privKeyBytes.fill(0);\n this.privKeyBytes = undefined;\n }\n return this;\n }\n public toJSON(): { xpriv: string; xpub: string } {\n return {\n xpriv: this.privateExtendedKey,\n xpub: this.publicExtendedKey,\n };\n }\n\n private serialize(version: number, key: Uint8Array) {\n if (!this.chainCode) {\n throw new Error('No chainCode set');\n }\n abytes(key, 33);\n // version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)\n return concatBytes(\n toU32(version),\n new Uint8Array([this.depth]),\n toU32(this.parentFingerprint),\n toU32(this.index),\n this.chainCode,\n key\n );\n }\n}\n","/**\n * Daemon configuration: resolved from a JSON config file and/or environment\n * variables, then expanded into a `ToonClientConfig` (BTP + channels + signer)\n * plus daemon-only settings (HTTP port, relay URL, apex negotiation).\n *\n * The mnemonic is sourced from (in precedence order):\n * 1. `TOON_CLIENT_MNEMONIC` env var,\n * 2. an encrypted keystore (#207) at `keystorePath`, decrypted with\n * `TOON_CLIENT_KEYSTORE_PASSWORD`,\n * 3. the `mnemonic` field of the config file (discouraged — plaintext on disk).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { loadKeystore } from '@toon-protocol/client';\nimport { encodeEventToToon, decodeEventFromToon } from '@toon-protocol/core';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport type { SettlementChain } from '../control-api.js';\n\n/** Apex/town settlement parameters injected as a peer negotiation. */\nexport interface ApexNegotiationConfig {\n /** ILP destination address, e.g. `g.townhouse.town`. */\n destination: string;\n /** Peer id key used in the negotiation map (last ILP segment, e.g. `town`). */\n peerId: string;\n /** Settlement chain family. */\n chain: SettlementChain;\n /** Negotiated chain key, e.g. `evm:base:84532`. */\n chainKey: string;\n /** Numeric chain id (EVM only; 0 for solana/mina). */\n chainId: number;\n /** The apex's settlement (receive) address on `chain`. */\n settlementAddress: string;\n /** Token contract / mint / zkApp address. */\n tokenAddress?: string;\n /** EVM TokenNetwork / Solana programId / Mina zkApp address. */\n tokenNetwork?: string;\n}\n\nexport interface DaemonConfigFile {\n /** Named network tier (drives settlement presets, #209). */\n network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';\n mnemonic?: string;\n mnemonicAccountIndex?: number;\n keystorePath?: string;\n /** BTP WebSocket URL of the apex/connector. */\n btpUrl?: string;\n /** Transport: `direct` or a `socks5h://` proxy for `.anyone` hosts. */\n socksProxy?: string;\n /** Auto-manage the anon proxy for `.anyone` BTP hosts. Default true for HS. */\n managedAnonProxy?: boolean;\n /** Loopback SOCKS port the managed anon proxy binds (also used for reads). Default 9050. */\n managedAnonSocksPort?: number;\n /** Town relay WS URL for FREE reads. */\n relayUrl?: string;\n /** Default ILP publish destination. Default `g.townhouse.town`. */\n destination?: string;\n /** Default fee per paid write, base units. Default `1`. */\n feePerEvent?: string;\n /** Channel nonce-watermark persistence file. Default `<dir>/channels.json`. */\n channelStorePath?: string;\n /** Localhost control-plane port. Default 8787. */\n httpPort?: number;\n /**\n * Active settlement chain for paid writes to the apex. A single daemon settles\n * to a given peer on ONE chain (the `ChannelManager` keys channels per peer +\n * each `ToonClient` owns one BTP session). Default `evm`. Override with\n * `TOON_CLIENT_CHAIN`. For simultaneous multi-chain, run one daemon per chain\n * (distinct `httpPort` + `channelStorePath`).\n */\n chain?: SettlementChain;\n /** Manual apex negotiation (HS / direct-apex mode where bootstrap finds 0 peers). */\n apex?: ApexNegotiationConfig;\n /**\n * Per-chain apex negotiations. The entry for the active `chain` is used; the\n * others are retained so switching chains needs only a `chain`/restart change.\n */\n apexChains?: Partial<Record<SettlementChain, ApexNegotiationConfig>>;\n /** Extra settlement overrides passed straight through to ToonClient. */\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n /** Solana on-chain payment-channel params (required when `chain` is solana). */\n solanaChannel?: ToonClientConfig['solanaChannel'];\n /** Mina on-chain payment-channel params (required when `chain` is mina). */\n minaChannel?: ToonClientConfig['minaChannel'];\n}\n\nexport interface ResolvedDaemonConfig {\n httpPort: number;\n relayUrl: string;\n socksProxy?: string;\n destination: string;\n feePerEvent: bigint;\n apex?: ApexNegotiationConfig;\n /** The active settlement chain for paid writes. */\n chain: SettlementChain;\n /** File mapping (destination, chain) → on-chain channelId for restart resume. */\n apexChannelStorePath: string;\n /** Fully-built config for the `ToonClient` constructor. */\n toonClientConfig: ToonClientConfig;\n network?: string;\n}\n\n/** Default config directory: `~/.toon-client`. Overridable via env. */\nexport function configDir(): string {\n return process.env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\n/** Default config file path. */\nexport function defaultConfigPath(): string {\n return join(configDir(), 'config.json');\n}\n\n/** Read + parse the JSON config file, returning `{}` when absent. */\nexport function readConfigFile(path: string): DaemonConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as DaemonConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `Failed to read daemon config at ${path}: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n}\n\n/** Resolve the mnemonic from env / keystore / config (in precedence order). */\nexport function resolveMnemonic(file: DaemonConfigFile): string {\n const envMnemonic = process.env['TOON_CLIENT_MNEMONIC'];\n if (envMnemonic) return envMnemonic.trim();\n\n if (file.keystorePath) {\n const password = process.env['TOON_CLIENT_KEYSTORE_PASSWORD'];\n if (!password) {\n throw new Error(\n 'keystorePath is set but TOON_CLIENT_KEYSTORE_PASSWORD is not provided'\n );\n }\n return loadKeystore(file.keystorePath, password);\n }\n\n if (file.mnemonic) return file.mnemonic.trim();\n\n throw new Error(\n 'No mnemonic configured. Set TOON_CLIENT_MNEMONIC, configure a keystorePath ' +\n '(+ TOON_CLIENT_KEYSTORE_PASSWORD), or add `mnemonic` to the config file.'\n );\n}\n\n/**\n * Build the full resolved daemon config (file overlaid with env, mnemonic\n * resolved, ToonClientConfig assembled). Env overrides supported:\n * TOON_CLIENT_BTP_URL, TOON_CLIENT_RELAY_URL, TOON_CLIENT_SOCKS,\n * TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK.\n */\nexport function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig {\n const mnemonic = resolveMnemonic(file);\n\n const btpUrl = process.env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl;\n if (!btpUrl) {\n throw new Error(\n 'No btpUrl configured. Set TOON_CLIENT_BTP_URL or add `btpUrl` to the config file.'\n );\n }\n const relayUrl =\n process.env['TOON_CLIENT_RELAY_URL'] ??\n file.relayUrl ??\n 'ws://localhost:7100';\n const explicitSocks = process.env['TOON_CLIENT_SOCKS'] ?? file.socksProxy;\n const httpPort = Number(\n process.env['TOON_CLIENT_HTTP_PORT'] ?? file.httpPort ?? 8787\n );\n const destination = file.destination ?? 'g.townhouse.town';\n const feePerEvent = BigInt(file.feePerEvent ?? '1');\n const network = (process.env['TOON_CLIENT_NETWORK'] ?? file.network) as\n | ToonClientConfig['network']\n | undefined;\n\n // Active settlement chain + the matching apex negotiation.\n const chain = (process.env['TOON_CLIENT_CHAIN'] ??\n file.chain ??\n 'evm') as SettlementChain;\n const apex = file.apexChains?.[chain] ?? file.apex;\n\n // Transport / proxy resolution. Three modes:\n // • explicit socksProxy → BTP uses it; reads route through it too.\n // • managed anon (auto for `.anyone` hosts with no explicit proxy, or forced\n // via managedAnonProxy:true) → the ToonClient spawns its own anon daemon on\n // a loopback SOCKS port; the relay subscription points at that SAME port so\n // free reads to a `.anyone` relay work with zero external setup.\n // • otherwise → direct, no proxy.\n const managedPort = Number(file.managedAnonSocksPort ?? 9050);\n const wantManaged =\n file.managedAnonProxy ?? (explicitSocks ? false : isAnyoneHost(btpUrl));\n\n let transport: ToonClientConfig['transport'];\n let managedAnonProxy: boolean;\n let managedAnonSocksPort: number | undefined;\n // The proxy the relay subscription uses for free reads (may differ from BTP).\n let readsSocksProxy: string | undefined;\n if (explicitSocks) {\n transport = { type: 'socks5', socksProxy: explicitSocks };\n managedAnonProxy = false;\n readsSocksProxy = explicitSocks;\n } else if (wantManaged) {\n transport = { type: 'direct' };\n managedAnonProxy = true;\n managedAnonSocksPort = managedPort;\n readsSocksProxy = `socks5h://127.0.0.1:${managedPort}`;\n } else {\n transport = { type: 'direct' };\n managedAnonProxy = false;\n }\n\n const channelStorePath =\n file.channelStorePath ?? join(configDir(), 'channels.json');\n const apexChannelStorePath = join(configDir(), 'apex-channels.json');\n\n const toonClientConfig: ToonClientConfig = {\n // Required by validateConfig but unused at runtime (BTP transport is used).\n connectorUrl: 'http://127.0.0.1:1',\n mnemonic,\n mnemonicAccountIndex: file.mnemonicAccountIndex ?? 0,\n ilpInfo: {\n pubkey: '00'.repeat(32),\n ilpAddress: 'g.toon.client',\n btpEndpoint: btpUrl,\n assetCode: 'USD',\n assetScale: 6,\n },\n toonEncoder: encodeEventToToon,\n toonDecoder: decodeEventFromToon,\n btpUrl,\n btpAuthToken: '',\n transport,\n managedAnonProxy,\n ...(managedAnonSocksPort !== undefined ? { managedAnonSocksPort } : {}),\n destinationAddress: destination,\n relayUrl: '', // reads use our own RelaySubscription, not bootstrap discovery\n knownPeers: [],\n channelStorePath,\n ...(network ? { network } : {}),\n ...(file.supportedChains ? { supportedChains: file.supportedChains } : {}),\n ...(file.settlementAddresses\n ? { settlementAddresses: file.settlementAddresses }\n : {}),\n ...(file.preferredTokens ? { preferredTokens: file.preferredTokens } : {}),\n ...(file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}),\n ...(file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {}),\n ...(file.solanaChannel ? { solanaChannel: file.solanaChannel } : {}),\n ...(file.minaChannel ? { minaChannel: file.minaChannel } : {}),\n };\n\n return {\n httpPort,\n relayUrl,\n ...(readsSocksProxy !== undefined ? { socksProxy: readsSocksProxy } : {}),\n destination,\n feePerEvent,\n apex,\n chain,\n apexChannelStorePath,\n toonClientConfig,\n network,\n };\n}\n\nfunction isAnyoneHost(url: string): boolean {\n try {\n return new URL(url).hostname.endsWith('.anyone');\n } catch {\n return false;\n }\n}\n","/**\n * Thin HTTP client for the `toon-clientd` localhost control plane. Used by the\n * MCP server (and any other caller) to drive the daemon without holding any\n * chain keys or long-lived connections itself.\n */\n\nimport type {\n ChannelsResponse,\n ErrorResponse,\n EventsQuery,\n EventsResponse,\n OpenChannelRequest,\n PublishRequest,\n PublishResponse,\n StatusResponse,\n SubscribeRequest,\n SubscribeResponse,\n SwapRequest,\n SwapResponse,\n} from './control-api.js';\n\n/** Error thrown when the daemon returns a non-2xx response. */\nexport class ControlApiError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly retryable: boolean,\n readonly detail?: string\n ) {\n super(message);\n this.name = 'ControlApiError';\n }\n}\n\n/** Thrown when the daemon is unreachable (not running / wrong port). */\nexport class DaemonUnreachableError extends Error {\n constructor(\n readonly baseUrl: string,\n readonly causedBy?: unknown\n ) {\n super(`toon-clientd not reachable at ${baseUrl}`);\n this.name = 'DaemonUnreachableError';\n }\n}\n\nexport interface ControlClientOptions {\n /** Base URL of the daemon, e.g. `http://127.0.0.1:8787`. */\n baseUrl: string;\n /** Per-request timeout, ms. Default 35000 (publishes can wait on FULFILL). */\n timeoutMs?: number;\n /** Inject a fetch implementation (tests). Defaults to global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport class ControlClient {\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ControlClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '');\n this.timeoutMs = opts.timeoutMs ?? 35_000;\n this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n }\n\n /** True if the daemon answers `GET /status` (used as a liveness probe). */\n async ping(): Promise<boolean> {\n try {\n await this.request<StatusResponse>('GET', '/status');\n return true;\n } catch (err) {\n if (err instanceof DaemonUnreachableError) return false;\n // A reachable daemon that errored still counts as \"up\".\n return err instanceof ControlApiError;\n }\n }\n\n status(): Promise<StatusResponse> {\n return this.request<StatusResponse>('GET', '/status');\n }\n\n publish(body: PublishRequest): Promise<PublishResponse> {\n return this.request<PublishResponse>('POST', '/publish', body);\n }\n\n subscribe(body: SubscribeRequest): Promise<SubscribeResponse> {\n return this.request<SubscribeResponse>('POST', '/subscribe', body);\n }\n\n events(query: EventsQuery = {}): Promise<EventsResponse> {\n const qs = new URLSearchParams();\n if (query.subId) qs.set('subId', query.subId);\n if (query.cursor !== undefined) qs.set('cursor', String(query.cursor));\n if (query.limit !== undefined) qs.set('limit', String(query.limit));\n const suffix = qs.toString() ? `?${qs.toString()}` : '';\n return this.request<EventsResponse>('GET', `/events${suffix}`);\n }\n\n openChannel(body: OpenChannelRequest = {}): Promise<{ channelId: string }> {\n return this.request<{ channelId: string }>('POST', '/channels', body);\n }\n\n channels(): Promise<ChannelsResponse> {\n return this.request<ChannelsResponse>('GET', '/channels');\n }\n\n swap(body: SwapRequest): Promise<SwapResponse> {\n return this.request<SwapResponse>('POST', '/swap', body);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers: body ? { 'content-type': 'application/json' } : undefined,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n throw new DaemonUnreachableError(this.baseUrl, err);\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n const json = text ? safeJson(text) : undefined;\n if (!res.ok) {\n const e = (json ?? {}) as ErrorResponse;\n throw new ControlApiError(\n e.error ?? `HTTP ${res.status}`,\n res.status,\n e.retryable ?? res.status === 503,\n e.detail\n );\n }\n return json as T;\n }\n}\n\nfunction safeJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n","/**\n * Daemon process lifecycle: a single-instance PID lock, a detached spawn helper\n * (used by the MCP server to auto-start the daemon), and a readiness probe.\n *\n * Why single-instance matters: two daemons would open two BTP sessions against\n * the same channel and race the nonce watermark — corrupting the payment proof.\n * The lock is a PID file; a stale file (process gone) is reclaimed.\n */\n\nimport { spawn } from 'node:child_process';\nimport {\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { configDir } from './config.js';\nimport { ControlClient } from '../control-client.js';\n\n/** Default PID file path. */\nexport function pidFilePath(): string {\n return join(configDir(), 'daemon.pid');\n}\n\n/** Whether a process with `pid` is currently alive. */\nexport function isProcessAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n // Signal 0 performs error checking without actually sending a signal.\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means the process exists but we can't signal it — still alive.\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Read the recorded daemon PID, or null when absent/invalid. */\nexport function readPid(path = pidFilePath()): number | null {\n try {\n const pid = parseInt(readFileSync(path, 'utf8').trim(), 10);\n return Number.isInteger(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Acquire the single-instance lock for the current process. Throws if another\n * live daemon already holds it. Reclaims a stale lock (dead PID).\n */\nexport function acquireLock(path = pidFilePath()): void {\n const existing = readPid(path);\n if (\n existing !== null &&\n existing !== process.pid &&\n isProcessAlive(existing)\n ) {\n throw new Error(\n `Another toon-clientd is already running (pid ${existing}). ` +\n `Stop it first or remove ${path} if it is stale.`\n );\n }\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, String(process.pid), { mode: 0o600 });\n}\n\n/** Release the lock if it belongs to this process. */\nexport function releaseLock(path = pidFilePath()): void {\n const pid = readPid(path);\n if (pid === process.pid) {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n }\n}\n\n/** Whether a daemon is currently running per the PID lock. */\nexport function isDaemonRunning(path = pidFilePath()): boolean {\n const pid = readPid(path);\n return pid !== null && isProcessAlive(pid);\n}\n\nexport interface SpawnDaemonOptions {\n /** Path to the `toon-clientd` bin (defaults to this package's daemon entry). */\n daemonEntry?: string;\n /** Extra env for the spawned process. */\n env?: NodeJS.ProcessEnv;\n /** Directory for the detached stdout/stderr log. */\n logDir?: string;\n}\n\n/**\n * Spawn the daemon as a detached, fully background process (survives the\n * parent — e.g. the ephemeral MCP/agent session — exiting). Returns the child\n * PID. The caller should poll {@link waitForReady} before issuing requests.\n */\nexport function spawnDaemonDetached(opts: SpawnDaemonOptions = {}): number {\n const entry = opts.daemonEntry ?? defaultDaemonEntry();\n const logDir = opts.logDir ?? configDir();\n mkdirSync(logDir, { recursive: true });\n // A single appended log file; the detached child writes here so the parent\n // can close its handles and exit.\n const logPath = join(logDir, 'daemon.log');\n // Use 'a' so restarts append rather than truncate the operator's log.\n const out = openSync(logPath, 'a');\n const child = spawn(process.execPath, [entry, 'run'], {\n detached: true,\n stdio: ['ignore', out, out],\n env: { ...process.env, ...opts.env },\n });\n child.unref();\n if (child.pid === undefined) {\n throw new Error('Failed to spawn toon-clientd (no pid)');\n }\n return child.pid;\n}\n\n/** Resolve the path to this package's built daemon entry (`dist/daemon.js`). */\nexport function defaultDaemonEntry(): string {\n // When running from the built bin, the daemon entry sits next to this module.\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, 'daemon.js');\n}\n\n/**\n * Poll the control plane until the daemon answers `GET /status`, up to\n * `timeoutMs`. Resolves true once reachable (NOT necessarily done\n * bootstrapping — anon can take 30–90s; callers surface `bootstrapping`).\n */\nexport async function waitForReady(\n baseUrl: string,\n timeoutMs = 15_000,\n intervalMs = 300\n): Promise<boolean> {\n const client = new ControlClient({ baseUrl, timeoutMs: intervalMs * 3 });\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await client.ping()) return true;\n await delay(intervalMs);\n }\n return false;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Whether the daemon entry file exists (used for friendlier error messages). */\nexport function daemonEntryExists(entry = defaultDaemonEntry()): boolean {\n return existsSync(entry);\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA+/DnB,MAAM,IAAI;;;ACx+Df,IAAM,MAAsB,uBAAO,CAAC;AACpC,IAAM,MAAsB,uBAAO,CAAC;AAgB9B,SAAU,QAAQ,OAAgB,QAAgB,IAAE;AACxD,MAAI,OAAO,UAAU,WAAW;AAC9B,UAAM,SAAS,SAAS,IAAI,KAAK;AACjC,UAAM,IAAI,MAAM,SAAS,gCAAgC,OAAO,KAAK;EACvE;AACA,SAAO;AACT;AAIM,SAAU,SAAS,OAAmB,QAAiB,QAAgB,IAAE;AAC7E,QAAM,QAAQ,QAAS,KAAK;AAC5B,QAAM,MAAM,OAAO;AACnB,QAAM,WAAW,WAAW;AAC5B,MAAI,CAAC,SAAU,YAAY,QAAQ,QAAS;AAC1C,UAAM,SAAS,SAAS,IAAI,KAAK;AACjC,UAAM,QAAQ,WAAW,cAAc,MAAM,KAAK;AAClD,UAAM,MAAM,QAAQ,UAAU,GAAG,KAAK,QAAQ,OAAO,KAAK;AAC1D,UAAM,IAAI,MAAM,SAAS,wBAAwB,QAAQ,WAAW,GAAG;EACzE;AACA,SAAO;AACT;AAGM,SAAU,oBAAoB,KAAoB;AACtD,QAAM,MAAM,IAAI,SAAS,EAAE;AAC3B,SAAO,IAAI,SAAS,IAAI,MAAM,MAAM;AACtC;AAEM,SAAU,YAAY,KAAW;AACrC,MAAI,OAAO,QAAQ;AAAU,UAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG;AACrF,SAAO,QAAQ,KAAK,MAAM,OAAO,OAAO,GAAG;AAC7C;AAGM,SAAU,gBAAgB,OAAiB;AAC/C,SAAO,YAAY,WAAY,KAAK,CAAC;AACvC;AACM,SAAU,gBAAgB,OAAiB;AAC/C,SAAQ,KAAK;AACb,SAAO,YAAY,WAAY,WAAW,KAAK,KAAK,EAAE,QAAO,CAAE,CAAC;AAClE;AAEM,SAAU,gBAAgB,GAAoB,KAAW;AAC7D,SAAOC,YAAY,EAAE,SAAS,EAAE,EAAE,SAAS,MAAM,GAAG,GAAG,CAAC;AAC1D;AACM,SAAU,gBAAgB,GAAoB,KAAW;AAC7D,SAAO,gBAAgB,GAAG,GAAG,EAAE,QAAO;AACxC;AAeM,SAAU,YAAY,OAAe,KAAU,gBAAuB;AAC1E,MAAI;AACJ,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI;AACF,YAAMC,YAAY,GAAG;IACvB,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,QAAQ,+CAA+C,CAAC;IAC1E;EACF,WAAW,QAAS,GAAG,GAAG;AAGxB,UAAM,WAAW,KAAK,GAAG;EAC3B,OAAO;AACL,UAAM,IAAI,MAAM,QAAQ,mCAAmC;EAC7D;AACA,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,mBAAmB,YAAY,QAAQ;AAChD,UAAM,IAAI,MAAM,QAAQ,gBAAgB,iBAAiB,oBAAoB,GAAG;AAClF,SAAO;AACT;AA6CA,IAAM,WAAW,CAAC,MAAc,OAAO,MAAM,YAAY,OAAO;AAE1D,SAAU,QAAQ,GAAW,KAAa,KAAW;AACzD,SAAO,SAAS,CAAC,KAAK,SAAS,GAAG,KAAK,SAAS,GAAG,KAAK,OAAO,KAAK,IAAI;AAC1E;AAOM,SAAU,SAAS,OAAe,GAAW,KAAa,KAAW;AAMzE,MAAI,CAAC,QAAQ,GAAG,KAAK,GAAG;AACtB,UAAM,IAAI,MAAM,oBAAoB,QAAQ,OAAO,MAAM,aAAa,MAAM,WAAW,CAAC;AAC5F;AASM,SAAU,OAAO,GAAS;AAC9B,MAAI;AACJ,OAAK,MAAM,GAAG,IAAI,KAAK,MAAM,KAAK,OAAO;AAAE;AAC3C,SAAO;AACT;AAsBO,IAAM,UAAU,CAAC,OAAuB,OAAO,OAAO,CAAC,KAAK;AAY7D,SAAU,eACd,SACA,UACA,QAAkE;AAElE,MAAI,OAAO,YAAY,YAAY,UAAU;AAAG,UAAM,IAAI,MAAM,0BAA0B;AAC1F,MAAI,OAAO,aAAa,YAAY,WAAW;AAAG,UAAM,IAAI,MAAM,2BAA2B;AAC7F,MAAI,OAAO,WAAW;AAAY,UAAM,IAAI,MAAM,2BAA2B;AAE7E,QAAM,MAAM,CAAC,QAAgB,IAAI,WAAW,GAAG;AAC/C,QAAM,OAAO,CAAC,SAAiB,WAAW,GAAG,IAAI;AACjD,MAAI,IAAI,IAAI,OAAO;AACnB,MAAI,IAAI,IAAI,OAAO;AACnB,MAAI,IAAI;AACR,QAAM,QAAQ,MAAK;AACjB,MAAE,KAAK,CAAC;AACR,MAAE,KAAK,CAAC;AACR,QAAI;EACN;AACA,QAAM,IAAI,IAAI,MAAoB,OAAO,GAAG,GAAG,GAAG,CAAC;AACnD,QAAM,SAAS,CAAC,OAAO,IAAI,CAAC,MAAK;AAE/B,QAAI,EAAE,KAAK,CAAI,GAAG,IAAI;AACtB,QAAI,EAAC;AACL,QAAI,KAAK,WAAW;AAAG;AACvB,QAAI,EAAE,KAAK,CAAI,GAAG,IAAI;AACtB,QAAI,EAAC;EACP;AACA,QAAM,MAAM,MAAK;AAEf,QAAI,OAAO;AAAM,YAAM,IAAI,MAAM,yBAAyB;AAC1D,QAAI,MAAM;AACV,UAAM,MAAoB,CAAA;AAC1B,WAAO,MAAM,UAAU;AACrB,UAAI,EAAC;AACL,YAAM,KAAK,EAAE,MAAK;AAClB,UAAI,KAAK,EAAE;AACX,aAAO,EAAE;IACX;AACA,WAAO,YAAa,GAAG,GAAG;EAC5B;AACA,QAAM,WAAW,CAAC,MAAkB,SAAoB;AACtD,UAAK;AACL,WAAO,IAAI;AACX,QAAI,MAAqB;AACzB,WAAO,EAAE,MAAM,KAAK,IAAG,CAAE;AAAI,aAAM;AACnC,UAAK;AACL,WAAO;EACT;AACA,SAAO;AACT;AAoDM,SAAU,gBACd,QACA,QACA,YAAoC,CAAA,GAAE;AAEtC,MAAI,CAAC,UAAU,OAAO,WAAW;AAAU,UAAM,IAAI,MAAM,+BAA+B;AAE1F,WAAS,WAAW,WAAiB,cAAsB,OAAc;AACvE,UAAM,MAAM,OAAO,SAAS;AAC5B,QAAI,SAAS,QAAQ;AAAW;AAChC,UAAM,UAAU,OAAO;AACvB,QAAI,YAAY,gBAAgB,QAAQ;AACtC,YAAM,IAAI,MAAM,UAAU,SAAS,0BAA0B,YAAY,SAAS,OAAO,EAAE;EAC/F;AACA,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,WAAW,GAAG,GAAG,KAAK,CAAC;AAClE,SAAO,QAAQ,SAAS,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC;AACtE;AAaM,SAAU,SACd,IAA6B;AAE7B,QAAM,MAAM,oBAAI,QAAO;AACvB,SAAO,CAAC,QAAW,SAAc;AAC/B,UAAM,MAAM,IAAI,IAAI,GAAG;AACvB,QAAI,QAAQ;AAAW,aAAO;AAC9B,UAAM,WAAW,GAAG,KAAK,GAAG,IAAI;AAChC,QAAI,IAAI,KAAK,QAAQ;AACrB,WAAO;EACT;AACF;;;ACpWA,IAAMC,OAAM,OAAO,CAAC;AAApB,IAAuBC,OAAM,OAAO,CAAC;AAArC,IAAwC,MAAsB,uBAAO,CAAC;AAAtE,IAAyE,MAAsB,uBAAO,CAAC;AAEvG,IAAM,MAAsB,uBAAO,CAAC;AAApC,IAAuC,MAAsB,uBAAO,CAAC;AAArE,IAAwE,MAAsB,uBAAO,CAAC;AAEtG,IAAM,MAAsB,uBAAO,CAAC;AAApC,IAAuC,MAAsB,uBAAO,CAAC;AAArE,IAAwE,OAAuB,uBAAO,EAAE;AAGlG,SAAU,IAAI,GAAW,GAAS;AACtC,QAAM,SAAS,IAAI;AACnB,SAAO,UAAUD,OAAM,SAAS,IAAI;AACtC;AAYM,SAAU,KAAK,GAAW,OAAe,QAAc;AAC3D,MAAI,MAAM;AACV,SAAO,UAAUE,MAAK;AACpB,WAAO;AACP,WAAO;EACT;AACA,SAAO;AACT;AAMM,SAAU,OAAO,QAAgB,QAAc;AACnD,MAAI,WAAWA;AAAK,UAAM,IAAI,MAAM,kCAAkC;AACtE,MAAI,UAAUA;AAAK,UAAM,IAAI,MAAM,4CAA4C,MAAM;AAErF,MAAI,IAAI,IAAI,QAAQ,MAAM;AAC1B,MAAI,IAAI;AAER,MAAI,IAAIA,MAAK,IAAIC,MAAK,IAAIA,MAAK,IAAID;AACnC,SAAO,MAAMA,MAAK;AAEhB,UAAM,IAAI,IAAI;AACd,UAAM,IAAI,IAAI;AACd,UAAM,IAAI,IAAI,IAAI;AAClB,UAAM,IAAI,IAAI,IAAI;AAElB,QAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;EACzC;AACA,QAAME,OAAM;AACZ,MAAIA,SAAQD;AAAK,UAAM,IAAI,MAAM,wBAAwB;AACzD,SAAO,IAAI,GAAG,MAAM;AACtB;AAEA,SAAS,eAAkB,IAAe,MAAS,GAAI;AACrD,MAAI,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,GAAG,CAAC;AAAG,UAAM,IAAI,MAAM,yBAAyB;AACzE;AAMA,SAAS,UAAa,IAAe,GAAI;AACvC,QAAM,UAAU,GAAG,QAAQA,QAAO;AAClC,QAAM,OAAO,GAAG,IAAI,GAAG,MAAM;AAC7B,iBAAe,IAAI,MAAM,CAAC;AAC1B,SAAO;AACT;AAEA,SAAS,UAAa,IAAe,GAAI;AACvC,QAAM,UAAU,GAAG,QAAQ,OAAO;AAClC,QAAM,KAAK,GAAG,IAAI,GAAG,GAAG;AACxB,QAAM,IAAI,GAAG,IAAI,IAAI,MAAM;AAC3B,QAAM,KAAK,GAAG,IAAI,GAAG,CAAC;AACtB,QAAM,IAAI,GAAG,IAAI,GAAG,IAAI,IAAI,GAAG,GAAG,CAAC;AACnC,QAAM,OAAO,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,CAAC;AACzC,iBAAe,IAAI,MAAM,CAAC;AAC1B,SAAO;AACT;AAIA,SAAS,WAAW,GAAS;AAC3B,QAAM,MAAM,MAAM,CAAC;AACnB,QAAM,KAAK,cAAc,CAAC;AAC1B,QAAM,KAAK,GAAG,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC;AACnC,QAAM,KAAK,GAAG,KAAK,EAAE;AACrB,QAAM,KAAK,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;AAC9B,QAAM,MAAM,IAAI,OAAO;AACvB,SAAO,CAAI,IAAe,MAAQ;AAChC,QAAI,MAAM,GAAG,IAAI,GAAG,EAAE;AACtB,QAAI,MAAM,GAAG,IAAI,KAAK,EAAE;AACxB,UAAM,MAAM,GAAG,IAAI,KAAK,EAAE;AAC1B,UAAM,MAAM,GAAG,IAAI,KAAK,EAAE;AAC1B,UAAM,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAChC,UAAM,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAChC,UAAM,GAAG,KAAK,KAAK,KAAK,EAAE;AAC1B,UAAM,GAAG,KAAK,KAAK,KAAK,EAAE;AAC1B,UAAM,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAChC,UAAM,OAAO,GAAG,KAAK,KAAK,KAAK,EAAE;AACjC,mBAAe,IAAI,MAAM,CAAC;AAC1B,WAAO;EACT;AACF;AASM,SAAU,cAAc,GAAS;AAGrC,MAAI,IAAI;AAAK,UAAM,IAAI,MAAM,qCAAqC;AAElE,MAAI,IAAI,IAAIA;AACZ,MAAI,IAAI;AACR,SAAO,IAAI,QAAQD,MAAK;AACtB,SAAK;AACL;EACF;AAGA,MAAI,IAAI;AACR,QAAM,MAAM,MAAM,CAAC;AACnB,SAAO,WAAW,KAAK,CAAC,MAAM,GAAG;AAG/B,QAAI,MAAM;AAAM,YAAM,IAAI,MAAM,+CAA+C;EACjF;AAEA,MAAI,MAAM;AAAG,WAAO;AAIpB,MAAI,KAAK,IAAI,IAAI,GAAG,CAAC;AACrB,QAAM,UAAU,IAAIC,QAAO;AAC3B,SAAO,SAAS,YAAe,IAAe,GAAI;AAChD,QAAI,GAAG,IAAI,CAAC;AAAG,aAAO;AAEtB,QAAI,WAAW,IAAI,CAAC,MAAM;AAAG,YAAM,IAAI,MAAM,yBAAyB;AAGtE,QAAI,IAAI;AACR,QAAI,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE;AACzB,QAAI,IAAI,GAAG,IAAI,GAAG,CAAC;AACnB,QAAI,IAAI,GAAG,IAAI,GAAG,MAAM;AAIxB,WAAO,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG;AACzB,UAAI,GAAG,IAAI,CAAC;AAAG,eAAO,GAAG;AACzB,UAAI,IAAI;AAGR,UAAI,QAAQ,GAAG,IAAI,CAAC;AACpB,aAAO,CAAC,GAAG,IAAI,OAAO,GAAG,GAAG,GAAG;AAC7B;AACA,gBAAQ,GAAG,IAAI,KAAK;AACpB,YAAI,MAAM;AAAG,gBAAM,IAAI,MAAM,yBAAyB;MACxD;AAGA,YAAM,WAAWA,QAAO,OAAO,IAAI,IAAI,CAAC;AACxC,YAAM,IAAI,GAAG,IAAI,GAAG,QAAQ;AAG5B,UAAI;AACJ,UAAI,GAAG,IAAI,CAAC;AACZ,UAAI,GAAG,IAAI,GAAG,CAAC;AACf,UAAI,GAAG,IAAI,GAAG,CAAC;IACjB;AACA,WAAO;EACT;AACF;AAaM,SAAU,OAAO,GAAS;AAE9B,MAAI,IAAI,QAAQ;AAAK,WAAO;AAE5B,MAAI,IAAI,QAAQ;AAAK,WAAO;AAE5B,MAAI,IAAI,SAAS;AAAK,WAAO,WAAW,CAAC;AAEzC,SAAO,cAAc,CAAC;AACxB;AAmDA,IAAM,eAAe;EACnB;EAAU;EAAW;EAAO;EAAO;EAAO;EAAQ;EAClD;EAAO;EAAO;EAAO;EAAO;EAAO;EACnC;EAAQ;EAAQ;EAAQ;;AAEpB,SAAU,cAAiB,OAAgB;AAC/C,QAAM,UAAU;IACd,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;;AAER,QAAM,OAAO,aAAa,OAAO,CAAC,KAAK,QAAe;AACpD,QAAI,GAAG,IAAI;AACX,WAAO;EACT,GAAG,OAAO;AACV,kBAAgB,OAAO,IAAI;AAI3B,SAAO;AACT;AAQM,SAAU,MAAS,IAAe,KAAQ,OAAa;AAC3D,MAAI,QAAQE;AAAK,UAAM,IAAI,MAAM,yCAAyC;AAC1E,MAAI,UAAUA;AAAK,WAAO,GAAG;AAC7B,MAAI,UAAUC;AAAK,WAAO;AAC1B,MAAI,IAAI,GAAG;AACX,MAAI,IAAI;AACR,SAAO,QAAQD,MAAK;AAClB,QAAI,QAAQC;AAAK,UAAI,GAAG,IAAI,GAAG,CAAC;AAChC,QAAI,GAAG,IAAI,CAAC;AACZ,cAAUA;EACZ;AACA,SAAO;AACT;AAOM,SAAU,cAAiB,IAAe,MAAW,WAAW,OAAK;AACzE,QAAM,WAAW,IAAI,MAAM,KAAK,MAAM,EAAE,KAAK,WAAW,GAAG,OAAO,MAAS;AAE3E,QAAM,gBAAgB,KAAK,OAAO,CAAC,KAAK,KAAK,MAAK;AAChD,QAAI,GAAG,IAAI,GAAG;AAAG,aAAO;AACxB,aAAS,CAAC,IAAI;AACd,WAAO,GAAG,IAAI,KAAK,GAAG;EACxB,GAAG,GAAG,GAAG;AAET,QAAM,cAAc,GAAG,IAAI,aAAa;AAExC,OAAK,YAAY,CAAC,KAAK,KAAK,MAAK;AAC/B,QAAI,GAAG,IAAI,GAAG;AAAG,aAAO;AACxB,aAAS,CAAC,IAAI,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC;AACrC,WAAO,GAAG,IAAI,KAAK,GAAG;EACxB,GAAG,WAAW;AACd,SAAO;AACT;AAgBM,SAAU,WAAc,IAAe,GAAI;AAG/C,QAAM,UAAU,GAAG,QAAQC,QAAO;AAClC,QAAM,UAAU,GAAG,IAAI,GAAG,MAAM;AAChC,QAAM,MAAM,GAAG,IAAI,SAAS,GAAG,GAAG;AAClC,QAAM,OAAO,GAAG,IAAI,SAAS,GAAG,IAAI;AACpC,QAAM,KAAK,GAAG,IAAI,SAAS,GAAG,IAAI,GAAG,GAAG,CAAC;AACzC,MAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;AAAI,UAAM,IAAI,MAAM,gCAAgC;AAC1E,SAAO,MAAM,IAAI,OAAO,IAAI;AAC9B;AAUM,SAAU,QAAQ,GAAW,YAAmB;AAEpD,MAAI,eAAe;AAAW,YAAQ,UAAU;AAChD,QAAM,cAAc,eAAe,SAAY,aAAa,EAAE,SAAS,CAAC,EAAE;AAC1E,QAAM,cAAc,KAAK,KAAK,cAAc,CAAC;AAC7C,SAAO,EAAE,YAAY,aAAa,YAAW;AAC/C;AA8BM,SAAU,MACd,OACA,cACA,OAAO,OACP,OAA0B,CAAA,GAAE;AAE5B,MAAI,SAASC;AAAK,UAAM,IAAI,MAAM,4CAA4C,KAAK;AACnF,MAAI,cAAkC;AACtC,MAAI,QAA4B;AAChC,MAAI,eAAwB;AAC5B,MAAI,iBAAgD;AACpD,MAAI,OAAO,iBAAiB,YAAY,gBAAgB,MAAM;AAC5D,QAAI,KAAK,QAAQ;AAAM,YAAM,IAAI,MAAM,sCAAsC;AAC7E,UAAM,QAAQ;AACd,QAAI,MAAM;AAAM,oBAAc,MAAM;AACpC,QAAI,MAAM;AAAM,cAAQ,MAAM;AAC9B,QAAI,OAAO,MAAM,SAAS;AAAW,aAAO,MAAM;AAClD,QAAI,OAAO,MAAM,iBAAiB;AAAW,qBAAe,MAAM;AAClE,qBAAiB,MAAM;EACzB,OAAO;AACL,QAAI,OAAO,iBAAiB;AAAU,oBAAc;AACpD,QAAI,KAAK;AAAM,cAAQ,KAAK;EAC9B;AACA,QAAM,EAAE,YAAY,MAAM,aAAa,MAAK,IAAK,QAAQ,OAAO,WAAW;AAC3E,MAAI,QAAQ;AAAM,UAAM,IAAI,MAAM,gDAAgD;AAClF,MAAI;AACJ,QAAM,IAAuB,OAAO,OAAO;IACzC;IACA;IACA;IACA;IACA,MAAM,QAAQ,IAAI;IAClB,MAAMA;IACN,KAAKC;IACL;IACA,QAAQ,CAAC,QAAQ,IAAI,KAAK,KAAK;IAC/B,SAAS,CAAC,QAAO;AACf,UAAI,OAAO,QAAQ;AACjB,cAAM,IAAI,MAAM,iDAAiD,OAAO,GAAG;AAC7E,aAAOD,QAAO,OAAO,MAAM;IAC7B;IACA,KAAK,CAAC,QAAQ,QAAQA;;IAEtB,aAAa,CAAC,QAAgB,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,QAAQ,GAAG;IAC1D,OAAO,CAAC,SAAS,MAAMC,UAASA;IAChC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,KAAK;IAC7B,KAAK,CAAC,KAAK,QAAQ,QAAQ;IAE3B,KAAK,CAAC,QAAQ,IAAI,MAAM,KAAK,KAAK;IAClC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK;IACvC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK;IACvC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK;IACvC,KAAK,CAAC,KAAK,UAAU,MAAM,GAAG,KAAK,KAAK;IACxC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,OAAO,KAAK,KAAK,GAAG,KAAK;;IAGtD,MAAM,CAAC,QAAQ,MAAM;IACrB,MAAM,CAAC,KAAK,QAAQ,MAAM;IAC1B,MAAM,CAAC,KAAK,QAAQ,MAAM;IAC1B,MAAM,CAAC,KAAK,QAAQ,MAAM;IAE1B,KAAK,CAAC,QAAQ,OAAO,KAAK,KAAK;IAC/B,MACE,UACC,CAAC,MAAK;AACL,UAAI,CAAC;AAAO,gBAAQ,OAAO,KAAK;AAChC,aAAO,MAAM,GAAG,CAAC;IACnB;IACF,SAAS,CAAC,QAAS,OAAO,gBAAgB,KAAK,KAAK,IAAI,gBAAgB,KAAK,KAAK;IAClF,WAAW,CAAC,OAAO,iBAAiB,SAAQ;AAC1C,UAAI,gBAAgB;AAClB,YAAI,CAAC,eAAe,SAAS,MAAM,MAAM,KAAK,MAAM,SAAS,OAAO;AAClE,gBAAM,IAAI,MACR,+BAA+B,iBAAiB,iBAAiB,MAAM,MAAM;QAEjF;AACA,cAAM,SAAS,IAAI,WAAW,KAAK;AAEnC,eAAO,IAAI,OAAO,OAAO,IAAI,OAAO,SAAS,MAAM,MAAM;AACzD,gBAAQ;MACV;AACA,UAAI,MAAM,WAAW;AACnB,cAAM,IAAI,MAAM,+BAA+B,QAAQ,iBAAiB,MAAM,MAAM;AACtF,UAAI,SAAS,OAAO,gBAAgB,KAAK,IAAI,gBAAgB,KAAK;AAClE,UAAI;AAAc,iBAAS,IAAI,QAAQ,KAAK;AAC5C,UAAI,CAAC;AACH,YAAI,CAAC,EAAE,QAAQ,MAAM;AAAG,gBAAM,IAAI,MAAM,kDAAkD;;AAG5F,aAAO;IACT;;IAEA,aAAa,CAAC,QAAQ,cAAc,GAAG,GAAG;;;IAG1C,MAAM,CAAC,GAAG,GAAG,MAAO,IAAI,IAAI;GAClB;AACZ,SAAO,OAAO,OAAO,CAAC;AACxB;AAwDM,SAAU,oBAAoB,YAAkB;AACpD,MAAI,OAAO,eAAe;AAAU,UAAM,IAAI,MAAM,4BAA4B;AAChF,QAAM,YAAY,WAAW,SAAS,CAAC,EAAE;AACzC,SAAO,KAAK,KAAK,YAAY,CAAC;AAChC;AASM,SAAU,iBAAiB,YAAkB;AACjD,QAAM,SAAS,oBAAoB,UAAU;AAC7C,SAAO,SAAS,KAAK,KAAK,SAAS,CAAC;AACtC;AAeM,SAAU,eAAe,KAAiB,YAAoB,OAAO,OAAK;AAC9E,QAAM,MAAM,IAAI;AAChB,QAAM,WAAW,oBAAoB,UAAU;AAC/C,QAAM,SAAS,iBAAiB,UAAU;AAE1C,MAAI,MAAM,MAAM,MAAM,UAAU,MAAM;AACpC,UAAM,IAAI,MAAM,cAAc,SAAS,+BAA+B,GAAG;AAC3E,QAAM,MAAM,OAAO,gBAAgB,GAAG,IAAI,gBAAgB,GAAG;AAE7D,QAAM,UAAU,IAAI,KAAK,aAAaC,IAAG,IAAIA;AAC7C,SAAO,OAAO,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB,SAAS,QAAQ;AACtF;;;ACnlBA,IAAMC,OAAM,OAAO,CAAC;AACpB,IAAMC,OAAM,OAAO,CAAC;AA0Id,SAAU,SAAwC,WAAoB,MAAO;AACjF,QAAM,MAAM,KAAK,OAAM;AACvB,SAAO,YAAY,MAAM;AAC3B;AAQM,SAAU,WACd,GACA,QAAW;AAEX,QAAM,aAAa,cACjB,EAAE,IACF,OAAO,IAAI,CAAC,MAAM,EAAE,CAAE,CAAC;AAEzB,SAAO,OAAO,IAAI,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,CAAC;AACrE;AAEA,SAAS,UAAU,GAAW,MAAY;AACxC,MAAI,CAAC,OAAO,cAAc,CAAC,KAAK,KAAK,KAAK,IAAI;AAC5C,UAAM,IAAI,MAAM,uCAAuC,OAAO,cAAc,CAAC;AACjF;AAWA,SAAS,UAAU,GAAW,YAAkB;AAC9C,YAAU,GAAG,UAAU;AACvB,QAAM,UAAU,KAAK,KAAK,aAAa,CAAC,IAAI;AAC5C,QAAM,aAAa,MAAM,IAAI;AAC7B,QAAM,YAAY,KAAK;AACvB,QAAM,OAAO,QAAQ,CAAC;AACtB,QAAM,UAAU,OAAO,CAAC;AACxB,SAAO,EAAE,SAAS,YAAY,MAAM,WAAW,QAAO;AACxD;AAEA,SAAS,YAAY,GAAWC,SAAgB,OAAY;AAC1D,QAAM,EAAE,YAAY,MAAM,WAAW,QAAO,IAAK;AACjD,MAAI,QAAQ,OAAO,IAAI,IAAI;AAC3B,MAAI,QAAQ,KAAK;AAQjB,MAAI,QAAQ,YAAY;AAEtB,aAAS;AACT,aAASD;EACX;AACA,QAAM,cAAcC,UAAS;AAC7B,QAAM,SAAS,cAAc,KAAK,IAAI,KAAK,IAAI;AAC/C,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,QAAQ;AACtB,QAAM,SAASA,UAAS,MAAM;AAC9B,QAAM,UAAU;AAChB,SAAO,EAAE,OAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAO;AACxD;AAEA,SAAS,kBAAkB,QAAe,GAAM;AAC9C,MAAI,CAAC,MAAM,QAAQ,MAAM;AAAG,UAAM,IAAI,MAAM,gBAAgB;AAC5D,SAAO,QAAQ,CAAC,GAAG,MAAK;AACtB,QAAI,EAAE,aAAa;AAAI,YAAM,IAAI,MAAM,4BAA4B,CAAC;EACtE,CAAC;AACH;AACA,SAAS,mBAAmB,SAAgB,OAAU;AACpD,MAAI,CAAC,MAAM,QAAQ,OAAO;AAAG,UAAM,IAAI,MAAM,2BAA2B;AACxE,UAAQ,QAAQ,CAAC,GAAG,MAAK;AACvB,QAAI,CAAC,MAAM,QAAQ,CAAC;AAAG,YAAM,IAAI,MAAM,6BAA6B,CAAC;EACvE,CAAC;AACH;AAKA,IAAM,mBAAmB,oBAAI,QAAO;AACpC,IAAM,mBAAmB,oBAAI,QAAO;AAEpC,SAAS,KAAK,GAAM;AAGlB,SAAO,iBAAiB,IAAI,CAAC,KAAK;AACpC;AAEA,SAAS,QAAQ,GAAS;AACxB,MAAI,MAAMF;AAAK,UAAM,IAAI,MAAM,cAAc;AAC/C;AAoBM,IAAO,OAAP,MAAW;;EAOf,YAAYG,QAAW,MAAY;AACjC,SAAK,OAAOA,OAAM;AAClB,SAAK,OAAOA,OAAM;AAClB,SAAK,KAAKA,OAAM;AAChB,SAAK,OAAO;EACd;;EAGA,cAAc,KAAe,GAAW,IAAc,KAAK,MAAI;AAC7D,QAAI,IAAc;AAClB,WAAO,IAAIH,MAAK;AACd,UAAI,IAAIC;AAAK,YAAI,EAAE,IAAI,CAAC;AACxB,UAAI,EAAE,OAAM;AACZ,YAAMA;IACR;AACA,WAAO;EACT;;;;;;;;;;;;;EAcQ,iBAAiB,OAAiB,GAAS;AACjD,UAAM,EAAE,SAAS,WAAU,IAAK,UAAU,GAAG,KAAK,IAAI;AACtD,UAAM,SAAqB,CAAA;AAC3B,QAAI,IAAc;AAClB,QAAI,OAAO;AACX,aAASC,UAAS,GAAGA,UAAS,SAASA,WAAU;AAC/C,aAAO;AACP,aAAO,KAAK,IAAI;AAEhB,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,eAAO,KAAK,IAAI,CAAC;AACjB,eAAO,KAAK,IAAI;MAClB;AACA,UAAI,KAAK,OAAM;IACjB;AACA,WAAO;EACT;;;;;;;EAQQ,KAAK,GAAW,aAAyB,GAAS;AAExD,QAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;AAAG,YAAM,IAAI,MAAM,gBAAgB;AAEzD,QAAI,IAAI,KAAK;AACb,QAAI,IAAI,KAAK;AAMb,UAAM,KAAK,UAAU,GAAG,KAAK,IAAI;AACjC,aAASA,UAAS,GAAGA,UAAS,GAAG,SAASA,WAAU;AAElD,YAAM,EAAE,OAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAO,IAAK,YAAY,GAAGA,SAAQ,EAAE;AACnF,UAAI;AACJ,UAAI,QAAQ;AAGV,YAAI,EAAE,IAAI,SAAS,QAAQ,YAAY,OAAO,CAAC,CAAC;MAClD,OAAO;AAEL,YAAI,EAAE,IAAI,SAAS,OAAO,YAAY,MAAM,CAAC,CAAC;MAChD;IACF;AACA,YAAQ,CAAC;AAIT,WAAO,EAAE,GAAG,EAAC;EACf;;;;;;EAOQ,WACN,GACA,aACA,GACA,MAAgB,KAAK,MAAI;AAEzB,UAAM,KAAK,UAAU,GAAG,KAAK,IAAI;AACjC,aAASA,UAAS,GAAGA,UAAS,GAAG,SAASA,WAAU;AAClD,UAAI,MAAMF;AAAK;AACf,YAAM,EAAE,OAAO,QAAQ,QAAQ,MAAK,IAAK,YAAY,GAAGE,SAAQ,EAAE;AAClE,UAAI;AACJ,UAAI,QAAQ;AAGV;MACF,OAAO;AACL,cAAM,OAAO,YAAY,MAAM;AAC/B,cAAM,IAAI,IAAI,QAAQ,KAAK,OAAM,IAAK,IAAI;MAC5C;IACF;AACA,YAAQ,CAAC;AACT,WAAO;EACT;EAEQ,eAAe,GAAW,OAAiB,WAA4B;AAE7E,QAAI,OAAO,iBAAiB,IAAI,KAAK;AACrC,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,iBAAiB,OAAO,CAAC;AACrC,UAAI,MAAM,GAAG;AAEX,YAAI,OAAO,cAAc;AAAY,iBAAO,UAAU,IAAI;AAC1D,yBAAiB,IAAI,OAAO,IAAI;MAClC;IACF;AACA,WAAO;EACT;EAEA,OACE,OACA,QACA,WAA4B;AAE5B,UAAM,IAAI,KAAK,KAAK;AACpB,WAAO,KAAK,KAAK,GAAG,KAAK,eAAe,GAAG,OAAO,SAAS,GAAG,MAAM;EACtE;EAEA,OAAO,OAAiB,QAAgB,WAA8B,MAAe;AACnF,UAAM,IAAI,KAAK,KAAK;AACpB,QAAI,MAAM;AAAG,aAAO,KAAK,cAAc,OAAO,QAAQ,IAAI;AAC1D,WAAO,KAAK,WAAW,GAAG,KAAK,eAAe,GAAG,OAAO,SAAS,GAAG,QAAQ,IAAI;EAClF;;;;EAKA,YAAY,GAAa,GAAS;AAChC,cAAU,GAAG,KAAK,IAAI;AACtB,qBAAiB,IAAI,GAAG,CAAC;AACzB,qBAAiB,OAAO,CAAC;EAC3B;EAEA,SAAS,KAAa;AACpB,WAAO,KAAK,GAAG,MAAM;EACvB;;AAOI,SAAU,cACdC,QACA,OACA,IACA,IAAU;AAEV,MAAI,MAAM;AACV,MAAI,KAAKA,OAAM;AACf,MAAI,KAAKA,OAAM;AACf,SAAO,KAAKH,QAAO,KAAKA,MAAK;AAC3B,QAAI,KAAKC;AAAK,WAAK,GAAG,IAAI,GAAG;AAC7B,QAAI,KAAKA;AAAK,WAAK,GAAG,IAAI,GAAG;AAC7B,UAAM,IAAI,OAAM;AAChB,WAAOA;AACP,WAAOA;EACT;AACA,SAAO,EAAE,IAAI,GAAE;AACjB;AAYM,SAAU,UACd,GACA,QACA,QACA,SAAiB;AAQjB,oBAAkB,QAAQ,CAAC;AAC3B,qBAAmB,SAAS,MAAM;AAClC,QAAM,UAAU,OAAO;AACvB,QAAM,UAAU,QAAQ;AACxB,MAAI,YAAY;AAAS,UAAM,IAAI,MAAM,qDAAqD;AAE9F,QAAM,OAAO,EAAE;AACf,QAAM,QAAQ,OAAO,OAAO,OAAO,CAAC;AACpC,MAAI,aAAa;AACjB,MAAI,QAAQ;AAAI,iBAAa,QAAQ;WAC5B,QAAQ;AAAG,iBAAa,QAAQ;WAChC,QAAQ;AAAG,iBAAa;AACjC,QAAM,OAAO,QAAQ,UAAU;AAC/B,QAAM,UAAU,IAAI,MAAM,OAAO,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI;AACrD,QAAM,WAAW,KAAK,OAAO,OAAO,OAAO,KAAK,UAAU,IAAI;AAC9D,MAAI,MAAM;AACV,WAAS,IAAI,UAAU,KAAK,GAAG,KAAK,YAAY;AAC9C,YAAQ,KAAK,IAAI;AACjB,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAM,SAAS,QAAQ,CAAC;AACxB,YAAMG,SAAQ,OAAQ,UAAU,OAAO,CAAC,IAAK,IAAI;AACjD,cAAQA,MAAK,IAAI,QAAQA,MAAK,EAAE,IAAI,OAAO,CAAC,CAAC;IAC/C;AACA,QAAI,OAAO;AAEX,aAAS,IAAI,QAAQ,SAAS,GAAG,OAAO,MAAM,IAAI,GAAG,KAAK;AACxD,aAAO,KAAK,IAAI,QAAQ,CAAC,CAAC;AAC1B,aAAO,KAAK,IAAI,IAAI;IACtB;AACA,UAAM,IAAI,IAAI,IAAI;AAClB,QAAI,MAAM;AAAG,eAAS,IAAI,GAAG,IAAI,YAAY;AAAK,cAAM,IAAI,OAAM;EACpE;AACA,SAAO;AACT;AAkJA,SAAS,YAAe,OAAe,OAAmB,MAAc;AACtE,MAAI,OAAO;AACT,QAAI,MAAM,UAAU;AAAO,YAAM,IAAI,MAAM,gDAAgD;AAC3F,kBAAc,KAAK;AACnB,WAAO;EACT,OAAO;AACL,WAAO,MAAM,OAAO,EAAE,KAAI,CAAE;EAC9B;AACF;AAIM,SAAU,mBACd,MACA,OACA,YAA8B,CAAA,GAC9B,QAAgB;AAEhB,MAAI,WAAW;AAAW,aAAS,SAAS;AAC5C,MAAI,CAAC,SAAS,OAAO,UAAU;AAAU,UAAM,IAAI,MAAM,kBAAkB,IAAI,eAAe;AAC9F,aAAW,KAAK,CAAC,KAAK,KAAK,GAAG,GAAY;AACxC,UAAM,MAAM,MAAM,CAAC;AACnB,QAAI,EAAE,OAAO,QAAQ,YAAY,MAAMC;AACrC,YAAM,IAAI,MAAM,SAAS,CAAC,0BAA0B;EACxD;AACA,QAAM,KAAK,YAAY,MAAM,GAAG,UAAU,IAAI,MAAM;AACpD,QAAM,KAAK,YAAY,MAAM,GAAG,UAAU,IAAI,MAAM;AACpD,QAAM,KAAgB,SAAS,gBAAgB,MAAM;AACrD,QAAM,SAAS,CAAC,MAAM,MAAM,KAAK,EAAE;AACnC,aAAW,KAAK,QAAQ;AAEtB,QAAI,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC;AACtB,YAAM,IAAI,MAAM,SAAS,CAAC,0CAA0C;EACxE;AACA,UAAQ,OAAO,OAAO,OAAO,OAAO,CAAA,GAAI,KAAK,CAAC;AAC9C,SAAO,EAAE,OAAO,IAAI,GAAE;AACxB;;;ACtkBA,IAAM,aAAa,CAAC,KAAa,SAAiB,OAAO,OAAO,IAAI,MAAM,CAAC,OAAOC,QAAO;AAOnF,SAAU,iBAAiB,GAAW,OAAkB,GAAS;AAIrE,QAAM,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI;AAC7B,QAAM,KAAK,WAAW,KAAK,GAAG,CAAC;AAC/B,QAAM,KAAK,WAAW,CAAC,KAAK,GAAG,CAAC;AAGhC,MAAI,KAAK,IAAI,KAAK,KAAK,KAAK;AAC5B,MAAI,KAAK,CAAC,KAAK,KAAK,KAAK;AACzB,QAAM,QAAQ,KAAKC;AACnB,QAAM,QAAQ,KAAKA;AACnB,MAAI;AAAO,SAAK,CAAC;AACjB,MAAI;AAAO,SAAK,CAAC;AAGjB,QAAM,UAAU,QAAQ,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,IAAIC;AACpD,MAAI,KAAKD,QAAO,MAAM,WAAW,KAAKA,QAAO,MAAM,SAAS;AAC1D,UAAM,IAAI,MAAM,2CAA2C,CAAC;EAC9D;AACA,SAAO,EAAE,OAAO,IAAI,OAAO,GAAE;AAC/B;AAkBA,SAAS,kBAAkB,QAAc;AACvC,MAAI,CAAC,CAAC,WAAW,aAAa,KAAK,EAAE,SAAS,MAAM;AAClD,UAAM,IAAI,MAAM,2DAA2D;AAC7E,SAAO;AACT;AAEA,SAAS,gBACP,MACA,KAAM;AAEN,QAAM,QAAuB,CAAA;AAC7B,WAAS,WAAW,OAAO,KAAK,GAAG,GAAG;AAEpC,UAAM,OAAO,IAAI,KAAK,OAAO,MAAM,SAAY,IAAI,OAAO,IAAI,KAAK,OAAO;EAC5E;AACA,UAAM,MAAM,MAAO,MAAM;AACzB,UAAM,MAAM,SAAU,SAAS;AAC/B,MAAI,MAAM,WAAW;AAAW,sBAAkB,MAAM,MAAM;AAC9D,SAAO;AACT;AAmJM,IAAO,SAAP,cAAsB,MAAK;EAC/B,YAAY,IAAI,IAAE;AAChB,UAAM,CAAC;EACT;;AA6BK,IAAM,MAAY;;EAEvB,KAAK;;EAEL,MAAM;IACJ,QAAQ,CAAC,KAAa,SAAwB;AAC5C,YAAM,EAAE,KAAK,EAAC,IAAK;AACnB,UAAI,MAAM,KAAK,MAAM;AAAK,cAAM,IAAI,EAAE,uBAAuB;AAC7D,UAAI,KAAK,SAAS;AAAG,cAAM,IAAI,EAAE,2BAA2B;AAC5D,YAAM,UAAU,KAAK,SAAS;AAC9B,YAAM,MAAM,oBAAoB,OAAO;AACvC,UAAK,IAAI,SAAS,IAAK;AAAa,cAAM,IAAI,EAAE,sCAAsC;AAEtF,YAAM,SAAS,UAAU,MAAM,oBAAqB,IAAI,SAAS,IAAK,GAAW,IAAI;AACrF,YAAM,IAAI,oBAAoB,GAAG;AACjC,aAAO,IAAI,SAAS,MAAM;IAC5B;;IAEA,OAAO,KAAa,MAAgB;AAClC,YAAM,EAAE,KAAK,EAAC,IAAK;AACnB,UAAI,MAAM;AACV,UAAI,MAAM,KAAK,MAAM;AAAK,cAAM,IAAI,EAAE,uBAAuB;AAC7D,UAAI,KAAK,SAAS,KAAK,KAAK,KAAK,MAAM;AAAK,cAAM,IAAI,EAAE,uBAAuB;AAC/E,YAAM,QAAQ,KAAK,KAAK;AACxB,YAAM,SAAS,CAAC,EAAE,QAAQ;AAC1B,UAAI,SAAS;AACb,UAAI,CAAC;AAAQ,iBAAS;WACjB;AAEH,cAAM,SAAS,QAAQ;AACvB,YAAI,CAAC;AAAQ,gBAAM,IAAI,EAAE,mDAAmD;AAC5E,YAAI,SAAS;AAAG,gBAAM,IAAI,EAAE,0CAA0C;AACtE,cAAM,cAAc,KAAK,SAAS,KAAK,MAAM,MAAM;AACnD,YAAI,YAAY,WAAW;AAAQ,gBAAM,IAAI,EAAE,uCAAuC;AACtF,YAAI,YAAY,CAAC,MAAM;AAAG,gBAAM,IAAI,EAAE,sCAAsC;AAC5E,mBAAW,KAAK;AAAa,mBAAU,UAAU,IAAK;AACtD,eAAO;AACP,YAAI,SAAS;AAAK,gBAAM,IAAI,EAAE,wCAAwC;MACxE;AACA,YAAM,IAAI,KAAK,SAAS,KAAK,MAAM,MAAM;AACzC,UAAI,EAAE,WAAW;AAAQ,cAAM,IAAI,EAAE,gCAAgC;AACrE,aAAO,EAAE,GAAG,GAAG,KAAK,SAAS,MAAM,MAAM,EAAC;IAC5C;;;;;;EAMF,MAAM;IACJ,OAAO,KAAW;AAChB,YAAM,EAAE,KAAK,EAAC,IAAK;AACnB,UAAI,MAAMA;AAAK,cAAM,IAAI,EAAE,4CAA4C;AACvE,UAAI,MAAM,oBAAoB,GAAG;AAEjC,UAAI,OAAO,SAAS,IAAI,CAAC,GAAG,EAAE,IAAI;AAAQ,cAAM,OAAO;AACvD,UAAI,IAAI,SAAS;AAAG,cAAM,IAAI,EAAE,gDAAgD;AAChF,aAAO;IACT;IACA,OAAO,MAAgB;AACrB,YAAM,EAAE,KAAK,EAAC,IAAK;AACnB,UAAI,KAAK,CAAC,IAAI;AAAa,cAAM,IAAI,EAAE,qCAAqC;AAC5E,UAAI,KAAK,CAAC,MAAM,KAAQ,EAAE,KAAK,CAAC,IAAI;AAClC,cAAM,IAAI,EAAE,qDAAqD;AACnE,aAAO,gBAAgB,IAAI;IAC7B;;EAEF,MAAM,KAAwB;AAE5B,UAAM,EAAE,KAAK,GAAG,MAAM,KAAK,MAAM,IAAG,IAAK;AACzC,UAAM,OAAO,YAAY,aAAa,GAAG;AACzC,UAAM,EAAE,GAAG,UAAU,GAAG,aAAY,IAAK,IAAI,OAAO,IAAM,IAAI;AAC9D,QAAI,aAAa;AAAQ,YAAM,IAAI,EAAE,6CAA6C;AAClF,UAAM,EAAE,GAAG,QAAQ,GAAG,WAAU,IAAK,IAAI,OAAO,GAAM,QAAQ;AAC9D,UAAM,EAAE,GAAG,QAAQ,GAAG,WAAU,IAAK,IAAI,OAAO,GAAM,UAAU;AAChE,QAAI,WAAW;AAAQ,YAAM,IAAI,EAAE,6CAA6C;AAChF,WAAO,EAAE,GAAG,IAAI,OAAO,MAAM,GAAG,GAAG,IAAI,OAAO,MAAM,EAAC;EACvD;EACA,WAAW,KAA6B;AACtC,UAAM,EAAE,MAAM,KAAK,MAAM,IAAG,IAAK;AACjC,UAAM,KAAK,IAAI,OAAO,GAAM,IAAI,OAAO,IAAI,CAAC,CAAC;AAC7C,UAAM,KAAK,IAAI,OAAO,GAAM,IAAI,OAAO,IAAI,CAAC,CAAC;AAC7C,UAAM,MAAM,KAAK;AACjB,WAAO,IAAI,OAAO,IAAM,GAAG;EAC7B;;AAKF,IAAMA,OAAM,OAAO,CAAC;AAApB,IAAuBC,OAAM,OAAO,CAAC;AAArC,IAAwCF,OAAM,OAAO,CAAC;AAAtD,IAAyDG,OAAM,OAAO,CAAC;AAAvE,IAA0EC,OAAM,OAAO,CAAC;AAElF,SAAU,eAAe,IAAoB,KAAY;AAC7D,QAAM,EAAE,OAAO,SAAQ,IAAK;AAC5B,MAAI;AACJ,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM;EACR,OAAO;AACL,QAAI,QAAQ,YAAY,eAAe,GAAG;AAC1C,QAAI;AACF,YAAM,GAAG,UAAU,KAAK;IAC1B,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,8CAA8C,QAAQ,SAAS,OAAO,GAAG,EAAE;IAC7F;EACF;AACA,MAAI,CAAC,GAAG,YAAY,GAAG;AAAG,UAAM,IAAI,MAAM,4CAA4C;AACtF,SAAO;AACT;AAmBM,SAAU,aACd,QACA,YAAqC,CAAA,GAAE;AAEvC,QAAM,YAAY,mBAAmB,eAAe,QAAQ,SAAS;AACrE,QAAM,EAAE,IAAI,GAAE,IAAK;AACnB,MAAI,QAAQ,UAAU;AACtB,QAAM,EAAE,GAAG,UAAU,GAAG,YAAW,IAAK;AACxC,kBACE,WACA,CAAA,GACA;IACE,oBAAoB;IACpB,eAAe;IACf,eAAe;IACf,WAAW;IACX,SAAS;IACT,MAAM;IACN,gBAAgB;GACjB;AAGH,QAAM,EAAE,KAAI,IAAK;AACjB,MAAI,MAAM;AAER,QAAI,CAAC,GAAG,IAAI,MAAM,CAAC,KAAK,OAAO,KAAK,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,OAAO,GAAG;AACrF,YAAM,IAAI,MAAM,4DAA4D;IAC9E;EACF;AAEA,QAAM,UAAU,YAAY,IAAI,EAAE;AAElC,WAAS,+BAA4B;AACnC,QAAI,CAAC,GAAG;AAAO,YAAM,IAAI,MAAM,4DAA4D;EAC7F;AAGA,WAAS,aACP,IACA,OACA,cAAqB;AAErB,UAAM,EAAE,GAAG,EAAC,IAAK,MAAM,SAAQ;AAC/B,UAAM,KAAK,GAAG,QAAQ,CAAC;AACvB,YAAM,cAAc,cAAc;AAClC,QAAI,cAAc;AAChB,mCAA4B;AAC5B,YAAM,WAAW,CAAC,GAAG,MAAO,CAAC;AAC7B,aAAO,YAAY,QAAQ,QAAQ,GAAG,EAAE;IAC1C,OAAO;AACL,aAAO,YAAY,WAAW,GAAG,CAAI,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;IAC3D;EACF;AACA,WAAS,eAAe,OAAiB;AACvC,aAAO,OAAO,QAAW,OAAO;AAChC,UAAM,EAAE,WAAW,MAAM,uBAAuB,OAAM,IAAK;AAC3D,UAAM,SAAS,MAAM;AACrB,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,OAAO,MAAM,SAAS,CAAC;AAE7B,QAAI,WAAW,SAAS,SAAS,KAAQ,SAAS,IAAO;AACvD,YAAM,IAAI,GAAG,UAAU,IAAI;AAC3B,UAAI,CAAC,GAAG,QAAQ,CAAC;AAAG,cAAM,IAAI,MAAM,qCAAqC;AACzE,YAAM,KAAK,oBAAoB,CAAC;AAChC,UAAI;AACJ,UAAI;AACF,YAAI,GAAG,KAAK,EAAE;MAChB,SAAS,WAAW;AAClB,cAAM,MAAM,qBAAqB,QAAQ,OAAO,UAAU,UAAU;AACpE,cAAM,IAAI,MAAM,2CAA2C,GAAG;MAChE;AACA,mCAA4B;AAC5B,YAAM,SAAS,GAAG,MAAO,CAAC;AAC1B,YAAM,aAAa,OAAO,OAAO;AACjC,UAAI,cAAc;AAAQ,YAAI,GAAG,IAAI,CAAC;AACtC,aAAO,EAAE,GAAG,EAAC;IACf,WAAW,WAAW,UAAU,SAAS,GAAM;AAE7C,YAAM,IAAI,GAAG;AACb,YAAM,IAAI,GAAG,UAAU,KAAK,SAAS,GAAG,CAAC,CAAC;AAC1C,YAAM,IAAI,GAAG,UAAU,KAAK,SAAS,GAAG,IAAI,CAAC,CAAC;AAC9C,UAAI,CAAC,UAAU,GAAG,CAAC;AAAG,cAAM,IAAI,MAAM,4BAA4B;AAClE,aAAO,EAAE,GAAG,EAAC;IACf,OAAO;AACL,YAAM,IAAI,MACR,yBAAyB,MAAM,yBAAyB,IAAI,oBAAoB,MAAM,EAAE;IAE5F;EACF;AAEA,QAAM,cAAc,UAAU,WAAW;AACzC,QAAM,cAAc,UAAU,aAAa;AAC3C,WAAS,oBAAoB,GAAI;AAC/B,UAAM,KAAK,GAAG,IAAI,CAAC;AACnB,UAAM,KAAK,GAAG,IAAI,IAAI,CAAC;AACvB,WAAO,GAAG,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC;EACvD;AAIA,WAAS,UAAU,GAAM,GAAI;AAC3B,UAAM,OAAO,GAAG,IAAI,CAAC;AACrB,UAAM,QAAQ,oBAAoB,CAAC;AACnC,WAAO,GAAG,IAAI,MAAM,KAAK;EAC3B;AAIA,MAAI,CAAC,UAAU,MAAM,IAAI,MAAM,EAAE;AAAG,UAAM,IAAI,MAAM,mCAAmC;AAIvF,QAAM,OAAO,GAAG,IAAI,GAAG,IAAI,MAAM,GAAGD,IAAG,GAAGC,IAAG;AAC7C,QAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;AAChD,MAAI,GAAG,IAAI,GAAG,IAAI,MAAM,KAAK,CAAC;AAAG,UAAM,IAAI,MAAM,0BAA0B;AAG3E,WAAS,OAAO,OAAe,GAAM,UAAU,OAAK;AAClD,QAAI,CAAC,GAAG,QAAQ,CAAC,KAAM,WAAW,GAAG,IAAI,CAAC;AAAI,YAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;AAC7F,WAAO;EACT;AAEA,WAAS,UAAU,OAAc;AAC/B,QAAI,EAAE,iBAAiBC;AAAQ,YAAM,IAAI,MAAM,0BAA0B;EAC3E;AAEA,WAAS,iBAAiB,GAAS;AACjC,QAAI,CAAC,QAAQ,CAAC,KAAK;AAAS,YAAM,IAAI,MAAM,SAAS;AACrD,WAAO,iBAAiB,GAAG,KAAK,SAAS,GAAG,KAAK;EACnD;AAOA,QAAM,eAAe,SAAS,CAAC,GAAU,OAA0B;AACjE,UAAM,EAAE,GAAG,GAAG,EAAC,IAAK;AAEpB,QAAI,GAAG,IAAI,GAAG,GAAG,GAAG;AAAG,aAAO,EAAE,GAAG,GAAG,GAAG,EAAC;AAC1C,UAAM,MAAM,EAAE,IAAG;AAGjB,QAAI,MAAM;AAAM,WAAK,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAC5C,UAAM,IAAI,GAAG,IAAI,GAAG,EAAE;AACtB,UAAM,IAAI,GAAG,IAAI,GAAG,EAAE;AACtB,UAAM,KAAK,GAAG,IAAI,GAAG,EAAE;AACvB,QAAI;AAAK,aAAO,EAAE,GAAG,GAAG,MAAM,GAAG,GAAG,KAAI;AACxC,QAAI,CAAC,GAAG,IAAI,IAAI,GAAG,GAAG;AAAG,YAAM,IAAI,MAAM,kBAAkB;AAC3D,WAAO,EAAE,GAAG,EAAC;EACf,CAAC;AAGD,QAAM,kBAAkB,SAAS,CAAC,MAAY;AAC5C,QAAI,EAAE,IAAG,GAAI;AAIX,UAAI,UAAU,sBAAsB,CAAC,GAAG,IAAI,EAAE,CAAC;AAAG;AAClD,YAAM,IAAI,MAAM,iBAAiB;IACnC;AAEA,UAAM,EAAE,GAAG,EAAC,IAAK,EAAE,SAAQ;AAC3B,QAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;AAAG,YAAM,IAAI,MAAM,sCAAsC;AAC5F,QAAI,CAAC,UAAU,GAAG,CAAC;AAAG,YAAM,IAAI,MAAM,mCAAmC;AACzE,QAAI,CAAC,EAAE,cAAa;AAAI,YAAM,IAAI,MAAM,wCAAwC;AAChF,WAAO;EACT,CAAC;AAED,WAAS,WACP,UACA,KACA,KACA,OACA,OAAc;AAEd,UAAM,IAAIA,OAAM,GAAG,IAAI,IAAI,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;AACrD,UAAM,SAAS,OAAO,GAAG;AACzB,UAAM,SAAS,OAAO,GAAG;AACzB,WAAO,IAAI,IAAI,GAAG;EACpB;EAOA,MAAMA,OAAK;;IAeT,YAAY,GAAM,GAAM,GAAI;AAC1B,WAAK,IAAI,OAAO,KAAK,CAAC;AACtB,WAAK,IAAI,OAAO,KAAK,GAAG,IAAI;AAC5B,WAAK,IAAI,OAAO,KAAK,CAAC;AACtB,aAAO,OAAO,IAAI;IACpB;IAEA,OAAO,QAAK;AACV,aAAO;IACT;;IAGA,OAAO,WAAW,GAAiB;AACjC,YAAM,EAAE,GAAG,EAAC,IAAK,KAAK,CAAA;AACtB,UAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;AAAG,cAAM,IAAI,MAAM,sBAAsB;AAClF,UAAI,aAAaA;AAAO,cAAM,IAAI,MAAM,8BAA8B;AAEtE,UAAI,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;AAAG,eAAOA,OAAM;AACzC,aAAO,IAAIA,OAAM,GAAG,GAAG,GAAG,GAAG;IAC/B;IAEA,OAAO,UAAU,OAAiB;AAChC,YAAM,IAAIA,OAAM,WAAW,YAAY,SAAO,OAAO,QAAW,OAAO,CAAC,CAAC;AACzE,QAAE,eAAc;AAChB,aAAO;IACT;IACA,OAAO,QAAQ,KAAQ;AACrB,aAAOA,OAAM,UAAU,YAAY,YAAY,GAAG,CAAC;IACrD;IAEA,IAAI,IAAC;AACH,aAAO,KAAK,SAAQ,EAAG;IACzB;IACA,IAAI,IAAC;AACH,aAAO,KAAK,SAAQ,EAAG;IACzB;;;;;;;IAQA,WAAW,aAAqB,GAAG,SAAS,MAAI;AAC9C,WAAK,YAAY,MAAM,UAAU;AACjC,UAAI,CAAC;AAAQ,aAAK,SAASF,IAAG;AAC9B,aAAO;IACT;;;IAIA,iBAAc;AACZ,sBAAgB,IAAI;IACtB;IAEA,WAAQ;AACN,YAAM,EAAE,EAAC,IAAK,KAAK,SAAQ;AAC3B,UAAI,CAAC,GAAG;AAAO,cAAM,IAAI,MAAM,6BAA6B;AAC5D,aAAO,CAAC,GAAG,MAAM,CAAC;IACpB;;IAGA,OAAO,OAAY;AACjB,gBAAU,KAAK;AACf,YAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,GAAE,IAAK;AAChC,YAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,GAAE,IAAK;AAChC,YAAM,KAAK,GAAG,IAAI,GAAG,IAAI,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;AAChD,YAAM,KAAK,GAAG,IAAI,GAAG,IAAI,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;AAChD,aAAO,MAAM;IACf;;IAGA,SAAM;AACJ,aAAO,IAAIE,OAAM,KAAK,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC;IACjD;;;;;IAMA,SAAM;AACJ,YAAM,EAAE,GAAG,EAAC,IAAK;AACjB,YAAM,KAAK,GAAG,IAAI,GAAGF,IAAG;AACxB,YAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,GAAE,IAAK;AAChC,UAAI,KAAK,GAAG,MAAM,KAAK,GAAG,MAAM,KAAK,GAAG;AACxC,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,aAAO,IAAIE,OAAM,IAAI,IAAI,EAAE;IAC7B;;;;;IAMA,IAAI,OAAY;AACd,gBAAU,KAAK;AACf,YAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,GAAE,IAAK;AAChC,YAAM,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,GAAE,IAAK;AAChC,UAAI,KAAK,GAAG,MAAM,KAAK,GAAG,MAAM,KAAK,GAAG;AACxC,YAAM,IAAI,MAAM;AAChB,YAAM,KAAK,GAAG,IAAI,MAAM,GAAGF,IAAG;AAC9B,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,UAAI,KAAK,GAAG,IAAI,IAAI,EAAE;AACtB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,GAAG,EAAE;AACjB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,WAAK,GAAG,IAAI,IAAI,EAAE;AAClB,aAAO,IAAIE,OAAM,IAAI,IAAI,EAAE;IAC7B;IAEA,SAAS,OAAY;AACnB,aAAO,KAAK,IAAI,MAAM,OAAM,CAAE;IAChC;IAEA,MAAG;AACD,aAAO,KAAK,OAAOA,OAAM,IAAI;IAC/B;;;;;;;;;;IAWA,SAAS,QAAc;AACrB,YAAM,EAAE,MAAAC,MAAI,IAAK;AACjB,UAAI,CAAC,GAAG,YAAY,MAAM;AAAG,cAAM,IAAI,MAAM,8BAA8B;AAC3E,UAAI,OAAc;AAClB,YAAM,MAAM,CAAC,MAAc,KAAK,OAAO,MAAM,GAAG,CAAC,MAAM,WAAWD,QAAO,CAAC,CAAC;AAE3E,UAAIC,OAAM;AACR,cAAM,EAAE,OAAO,IAAI,OAAO,GAAE,IAAK,iBAAiB,MAAM;AACxD,cAAM,EAAE,GAAG,KAAK,GAAG,IAAG,IAAK,IAAI,EAAE;AACjC,cAAM,EAAE,GAAG,KAAK,GAAG,IAAG,IAAK,IAAI,EAAE;AACjC,eAAO,IAAI,IAAI,GAAG;AAClB,gBAAQ,WAAWA,MAAK,MAAM,KAAK,KAAK,OAAO,KAAK;MACtD,OAAO;AACL,cAAM,EAAE,GAAG,EAAC,IAAK,IAAI,MAAM;AAC3B,gBAAQ;AACR,eAAO;MACT;AAEA,aAAO,WAAWD,QAAO,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC;IAC3C;;;;;;IAOA,eAAe,IAAU;AACvB,YAAM,EAAE,MAAAC,MAAI,IAAK;AACjB,YAAM,IAAI;AACV,UAAI,CAAC,GAAG,QAAQ,EAAE;AAAG,cAAM,IAAI,MAAM,8BAA8B;AACnE,UAAI,OAAOL,QAAO,EAAE,IAAG;AAAI,eAAOI,OAAM;AACxC,UAAI,OAAOH;AAAK,eAAO;AACvB,UAAI,KAAK,SAAS,IAAI;AAAG,eAAO,KAAK,SAAS,EAAE;AAChD,UAAII,OAAM;AACR,cAAM,EAAE,OAAO,IAAI,OAAO,GAAE,IAAK,iBAAiB,EAAE;AACpD,cAAM,EAAE,IAAI,GAAE,IAAK,cAAcD,QAAO,GAAG,IAAI,EAAE;AACjD,eAAO,WAAWC,MAAK,MAAM,IAAI,IAAI,OAAO,KAAK;MACnD,OAAO;AACL,eAAO,KAAK,OAAO,GAAG,EAAE;MAC1B;IACF;IAEA,qBAAqB,GAAU,GAAW,GAAS;AACjD,YAAM,MAAM,KAAK,eAAe,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;AAC1D,aAAO,IAAI,IAAG,IAAK,SAAY;IACjC;;;;;IAMA,SAAS,WAAa;AACpB,aAAO,aAAa,MAAM,SAAS;IACrC;;;;;IAMA,gBAAa;AACX,YAAM,EAAE,cAAa,IAAK;AAC1B,UAAI,aAAaJ;AAAK,eAAO;AAC7B,UAAI;AAAe,eAAO,cAAcG,QAAO,IAAI;AACnD,aAAO,KAAK,OAAO,MAAM,WAAW,EAAE,IAAG;IAC3C;IAEA,gBAAa;AACX,YAAM,EAAE,cAAa,IAAK;AAC1B,UAAI,aAAaH;AAAK,eAAO;AAC7B,UAAI;AAAe,eAAO,cAAcG,QAAO,IAAI;AACnD,aAAO,KAAK,eAAe,QAAQ;IACrC;IAEA,eAAY;AAEV,aAAO,KAAK,eAAe,QAAQ,EAAE,IAAG;IAC1C;IAEA,QAAQ,eAAe,MAAI;AACzB,cAAM,cAAc,cAAc;AAClC,WAAK,eAAc;AACnB,aAAO,YAAYA,QAAO,MAAM,YAAY;IAC9C;IAEA,MAAM,eAAe,MAAI;AACvB,aAAO,WAAW,KAAK,QAAQ,YAAY,CAAC;IAC9C;IAEA,WAAQ;AACN,aAAO,UAAU,KAAK,IAAG,IAAK,SAAS,KAAK,MAAK,CAAE;IACrD;;IAGA,IAAI,KAAE;AACJ,aAAO,KAAK;IACd;IACA,IAAI,KAAE;AACJ,aAAO,KAAK;IACd;IACA,IAAI,KAAE;AACJ,aAAO,KAAK;IACd;IACA,WAAW,eAAe,MAAI;AAC5B,aAAO,KAAK,QAAQ,YAAY;IAClC;IACA,eAAe,YAAkB;AAC/B,WAAK,WAAW,UAAU;IAC5B;IACA,OAAO,WAAW,QAAe;AAC/B,aAAO,WAAWA,QAAO,MAAM;IACjC;IACA,OAAO,IAAI,QAAiB,SAAiB;AAC3C,aAAO,UAAUA,QAAO,IAAI,QAAQ,OAAO;IAC7C;IACA,OAAO,eAAe,YAAmB;AACvC,aAAOA,OAAM,KAAK,SAAS,eAAe,IAAI,UAAU,CAAC;IAC3D;;AA/TgB,EAAAA,OAAA,OAAO,IAAIA,OAAM,MAAM,IAAI,MAAM,IAAI,GAAG,GAAG;AAE3C,EAAAA,OAAA,OAAO,IAAIA,OAAM,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI;AAEzC,EAAAA,OAAA,KAAK;AAEL,EAAAA,OAAA,KAAK;AA2TvB,QAAM,OAAO,GAAG;AAChB,QAAM,OAAO,IAAI,KAAKA,QAAO,UAAU,OAAO,KAAK,KAAK,OAAO,CAAC,IAAI,IAAI;AACxE,EAAAA,OAAM,KAAK,WAAW,CAAC;AACvB,SAAOA;AACT;AA2CA,SAAS,QAAQ,UAAiB;AAChC,SAAO,WAAW,GAAG,WAAW,IAAO,CAAI;AAC7C;AAuIA,SAAS,YAAe,IAAe,IAAkB;AACvD,SAAO;IACL,WAAW,GAAG;IACd,WAAW,IAAI,GAAG;IAClB,uBAAuB,IAAI,IAAI,GAAG;IAClC,oBAAoB;IACpB,WAAW,IAAI,GAAG;;AAEtB;AAMM,SAAU,KACdE,QACA,WAAmE,CAAA,GAAE;AAErE,QAAM,EAAE,GAAE,IAAKA;AACf,QAAM,eAAe,SAAS,eAAe;AAC7C,QAAM,UAAU,OAAO,OAAO,YAAYA,OAAM,IAAI,EAAE,GAAG,EAAE,MAAM,iBAAiB,GAAG,KAAK,EAAC,CAAE;AAE7F,WAAS,iBAAiB,WAAkB;AAC1C,QAAI;AACF,aAAO,CAAC,CAAC,eAAe,IAAI,SAAS;IACvC,SAAS,OAAO;AACd,aAAO;IACT;EACF;AAEA,WAAS,iBAAiB,WAAuB,cAAsB;AACrE,UAAM,EAAE,WAAW,MAAM,sBAAqB,IAAK;AACnD,QAAI;AACF,YAAM,IAAI,UAAU;AACpB,UAAI,iBAAiB,QAAQ,MAAM;AAAM,eAAO;AAChD,UAAI,iBAAiB,SAAS,MAAM;AAAuB,eAAO;AAClE,aAAO,CAAC,CAACA,OAAM,UAAU,SAAS;IACpC,SAAS,OAAO;AACd,aAAO;IACT;EACF;AAMA,WAAS,gBAAgB,OAAO,aAAa,QAAQ,IAAI,GAAC;AACxD,WAAO,eAAe,SAAO,MAAM,QAAQ,MAAM,MAAM,GAAG,GAAG,KAAK;EACpE;AAOA,WAASC,cAAa,WAAoB,eAAe,MAAI;AAC3D,WAAOD,OAAM,KAAK,SAAS,eAAe,IAAI,SAAS,CAAC,EAAE,QAAQ,YAAY;EAChF;AAEA,WAAS,OAAO,MAAiB;AAC/B,UAAM,YAAY,gBAAgB,IAAI;AACtC,WAAO,EAAE,WAAW,WAAWC,cAAa,SAAS,EAAC;EACxD;AAKA,WAAS,UAAU,MAAsB;AACvC,QAAI,OAAO,SAAS;AAAU,aAAO;AACrC,QAAI,gBAAgBD;AAAO,aAAO;AAClC,UAAM,EAAE,WAAW,WAAW,sBAAqB,IAAK;AACxD,QAAI,GAAG,kBAAkB,cAAc;AAAW,aAAO;AACzD,UAAM,IAAI,YAAY,OAAO,IAAI,EAAE;AACnC,WAAO,MAAM,aAAa,MAAM;EAClC;AAUA,WAAS,gBAAgB,YAAqB,YAAiB,eAAe,MAAI;AAChF,QAAI,UAAU,UAAU,MAAM;AAAM,YAAM,IAAI,MAAM,+BAA+B;AACnF,QAAI,UAAU,UAAU,MAAM;AAAO,YAAM,IAAI,MAAM,+BAA+B;AACpF,UAAM,IAAI,eAAe,IAAI,UAAU;AACvC,UAAM,IAAIA,OAAM,QAAQ,UAAU;AAClC,WAAO,EAAE,SAAS,CAAC,EAAE,QAAQ,YAAY;EAC3C;AAEA,QAAME,SAAQ;IACZ;IACA;IACA;;IAGA,mBAAmB;IACnB,kBAAkB;IAClB,wBAAwB,CAAC,QAAiB,eAAe,IAAI,GAAG;IAChE,WAAW,aAAa,GAAG,QAAQF,OAAM,MAAI;AAC3C,aAAO,MAAM,WAAW,YAAY,KAAK;IAC3C;;AAGF,SAAO,OAAO,OAAO,EAAE,cAAAC,eAAc,iBAAiB,QAAQ,OAAAD,QAAO,OAAAE,QAAO,QAAO,CAAE;AACvF;AAkBM,SAAU,MACdF,QACA,MACA,YAAuB,CAAA,GAAE;AAEzB,QAAM,IAAI;AACV,kBACE,WACA,CAAA,GACA;IACE,MAAM;IACN,MAAM;IACN,aAAa;IACb,UAAU;IACV,eAAe;GAChB;AAGH,QAAMG,eAAc,UAAU,eAAe;AAC7C,QAAMC,QACJ,UAAU,SACR,CAAC,QAAQ,SAAS,KAAU,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC;AAE/D,QAAM,EAAE,IAAI,GAAE,IAAKJ;AACnB,QAAM,EAAE,OAAO,aAAa,MAAM,OAAM,IAAK;AAC7C,QAAM,EAAE,QAAQ,cAAAC,eAAc,iBAAiB,OAAAC,QAAO,QAAO,IAAK,KAAKF,QAAO,SAAS;AACvF,QAAM,iBAA0C;IAC9C,SAAS;IACT,MAAM,OAAO,UAAU,SAAS,YAAY,UAAU,OAAO;IAC7D,QAAQ;;IACR,cAAc;;AAEhB,QAAM,wBAAwB;AAE9B,WAAS,sBAAsB,QAAc;AAC3C,UAAM,OAAO,eAAeK;AAC5B,WAAO,SAAS;EAClB;AACA,WAAS,WAAW,OAAe,KAAW;AAC5C,QAAI,CAAC,GAAG,YAAY,GAAG;AACrB,YAAM,IAAI,MAAM,qBAAqB,KAAK,kCAAkC;AAC9E,WAAO;EACT;AACA,WAAS,kBAAkB,OAAmB,QAAsB;AAClE,sBAAkB,MAAM;AACxB,UAAM,OAAO,QAAQ;AACrB,UAAM,QAAQ,WAAW,YAAY,OAAO,WAAW,cAAc,OAAO,IAAI;AAChF,WAAO,SAAO,OAAO,OAAO,GAAG,MAAM,YAAY;EACnD;EAKA,MAAM,UAAS;IAIb,YAAY,GAAW,GAAW,UAAiB;AACjD,WAAK,IAAI,WAAW,KAAK,CAAC;AAC1B,WAAK,IAAI,WAAW,KAAK,CAAC;AAC1B,UAAI,YAAY;AAAM,aAAK,WAAW;AACtC,aAAO,OAAO,IAAI;IACpB;IAEA,OAAO,UAAU,OAAmB,SAAyB,uBAAqB;AAChF,wBAAkB,OAAO,MAAM;AAC/B,UAAI;AACJ,UAAI,WAAW,OAAO;AACpB,cAAM,EAAE,GAAAC,IAAG,GAAAC,GAAC,IAAK,IAAI,MAAM,SAAO,KAAK,CAAC;AACxC,eAAO,IAAI,UAAUD,IAAGC,EAAC;MAC3B;AACA,UAAI,WAAW,aAAa;AAC1B,gBAAQ,MAAM,CAAC;AACf,iBAAS;AACT,gBAAQ,MAAM,SAAS,CAAC;MAC1B;AACA,YAAM,IAAI,GAAG;AACb,YAAM,IAAI,MAAM,SAAS,GAAG,CAAC;AAC7B,YAAM,IAAI,MAAM,SAAS,GAAG,IAAI,CAAC;AACjC,aAAO,IAAI,UAAU,GAAG,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,KAAK;IAC9D;IAEA,OAAO,QAAQ,KAAa,QAAuB;AACjD,aAAO,KAAK,UAAUC,YAAW,GAAG,GAAG,MAAM;IAC/C;IAEA,eAAe,UAAgB;AAC7B,aAAO,IAAI,UAAU,KAAK,GAAG,KAAK,GAAG,QAAQ;IAC/C;IAEA,iBAAiB,aAAgB;AAC/B,YAAM,cAAc,GAAG;AACvB,YAAM,EAAE,GAAG,GAAG,UAAU,IAAG,IAAK;AAChC,UAAI,OAAO,QAAQ,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,SAAS,GAAG;AAAG,cAAM,IAAI,MAAM,qBAAqB;AAUrF,YAAM,cAAc,cAAcC,OAAM;AACxC,UAAI,eAAe,MAAM;AAAG,cAAM,IAAI,MAAM,wCAAwC;AAEpF,YAAM,OAAO,QAAQ,KAAK,QAAQ,IAAI,IAAI,cAAc;AACxD,UAAI,CAAC,GAAG,QAAQ,IAAI;AAAG,cAAM,IAAI,MAAM,4BAA4B;AACnE,YAAM,IAAI,GAAG,QAAQ,IAAI;AACzB,YAAM,IAAIT,OAAM,UAAU,YAAY,SAAS,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;AAClE,YAAM,KAAK,GAAG,IAAI,IAAI;AACtB,YAAM,IAAI,cAAc,YAAY,WAAW,WAAW,CAAC;AAC3D,YAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE;AAC5B,YAAM,KAAK,GAAG,OAAO,IAAI,EAAE;AAE3B,YAAM,IAAIA,OAAM,KAAK,eAAe,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AAChE,UAAI,EAAE,IAAG;AAAI,cAAM,IAAI,MAAM,mBAAmB;AAChD,QAAE,eAAc;AAChB,aAAO;IACT;;IAGA,WAAQ;AACN,aAAO,sBAAsB,KAAK,CAAC;IACrC;IAEA,QAAQ,SAAyB,uBAAqB;AACpD,wBAAkB,MAAM;AACxB,UAAI,WAAW;AAAO,eAAOQ,YAAW,IAAI,WAAW,IAAI,CAAC;AAC5D,YAAM,IAAI,GAAG,QAAQ,KAAK,CAAC;AAC3B,YAAM,IAAI,GAAG,QAAQ,KAAK,CAAC;AAC3B,UAAI,WAAW,aAAa;AAC1B,YAAI,KAAK,YAAY;AAAM,gBAAM,IAAI,MAAM,8BAA8B;AACzE,eAAO,YAAY,WAAW,GAAG,KAAK,QAAQ,GAAG,GAAG,CAAC;MACvD;AACA,aAAO,YAAY,GAAG,CAAC;IACzB;IAEA,MAAM,QAAuB;AAC3B,aAAO,WAAW,KAAK,QAAQ,MAAM,CAAC;IACxC;;IAGA,iBAAc;IAAU;IACxB,OAAO,YAAY,KAAQ;AACzB,aAAO,UAAU,UAAU,YAAY,OAAO,GAAG,GAAG,SAAS;IAC/D;IACA,OAAO,QAAQ,KAAQ;AACrB,aAAO,UAAU,UAAU,YAAY,OAAO,GAAG,GAAG,KAAK;IAC3D;IACA,aAAU;AACR,aAAO,KAAK,SAAQ,IAAK,IAAI,UAAU,KAAK,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI;IAClF;IACA,gBAAa;AACX,aAAO,KAAK,QAAQ,KAAK;IAC3B;IACA,WAAQ;AACN,aAAO,WAAW,KAAK,QAAQ,KAAK,CAAC;IACvC;IACA,oBAAiB;AACf,aAAO,KAAK,QAAQ,SAAS;IAC/B;IACA,eAAY;AACV,aAAO,WAAW,KAAK,QAAQ,SAAS,CAAC;IAC3C;;AAQF,QAAM,WACJ,UAAU,YACV,SAAS,aAAa,OAAiB;AAErC,QAAI,MAAM,SAAS;AAAM,YAAM,IAAI,MAAM,oBAAoB;AAG7D,UAAM,MAAM,gBAAgB,KAAK;AACjC,UAAM,QAAQ,MAAM,SAAS,IAAI;AACjC,WAAO,QAAQ,IAAI,OAAO,OAAO,KAAK,IAAI;EAC5C;AACF,QAAM,gBACJ,UAAU,iBACV,SAAS,kBAAkB,OAAiB;AAC1C,WAAO,GAAG,OAAO,SAAS,KAAK,CAAC;EAClC;AAEF,QAAM,aAAa,QAAQ,MAAM;AAEjC,WAAS,WAAW,KAAW;AAE7B,aAAS,aAAa,QAAQ,KAAKE,MAAK,UAAU;AAClD,WAAO,GAAG,QAAQ,GAAG;EACvB;AAEA,WAAS,mBAAmB,SAAqB,SAAgB;AAC/D,aAAO,SAAS,QAAW,SAAS;AACpC,WAAO,UAAU,SAAO,KAAK,OAAO,GAAG,QAAW,mBAAmB,IAAI;EAC3E;AAUA,WAAS,QAAQ,SAAqB,YAAqB,MAAmB;AAC5E,QAAI,CAAC,aAAa,WAAW,EAAE,KAAK,CAAC,MAAM,KAAK,IAAI;AAClD,YAAM,IAAI,MAAM,qCAAqC;AACvD,UAAM,EAAE,MAAM,SAAS,aAAY,IAAK,gBAAgB,MAAM,cAAc;AAC5E,cAAU,mBAAmB,SAAS,OAAO;AAI7C,UAAM,QAAQ,cAAc,OAAO;AACnC,UAAM,IAAI,eAAe,IAAI,UAAU;AACvC,UAAM,WAAW,CAAC,WAAW,CAAC,GAAG,WAAW,KAAK,CAAC;AAElD,QAAI,gBAAgB,QAAQ,iBAAiB,OAAO;AAGlD,YAAM,IAAI,iBAAiB,OAAOP,aAAY,QAAQ,SAAS,IAAI;AACnE,eAAS,KAAK,YAAY,gBAAgB,CAAC,CAAC;IAC9C;AACA,UAAM,OAAO,YAAY,GAAG,QAAQ;AACpC,UAAM,IAAI;AASV,aAAS,MAAM,QAAkB;AAG/B,YAAM,IAAI,SAAS,MAAM;AACzB,UAAI,CAAC,GAAG,YAAY,CAAC;AAAG;AACxB,YAAM,KAAK,GAAG,IAAI,CAAC;AACnB,YAAM,IAAIH,OAAM,KAAK,SAAS,CAAC,EAAE,SAAQ;AACzC,YAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AACvB,UAAI,MAAMU;AAAK;AACf,YAAM,IAAI,GAAG,OAAO,KAAK,GAAG,OAAO,IAAI,IAAI,CAAC,CAAC;AAC7C,UAAI,MAAMA;AAAK;AACf,UAAI,YAAY,EAAE,MAAM,IAAI,IAAI,KAAK,OAAO,EAAE,IAAIL,IAAG;AACrD,UAAI,QAAQ;AACZ,UAAI,QAAQ,sBAAsB,CAAC,GAAG;AACpC,gBAAQ,GAAG,IAAI,CAAC;AAChB,oBAAY;MACd;AACA,aAAO,IAAI,UAAU,GAAG,OAAO,QAAQ;IACzC;AACA,WAAO,EAAE,MAAM,MAAK;EACtB;AAaA,WAAS,KAAK,SAAc,WAAoB,OAAsB,CAAA,GAAE;AACtE,cAAU,YAAY,WAAW,OAAO;AACxC,UAAM,EAAE,MAAM,MAAK,IAAK,QAAQ,SAAS,WAAW,IAAI;AACxD,UAAM,OAAO,eAAmC,KAAK,WAAW,GAAG,OAAOD,KAAI;AAC9E,UAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,WAAO;EACT;AAEA,WAAS,cAAc,IAAuB;AAE5C,QAAI,MAA6B;AACjC,UAAM,QAAQ,OAAO,OAAO,YAAY,QAAQ,EAAE;AAClD,UAAM,QACJ,CAAC,SACD,OAAO,QACP,OAAO,OAAO,YACd,OAAO,GAAG,MAAM,YAChB,OAAO,GAAG,MAAM;AAClB,QAAI,CAAC,SAAS,CAAC;AACb,YAAM,IAAI,MAAM,0EAA0E;AAC5F,QAAI,OAAO;AACT,YAAM,IAAI,UAAU,GAAG,GAAG,GAAG,CAAC;IAChC,WAAW,OAAO;AAChB,UAAI;AACF,cAAM,UAAU,UAAU,YAAY,OAAO,EAAE,GAAG,KAAK;MACzD,SAAS,UAAU;AACjB,YAAI,EAAE,oBAAoB,IAAI;AAAM,gBAAM;MAC5C;AACA,UAAI,CAAC,KAAK;AACR,YAAI;AACF,gBAAM,UAAU,UAAU,YAAY,OAAO,EAAE,GAAG,SAAS;QAC7D,SAAS,OAAO;AACd,iBAAO;QACT;MACF;IACF;AACA,QAAI,CAAC;AAAK,aAAO;AACjB,WAAO;EACT;AAeA,WAAS,OACP,WACA,SACA,WACA,OAAwB,CAAA,GAAE;AAE1B,UAAM,EAAE,MAAM,SAAS,OAAM,IAAK,gBAAgB,MAAM,cAAc;AACtE,gBAAY,YAAY,aAAa,SAAS;AAC9C,cAAU,mBAAmB,YAAY,WAAW,OAAO,GAAG,OAAO;AACrE,QAAI,YAAY;AAAM,YAAM,IAAI,MAAM,oCAAoC;AAC1E,UAAM,MACJ,WAAW,SACP,cAAc,SAAS,IACvB,UAAU,UAAU,YAAY,OAAO,SAAgB,GAAG,MAAM;AACtE,QAAI,QAAQ;AAAO,aAAO;AAC1B,QAAI;AACF,YAAM,IAAIJ,OAAM,UAAU,SAAS;AACnC,UAAI,QAAQ,IAAI,SAAQ;AAAI,eAAO;AACnC,YAAM,EAAE,GAAG,EAAC,IAAK;AACjB,YAAM,IAAI,cAAc,OAAO;AAC/B,YAAM,KAAK,GAAG,IAAI,CAAC;AACnB,YAAM,KAAK,GAAG,OAAO,IAAI,EAAE;AAC3B,YAAM,KAAK,GAAG,OAAO,IAAI,EAAE;AAC3B,YAAM,IAAIA,OAAM,KAAK,eAAe,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AAChE,UAAI,EAAE,IAAG;AAAI,eAAO;AACpB,YAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AACvB,aAAO,MAAM;IACf,SAAS,GAAG;AACV,aAAO;IACT;EACF;AAEA,WAAS,iBACP,WACA,SACA,OAAyB,CAAA,GAAE;AAE3B,UAAM,EAAE,QAAO,IAAK,gBAAgB,MAAM,cAAc;AACxD,cAAU,mBAAmB,SAAS,OAAO;AAC7C,WAAO,UAAU,UAAU,WAAW,WAAW,EAAE,iBAAiB,OAAO,EAAE,QAAO;EACtF;AAEA,SAAO,OAAO,OAAO;IACnB;IACA,cAAAC;IACA;IACA,OAAAC;IACA;IACA,OAAAF;IACA;IACA;IACA;IACA;IACA;GACD;AACH;AAsHA,SAAS,gCAAmC,GAAqB;AAC/D,QAAM,QAA4B;IAChC,GAAG,EAAE;IACL,GAAG,EAAE;IACL,GAAG,EAAE,GAAG;IACR,GAAG,EAAE;IACL,GAAG,EAAE;IACL,IAAI,EAAE;IACN,IAAI,EAAE;;AAER,QAAM,KAAK,EAAE;AACb,MAAI,iBAAiB,EAAE,2BACnB,MAAM,KAAK,IAAI,IAAI,EAAE,yBAAyB,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAC3E;AACJ,QAAM,KAAK,MAAM,MAAM,GAAG;IACxB,MAAM,EAAE;IACR;IACA,cAAc,EAAE;GACjB;AACD,QAAM,YAAqC;IACzC;IACA;IACA,oBAAoB,EAAE;IACtB,MAAM,EAAE;IACR,eAAe,EAAE;IACjB,eAAe,EAAE;IACjB,WAAW,EAAE;IACb,SAAS,EAAE;;AAEb,SAAO,EAAE,OAAO,UAAS;AAC3B;AACA,SAAS,0BAA0B,GAAY;AAC7C,QAAM,EAAE,OAAO,UAAS,IAAK,gCAAgC,CAAC;AAC9D,QAAM,YAAuB;IAC3B,MAAM,EAAE;IACR,aAAa,EAAE;IACf,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,eAAe,EAAE;;AAEnB,SAAO,EAAE,OAAO,WAAW,MAAM,EAAE,MAAM,UAAS;AACpD;AAkCA,SAAS,4BAA4B,GAAc,QAAa;AAC9D,QAAMW,SAAQ,OAAO;AACrB,SAAO,OAAO,OAAO,CAAA,GAAI,QAAQ;IAC/B,iBAAiBA;IACjB,OAAO,OAAO,OAAO,CAAA,GAAI,GAAG,QAAQA,OAAM,GAAG,OAAOA,OAAM,GAAG,IAAI,CAAC;GACnE;AACH;AAGM,SAAU,YAAY,GAAY;AACtC,QAAM,EAAE,OAAO,WAAW,MAAM,UAAS,IAAK,0BAA0B,CAAC;AACzE,QAAMA,SAAQ,aAAa,OAAO,SAAS;AAC3C,QAAM,QAAQ,MAAMA,QAAO,MAAM,SAAS;AAC1C,SAAO,4BAA4B,GAAG,KAAK;AAC7C;;;AC10DM,SAAU,YAAY,UAAoB,SAAc;AAC5D,QAAM,SAAS,CAAC,SAAyB,YAAY,EAAE,GAAG,UAAU,KAAU,CAAE;AAChF,SAAO,EAAE,GAAG,OAAO,OAAO,GAAG,OAAM;AACrC;;;ACoBA,IAAM,kBAA2C;EAC/C,GAAG,OAAO,oEAAoE;EAC9E,GAAG,OAAO,oEAAoE;EAC9E,GAAG,OAAO,CAAC;EACX,GAAG,OAAO,CAAC;EACX,GAAG,OAAO,CAAC;EACX,IAAI,OAAO,oEAAoE;EAC/E,IAAI,OAAO,oEAAoE;;AAGjF,IAAM,iBAAmC;EACvC,MAAM,OAAO,oEAAoE;EACjF,SAAS;IACP,CAAC,OAAO,oCAAoC,GAAG,CAAC,OAAO,oCAAoC,CAAC;IAC5F,CAAC,OAAO,qCAAqC,GAAG,OAAO,oCAAoC,CAAC;;;AAMhG,IAAMC,OAAsB,uBAAO,CAAC;AAMpC,SAAS,QAAQ,GAAS;AACxB,QAAM,IAAI,gBAAgB;AAE1B,QAAMC,OAAM,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE;AAE3E,QAAM,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,EAAE;AAC5D,QAAM,KAAM,IAAI,IAAI,IAAK;AACzB,QAAM,KAAM,KAAK,KAAK,IAAK;AAC3B,QAAM,KAAM,KAAK,IAAIA,MAAK,CAAC,IAAI,KAAM;AACrC,QAAM,KAAM,KAAK,IAAIA,MAAK,CAAC,IAAI,KAAM;AACrC,QAAM,MAAO,KAAK,IAAID,MAAK,CAAC,IAAI,KAAM;AACtC,QAAM,MAAO,KAAK,KAAK,MAAM,CAAC,IAAI,MAAO;AACzC,QAAM,MAAO,KAAK,KAAK,MAAM,CAAC,IAAI,MAAO;AACzC,QAAM,MAAO,KAAK,KAAK,MAAM,CAAC,IAAI,MAAO;AACzC,QAAM,OAAQ,KAAK,KAAK,MAAM,CAAC,IAAI,MAAO;AAC1C,QAAM,OAAQ,KAAK,MAAM,MAAM,CAAC,IAAI,MAAO;AAC3C,QAAM,OAAQ,KAAK,MAAMC,MAAK,CAAC,IAAI,KAAM;AACzC,QAAM,KAAM,KAAK,MAAM,MAAM,CAAC,IAAI,MAAO;AACzC,QAAM,KAAM,KAAK,IAAI,KAAK,CAAC,IAAI,KAAM;AACrC,QAAM,OAAO,KAAK,IAAID,MAAK,CAAC;AAC5B,MAAI,CAAC,KAAK,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC;AAAG,UAAM,IAAI,MAAM,yBAAyB;AAC3E,SAAO;AACT;AAEA,IAAM,OAAO,MAAM,gBAAgB,GAAG,EAAE,MAAM,QAAO,CAAE;AAgBhD,IAAM,YAA+B,YAC1C,EAAE,GAAG,iBAAiB,IAAI,MAAM,MAAM,MAAM,MAAM,eAAc,GAChEE,OAAM;;;ACwER,IAAM,SAAyB,2BAAW,KAAK;EAC7C;EAAG;EAAG;EAAI;EAAG;EAAI;EAAG;EAAI;EAAG;EAAI;EAAG;EAAG;EAAG;EAAG;EAAI;EAAI;CACpD;AACD,IAAM,QAAyB,uBAAM,WAAW,KAAK,IAAI,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,GAAE;AAC7F,IAAM,QAAyB,uBAAM,MAAM,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,EAAE,GAAE;AACxE,IAAM,QAAyB,uBAAK;AAClC,QAAM,IAAI,CAAC,KAAK;AAChB,QAAM,IAAI,CAAC,KAAK;AAChB,QAAM,MAAM,CAAC,GAAG,CAAC;AACjB,WAAS,IAAI,GAAG,IAAI,GAAG;AAAK,aAAS,KAAK;AAAK,QAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC;AAChF,SAAO;AACT,GAAE;AACF,IAAM,OAAwB,uBAAM,MAAM,CAAC,GAAE;AAC7C,IAAM,OAAwB,uBAAM,MAAM,CAAC,GAAE;AAG7C,IAAM,YAA4B;EAChC,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC;EACvD,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC;EACvD,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC;EACvD,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC;EACvD,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC;EACvD,IAAI,CAAC,MAAM,WAAW,KAAK,CAAC,CAAC;AAC/B,IAAM,aAA6B,qBAAK,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AACvF,IAAM,aAA6B,qBAAK,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AACvF,IAAM,QAAwB,4BAAY,KAAK;EAC7C;EAAY;EAAY;EAAY;EAAY;CACjD;AACD,IAAM,QAAwB,4BAAY,KAAK;EAC7C;EAAY;EAAY;EAAY;EAAY;CACjD;AAED,SAAS,SAAS,OAAe,GAAW,GAAW,GAAS;AAC9D,MAAI,UAAU;AAAG,WAAO,IAAI,IAAI;AAChC,MAAI,UAAU;AAAG,WAAQ,IAAI,IAAM,CAAC,IAAI;AACxC,MAAI,UAAU;AAAG,YAAQ,IAAI,CAAC,KAAK;AACnC,MAAI,UAAU;AAAG,WAAQ,IAAI,IAAM,IAAI,CAAC;AACxC,SAAO,KAAK,IAAI,CAAC;AACnB;AAEA,IAAM,UAA0B,oBAAI,YAAY,EAAE;AAC5C,IAAO,YAAP,cAAyB,OAAiB;EAO9C,cAAA;AACE,UAAM,IAAI,IAAI,GAAG,IAAI;AAPf,SAAA,KAAK,aAAa;AAClB,SAAA,KAAK,aAAa;AAClB,SAAA,KAAK,aAAa;AAClB,SAAA,KAAK,YAAa;AAClB,SAAA,KAAK,aAAa;EAI1B;EACU,MAAG;AACX,UAAM,EAAE,IAAI,IAAI,IAAI,IAAI,GAAE,IAAK;AAC/B,WAAO,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;EAC5B;EACU,IAAI,IAAY,IAAY,IAAY,IAAY,IAAU;AACtE,SAAK,KAAK,KAAK;AACf,SAAK,KAAK,KAAK;AACf,SAAK,KAAK,KAAK;AACf,SAAK,KAAK,KAAK;AACf,SAAK,KAAK,KAAK;EACjB;EACU,QAAQ,MAAgB,QAAc;AAC9C,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK,UAAU;AAAG,cAAQ,CAAC,IAAI,KAAK,UAAU,QAAQ,IAAI;AAElF,QAAI,KAAK,KAAK,KAAK,GAAG,KAAK,IACvB,KAAK,KAAK,KAAK,GAAG,KAAK,IACvB,KAAK,KAAK,KAAK,GAAG,KAAK,IACvB,KAAK,KAAK,KAAK,GAAG,KAAK,IACvB,KAAK,KAAK,KAAK,GAAG,KAAK;AAI3B,aAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS;AACtC,YAAM,SAAS,IAAI;AACnB,YAAM,MAAM,MAAM,KAAK,GAAG,MAAM,MAAM,KAAK;AAC3C,YAAM,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK;AACvC,YAAM,KAAK,WAAW,KAAK,GAAG,KAAK,WAAW,KAAK;AACnD,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAM,KAAM,KAAK,KAAK,SAAS,OAAO,IAAI,IAAI,EAAE,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,KAAM;AACzF,aAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,GAAG,KAAK,IAAI,KAAK;MACzD;AAEA,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAM,KAAM,KAAK,KAAK,SAAS,QAAQ,IAAI,IAAI,EAAE,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,KAAM;AAC1F,aAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,GAAG,KAAK,IAAI,KAAK;MACzD;IACF;AAEA,SAAK,IACF,KAAK,KAAK,KAAK,KAAM,GACrB,KAAK,KAAK,KAAK,KAAM,GACrB,KAAK,KAAK,KAAK,KAAM,GACrB,KAAK,KAAK,KAAK,KAAM,GACrB,KAAK,KAAK,KAAK,KAAM,CAAC;EAE3B;EACU,aAAU;AAClB,UAAM,OAAO;EACf;EACA,UAAO;AACL,SAAK,YAAY;AACjB,UAAM,KAAK,MAAM;AACjB,SAAK,IAAI,GAAG,GAAG,GAAG,GAAG,CAAC;EACxB;;AAQK,IAAM,YAAmC,6BAAa,MAAM,IAAI,UAAS,CAAE;;;ACnQlF,IAAM,QAAQ,UAAK;AACnB,IAAM,cAAc,kBAAkBC,OAAM;AAE5C,SAAS,cAAc,OAAiB;AACtC,SAAO,KAAK;AACZ,QAAM,IAAI,MAAM,WAAW,IAAI,MAAM,WAAW,KAAK;AACrD,SAAO,OAAO,OAAO,CAAC;AACxB;AAEA,SAAS,cAAc,KAAW;AAChC,MAAI,OAAO,QAAQ;AAAU,UAAM,IAAI,MAAM,iBAAiB;AAC9D,SAAOC,YAAW,IAAI,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG,CAAC;AACtD;AAEA,IAAM,gBAAgB,YAAY,cAAc;AAEhD,IAAM,mBAA6B,EAAE,SAAS,UAAY,QAAQ,SAAU;AACrE,IAAM,kBAA0B;AAOvC,IAAM,UAAU,CAAC,SAAqB,UAAUD,QAAO,IAAI,CAAC;AAC5D,IAAM,UAAU,CAAC,SAAqB,WAAW,IAAI,EAAE,UAAU,GAAG,KAAK;AACzE,IAAM,QAAQ,CAAC,MAAa;AAC1B,MAAI,CAAC,OAAO,cAAc,CAAC,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,GAAG;AACxD,UAAM,IAAI,MAAM,sDAAsD,CAAC;EACzE;AACA,QAAM,MAAM,IAAI,WAAW,CAAC;AAC5B,aAAW,GAAG,EAAE,UAAU,GAAG,GAAG,KAAK;AACrC,SAAO;AACT;AAYM,IAAO,QAAP,MAAO,OAAK;EAChB,IAAI,cAAW;AACb,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,mBAAmB;IACrC;AACA,WAAO,QAAQ,KAAK,OAAO;EAC7B;EACA,IAAI,aAAU;AACZ,WAAO,KAAK;EACd;EACA,IAAI,aAAU;AACZ,WAAO,KAAK;EACd;EACA,IAAI,aAAU;AACZ,WAAO,KAAK,gBAAgB;EAC9B;EACA,IAAI,YAAS;AACX,WAAO,KAAK,UAAU;EACxB;EACA,IAAI,qBAAkB;AACpB,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,gBAAgB;IAClC;AACA,WAAO,YAAY,OACjB,KAAK,UAAU,KAAK,SAAS,SAAS,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;EAEjF;EACA,IAAI,oBAAiB;AACnB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,eAAe;IACjC;AACA,WAAO,YAAY,OAAO,KAAK,UAAU,KAAK,SAAS,QAAQ,KAAK,MAAM,CAAC;EAC7E;EAEO,OAAO,eAAe,MAAkB,WAAqB,kBAAgB;AAClF,WAAO,IAAI;AACX,QAAI,IAAI,KAAK,SAAS,OAAO,IAAI,KAAK,SAAS,KAAK;AAClD,YAAM,IAAI,MACR,mFACE,KAAK,MAAM;IAEjB;AACA,UAAM,IAAI,KAAK,QAAQ,eAAe,IAAI;AAC1C,WAAO,IAAI,OAAM;MACf;MACA,WAAW,EAAE,MAAM,EAAE;MACrB,YAAY,EAAE,MAAM,GAAG,EAAE;KAC1B;EACH;EAEO,OAAO,gBAAgB,WAAmB,WAAqB,kBAAgB;AAEpF,UAAM,YAAwB,YAAY,OAAO,SAAS;AAC1D,UAAM,UAAU,WAAW,SAAS;AACpC,UAAM,UAAU,QAAQ,UAAU,GAAG,KAAK;AAC1C,UAAM,MAAM;MACV;MACA,OAAO,UAAU,CAAC;MAClB,mBAAmB,QAAQ,UAAU,GAAG,KAAK;MAC7C,OAAO,QAAQ,UAAU,GAAG,KAAK;MACjC,WAAW,UAAU,MAAM,IAAI,EAAE;;AAEnC,UAAM,MAAM,UAAU,MAAM,EAAE;AAC9B,UAAM,SAAS,IAAI,CAAC,MAAM;AAC1B,QAAI,YAAY,SAAS,SAAS,YAAY,QAAQ,GAAG;AACvD,YAAM,IAAI,MAAM,kBAAkB;IACpC;AACA,QAAI,QAAQ;AACV,aAAO,IAAI,OAAM,EAAE,GAAG,KAAK,YAAY,IAAI,MAAM,CAAC,EAAC,CAAE;IACvD,OAAO;AACL,aAAO,IAAI,OAAM,EAAE,GAAG,KAAK,WAAW,IAAG,CAAE;IAC7C;EACF;EAEO,OAAO,SAAS,MAAuB;AAC5C,WAAO,OAAM,gBAAgB,KAAK,KAAK;EACzC;EAWA,YAAY,KAAa;AATT,SAAA,QAAgB;AAChB,SAAA,QAAgB;AAChB,SAAA,YAA+B;AAC/B,SAAA,oBAA4B;AAO1C,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,YAAM,IAAI,MAAM,+CAA+C;IACjE;AACA,SAAK,WAAW,IAAI,YAAY;AAChC,SAAK,QAAQ,IAAI,SAAS;AAC1B,SAAK,YAAY,IAAI,aAAa;AAClC,SAAK,QAAQ,IAAI,SAAS;AAC1B,SAAK,oBAAoB,IAAI,qBAAqB;AAClD,QAAI,CAAC,KAAK,OAAO;AACf,UAAI,KAAK,qBAAqB,KAAK,OAAO;AACxC,cAAM,IAAI,MAAM,0DAA0D;MAC5E;IACF;AACA,QAAI,IAAI,aAAa,IAAI,YAAY;AACnC,YAAM,IAAI,MAAM,+CAA+C;IACjE;AACA,QAAI,IAAI,YAAY;AAClB,UAAI,CAAC,UAAK,MAAM,kBAAkB,IAAI,UAAU,GAAG;AACjD,cAAM,IAAI,MAAM,qBAAqB;MACvC;AACA,WAAK,UACH,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa,cAAc,IAAI,UAAU;AACpF,WAAK,eAAe,cAAc,KAAK,OAAO;AAC9C,WAAK,SAAS,UAAK,aAAa,IAAI,YAAY,IAAI;IACtD,WAAW,IAAI,WAAW;AACxB,WAAK,SAAS,MAAM,QAAQ,IAAI,SAAS,EAAE,WAAW,IAAI;IAC5D,OAAO;AACL,YAAM,IAAI,MAAM,0CAA0C;IAC5D;AACA,SAAK,UAAU,QAAQ,KAAK,MAAM;EACpC;EAEO,OAAO,MAAY;AACxB,QAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,YAAM,IAAI,MAAM,iCAAiC;IACnD;AACA,QAAI,WAAW,KAAK,IAAI,GAAG;AACzB,aAAO;IACT;AACA,UAAM,QAAQ,KAAK,QAAQ,aAAa,EAAE,EAAE,MAAM,GAAG;AAErD,QAAI,QAAe;AACnB,eAAW,KAAK,OAAO;AACrB,YAAM,IAAI,cAAc,KAAK,CAAC;AAC9B,YAAM,KAAK,KAAK,EAAE,CAAC;AACnB,UAAI,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,OAAO;AACxC,cAAM,IAAI,MAAM,0BAA0B,CAAC;AAC7C,UAAI,MAAM,CAAC;AACX,UAAI,CAAC,OAAO,cAAc,GAAG,KAAK,OAAO,iBAAiB;AACxD,cAAM,IAAI,MAAM,eAAe;MACjC;AAEA,UAAI,EAAE,CAAC,MAAM,KAAK;AAChB,eAAO;MACT;AACA,cAAQ,MAAM,YAAY,GAAG;IAC/B;AACA,WAAO;EACT;EAEO,YAAY,OAAa;AAC9B,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,WAAW;AACnC,YAAM,IAAI,MAAM,+BAA+B;IACjD;AACA,QAAI,OAAO,MAAM,KAAK;AACtB,QAAI,SAAS,iBAAiB;AAE5B,YAAM,OAAO,KAAK;AAClB,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,MAAM,qCAAqC;MACvD;AAEA,aAAO,YAAY,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IACpD,OAAO;AAEL,aAAO,YAAY,KAAK,QAAQ,IAAI;IACtC;AACA,UAAM,IAAI,KAAK,QAAQ,KAAK,WAAW,IAAI;AAC3C,UAAM,aAAa,cAAc,EAAE,MAAM,GAAG,EAAE,CAAC;AAC/C,UAAM,YAAY,EAAE,MAAM,EAAE;AAC5B,QAAI,CAAC,UAAK,MAAM,kBAAkB,UAAU,GAAG;AAC7C,YAAM,IAAI,MAAM,+BAA+B;IACjD;AACA,UAAM,MAAgB;MACpB,UAAU,KAAK;MACf;MACA,OAAO,KAAK,QAAQ;MACpB,mBAAmB,KAAK;MACxB;;AAEF,QAAI;AAEF,UAAI,KAAK,YAAY;AACnB,cAAM,QAAQ,IAAI,KAAK,UAAW,YAAY,UAAK,MAAM,CAAC;AAC1D,YAAI,CAAC,UAAK,MAAM,kBAAkB,KAAK,GAAG;AACxC,gBAAM,IAAI,MAAM,mEAAmE;QACrF;AACA,YAAI,aAAa;MACnB,OAAO;AACL,cAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,EAAE,IAAI,MAAM,eAAe,UAAU,CAAC;AAE7E,YAAI,MAAM,OAAO,MAAM,IAAI,GAAG;AAC5B,gBAAM,IAAI,MAAM,sEAAsE;QACxF;AACA,YAAI,YAAY,MAAM,WAAW,IAAI;MACvC;AACA,aAAO,IAAI,OAAM,GAAG;IACtB,SAAS,KAAK;AACZ,aAAO,KAAK,YAAY,QAAQ,CAAC;IACnC;EACF;EAEO,KAAK,MAAgB;AAC1B,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,oBAAoB;IACtC;AACA,WAAO,MAAM,EAAE;AACf,WAAO,UAAK,KAAK,MAAM,KAAK,OAAQ,EAAE,kBAAiB;EACzD;EAEO,OAAO,MAAkB,WAAqB;AACnD,WAAO,MAAM,EAAE;AACf,WAAO,WAAW,EAAE;AACpB,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,mBAAmB;IACrC;AACA,QAAI;AACJ,QAAI;AACF,YAAM,UAAK,UAAU,YAAY,SAAS;IAC5C,SAAS,OAAO;AACd,aAAO;IACT;AACA,WAAO,UAAK,OAAO,KAAK,MAAM,KAAK,SAAS;EAC9C;EAEO,kBAAe;AACpB,SAAK,UAAU;AACf,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,KAAK,CAAC;AACxB,WAAK,eAAe;IACtB;AACA,WAAO;EACT;EACO,SAAM;AACX,WAAO;MACL,OAAO,KAAK;MACZ,MAAM,KAAK;;EAEf;EAEQ,UAAU,SAAiB,KAAe;AAChD,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,kBAAkB;IACpC;AACA,WAAO,KAAK,EAAE;AAEd,WAAO,YACL,MAAM,OAAO,GACb,IAAI,WAAW,CAAC,KAAK,KAAK,CAAC,GAC3B,MAAM,KAAK,iBAAiB,GAC5B,MAAM,KAAK,KAAK,GAChB,KAAK,WACL,GAAG;EAEP;;;;AnG3UF;EACE;EACA;EACA;EACA;EACA;EACA;OAGK;AGTP,SAAS,uBAAAE,4BAAmD;AAC5D,SAAmB,SAAAC,cAAa;AODhC,SAAS,cAAc,eAAe,kBAAkB;AYAxD,SAAS,iBAAAC,uBAAqB;AAC9B,SAAS,aAAa;AIDtB,SAAS,gBAAAC,sBAAoB;ACiB7B;EACE;EACA;EACA;EACA,eAAAC;OACK;AACP,SAAS,iBAAAC,gBAAe,gBAAAC,qBAAoB;ApCpBrC,IAAM,kBAAN,cAA8B,MAAM;EACzC,YACE,SACgB,MAChB,OACA;AACA,UAAM,SAAS,EAAE,MAAM,CAAC;AAHR,SAAA,OAAA;AAIhB,SAAK,OAAO;EACd;AACF;AAMO,IAAM,eAAN,cAA2B,gBAAgB;EAChD,YAAY,SAAiB,OAAe;AAC1C,UAAM,SAAS,iBAAiB,KAAK;AACrC,SAAK,OAAO;EACd;AACF;AAMO,IAAM,iBAAN,cAA6B,gBAAgB;EAClD,YAAY,SAAiB,OAAe;AAC1C,UAAM,SAAS,mBAAmB,KAAK;AACvC,SAAK,OAAO;EACd;AACF;AAMO,IAAM,kBAAN,cAA8B,gBAAgB;EACnD,YAAY,SAAiB,OAAe;AAC1C,UAAM,SAAS,oBAAoB,KAAK;AACxC,SAAK,OAAO;EACd;AACF;ACtBO,SAASC,kBAAiB,UAA2B;AAC1D,SAAO,iBAAkB,UAAU,QAAO;AAC5C;AAMA,IAAM,kBAAkB;AAMxB,SAAS,wBAAwB,cAA4B;AAC3D,MACE,CAAC,OAAO,UAAU,YAAY,KAC9B,eAAe,KACf,eAAe,iBACf;AACA,UAAM,IAAI;MACR,+DAA+D,eAAe,UAAU,OAAO,YAAY,CAAC;IAC9G;EACF;AACF;AAOA,SAAS,eACP,MACA,eAAe,GAIf;AACA,QAAM,SAAS,MAAM,eAAe,IAAI;AACxC,QAAM,QAAQ,OAAO,OAAO,oBAAoB,YAAY,EAAE;AAC9D,MAAI,CAAC,MAAM,YAAY;AACrB,UAAM,IAAI,MAAM,8CAA8C;EAChE;AACA,QAAM,YAAY,IAAI,WAAW,MAAM,UAAU;AACjD,QAAM,SAASC,cAAa,SAAS;AACrC,SAAO,EAAE,WAAW,OAAO;AAC7B;AAKA,SAAS,kBAAkB,WAGzB;AACA,QAAM,UAAU,oBAAoB,MAAM,SAAS,CAAC;AACpD,SAAO;IACL,YAAY;IACZ,SAAS,QAAQ;EACnB;AACF;AAQA,eAAe,gBACb,MACA,eAAe,GAId;AAGD,QAAM,EAAE,MAAAC,MAAK,IAAI,MAAM,OAAO,oBAAoB;AAClD,QAAM,EAAE,QAAAC,QAAO,IAAI,MAAM,OAAO,sBAAsB;AACtD,QAAM,EAAE,SAAAC,SAAQ,IAAI,MAAM,OAAO,uBAA0B;AAG3D,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,IAAIF,MAAKC,SAAQ,QAAQ,OAAO,cAAc,GAAG,IAAI;AACzD,MAAI,MAAM,EAAE,MAAM,GAAG,EAAE;AACvB,MAAI,YAAY,EAAE,MAAM,EAAE;AAG1B,QAAM,UAAU;IACd;;IACA;;IACC,aAAa,iBAAkB;;IAChC;;EACF;AAEA,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,SAAK,CAAC,IAAI;AACV,SAAK,IAAI,KAAK,CAAC;AAEf,SAAK,EAAE,IAAK,UAAU,KAAM;AAC5B,SAAK,EAAE,IAAK,UAAU,KAAM;AAC5B,SAAK,EAAE,IAAK,UAAU,IAAK;AAC3B,SAAK,EAAE,IAAI,QAAQ;AAEnB,QAAID,MAAKC,SAAQ,WAAW,IAAI;AAChC,UAAM,EAAE,MAAM,GAAG,EAAE;AACnB,gBAAY,EAAE,MAAM,EAAE;EACxB;AAEA,QAAM,iBAA6BC,SAAQ,aAAa,GAAG;AAG3D,QAAM,UAAU,IAAI,WAAW,EAAE;AACjC,UAAQ,IAAI,KAAK,CAAC;AAClB,UAAQ,IAAI,gBAAgB,EAAE;AAG9B,QAAM,YAAY,SAAS,cAAc;AAEzC,SAAO,EAAE,WAAW,SAAS,UAAU;AACzC;AAQA,eAAe,cACb,MACA,eAAe,GAId;AACD,QAAM,SAAS,MAAM,eAAe,IAAI;AAExC,QAAM,QAAQ,OAAO,OAAO,gBAAgB,YAAY,OAAO;AAC/D,MAAI,CAAC,MAAM,YAAY;AACrB,UAAM,IAAI,MAAM,6CAA6C;EAC/D;AACA,QAAM,WAAW,IAAI,WAAW,MAAM,UAAU;AAKhD,WAAS,CAAC,KAAK,SAAS,CAAC,KAAK,KAAK;AAKnC,MAAI;AACF,UAAM,gBAAgB,MAAM,OAAO,aAAa;AAChD,UAAM,SACJ,aAAa,gBAAgB,cAAc,UAAU;AACvD,UAAM,SAAS,IAAI,OAAO,EAAE,SAAS,UAAU,CAAC;AAEhD,UAAM,SAAS,MAAM,KAAK,QAAQ,EAC/B,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACV,UAAM,iBAAiB,0BAA0B,MAAM;AACvD,UAAM,YAAY,OAAO,gBAAgB,cAAc;AACvD,WAAO;;;MAGL,YAAY;MACZ;IACF;EACF,QAAQ;AACN,UAAM,IAAI;MACR;IACF;EACF;AACF;AAWO,SAAS,2BACd,UACA,eAAe,GAIf;AACA,0BAAwB,YAAY;AACpC,QAAM,OAAO,mBAAmB,QAAQ;AACxC,QAAM,SAAS,eAAe,MAAM,YAAY;AAChD,OAAK,KAAK,CAAC;AACX,SAAO;AACT;AAoBA,eAAsB,mBACpB,UACA,eAAe,GACQ;AACvB,0BAAwB,YAAY;AACpC,QAAM,OAAO,mBAAmB,QAAQ;AAExC,QAAM,QAAQ,eAAe,MAAM,YAAY;AAC/C,QAAM,MAAM,kBAAkB,MAAM,SAAS;AAG7C,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,gBAAgB,MAAM,YAAY;EACnD,QAAQ;AACN,aAAS,EAAE,WAAW,IAAI,WAAW,EAAE,GAAG,WAAW,GAAG;EAC1D;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,cAAc,MAAM,YAAY;EAC/C,QAAQ;AACN,WAAO,EAAE,YAAY,IAAI,WAAW,GAAG;EACzC;AAGA,OAAK,KAAK,CAAC;AAEX,SAAO,EAAE,OAAO,KAAK,QAAQ,KAAK;AACpC;AA+BA,IAAMC,mBACJ;AAEF,SAAS,SAAS,OAA2B;AAC3C,MAAI,MAAM,OAAO,CAAC;AAClB,aAAW,KAAK,MAAO,OAAM,MAAM,OAAO,OAAO,CAAC;AAClD,MAAI,SAAS;AACb,SAAO,MAAM,IAAI;AACf,aAASA,iBAAgB,OAAO,MAAM,GAAG,CAAC,IAAI;AAC9C,UAAM,MAAM;EACd;AACA,aAAW,KAAK,OAAO;AACrB,QAAI,MAAM,EAAG,UAAS,MAAM;QACvB;EACP;AACA,SAAO;AACT;AFtRO,SAAS,oBACd,QACkB;AAClB,QAAM,EAAE,QAAQ,IAAI;AACpB,MAAI,CAAC,WAAW,YAAY,SAAU,QAAO;AAE7C,QAAM,UAAU,qBAAqB,OAAO;AAG5C,QAAM,cAAc,CAClB,UACA,YAC4B,EAAE,GAAG,QAAQ,GAAG,SAAS;AAGvD,QAAM,kBAAkB,OAAO,kBAC3B,MAAM;IACJ,oBAAI,IAAI,CAAC,GAAG,QAAQ,iBAAiB,GAAG,OAAO,eAAe,CAAC;EACjE,IACA,QAAQ;AAEZ,SAAO;IACL,GAAG;IACH;IACA,cAAc,YAAY,OAAO,cAAc,QAAQ,YAAY;IACnE,iBAAiB;MACf,OAAO;MACP,QAAQ;IACV;IACA,eAAe,YAAY,OAAO,eAAe,QAAQ,aAAa;;;IAGtE,GAAI,OAAO,uBAAuB;MAChC,qBAAqB,OAAO;IAC9B;;;IAGA,GAAI,QAAQ,iBAAiB;MAC3B,eAAe,OAAO,iBAAiB,QAAQ;IACjD;IACA,GAAI,QAAQ,eAAe;MACzB,aAAa,OAAO,eAAe,QAAQ;IAC7C;EACF;AACF;AAOO,SAAS,iBACd,QACiC;AACjC,QAAM,EAAE,QAAQ,IAAI;AACpB,MAAI,CAAC,WAAW,YAAY,SAAU,QAAO;AAC7C,SAAO,qBAAqB,OAAO,EAAE;AACvC;AASO,SAAS,eAAe,QAAgC;AAE7D,MAAI,OAAO,cAAc,QAAW;AAClC,UAAM,IAAI;MACR;IACF;EACF;AAGA,MAAI,CAAC,OAAO,cAAc;AACxB,UAAM,IAAI;MACR;IACF;EACF;AAGA,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,YAAY;AACvC,QAAI,CAAC,IAAI,SAAS,WAAW,MAAM,GAAG;AACpC,YAAM,IAAI,MAAM,uBAAuB;IACzC;EACF,SAAS,OAAO;AACd,UAAM,IAAI;MACR,gGACY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;IACpE;EACF;AAGA,MAAI,OAAO,cAAc,QAAW;AAClC,QAAI,CAAC,OAAO,aAAa,OAAO,UAAU,WAAW,IAAI;AACvD,YAAM,IAAI;QACR;MACF;IACF;EACF;AAMA,MAAI,OAAO,aAAa,QAAW;AACjC,QAAI,OAAO,cAAc,QAAW;AAClC,YAAM,IAAI;QACR;MAGF;IACF;AACA,QACE,OAAO,OAAO,aAAa,YAC3B,CAACC,kBAAiB,OAAO,QAAQ,GACjC;AACA,YAAM,IAAI,gBAAgB,wCAAwC;IACpE;EACF;AAIA,MAAI,OAAO,yBAAyB,QAAW;AAC7C,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,MAAM,YAAY;AACzD,YAAM,IAAI;QACR;MACF;IACF;EACF;AAEA,MAAI,CAAC,OAAO,SAAS,YAAY;AAC/B,UAAM,IAAI,gBAAgB,gCAAgC;EAC5D;AAEA,MAAI,CAAC,OAAO,eAAe,OAAO,OAAO,gBAAgB,YAAY;AACnE,UAAM,IAAI,gBAAgB,kCAAkC;EAC9D;AAEA,MAAI,CAAC,OAAO,eAAe,OAAO,OAAO,gBAAgB,YAAY;AACnE,UAAM,IAAI,gBAAgB,kCAAkC;EAC9D;AAGA,MAAI,OAAO,kBAAkB,QAAW;AACtC,QAAI,OAAO,yBAAyB,YAAY;AAC9C,UAAI,OAAO,cAAc,WAAW,IAAI;AACtC,cAAM,IAAI,gBAAgB,gCAAgC;MAC5D;IACF,WAAW,OAAO,OAAO,kBAAkB,UAAU;AACnD,YAAM,MAAM,OAAO,cAAc,WAAW,IAAI,IAC5C,OAAO,cAAc,MAAM,CAAC,IAC5B,OAAO;AACX,UAAI,CAAC,oBAAoB,KAAK,GAAG,GAAG;AAClC,cAAM,IAAI,gBAAgB,4CAA4C;MACxE;IACF,OAAO;AACL,YAAM,IAAI;QACR;MACF;IACF;EACF;AAGA,MAAI,OAAO,WAAW,QAAW;AAC/B,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,OAAO,MAAM;AACjC,UAAI,CAAC,IAAI,SAAS,WAAW,IAAI,GAAG;AAClC,cAAM,IAAI,MAAM,mBAAmB;MACrC;IACF,SAAS,OAAO;AACd,YAAM,IAAI;QACR,uFACY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;MACpE;IACF;EACF;AAGA,MAAI,OAAO,WAAW;AACpB,QAAI,OAAO,UAAU,SAAS,UAAU;AACtC,UAAI,CAAC,OAAO,UAAU,YAAY,WAAW,YAAY,GAAG;AAC1D,cAAM,IAAI;UACR;QAEF;MACF;IACF,WAAW,OAAO,UAAU,SAAS,WAAW;AAC9C,UAAI,CAAC,OAAO,UAAU,YAAY;AAChC,cAAM,IAAI;UACR;QACF;MACF;IACF,WAAW,OAAO,UAAU,SAAS,UAAU;AAC7C,YAAM,IAAI;QACR,4BAA6B,OAAO,UAA+B,IAAI;MACzE;IACF;EACF;AAGA,MAAI,OAAO,gBAAgB,OAAO,iBAAiB;AACjD,eAAWC,UAAS,OAAO,KAAK,OAAO,YAAY,GAAG;AACpD,UAAI,CAAC,OAAO,gBAAgB,SAASA,MAAK,GAAG;AAC3C,cAAM,IAAI;UACR,qBAAqBA,MAAK;QAC5B;MACF;IACF;EACF;AACF;AAmFO,SAAS,cAAc,WAA6C;AAGzE,QAAM,SAAS,oBAAoB,SAAS;AAO5C,QAAM,YACJ,OAAO,cACN,OAAO,WACJ;IACE,OAAO;IACP,OAAO,wBAAwB;EACjC,EAAE,YACFC,mBAAkB;AAIxB,MAAI,SAAS,OAAO;AACpB,MAAI,CAAC,UAAU,OAAO,cAAc;AAClC,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,OAAO,YAAY;AACvC,YAAM,aAAa,IAAI,aAAa,WAAW,SAAS;AACxD,eAAS,GAAG,UAAU,KAAK,IAAI,QAAQ;IACzC,QAAQ;IAER;EACF;AAOA,MAAI,qBAAqB,OAAO;AAChC,MAAI,CAAC,sBAAsB,OAAO,cAAc;AAC9C,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,OAAO,YAAY;AACvC,UAAI,IAAI,aAAa,eAAe,IAAI,aAAa,aAAa;AAEhE,YAAI,IAAI,SAAS,QAAQ;AACvB,+BAAqB;QACvB,WAAW,IAAI,SAAS,QAAQ;AAC9B,+BAAqB;QACvB,WAAW,IAAI,SAAS,QAAQ;AAC9B,+BAAqB;QACvB,OAAO;AAEL,+BAAqB,OAAO,SAAS,cAAc;QACrD;MACF,OAAO;AAEL,6BAAqB,OAAO,SAAS,cAAc;MACrD;IACF,QAAQ;AACN,2BAAqB,OAAO,SAAS,cAAc;IACrD;EACF;AAIA,QAAM,gBAAgB,OAAO,iBAAiB;AAE9C,SAAO;IACL,GAAG;IACH;IACA;IACA,cAAc,OAAO;;IACrB,UAAU,OAAO,YAAY;IAC7B,cAAc,OAAO,gBAAgB;IACrC,YAAY,OAAO,cAAc;IACjC,YAAY,OAAO,cAAc;IACjC;IACA;;EACF;AACF;AAMO,SAAS,oBACd,WACkC;AAGlC,QAAM,SAAS,oBAAoB,SAAS;AAE5C,MACE,CAAC,OAAO,iBAAiB,UACzB,CAAC,OAAO,uBACR,CAAC,OAAO,mBACR,CAAC,OAAO,eACR;AACA,WAAO;EACT;AAEA,SAAO;IACL,YAAY,OAAO,SAAS;IAC5B,iBAAiB,OAAO;IACxB,qBAAqB,OAAO;IAC5B,iBAAiB,OAAO;IACxB,eAAe,OAAO;EACxB;AACF;AGhbO,SAAS,SAAS,OAA2B;AAElD,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,KAAK,GAAG;AAC3D,WAAQ,MAAiB,SAAS,QAAQ;EAC5C;AAEA,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,cAAU,OAAO,aAAa,IAAI;EACpC;AACA,SAAO,KAAK,MAAM;AACpB;AAGO,SAAS,WAAW,QAA4B;AACrD,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,SAAS,YAAY;AACtE,WAAO,IAAI,WAAW,OAAO,KAAK,QAAQ,QAAQ,CAAC;EACrD;AACA,QAAM,SAAS,KAAK,MAAM;AAC1B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;EAChC;AACA,SAAO;AACT;AAGO,SAASC,OAAM,OAA2B;AAC/C,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,WAAO,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;EAC1C;AACA,SAAO;AACT;AAaO,SAAS,WAAW,KAAyB;AAClD,SAAO,IAAI,YAAY,EAAE,OAAO,GAAG;AACrC;AAQO,SAAS,SAAS,KAAsB;AAC7C,SAAO,yBAAyB,KAAK,GAAG;AAC1C;AE/BA,eAAsB,UACpB,WACA,SACY;AACZ,QAAM;IACJ;IACA;IACA,qBAAqB;IACrB,WAAW;IACX;EACF,IAAI;AAEJ,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,QAAI;AACF,aAAO,MAAM,UAAU;IACzB,SAAS,OAAO;AACd,kBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAGpE,UAAI,eAAe,CAAC,YAAY,SAAS,GAAG;AAC1C,cAAM;MACR;AAGA,UAAI,YAAY,YAAY;AAC1B,cAAM;MACR;AAGA,YAAM,eAAe,qBACjB,KAAK,IAAI,aAAa,KAAK,IAAI,GAAG,OAAO,GAAG,QAAQ,IACpD;AAGJ,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,YAAY,CAAC;IAClE;EACF;AAGA,QAAM,aAAa,IAAI,MAAM,4BAA4B;AAC3D;AC1BO,IAAM,oBAAN,MAA6C;EACjC;EACA;EACA;EACA;EAEjB,YAAY,QAAiC;AAE3C,SAAK,eAAe,OAAO,aAAa,QAAQ,OAAO,EAAE;AACzD,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,cAAc;MACjB,YAAY,OAAO,cAAc;MACjC,YAAY,OAAO,cAAc;IACnC;AACA,SAAK,aAAa,OAAO,cAAc;EACzC;;;;;;;;;;EAWA,MAAM,cAAc,QAKO;AAEzB,SAAK,gBAAgB,MAAM;AAG3B,WAAO,UAAU,YAAY,KAAK,gBAAgB,MAAM,GAAG;MACzD,YAAY,KAAK,YAAY;MAC7B,YAAY,KAAK,YAAY;MAC7B,oBAAoB;MACpB,aAAa,CAAC,UAAU;AAGtB,eAAO,iBAAiB;MAC1B;IACF,CAAC;EACH;;;;;;EAOQ,gBAAgB,QAIf;AAEP,QAAI,CAAC,OAAO,eAAe,OAAO,YAAY,KAAK,MAAM,IAAI;AAC3D,YAAM,IAAI,gBAAgB,6BAA6B;IACzD;AACA,QAAI,CAAC,OAAO,YAAY,WAAW,IAAI,GAAG;AACxC,YAAM,IAAI;QACR,gCAAgC,OAAO,WAAW;MACpD;IACF;AAGA,QAAI,CAAC,OAAO,UAAU,OAAO,OAAO,KAAK,MAAM,IAAI;AACjD,YAAM,IAAI,gBAAgB,wBAAwB;IACpD;AACA,QAAI;AACF,YAAM,eAAe,OAAO,OAAO,MAAM;AACzC,UAAI,gBAAgB,IAAI;AACtB,cAAM,IAAI;UACR,6BAA6B,OAAO,MAAM;QAC5C;MACF;IACF,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAiB,OAAM;AAC5C,YAAM,IAAI;QACR,oCAAoC,OAAO,MAAM;QACjD,iBAAiB,QAAQ,QAAQ;MACnC;IACF;AAGA,QAAI,CAAC,OAAO,QAAQ,OAAO,KAAK,KAAK,MAAM,IAAI;AAC7C,YAAM,IAAI,gBAAgB,sBAAsB;IAClD;AACA,QAAI;AACF,UAAI,CAAC,SAAS,OAAO,IAAI,GAAG;AAC1B,cAAM,IAAI;UACR,wCAAwC,OAAO,IAAI;QACrD;MACF;IACF,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAiB,OAAM;AAC5C,YAAM,IAAI;QACR,wCAAwC,OAAO,IAAI;QACnD,iBAAiB,QAAQ,QAAQ;MACnC;IACF;EACF;;;;;;;;EASA,MAAc,gBAAgB,QAKH;AACzB,UAAM,iBAAiB,OAAO,WAAW,KAAK;AAC9C,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,cAAc;AAErE,QAAI;AAEF,YAAM,WAAW,MAAM,KAAK;QAC1B,GAAG,KAAK,YAAY;QACpB;UACE,QAAQ;UACR,SAAS;YACP,gBAAgB;UAClB;UACA,MAAM,KAAK,UAAU;YACnB,aAAa,OAAO;YACpB,QAAQ,OAAO;YACf,MAAM,OAAO;UACf,CAAC;UACD,QAAQ,WAAW;QACrB;MACF;AAEA,mBAAa,SAAS;AAGtB,UAAI,SAAS,IAAI;AAEf,cAAM,SAAU,MAAM,SAAS,KAAK;AACpC,eAAO;UACL,UAAW,OAAO,UAAU,KAAiB;UAC7C,MAAM,OAAO,MAAM;UACnB,MAAM,OAAO,MAAM;UACnB,SAAS,OAAO,SAAS;QAC3B;MACF,WAAW,SAAS,UAAU,OAAO,SAAS,SAAS,KAAK;AAE1D,cAAM,YAAa,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAIzD,eAAO;UACL,UAAU;UACV,MAAM,QAAQ,SAAS,MAAM;UAC7B,SACG,UAAU,SAAS,KACnB,UAAU,OAAO,KAClB,SAAS;QACb;MACF,WAAW,SAAS,UAAU,OAAO,SAAS,SAAS,KAAK;AAE1D,cAAM,YAAa,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAIzD,cAAM,IAAI;UACR,2BAA2B,SAAS,MAAM,MACvC,UAAU,SAAS,KACnB,UAAU,OAAO,KAClB,SAAS,UACX;QACF;MACF;AAGA,YAAM,IAAI;QACR,2BAA2B,SAAS,MAAM,IAAI,SAAS,UAAU;MACnE;IACF,SAAS,OAAO;AACd,mBAAa,SAAS;AAGtB,UAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;AACzD,cAAM,IAAI;UACR,yBAAyB,cAAc;UACvC;QACF;MACF;AAGA,UACE,iBAAiB,cAChB,MAAM,QAAQ,SAAS,cAAc,KACpC,MAAM,QAAQ,SAAS,cAAc,KACrC,MAAM,QAAQ,SAAS,WAAW,KAClC,MAAM,QAAQ,SAAS,SAAS,IAClC;AACA,cAAM,IAAI;UACR,8BAA8B,MAAM,OAAO;UAC3C;QACF;MACF;AAGA,UACE,iBAAiB,gBACjB,iBAAiB,kBACjB,iBAAiB,iBACjB;AACA,cAAM;MACR;AAGA,YAAM,IAAI;QACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;QAC/F,iBAAiB,QAAQ,QAAQ;MACnC;IACF;EACF;AACF;ACvQA,IAAM,cAAc,IAAI,YAAY;AACpC,IAAM,cAAc,IAAI,YAAY;AAI7B,IAAM,iBAAiB;EAC5B,UAAU;EACV,OAAO;EACP,SAAS;AACX;AAEO,IAAM,gBAAgB;EAC3B,SAAS;EACT,SAAS;EACT,QAAQ;AACV;AAmDA,SAAS,UAAU,QAAkC;AACnD,QAAM,cAAc,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC/D,QAAM,SAAS,IAAI,WAAW,WAAW;AACzC,MAAI,SAAS;AACb,aAAW,KAAK,QAAQ;AACtB,WAAO,IAAI,GAAG,MAAM;AACpB,cAAU,EAAE;EACd;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAAiB,QAAwB;AAC1D,MAAI,UAAU,IAAI,OAAQ,OAAM,IAAI,MAAM,gCAAgC;AAC1E,SAAO,IAAI,MAAM;AACnB;AAEA,SAAS,aAAa,KAAiB,QAAwB;AAC7D,MAAI,SAAS,IAAI,IAAI;AACnB,UAAM,IAAI,MAAM,iCAAiC;AACnD,SAAQ,IAAI,MAAM,KAAM,IAAK,IAAI,SAAS,CAAC;AAC7C;AAEA,SAAS,aAAa,KAAiB,QAAwB;AAC7D,MAAI,SAAS,IAAI,IAAI;AACnB,UAAM,IAAI,MAAM,iCAAiC;AACnD,UACI,IAAI,MAAM,KAAM,KACf,IAAI,SAAS,CAAC,KAAM,KACpB,IAAI,SAAS,CAAC,KAAM,IACrB,IAAI,SAAS,CAAC,OAChB;AAEJ;AAEA,SAAS,WAAW,OAA2B;AAC7C,SAAO,IAAI,WAAW,CAAC,KAAK,CAAC;AAC/B;AAEA,SAAS,cAAc,OAA2B;AAChD,SAAO,IAAI,WAAW,CAAE,SAAS,IAAK,KAAM,QAAQ,GAAI,CAAC;AAC3D;AAEA,SAAS,cAAc,OAA2B;AAChD,SAAO,IAAI,WAAW;IACnB,SAAS,KAAM;IACf,SAAS,KAAM;IACf,SAAS,IAAK;IACf,QAAQ;EACV,CAAC;AACH;AAEA,SAAS,UAAU,KAAiB,QAAgB,QAAwB;AAC1E,SAAO,YAAY,OAAO,IAAI,MAAM,QAAQ,SAAS,MAAM,CAAC;AAC9D;AAIA,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS,MAAM,SAAS,MAAM;AAChC,WAAO,IAAI,WAAW,CAAC,OAAO,KAAK,CAAC,CAAC;EACvC;AACA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,SAAO,YAAY,IAAI;AACrB,UAAM,QAAQ,OAAO,YAAY,KAAK,CAAC;AACvC,gBAAY,aAAa;EAC3B;AACA,SAAO,IAAI,WAAW,CAAC,MAAO,MAAM,QAAQ,GAAG,KAAK,CAAC;AACvD;AAEA,SAAS,cACP,KACA,QACsC;AACtC,QAAM,YAAY,UAAU,KAAK,MAAM;AACvC,MAAI,aAAa,KAAK;AACpB,WAAO,EAAE,OAAO,OAAO,SAAS,GAAG,WAAW,EAAE;EAClD;AACA,QAAM,SAAS,YAAY;AAC3B,MAAI,SAAS,IAAI,SAAS,IAAI;AAC5B,UAAM,IAAI,MAAM,0BAA0B;AAC5C,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,YAAS,SAAS,KAAM,OAAO,IAAI,SAAS,IAAI,CAAC,CAAE;EACrD;AACA,SAAO,EAAE,OAAO,WAAW,IAAI,OAAO;AACxC;AAEA,SAAS,qBAAqB,MAA8B;AAC1D,SAAO,OAAO,cAAc,OAAO,KAAK,MAAM,CAAC,GAAG,IAAI;AACxD;AAEA,SAAS,qBACP,KACA,QAC0C;AAC1C,QAAM,EAAE,OAAO,QAAQ,WAAW,SAAS,IAAI,cAAc,KAAK,MAAM;AACxE,QAAM,UAAU,OAAO,MAAM;AAC7B,QAAM,QAAQ,SAAS;AACvB,MAAI,QAAQ,UAAU,IAAI;AACxB,UAAM,IAAI,MAAM,iCAAiC;AACnD,SAAO;IACL,OAAO,IAAI,MAAM,OAAO,QAAQ,OAAO;IACvC,WAAW,WAAW;EACxB;AACF;AAEA,SAAS,sBAAsB,MAAwB;AACrD,QAAM,IAAI,KAAK,eAAe,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AAC1D,QAAM,MAAM,KAAK,YAAY,IAAI,GAAG,SAAS,EAAE,SAAS,GAAG,GAAG;AAC9D,QAAM,IAAI,KAAK,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACtD,QAAM,IAAI,KAAK,YAAY,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACvD,QAAM,KAAK,KAAK,cAAc,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AAC1D,QAAM,IAAI,KAAK,cAAc,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACzD,QAAM,KAAK,KAAK,mBAAmB,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AAC/D,SAAO,YAAY,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG;AAC/D;AAIO,SAAS,oBAAoB,QAAsC;AACxE,QAAM,YACJ,OAAO,mBAAmB,WAAW,KACjC,OAAO,qBACP,IAAI,WAAW,EAAE;AACvB,SAAO;IACL,WAAW,cAAc,OAAO;IAChC,cAAc,OAAO,MAAM;IAC3B,sBAAsB,OAAO,SAAS;IACtC;IACA,qBAAqB,YAAY,OAAO,OAAO,WAAW,CAAC;IAC3D,qBAAqB,OAAO,IAAI;EAClC;AACF;AAEO,SAAS,qBAAqB,KAAoC;AACvE,MAAI,IAAI,WAAW,EAAG,OAAM,IAAI,MAAM,kBAAkB;AACxD,QAAM,OAAO,IAAI,CAAC;AAClB,MAAI,SAAS,cAAc,QAAS,QAAO,sBAAsB,GAAG;AACpE,MAAI,SAAS,cAAc,OAAQ,QAAO,qBAAqB,GAAG;AAClE,QAAM,IAAI,MAAM,4BAA4B,IAAI,EAAE;AACpD;AAEA,SAAS,sBAAsB,KAAmC;AAChE,MAAI,SAAS;AAEb,YAAU;AACV,QAAM,EAAE,OAAO,KAAK,IAAI,qBAAqB,KAAK,MAAM;AACxD,SAAO,EAAE,MAAM,cAAc,SAAS,KAAK;AAC7C;AAEA,SAAS,qBAAqB,KAAkC;AAC9D,MAAI,SAAS;AAEb,QAAM,OAAO,UAAU,KAAK,QAAQ,CAAC;AACrC,YAAU;AAEV,QAAM,EAAE,WAAW,QAAQ,IAAI,qBAAqB,KAAK,MAAM;AAC/D,YAAU;AAEV,QAAM,EAAE,OAAO,QAAQ,WAAW,SAAS,IAAI;IAC7C;IACA;EACF;AACA,YAAU;AACV,QAAM,UAAU,YAAY,OAAO,MAAM;AAEzC,QAAM,EAAE,OAAO,KAAK,IAAI,qBAAqB,KAAK,MAAM;AACxD,SAAO,EAAE,MAAM,cAAc,QAAQ,MAAM,SAAS,KAAK;AAC3D;AAIO,SAAS,oBAAoB,SAAiC;AACnE,QAAM,QAAsB;IAC1B,WAAW,QAAQ,IAAI;IACvB,cAAc,QAAQ,SAAS;EACjC;AAEA,QAAM,OAAO,QAAQ;AACrB,QAAM,eAAe,KAAK,gBAAgB,CAAC;AAG3C,QAAM,KAAK,WAAW,aAAa,MAAM,CAAC;AAG1C,aAAW,MAAM,cAAc;AAC7B,UAAM,YAAY,YAAY,OAAO,GAAG,YAAY;AACpD,UAAM,KAAK,WAAW,UAAU,MAAM,CAAC;AACvC,UAAM,KAAK,SAAS;AACpB,UAAM,KAAK,cAAc,GAAG,WAAW,CAAC;AACxC,UAAM,KAAK,cAAc,GAAG,KAAK,MAAM,CAAC;AACxC,QAAI,GAAG,KAAK,SAAS,EAAG,OAAM,KAAK,GAAG,IAAI;EAC5C;AAGA,QAAM,YAAY,KAAK,aAAa,IAAI,WAAW,CAAC;AACpD,QAAM,KAAK,cAAc,UAAU,MAAM,CAAC;AAC1C,MAAI,UAAU,SAAS,EAAG,OAAM,KAAK,SAAS;AAE9C,SAAO,OAAO,GAAG,KAAK;AACxB;AAEO,SAAS,gBAAgB,KAA6B;AAC3D,MAAI,IAAI,SAAS,EAAG,OAAM,IAAI,MAAM,uBAAuB;AAC3D,MAAI,SAAS;AAEb,QAAM,OAAO,UAAU,KAAK,MAAM;AAClC,YAAU;AACV,QAAM,YAAY,aAAa,KAAK,MAAM;AAC1C,YAAU;AAEV,MAAI,SAAS,eAAe,OAAO;AAEjC,UAAM,UAAU,UAAU,KAAK,MAAM;AACrC,cAAU;AACV,UAAM,OAAO,UAAU,KAAK,QAAQ,OAAO;AAC3C,cAAU;AAEV,UAAM,UAAU,UAAU,KAAK,MAAM;AACrC,cAAU;AACV,UAAM,OAAO,UAAU,KAAK,QAAQ,OAAO;AAC3C,cAAU;AAEV,UAAM,QAAQ,UAAU,KAAK,MAAM;AACnC,cAAU;AACV,UAAM,cAAc,UAAU,KAAK,QAAQ,KAAK;AAChD,cAAU;AAEV,UAAM,UAAU,aAAa,KAAK,MAAM;AACxC,cAAU;AACV,UAAM,OAAO,IAAI,MAAM,QAAQ,SAAS,OAAO;AAC/C,WAAO,EAAE,MAAM,WAAW,MAAM,EAAE,MAAM,MAAM,aAAa,KAAK,EAAE;EACpE;AAGA,QAAM,UAAU,UAAU,KAAK,MAAM;AACrC,YAAU;AACV,QAAM,eAAkC,CAAC;AACzC,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,UAAU,UAAU,KAAK,MAAM;AACrC,cAAU;AACV,UAAM,eAAe,UAAU,KAAK,QAAQ,OAAO;AACnD,cAAU;AACV,UAAM,cAAc,aAAa,KAAK,MAAM;AAC5C,cAAU;AACV,UAAM,UAAU,aAAa,KAAK,MAAM;AACxC,cAAU;AACV,UAAM,OAAO,IAAI,MAAM,QAAQ,SAAS,OAAO;AAC/C,cAAU;AACV,iBAAa,KAAK,EAAE,cAAc,aAAa,KAAK,CAAC;EACvD;AAEA,MAAI;AACJ,MAAI,SAAS,KAAK,IAAI,QAAQ;AAC5B,UAAM,SAAS,aAAa,KAAK,MAAM;AACvC,cAAU;AACV,QAAI,SAAS,KAAK,SAAS,UAAU,IAAI,QAAQ;AAC/C,kBAAY,IAAI,MAAM,QAAQ,SAAS,MAAM;IAC/C;EACF;AAEA,SAAO,EAAE,MAAM,WAAW,MAAM,EAAE,cAAc,UAAU,EAAE;AAC9D;AClUA,IAAMC,eAAc,IAAI,YAAY;AAkB7B,IAAM,qBAAN,cAAiC,MAAM;EAC5C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAEO,IAAM,eAAN,cAA2B,MAAM;EACtC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAMO,IAAM,sBAAN,MAA0B;EACvB,KAAuB;EACvB,eAAe;EACf,mBAAmB;EACV,kBAAkB,oBAAI,IAA4B;EAClD;EAKjB,YAAY,QAAmC;AAC7C,SAAK,SAAS;MACZ,eAAe;MACf,eAAe;MACf,GAAG;IACL;EACF;EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;EACd;EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,aAAc;AAEvB,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAI;AACF,aAAK,KAAK,KAAK,OAAO,kBAClB,KAAK,OAAO,gBAAgB,KAAK,OAAO,GAAG,IAC3C,IAAI,UAAU,KAAK,OAAO,GAAG;AACjC,aAAK,GAAG,aAAa;MACvB,SAAS,KAAK;AACZ;UACE,IAAI;YACF,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;UACjF;QACF;AACA;MACF;AAEA,WAAK,GAAG,SAAS,YAAY;AAC3B,YAAI;AACF,gBAAM,KAAK,aAAa;AACxB,eAAK,eAAe;AACpB,kBAAQ;QACV,SAAS,KAAK;AACZ,eAAK,eAAe;AACpB,eAAK,IAAI,MAAM;AACf,iBAAO,GAAG;QACZ;MACF;AAEA,WAAK,GAAG,YAAY,CAAC,UAAwB;AAC3C,aAAK,cAAc,MAAM,IAAI;MAC/B;AAGA,WAAK,GAAG,UAAU,CAAC,UAAe;AAChC,cAAM,aAAa,OAAO,SAAS,OAAO;AAC1C,cAAM,SACJ,sBAAsB,QAClB,WAAW,UACX,OAAO,eAAe,WACpB,aACA;AACR;UACE,IAAI;YACF,SACI,+BAA+B,MAAM,KACrC;UACN;QACF;MACF;AAEA,WAAK,GAAG,UAAU,MAAM;AACtB,aAAK,eAAe;AAEpB,mBAAW,CAAC,IAAI,OAAO,KAAK,KAAK,iBAAiB;AAChD,uBAAa,QAAQ,SAAS;AAC9B,kBAAQ,OAAO,IAAI,mBAAmB,mBAAmB,CAAC;AAC1D,eAAK,gBAAgB,OAAO,EAAE;QAChC;MACF;IACF,CAAC;EACH;EAEA,MAAM,aAA4B;AAChC,SAAK,eAAe;AACpB,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM;AACd,WAAK,KAAK;IACZ;AACA,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,iBAAiB;AAChD,mBAAa,QAAQ,SAAS;AAC9B,cAAQ,OAAO,IAAI,mBAAmB,cAAc,CAAC;AACrD,WAAK,gBAAgB,OAAO,EAAE;IAChC;EACF;;;;;EAMA,MAAM,WACJ,QACA,cAC4B;AAC5B,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,IAAI;AAClC,YAAM,IAAI,mBAAmB,eAAe;IAC9C;AAEA,UAAM,gBAAgB,oBAAoB,MAAM;AAChD,UAAM,YAAY,KAAK,cAAc;AAErC,UAAM,aAAa,oBAAoB;MACrC,MAAM,eAAe;MACrB;MACA,MAAM;QACJ,cAAc,gBAAgB,CAAC;QAC/B,WAAW;MACb;IACF,CAAC;AAED,SAAK,GAAG,KAAK,UAAU;AAGvB,QAAI,YAAY,KAAK,OAAO;AAC5B,QAAI,OAAO,WAAW;AACpB,YAAM,YAAY,OAAO,UAAU,QAAQ,IAAI,KAAK,IAAI;AACxD,kBAAY,KAAK,IAAI,YAAY,KAAK,GAAI;IAC5C;AAEA,WAAO,IAAI,QAA2B,CAAC,SAAS,WAAW;AACzD,YAAM,YAAY,WAAW,MAAM;AACjC,aAAK,gBAAgB,OAAO,SAAS;AACrC,eAAO,IAAI,mBAAmB,wBAAwB,SAAS,KAAK,CAAC;MACvE,GAAG,SAAS;AAEZ,WAAK,gBAAgB,IAAI,WAAW,EAAE,SAAS,QAAQ,UAAU,CAAC;IACpE,CAAC;EACH;;;;;;;;;;EAWA,MAAM,iBACJ,cACA,aACA,MACe;AACf,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,IAAI;AAClC,YAAM,IAAI,mBAAmB,eAAe;IAC9C;AAEA,UAAM,YAAY,KAAK,cAAc;AACrC,UAAM,aAAa,oBAAoB;MACrC,MAAM,eAAe;MACrB;MACA,MAAM;QACJ,cAAc,CAAC,EAAE,cAAc,aAAa,KAAK,CAAC;QAClD,WAAW,IAAI,WAAW,CAAC;MAC7B;IACF,CAAC;AAED,SAAK,GAAG,KAAK,UAAU;EACzB;;EAIA,MAAc,eAA8B;AAC1C,QAAI,CAAC,KAAK,GAAI,OAAM,IAAI,aAAa,yBAAyB;AAE9D,UAAM,WAAW,KAAK,UAAU;MAC9B,QAAQ,KAAK,OAAO;MACpB,QAAQ,KAAK,OAAO;IACtB,CAAC;AAED,UAAM,YAAY,KAAK,cAAc;AACrC,UAAM,cAAc,oBAAoB;MACtC,MAAM,eAAe;MACrB;MACA,MAAM;QACJ,cAAc;UACZ;YACE,cAAc;YACd,aAAa;YACb,MAAMA,aAAY,OAAO,QAAQ;UACnC;QACF;QACA,WAAW,IAAI,WAAW,CAAC;MAC7B;IACF,CAAC;AAED,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,YAAM,UAAU,WAAW,MAAM;AAC/B,eAAO,IAAI,aAAa,wBAAwB,CAAC;MACnD,GAAG,KAAK,OAAO,aAAa;AAG5B,YAAM,kBAAkB,KAAK,GAAI;AACjC,WAAK,GAAI,YAAY,CAAC,UAAwB;AAC5C,YAAI;AACF,gBAAM,OAAO,KAAK,aAAa,MAAM,IAAI;AAGzC,cAAI;AACF,kBAAM,UAAU,IAAI,YAAY,EAAE,OAAO,IAAI;AAC7C,gBAAI,QAAQ,WAAW,GAAG,GAAG;YAE7B;UACF,QAAQ;UAER;AAGA,gBAAM,UAAU,gBAAgB,IAAI;AACpC,cAAI,QAAQ,cAAc,WAAW;AACnC,yBAAa,OAAO;AACpB,iBAAK,GAAI,YAAY;AAErB,gBAAI,QAAQ,SAAS,eAAe,OAAO;AACzC,oBAAM,UAAU,QAAQ;AACxB;gBACE,IAAI;kBACF,0BAA0B,QAAQ,IAAI,QAAQ,QAAQ,WAAW,EAAE,YAAY,QAAQ,eAAe,EAAE;gBAC1G;cACF;YACF,WAAW,QAAQ,SAAS,eAAe,UAAU;AACnD,sBAAQ;YACV;UACF;QACF,SAAS,KAAK;AACZ,uBAAa,OAAO;AACpB,eAAK,GAAI,YAAY;AACrB;YACE,IAAI,aAAa,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;UACnE;QACF;MACF;AAEA,WAAK,GAAI,KAAK,WAAW;IAC3B,CAAC;EACH;EAEQ,cAAc,KAAoB;AAExC,QAAI;AACF,YAAM,OAAO,KAAK,aAAa,GAAG;AAClC,YAAM,UAAU,IAAI,YAAY,EAAE,OAAO,IAAI;AAC7C,UAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,cAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,YAAI,KAAK,MAAM,MAAM,aAAa,KAAK,MAAM,MAAM,UAAU;AAC3D,gBAAM,QAAQ,KAAK,gBAAgB,QAAQ,EAAE,KAAK;AAClD,cAAI,CAAC,MAAM,MAAM;AACf,kBAAM,CAAC,IAAI,OAAO,IAAI,MAAM;AAC5B,yBAAa,QAAQ,SAAS;AAC9B,iBAAK,gBAAgB,OAAO,EAAE;AAE9B,gBAAI,KAAK,MAAM,MAAM,WAAW;AAC9B,oBAAM,eAAe,KAAK,MAAM,IAC5B,KAAK,mBAAmB,KAAK,MAAM,CAAW,IAC9C,IAAI,WAAW,CAAC;AACpB,sBAAQ,QAAQ;gBACd,MAAM,cAAc;gBACpB,MAAM;cACR,CAAC;YACH,OAAO;AACL,sBAAQ,QAAQ;gBACd,MAAM,cAAc;gBACpB,MAAO,KAAK,MAAM,KAAgB;gBAClC,SAAU,KAAK,SAAS,KAAgB;gBACxC,MAAM,KAAK,MAAM,IACb,KAAK,mBAAmB,KAAK,MAAM,CAAW,IAC9C,IAAI,WAAW,CAAC;cACtB,CAAC;YACH;UACF;AACA;QACF;MACF;IACF,QAAQ;IAER;AAGA,QAAI;AACF,YAAM,OAAO,KAAK,aAAa,GAAG;AAClC,YAAM,UAAU,gBAAgB,IAAI;AAEpC,UACE,QAAQ,SAAS,eAAe,YAChC,QAAQ,SAAS,eAAe,OAChC;AACA,cAAM,UAAU,KAAK,gBAAgB,IAAI,QAAQ,SAAS;AAC1D,YAAI,CAAC,QAAS;AAEd,qBAAa,QAAQ,SAAS;AAC9B,aAAK,gBAAgB,OAAO,QAAQ,SAAS;AAE7C,YAAI,QAAQ,SAAS,eAAe,OAAO;AACzC,gBAAM,UAAU,QAAQ;AACxB,kBAAQ;YACN,IAAI,mBAAmB,cAAc,QAAQ,IAAI,IAAI,QAAQ,IAAI,EAAE;UACrE;AACA;QACF;AAEA,cAAM,UAAU,QAAQ;AACxB,YAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,gBAAM,cAAc,qBAAqB,QAAQ,SAAS;AAC1D,kBAAQ,QAAQ,WAAW;QAC7B;MACF;IACF,QAAQ;IAER;EACF;EAEQ,aAAa,MAA2B;AAC9C,QAAI,gBAAgB,YAAa,QAAO,IAAI,WAAW,IAAI;AAC3D,QAAI,gBAAgB,WAAY,QAAO;AACvC,QAAI,OAAO,SAAS,SAAU,QAAOA,aAAY,OAAO,IAAI;AAC5D,UAAM,IAAI,MAAM,mCAAmC,OAAO,IAAI,EAAE;EAClE;EAEQ,mBAAmB,QAA4B;AACrD,UAAM,SAAS,KAAK,MAAM;AAC1B,UAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,CAAC,IAAI,OAAO,WAAW,CAAC;IAChC;AACA,WAAO;EACT;EAEQ,gBAAwB;AAC9B,SAAK,mBAAoB,KAAK,mBAAmB,IAAK;AACtD,WAAO,KAAK;EACd;AACF;AC1XA,SAAS,kBAAkB,OAAuB;AAChD,QAAM,MAAM,MAAM,QAAQ,YAAY;AACtC,SACE,IAAI,SAAS,eAAe,KAC5B,IAAI,SAAS,YAAY,KACzB,IAAI,SAAS,WAAW,KACxB,IAAI,SAAS,cAAc,KAC3B,IAAI,SAAS,YAAY,KACzB,IAAI,SAAS,gBAAgB,KAC7B,IAAI,SAAS,SAAS;AAE1B;AAMO,IAAM,mBAAN,MAA4C;EACzC,YAAwC;EAC/B;EACT,eAAe;EAEvB,YAAY,QAAgC;AAC1C,SAAK,SAAS;EAChB;;;;EAKA,MAAM,UAAyB;AAC7B,SAAK,YAAY,IAAI,oBAAoB;MACvC,KAAK,KAAK,OAAO;MACjB,QAAQ,KAAK,OAAO;MACpB,WAAW,KAAK,OAAO;MACvB,iBAAiB,KAAK,OAAO;IAC/B,CAAC;AAED,UAAM,KAAK,UAAU,QAAQ;AAC7B,SAAK,eAAe;EACtB;;;;EAKA,MAAM,YAA2B;AAC/B,QAAI,KAAK,WAAW;AAClB,UAAI;AACF,cAAM,KAAK,UAAU,WAAW;MAClC,QAAQ;MAER;AACA,WAAK,YAAY;AACjB,WAAK,eAAe;IACtB;AAEA,UAAM,KAAK,QAAQ;EACrB;;;;EAKA,MAAM,aAA4B;AAChC,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,UAAU,WAAW;AAChC,WAAK,eAAe;AACpB,WAAK,YAAY;IACnB;EACF;EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;EACd;;;;;EAMA,MAAM,cAAc,QAKO;AACzB,WAAO,UAAU,MAAM,KAAK,mBAAmB,MAAM,GAAG;MACtD,YAAY,KAAK,OAAO,cAAc;MACtC,YAAY,KAAK,OAAO,cAAc;MACtC,aAAa,CAAC,UAAU;AACtB,YAAI,CAAC,kBAAkB,KAAK,EAAG,QAAO;AACtC,aAAK,eAAe;AACpB,eAAO;MACT;IACF,CAAC;EACH;;;;;EAMA,MAAM,uBACJ,QAMA,OACwB;AACxB,WAAO,UAAU,MAAM,KAAK,4BAA4B,QAAQ,KAAK,GAAG;MACtE,YAAY,KAAK,OAAO,cAAc;MACtC,YAAY,KAAK,OAAO,cAAc;MACtC,aAAa,CAAC,UAAU;AACtB,YAAI,CAAC,kBAAkB,KAAK,EAAG,QAAO;AACtC,aAAK,eAAe;AACpB,eAAO;MACT;IACF,CAAC;EACH;;;;;;;EAQA,MAAM,iBAAiB,OAA+C;AACpE,WAAO,UAAU,MAAM,KAAK,sBAAsB,KAAK,GAAG;MACxD,YAAY,KAAK,OAAO,cAAc;MACtC,YAAY,KAAK,OAAO,cAAc;MACtC,aAAa,CAAC,UAAU;AACtB,YAAI,CAAC,kBAAkB,KAAK,EAAG,QAAO;AACtC,aAAK,eAAe;AACpB,eAAO;MACT;IACF,CAAC;EACH;EAEA,MAAc,sBACZ,OACe;AACf,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,KAAK,UAAU;IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,mBAAmB,0BAA0B;IACzD;AAEA,UAAM,KAAK,UAAU;MACnB;MACA;MACA,WAAW,KAAK,UAAU,KAAK,CAAC;IAClC;EACF;;;;EAKA,MAAc,mBAAmB,QAKN;AACzB,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,KAAK,UAAU;IACvB;AAGA,UAAM,WAAW,MAAM,KAAK,UAAW,WAAW;MAChD,MAAM;MACN,QAAQ,OAAO,OAAO,MAAM;MAC5B,aAAa,OAAO;MACpB,oBAAoB,IAAI,WAAW,EAAE;MACrC,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK,OAAO,WAAW,IAAM;MAC1D,MAAM,WAAW,OAAO,IAAI;IAC9B,CAAC;AAED,QAAI,SAAS,SAAS,cAAc,SAAS;AAC3C,aAAO;QACL,UAAU;QACV,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS,SAAS,IAAI,IAAI;MAC7D;IACF;AAGA,WAAO;MACL,UAAU;MACV,MAAM,SAAS;MACf,SAAS,SAAS;MAClB,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS,SAAS,IAAI,IAAI;IAC7D;EACF;;;;EAKA,MAAc,4BACZ,QAMA,OACwB;AACxB,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,KAAK,UAAU;IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,mBAAmB,0BAA0B;IACzD;AAEA,UAAM,eAAkC;MACtC;QACE,cAAc;QACd,aAAa;QACb,MAAM,WAAW,KAAK,UAAU,KAAK,CAAC;MACxC;IACF;AAEA,UAAM,WAAW,MAAM,KAAK,UAAU;MACpC;QACE,MAAM;QACN,QAAQ,OAAO,OAAO,MAAM;QAC5B,aAAa,OAAO;QACpB,oBAAoB,IAAI,WAAW,EAAE;QACrC,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK,OAAO,WAAW,IAAM;QAC1D,MAAM,WAAW,OAAO,IAAI;MAC9B;MACA;IACF;AAEA,QAAI,SAAS,SAAS,cAAc,SAAS;AAC3C,aAAO;QACL,UAAU;QACV,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS,SAAS,IAAI,IAAI;MAC7D;IACF;AAEA,WAAO;MACL,UAAU;MACV,MAAM,SAAS;MACf,SAAS,SAAS;MAClB,MAAM,SAAS,KAAK,SAAS,IAAI,SAAS,SAAS,IAAI,IAAI;IAC7D;EACF;AACF;AEzOA,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AAGvB,IAAM,wBAAwB,IAAI,WAAW,CAAC,GAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AACxE,IAAM,aAAa,IAAI,WAAW,CAAC,GAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;AAG7D,IAAM,wBAAwB,IAAI,WAAW;EAC3C;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;AAC5C,CAAC;AAGD,IAAM,uBAAuB;AAM7B,IAAM,WAAW,MAAM,OAAO;AAE9B,SAAS,WAAW,KAAiB,QAAgB,OAAqB;AACxE,MAAI,QAAQ,MAAM,QAAQ,SAAS;AACjC,UAAM,IAAI,WAAW,SAAS,KAAK,gCAAgC;EACrE;AACA,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,SAAS,CAAC,IAAI,OAAQ,SAAS,OAAO,IAAI,CAAC,IAAK,KAAK;EAC3D;AACF;AAGA,SAAS,QAAQ,OAA+B;AAC9C,MAAI,MAAM,WAAW,GAAI,QAAO;AAChC,MAAI,MAAM,SAAS,GAAI,QAAO,MAAM,MAAM,MAAM,SAAS,EAAE;AAC3D,QAAM,SAAS,IAAI,WAAW,EAAE;AAChC,SAAO,IAAI,OAAO,KAAK,MAAM,MAAM;AACnC,SAAO;AACT;AAGA,SAAS,YAAY,GAAe,GAAyC;AAC3E,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,QAAI,KAAK,GAAI,QAAO,CAAC,GAAG,CAAC;AACzB,QAAI,KAAK,GAAI,QAAO,CAAC,GAAG,CAAC;EAC3B;AACA,SAAO,CAAC,GAAG,CAAC;AACd;AAMA,SAAS,OAAO,MAAc,KAAaC,MAAqB;AAC9D,MAAI,SAAS;AACb,UAAS,OAAOA,OAAOA,QAAOA;AAC9B,SAAO,MAAM,IAAI;AACf,QAAI,MAAM,GAAI,UAAU,SAAS,OAAQA;AACzC,YAAQ;AACR,WAAQ,OAAO,OAAQA;EACzB;AACA,SAAO;AACT;AAEA,SAAS,WAAW,GAAW,GAAmB;AAChD,SAAO,QAAS,IAAI,IAAK,KAAK,GAAG,IAAI,IAAI,CAAC;AAC5C;AAGA,SAAS,UAAU,OAA4B;AAC7C,QAAM,KAAK,MAAM,QAAQ;AACzB,QAAM,SAAS,IAAI,WAAW,EAAE;AAChC,SAAO,IAAI,KAAK;AAChB,SAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK;AAEjC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,SAAK,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,IAAI,CAAC;EAC7C;AACA,MAAI,KAAK,EAAG,QAAO;AAEnB,QAAM,KAAM,IAAI,IAAK;AACrB,QAAM,KAAK,IAAM,UAAU,WAAW,SAAS,CAAC,IAAK,IAAK,KAAK;AAC/D,QAAM,aAAa,KAAK,KAAK,KAAK;AAClC,QAAM,eAAe,IAAI,KAAK,MAAM;AACpC,QAAM,KAAM,YAAY,WAAW,aAAa,CAAC,IAAK;AACtD,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO,OAAO,KAAK,IAAI,MAAM,IAAI,CAAC,MAAM;AAC1C;AAEA,SAAS,mBACP,OACA,WACmC;AACnC,QAAM,aAAa,IAAI,YAAY,EAAE,OAAO,uBAAuB;AACnE,WAAS,OAAO,KAAK,QAAQ,GAAG,QAAQ;AACtC,UAAM,WAAW,CAAC,GAAG,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;AAClD,QAAI,WAAW,UAAU,SAAS,WAAW;AAC7C,eAAW,KAAK,SAAU,aAAY,EAAE;AAExC,UAAM,QAAQ,IAAI,WAAW,QAAQ;AACrC,QAAI,SAAS;AACb,eAAW,KAAK,UAAU;AACxB,YAAM,IAAI,GAAG,MAAM;AACnB,gBAAU,EAAE;IACd;AACA,UAAM,IAAI,WAAW,MAAM;AAC3B,cAAU,UAAU;AACpB,UAAM,IAAI,YAAY,MAAM;AAE5B,UAAM,OAAOC,QAAO,KAAK;AACzB,QAAI,CAAC,UAAU,IAAI,EAAG,QAAO,EAAE,KAAK,MAAM,KAAK;EACjD;AACA,QAAM,IAAI,MAAM,uCAAuC;AACzD;AAQO,SAAS,iBACd,cACA,cACA,WACA,WAC+B;AAC/B,QAAM,IAAI,QAAQ,aAAa,YAAY,CAAC;AAC5C,QAAM,IAAI,QAAQ,aAAa,YAAY,CAAC;AAC5C,QAAM,OAAO,QAAQ,aAAa,SAAS,CAAC;AAC5C,QAAM,UAAU,QAAQ,aAAa,SAAS,CAAC;AAC/C,QAAM,CAAC,KAAK,GAAG,IAAI,YAAY,GAAG,CAAC;AACnC,QAAM,QAAQ,CAAC,IAAI,YAAY,EAAE,OAAO,SAAS,GAAG,KAAK,KAAK,IAAI;AAClE,QAAM,EAAE,KAAK,KAAK,IAAI,mBAAmB,OAAO,OAAO;AACvD,SAAO,EAAE,KAAK,aAAa,GAAG,GAAG,KAAK;AACxC;AAMO,SAAS,eACd,YACA,WAC+B;AAC/B,QAAM,UAAU,QAAQ,aAAa,UAAU,CAAC;AAChD,QAAM,UAAU,QAAQ,aAAa,SAAS,CAAC;AAC/C,QAAM,QAAQ,CAAC,IAAI,YAAY,EAAE,OAAO,OAAO,GAAG,OAAO;AACzD,QAAM,EAAE,KAAK,KAAK,IAAI,mBAAmB,OAAO,OAAO;AACvD,SAAO,EAAE,KAAK,aAAa,GAAG,GAAG,KAAK;AACxC;AAcO,SAAS,yBACd,YACA,OACA,mBACY;AACZ,QAAM,UAAU,IAAI,WAAW,EAAE;AACjC,UAAQ,IAAI,QAAQ,aAAa,UAAU,CAAC,GAAG,CAAC;AAChD,aAAW,SAAS,IAAI,KAAK;AAC7B,aAAW,SAAS,IAAI,iBAAiB;AACzC,SAAO;AACT;AAsCA,IAAI,eAAe;AAEnB,eAAe,UACb,QACA,QACA,SAAoB,CAAC,GACH;AAClB,QAAM,MAAM,MAAM,MAAM,QAAQ;IAC9B,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM,KAAK,UAAU;MACnB,SAAS;MACT;MACA;MACA,IAAI;IACN,CAAC;IACD,QAAQ,YAAY,QAAQ,GAAK;EACnC,CAAC;AACD,QAAM,OAAQ,MAAM,IAAI,KAAK;AAI7B,MAAI,KAAK,OAAO;AACd,UAAM,IAAI;MACR,qBAAqB,MAAM,MAAM,KAAK,MAAM,OAAO,UAAU,KAAK,MAAM,IAAI;IAC9E;EACF;AACA,SAAO,KAAK;AACd;AAEA,eAAe,mBAAmB,QAAiC;AACjE,QAAM,SAAU,MAAM,UAAU,QAAQ,sBAAsB;IAC5D,EAAE,YAAY,YAAY;EAC5B,CAAC;AACD,SAAO,OAAO,MAAM;AACtB;AAQA,eAAe,eACb,QACA,QAC6B;AAC7B,QAAM,SAAU,MAAM,UAAU,QAAQ,kBAAkB;IACxD;IACA,EAAE,UAAU,UAAU,YAAY,YAAY;EAChD,CAAC;AACD,SAAO,OAAO;AAChB;AAEA,eAAe,oBACb,QACA,WACA,YAAY,KACG;AACf,QAAM,QAAQ,KAAK,IAAI;AACvB,SAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,UAAM,SAAU,MAAM,UAAU,QAAQ,wBAAwB;MAC9D,CAAC,SAAS;IACZ,CAAC;AAGD,UAAM,SAAS,OAAO,MAAM,CAAC;AAC7B,QACE,QAAQ,uBAAuB,eAC/B,QAAQ,uBAAuB,aAC/B;AACA,UAAI,OAAO,KAAK;AACd,cAAM,IAAI;UACR,eAAe,SAAS,YAAY,KAAK,UAAU,OAAO,GAAG,CAAC;QAChE;MACF;AACA;IACF;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;EAC7C;AACA,QAAM,IAAI;IACR,eAAe,SAAS,yBAAyB,SAAS;EAC5D;AACF;AAEA,SAAS,eAAe,OAAuB;AAC7C,MAAI,QAAQ,OAAQ;AAClB,UAAM,IAAI,WAAW,qBAAqB,KAAK,2BAA2B;EAC5E;AACA,SAAO,QAAQ,MAAO,IAAI,QAAQ,QAAS,IAAI;AACjD;AAEA,SAAS,gBACP,KACA,QACA,OACQ;AACR,MAAI,QAAQ,KAAM;AAChB,QAAI,QAAQ,IAAI;EAClB,WAAW,QAAQ,OAAQ;AACzB,QAAI,QAAQ,IAAK,QAAQ,MAAQ;AACjC,QAAI,QAAQ,IAAI,SAAS;EAC3B,OAAO;AACL,QAAI,QAAQ,IAAK,QAAQ,MAAQ;AACjC,QAAI,QAAQ,IAAM,SAAS,IAAK,MAAQ;AACxC,QAAI,QAAQ,IAAI,SAAS;EAC3B;AACA,SAAO;AACT;AAYA,eAAe,wBACb,QACA,UACA,cACA,oBAA8B,CAAC,GACd;AACjB,QAAM,YAAY,MAAM,mBAAmB,MAAM;AACjD,QAAM,iBAAiB,aAAa,SAAS,SAAS;AAEtD,QAAM,aAAa,oBAAI,IAA0B;AACjD,aAAW,IAAI,gBAAgB;IAC7B,QAAQ;IACR,UAAU;IACV,YAAY;EACd,CAAC;AACD,aAAW,MAAM,cAAc;AAC7B,eAAW,OAAO,GAAG,MAAM;AACzB,YAAM,WAAW,WAAW,IAAI,IAAI,MAAM;AAC1C,UAAI,UAAU;AACZ,iBAAS,WAAW,SAAS,YAAY,IAAI;AAC7C,iBAAS,aAAa,SAAS,cAAc,IAAI;MACnD,OAAO;AACL,mBAAW,IAAI,IAAI,QAAQ,EAAE,GAAG,IAAI,CAAC;MACvC;IACF;AACA,QAAI,CAAC,WAAW,IAAI,GAAG,SAAS,GAAG;AACjC,iBAAW,IAAI,GAAG,WAAW;QAC3B,QAAQ,GAAG;QACX,UAAU;QACV,YAAY;MACd,CAAC;IACH;EACF;AAEA,QAAM,WAAW,CAAC,GAAG,WAAW,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AACvD,QAAI,EAAE,WAAW,eAAgB,QAAO;AACxC,QAAI,EAAE,WAAW,eAAgB,QAAO;AACxC,UAAM,UAAU,EAAE,WAAW,IAAI,MAAM,EAAE,aAAa,IAAI;AAC1D,UAAM,UAAU,EAAE,WAAW,IAAI,MAAM,EAAE,aAAa,IAAI;AAC1D,WAAO,SAAS;EAClB,CAAC;AAED,QAAM,aAAa,SAAS,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE;AACtD,QAAM,qBAAqB,SAAS;IAClC,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE;EAC1B,EAAE;AACF,QAAM,wBAAwB,SAAS;IACrC,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,EAAE;EAC3B,EAAE;AAEF,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,WAAS,QAAQ,CAAC,GAAG,MAAM,gBAAgB,IAAI,EAAE,QAAQ,CAAC,CAAC;AAE3D,QAAM,WAAW,aAAa,IAAI,CAAC,QAAQ;;IAEzC,gBAAgB,gBAAgB,IAAI,GAAG,SAAS;;IAEhD,gBAAgB,GAAG,KAAK,IAAI,CAAC,MAAM,gBAAgB,IAAI,EAAE,MAAM,CAAE;IACjE,MAAM,GAAG;EACX,EAAE;AAEF,QAAM,iBAAiB,aAAa,SAAS;AAE7C,MAAI,kBAAkB,eAAe,SAAS,MAAM;AACpD,aAAW,MAAM,UAAU;AACzB,uBAAmB;AACnB,uBACE,eAAe,GAAG,eAAe,MAAM,IAAI,GAAG,eAAe;AAC/D,uBAAmB,eAAe,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK;EAC9D;AAEA,QAAM,cACJ,IACA,eAAe,SAAS,MAAM,IAC9B,KAAK,SAAS,SACd,KACA;AACF,QAAM,UAAU,IAAI,WAAW,WAAW;AAC1C,MAAI,SAAS;AAEb,UAAQ,QAAQ,IAAI;AACpB,UAAQ,QAAQ,IAAI;AACpB,UAAQ,QAAQ,IAAI;AAEpB,WAAS,gBAAgB,SAAS,QAAQ,SAAS,MAAM;AACzD,aAAW,QAAQ,UAAU;AAC3B,YAAQ,IAAI,QAAQ,aAAa,KAAK,MAAM,CAAC,GAAG,MAAM;AACtD,cAAU;EACZ;AAEA,UAAQ,IAAI,QAAQ,cAAc,GAAG,MAAM;AAC3C,YAAU;AAEV,WAAS,gBAAgB,SAAS,QAAQ,SAAS,MAAM;AACzD,aAAW,MAAM,UAAU;AACzB,YAAQ,QAAQ,IAAI,GAAG;AACvB,aAAS,gBAAgB,SAAS,QAAQ,GAAG,eAAe,MAAM;AAClE,eAAW,OAAO,GAAG,eAAgB,SAAQ,QAAQ,IAAI;AACzD,aAAS,gBAAgB,SAAS,QAAQ,GAAG,KAAK,MAAM;AACxD,YAAQ,IAAI,GAAG,MAAM,MAAM;AAC3B,cAAU,GAAG,KAAK;EACpB;AAEA,QAAM,eAAe,QAAQ,MAAM,GAAG,MAAM;AAE5C,QAAM,aAAa,CAAC,UAAU,GAAG,iBAAiB;AAClD,QAAM,gBAAgB,SAAS,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAC5E,QAAM,aAA2B,CAAC;AAClC,aAAW,gBAAgB,eAAe;AACxC,UAAM,SAAS,WAAW;MACxB,CAAC,MAAM,aAAa,EAAE,SAAS,MAAM;IACvC;AACA,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,sBAAsB,YAAY,EAAE;AACjE,eAAW,KAAK,QAAQ,KAAK,cAAc,OAAO,UAAU,CAAC;EAC/D;AAEA,QAAM,SACJ,eAAe,WAAW,MAAM,IAChC,WAAW,SAAS,KACpB,aAAa;AACf,QAAM,KAAK,IAAI,WAAW,MAAM;AAChC,MAAI,WAAW;AACf,aAAW,gBAAgB,IAAI,UAAU,WAAW,MAAM;AAC1D,aAAW,OAAO,YAAY;AAC5B,OAAG,IAAI,KAAK,QAAQ;AACpB,gBAAY;EACd;AACA,KAAG,IAAI,cAAc,QAAQ;AAE7B,QAAM,WAAW,OAAO,KAAK,EAAE,EAAE,SAAS,QAAQ;AAClD,QAAM,QAAS,MAAM,UAAU,QAAQ,mBAAmB;IACxD;IACA;MACE,UAAU;MACV,eAAe;MACf,qBAAqB;IACvB;EACF,CAAC;AACD,QAAM,oBAAoB,QAAQ,KAAK;AACvC,SAAO;AACT;AAWA,IAAM,YAAY,CAAC,UAAU,UAAU,SAAS;AAGhD,eAAsB,uBACpB,QACA,YACoC;AACpC,QAAM,OAAO,MAAM,eAAe,QAAQ,UAAU;AACpD,MAAI,CAAC,KAAM,QAAO,EAAE,QAAQ,MAAM;AAClC,QAAM,OAAO,IAAI,WAAW,OAAO,KAAK,KAAK,KAAK,CAAC,GAAG,QAAQ,CAAC;AAC/D,MAAI,KAAK,SAAS,qBAAsB,QAAO,EAAE,QAAQ,MAAM;AAC/D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,KAAK,CAAC,MAAM,sBAAsB,CAAC,EAAG,QAAO,EAAE,QAAQ,MAAM;EACnE;AACA,SAAO;IACL,QAAQ;IACR,OAAO,UAAU,KAAK,GAAG,KAAK,CAAC,KAAK;IACpC,cAAc,aAAa,KAAK,MAAM,GAAG,EAAE,CAAC;IAC5C,cAAc,aAAa,KAAK,MAAM,IAAI,EAAE,CAAC;EAC/C;AACF;AAmCA,eAAsB,kBACpB,QACkC;AAClC,QAAM;IACJ;IACA;IACA;IACA;IACA;IACA;IACA;EACF,IAAI;AAEJ,QAAM,EAAE,KAAK,WAAW,IAAI;IAC1B;IACA;IACA;IACA;EACF;AAGA,QAAM,WAAW,MAAM,uBAAuB,QAAQ,UAAU;AAChE,MAAI,SAAS,QAAQ;AACnB,WAAO,EAAE,YAAY,QAAQ,MAAM;EACrC;AAEA,QAAM,iBAAiB,QAAQ,aAAa,WAAW,CAAC;AACxD,QAAM,QAAgB,EAAE,WAAW,gBAAgB,YAAY,UAAU;AAEzE,QAAM,EAAE,KAAK,SAAS,IAAI,eAAe,YAAY,SAAS;AAG9D,QAAM,WAAW,IAAI,WAAW,EAAE;AAClC,WAAS,IAAI,uBAAuB,CAAC;AACrC,aAAW,UAAU,GAAG,iBAAiB;AAEzC,QAAM,kBAAkB,MAAM,wBAAwB,QAAQ,OAAO;IACnE;MACE;MACA,MAAM;QACJ,EAAE,QAAQ,aAAa,UAAU,MAAM,YAAY,KAAK;QACxD,EAAE,QAAQ,aAAa,UAAU,OAAO,YAAY,MAAM;;QAC1D,EAAE,QAAQ,YAAY,UAAU,OAAO,YAAY,MAAM;;QACzD,EAAE,QAAQ,WAAW,UAAU,OAAO,YAAY,MAAM;QACxD,EAAE,QAAQ,YAAY,UAAU,OAAO,YAAY,KAAK;QACxD,EAAE,QAAQ,UAAU,UAAU,OAAO,YAAY,KAAK;QACtD,EAAE,QAAQ,mBAAmB,UAAU,OAAO,YAAY,MAAM;QAChE,EAAE,QAAQ,kBAAkB,UAAU,OAAO,YAAY,MAAM;QAC/D,EAAE,QAAQ,gBAAgB,UAAU,OAAO,YAAY,MAAM;MAC/D;MACA,MAAM;IACR;EACF,CAAC;AAED,MAAI;AACJ,MAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,IAAI;AAEhD,UAAM,cAAc,IAAI,WAAW,EAAE;AACrC,gBAAY,IAAI,YAAY,CAAC;AAC7B,eAAW,aAAa,GAAG,OAAO,QAAQ,MAAM;AAEhD,yBAAqB,MAAM,wBAAwB,QAAQ,OAAO;MAChE;QACE;QACA,MAAM;UACJ,EAAE,QAAQ,aAAa,UAAU,MAAM,YAAY,MAAM;UACzD;YACE,QAAQ,OAAO,QAAQ;YACvB,UAAU;YACV,YAAY;UACd;UACA,EAAE,QAAQ,UAAU,UAAU,OAAO,YAAY,KAAK;UACtD,EAAE,QAAQ,YAAY,UAAU,OAAO,YAAY,KAAK;UACxD,EAAE,QAAQ,kBAAkB,UAAU,OAAO,YAAY,MAAM;QACjE;QACA,MAAM;MACR;IACF,CAAC;EACH;AAEA,SAAO,EAAE,YAAY,QAAQ,MAAM,iBAAiB,mBAAmB;AACzE;ACxlBA,IAAI,aAA8B;AAClC,IAAI,uBAAmC;AACvC,IAAI,mBAA+B;AAQnC,IAAI,kBAEO;AA+BX,eAAe,kBAGZ;AACD,MAAI,cAAc,sBAAsB;AACtC,WAAO,EAAE,MAAM,YAAY,gBAAgB,qBAAqB;EAClE;AACA,MAAI,iBAAiB;AACnB,UAAM,WAAW,MAAM,gBAAgB;AACvC,iBAAa,SAAS;AACtB,2BAAuB,SAAS;AAChC,WAAO;EACT;AACA,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,QAAa;AACpD,QAAM,WAAW,MAAM,OAAO,MAAW;AAKzC,QAAM,cAAc,cAAc,YAAY,GAAG;AACjD,QAAM,aAAa,YAAY;IAC7B;EACF;AACA,QAAM,iBAAiB,cAAc,UAAU;AAG/C,QAAM,OAAO,eAAe,MAAM;AAMlC,QAAM,aAAgC,eAAe,UAAU;AAC/D,QAAM,SAAS,SAAS,QAAQ,UAAU;AAC1C,QAAM,WAAW,SAAS,KAAK,QAAQ,WAAW,QAAQ,eAAe;AACzE,QAAM,MAAW,eAAe,QAAQ;AACxC,QAAM,iBAAiB,IAAI,kBAAkB,IAAI,SAAS;AAC1D,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI;MACR;IACF;EACF;AACA,eAAa;AACb,yBAAuB;AACvB,SAAO,EAAE,MAAM,eAAe;AAChC;AAGA,eAAe,UAA6B;AAC1C,UAAQ,MAAM,gBAAgB,GAAG;AACnC;AAOA,eAAe,4BAA0C;AACvD,QAAM,EAAE,eAAe,IAAI,MAAM,gBAAgB;AACjD,MAAI,CAAC,kBAAkB;AACrB,UAAM,eAAe,QAAQ;AAC7B,uBAAmB;EACrB;AACA,SAAO;AACT;AAUA,IAAM,0BAA0B;AAEhC,IAAM,mCAAmC;AA+DzC,eAAsB,uBACpB,QACgC;AAChC,QAAM,EAAE,MAAM,YAAY,WAAW,OAAAC,QAAO,eAAe,aAAa,IACtE,MAAM,QAAQ;AAShB,QAAM,UAAU,KAAK,QAAQ,OAAO,UAAU;AAC9C,OAAK,kBAAkB,OAAO;AAI9B,QAAM,QAAQ,OAAO,eAAe;AAMpC,QAAM,iBAAiBC,0BAA0B,OAAO,eAAe;AACvE,QAAM,kBAAkB,WAAW,WAAW,cAAc;AAC5D,QAAM,iBAAiB,gBAAgB,YAAY;AACnD,QAAM,iBAAiB,UAAU,WAAW,OAAO,YAAY;AAQ/D,QAAM,mBAAmB,YAA6B;AACpD,UAAM,MAAM,MAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAC5D,QAAI,IAAI,SAAS,CAAC,IAAI,SAAS;AAC7B,YAAM,IAAI;QACR,sBAAsB,OAAO,YAAY,wBAAwB;UAC/D,IAAI;QACN,CAAC;MACH;IACF;AAGA,UAAM,WAAW,IAAI,QAAQ,OAAO;AACpC,UAAM,MAAM,WAAW,CAAC,GAAG,SAAS,KAAK;AACzC,WAAO,OAAO,GAAG;EACnB;AAOA,QAAM,mBAAmB,YAA6B;AACpD,UAAM,MAAM,MAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAC5D,QAAI,IAAI,SAAS,CAAC,IAAI,SAAS;AAC7B,YAAM,IAAI;QACR,sBAAsB,OAAO,YAAY,wBAAwB;UAC/D,IAAI;QACN,CAAC;MACH;IACF;AACA,UAAM,WAAW,IAAI,QAAQ,OAAO;AACpC,UAAM,MAAM,WAAW,CAAC,GAAG,SAAS,KAAK;AACzC,WAAO,OAAO,GAAG;EACnB;AAEA,QAAM,eAAe,MAAM,iBAAiB;AAC5C,QAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAChD,MAAI,SAAS;AACb,MAAI;AACJ,MAAI;AACJ,QAAM,WAAW,YAAY;AAC3B,QAAI,CAAC,OAAO;AACV,YAAM,iBAAiB,MAAM,0BAA0B;AACvD,cAAQ,IAAI,eAAe,cAAc;IAC3C;AACA,WAAO;EACT;AAEA,MAAI,iBAAiB,kCAAkC;AACrD,UAAM,UAAU,MAAM,SAAS;AAC/B,UAAM,eAAe;AACrB,UAAM,eAAe,OAAO,gBACxB,UAAU,WAAW,OAAO,aAAa,IACzC;AACJ,UAAM,QAAQD,OAAM,CAAC;AACrB,UAAM,eAAeA,QAAO,OAAO,WAAW,QAAQ,SAAS,CAAC;AAChE,UAAM,eAAeA,OAAM,OAAO,WAAW,GAAG;AAQhD,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAChD,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAEhD,UAAM,SAAS,MAAM,KAAK;MACxB,EAAE,QAAQ,gBAAgB,KAAK,OAAO,KAAK,EAAE;MAC7C,YAAY;AACV,cAAM,QAAQ;UACZ;UACA;UACA;UACA;UACA;QACF;MACF;IACF;AACA,UAAM,OAAO,MAAM;AACnB,UAAM,WAAW,MAAM,OAAO,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK;AAC3D,iBAAa,SAAS,QAAQ;AAC9B,aAAS;AAeT,UAAM,SAAS,KAAK;AACpB,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAChD,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;EAClD,WAAW,iBAAiB,yBAAyB;AAEnD,UAAM,IAAI;MACR,gBAAgB,OAAO,YAAY,gBAAgB,YAAY;IACjE;EACF;AAGA,MAAI;AACJ,MAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,IAAI;AAChD,UAAM,UAAU,MAAM,SAAS;AAE/B,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAChD,UAAM,cAAcA,OAAM,OAAO,QAAQ,OAAO,SAAS,CAAC;AAC1D,UAAM,YAAY,MAAM,KAAK;MAC3B,EAAE,QAAQ,gBAAgB,KAAK,OAAO,KAAK,EAAE;MAC7C,YAAY;AACV,cAAM,QAAQ,QAAQ,aAAa,cAAc;MACnD;IACF;AACA,UAAM,UAAU,MAAM;AACtB,UAAM,cAAc,MAAM,UAAU,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK;AACjE,oBAAgB,YAAY,QAAQ;AAUpC,UAAM,YAAY,KAAK;AACvB,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;AAChD,UAAM,aAAa,EAAE,WAAW,eAAe,CAAC;EAClD;AAMA,MAAI;AACJ,MAAI;AACF,iBAAa,OAAO,MAAM,iBAAiB,CAAC;EAC9C,QAAQ;AACN,iBAAa,SACT,OAAO,uBAAuB,IAC9B,OAAO,YAAY;EACzB;AACA,MAAI,UAAU,eAAe,OAAO,gCAAgC,GAAG;AACrE,iBAAa,OAAO,uBAAuB;EAC7C;AAIA,OAAK;AAKL,MAAI;AACJ,MAAI;AACF,mBAAe,MAAM,iBAAiB;EACxC,QAAQ;AACN,mBAAe;EACjB;AAEA,SAAO;IACL,cAAc,OAAO;IACrB;IACA;IACA;IACA,cAAc;IACd;EACF;AACF;AF3aA,IAAM,oBAAoB;EACxB;IACE,MAAM;IACN,MAAM;IACN,iBAAiB;IACjB,QAAQ;MACN,EAAE,MAAM,gBAAgB,MAAM,UAAU;MACxC,EAAE,MAAM,qBAAqB,MAAM,UAAU;IAC/C;IACA,SAAS,CAAC,EAAE,MAAM,UAAU,CAAC;EAC/B;EACA;IACE,MAAM;IACN,MAAM;IACN,iBAAiB;IACjB,QAAQ;MACN,EAAE,MAAM,aAAa,MAAM,UAAU;MACrC,EAAE,MAAM,eAAe,MAAM,UAAU;MACvC,EAAE,MAAM,gBAAgB,MAAM,UAAU;IAC1C;IACA,SAAS,CAAC;EACZ;EACA;IACE,MAAM;IACN,MAAM;IACN,iBAAiB;IACjB,QAAQ,CAAC,EAAE,MAAM,UAAU,CAAC;IAC5B,SAAS;MACP,EAAE,MAAM,qBAAqB,MAAM,UAAU;MAC7C,EAAE,MAAM,SAAS,MAAM,QAAQ;MAC/B,EAAE,MAAM,YAAY,MAAM,UAAU;MACpC,EAAE,MAAM,YAAY,MAAM,UAAU;MACpC,EAAE,MAAM,gBAAgB,MAAM,UAAU;MACxC,EAAE,MAAM,gBAAgB,MAAM,UAAU;IAC1C;EACF;EACA;IACE,MAAM;IACN,MAAM;IACN,QAAQ;MACN,EAAE,MAAM,aAAa,MAAM,WAAW,SAAS,KAAK;MACpD,EAAE,MAAM,gBAAgB,MAAM,WAAW,SAAS,KAAK;MACvD,EAAE,MAAM,gBAAgB,MAAM,WAAW,SAAS,KAAK;MACvD,EAAE,MAAM,qBAAqB,MAAM,WAAW,SAAS,MAAM;IAC/D;EACF;AACF;AAGA,IAAM,YAAY;EAChB;IACE,MAAM;IACN,MAAM;IACN,iBAAiB;IACjB,QAAQ;MACN,EAAE,MAAM,WAAW,MAAM,UAAU;MACnC,EAAE,MAAM,UAAU,MAAM,UAAU;IACpC;IACA,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;EAC5B;EACA;IACE,MAAM;IACN,MAAM;IACN,iBAAiB;IACjB,QAAQ;MACN,EAAE,MAAM,SAAS,MAAM,UAAU;MACjC,EAAE,MAAM,WAAW,MAAM,UAAU;IACrC;IACA,SAAS,CAAC,EAAE,MAAM,UAAU,CAAC;EAC/B;AACF;AAGA,IAAME,aAAoD;EACxD,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;AACL;AAqEO,IAAM,uBAAN,MAA6D;EACjD;EACA;EACT;EACA;EACS,iBAAiB,oBAAI,IAGpC;EAEF,YAAY,QAAoC;AAC9C,SAAK,YAAY,OAAO;AACxB,SAAK,eAAe,OAAO;AAC3B,SAAK,eAAe,OAAO;AAC3B,SAAK,aAAa,OAAO;EAC3B;;;;;;;;;;EAWA,gBAAgB,QAAmC;AACjD,SAAK,eAAe;EACtB;;;;;;;;;EAUA,cAAc,QAAiC;AAC7C,SAAK,aAAa;EACpB;;;;;EAMQ,aAAaC,QAAuB;AAC1C,UAAM,QAAQA,OAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,IAAI;QACR,0BAA0BA,MAAK;MACjC;IACF;AACA,UAAM,aAAa,MAAM,CAAC;AAC1B,QAAI,CAAC,YAAY;AACf,YAAM,IAAI;QACR,0BAA0BA,MAAK;MACjC;IACF;AACA,UAAM,UAAU,SAAS,YAAY,EAAE;AACvC,QAAI,MAAM,OAAO,GAAG;AAClB,YAAM,IAAI,MAAM,6BAA6BA,MAAK,IAAI;IACxD;AACA,WAAO;EACT;;;;EAKQ,cAAcA,QAAe;AACnC,UAAM,SAAS,KAAK,aAAaA,MAAK;AACtC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;QACR,oCAAoCA,MAAK,iBAAiB,OAAO,KAAK,KAAK,YAAY,EAAE,KAAK,IAAI,CAAC;MACrG;IACF;AAEA,UAAM,UAAU,KAAK,aAAaA,MAAK;AAEvC,UAAM,YAAY,YAAY;MAC5B,IAAI;MACJ,MAAMA;MACN,gBAAgB,EAAE,MAAM,OAAO,QAAQ,OAAO,UAAU,GAAG;MAC3D,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE;IACzC,CAAC;AAED,UAAM,eAAe,mBAAmB;MACtC,WAAW,KAAK,MAAM;MACtB,OAAO;IACT,CAAC;AAED,UAAM,eAAe,mBAAmB;MACtC,SAAS,KAAK,UAAU;MACxB,WAAW,KAAK,MAAM;MACtB,OAAO;IACT,CAAC;AAED,WAAO,EAAE,cAAc,aAAa;EACtC;;;;;;;;;EAUA,MAAM,YAAY,QAAuD;AACvE,UAAM,cAAc,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAG7C,QAAI,gBAAgB,SAAU,QAAO,KAAK,kBAAkB,MAAM;AAClE,QAAI,gBAAgB,OAAQ,QAAO,KAAK,gBAAgB,MAAM;AAG9D,WAAO,KAAK,eAAe,MAAM;EACnC;;;;;;;;;;;;;;;;EAiBA,MAAc,kBACZ,QAC4B;AAC5B,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,MAAM,KAAK;AAGjB,UAAM,YAAY,IAAI,QAAQ,MAAM,GAAG,EAAE;AACzC,UAAM,cAAcC;MAClB,IAAI,WAAWC,QAAQ,aAAa,SAAS,CAAC;IAChD;AAGA,UAAM,YAAY,OAAO,SAAS,IAAI;AACtC,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;QACR;MACF;IACF;AACA,QAAI,CAAC,OAAO,aAAa;AACvB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,oBAAoB;MACxB,IAAI,qBAAqB,OAAO,qBAAqB;IACvD;AAEA,UAAM,UAAU,IAAI,UAChB;MACE,QAAQ,OAAO,IAAI,QAAQ,MAAM;MACjC,mBAAmB,IAAI,QAAQ;IACjC,IACA;AAEJ,UAAM,EAAE,WAAW,IAAI,MAAM,kBAAyB;MACpD,QAAQ,IAAI;MACZ,WAAW,IAAI;MACf;MACA;MACA;MACA,YAAY,OAAO;MACnB;MACA;IACF,CAAC;AAGD,SAAK,eAAe,IAAI,YAAY;MAClC,OAAO,OAAO;MACd,qBAAqB,IAAI;IAC3B,CAAC;AAED,WAAO,EAAE,WAAW,YAAY,QAAQ,UAAU;EACpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BA,MAAc,gBACZ,QAC4B;AAC5B,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,eAAe,KAAK,WAAW;AACrC,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI;QACR;MACF;IACF;AAOA,QAAI,CAAC,OAAO,aAAa;AACvB,YAAM,IAAI;QACR;MAGF;IACF;AAEA,UAAM,UAAU;MACd,KAAK,WAAW,qBAAqB,OAAO,qBAAqB;IACnE;AACA,UAAM,UAAU,KAAK,WAAW,UAC5B,EAAE,QAAQ,OAAO,KAAK,WAAW,QAAQ,MAAM,EAAE,IACjD;AAEJ,UAAM,aAAa,MAAM,uBAAuB;MAC9C,YAAY,KAAK,WAAW;MAC5B;MACA,iBAAiB,KAAK,WAAW;;MAEjC,eAAe,OAAO;MACtB;MACA,SAAS,KAAK,WAAW;MACzB;MACA,WAAW,KAAK,WAAW;IAC7B,CAAC;AAGD,SAAK,eAAe,IAAI,cAAc;MACpC,OAAO,OAAO;MACd,qBAAqB;IACvB,CAAC;AAKD,WAAO;MACL,WAAW;MACX,QAAQ;MACR,cAAc,WAAW;IAC3B;EACF;;;;;;;;;EAUA,MAAc,eACZ,QAC4B;AAC5B,UAAM;MACJ,OAAAF;MACA;MACA;MACA;MACA;IACF,IAAI;AAEJ,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,EAAE,cAAc,aAAa,IAAI,KAAK,cAAcA,MAAK;AAC/D,UAAM,mBAAmB;AACzB,UAAM,UAAU,iBAAiB,OAAO,cAAc,IAAI;AAG1D,QAAI,UAAU,MAAM,OAAO,OAAO;AAChC,YAAM,YAAY,OAAO;AACzB,YAAM,YAAY,KAAK,UAAU;AAEjC,YAAM,mBAAmB,MAAM,aAAa,aAAa;QACvD,SAAS;QACT,KAAK;QACL,cAAc;QACd,MAAM,CAAC,WAAW,gBAAgB;MACpC,CAAC;AAED,UAAK,mBAA8B,SAAS;AAC1C,cAAM,cAAc,MAAM,aAAa,cAAc;UACnD,SAAS;UACT,KAAK;UACL,cAAc;UACd,MAAM,CAAC,kBAAkB,UAAU;QACrC,CAAC;AACD,cAAM,aAAa,0BAA0B,EAAE,MAAM,YAAY,CAAC;MACpE;IACF;AAGA,UAAM,UAAU,OAAO,qBAAqB,KAAK;AACjD,UAAM,WAAW,MAAM,aAAa,cAAc;MAChD,SAAS;MACT,KAAK;MACL,cAAc;MACd,MAAM,CAAC,aAAoB,OAAO;IACpC,CAAC;AAED,UAAM,UACJ,MAAM,aAAa,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAGjE,QAAI;AACJ,eAAW,OAAO,QAAQ,MAAM;AAC9B,UAAI;AACF,cAAM,UAAU,eAAe;UAC7B,KAAK;UACL,MAAM,IAAI;UACV,QAAQ,IAAI;QACd,CAAC;AACD,YAAI,QAAQ,cAAc,iBAAiB;AACzC,sBAAa,QAAQ,KACnB,WACF;AACA;QACF;MACF,QAAQ;MAER;IACF;AAEA,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,sDAAsD;IACxE;AAGA,SAAK,eAAe,IAAI,WAAW;MACjC,OAAAA;MACA,qBAAqB;IACvB,CAAC;AAGD,QAAI,UAAU,IAAI;AAChB,YAAM,cAAc,MAAM,aAAa,cAAc;QACnD,SAAS;QACT,KAAK;QACL,cAAc;QACd,MAAM,CAAC,WAAkB,KAAK,UAAU,SAAgB,OAAO;MACjE,CAAC;AACD,YAAM,aAAa,0BAA0B,EAAE,MAAM,YAAY,CAAC;IACpE;AAEA,WAAO,EAAE,WAAW,QAAQ,UAAU;EACxC;;;;EAKA,MAAM,gBAAgB,WAA0C;AAC9D,UAAM,UAAU,KAAK,eAAe,IAAI,SAAS;AACjD,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;QACR,2BAA2B,SAAS;MACtC;IACF;AAOA,QAAI,QAAQ,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,QAAQ;AAC1C,aAAO,EAAE,WAAW,QAAQ,QAAQ,OAAO,QAAQ,MAAM;IAC3D;AAGA,QAAI,QAAQ,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,YAAY,KAAK,cAAc;AACjE,YAAM,UAAU,MAAM;QACpB,KAAK,aAAa;QAClB;MACF;AACA,YAAMG,UAAiC,CAAC,QAAQ,SAC5C,YACA,QAAQ,UAAU,WAChB,SACA,QAAQ,UAAU,WAChB,WACA;AACR,aAAO,EAAE,WAAW,QAAAA,SAAQ,OAAO,QAAQ,MAAM;IACnD;AAEA,UAAM,EAAE,aAAa,IAAI,KAAK,cAAc,QAAQ,KAAK;AAEzD,UAAM,SAAS,MAAM,aAAa,aAAa;MAC7C,SAAS,QAAQ;MACjB,KAAK;MACL,cAAc;MACd,MAAM,CAAC,SAAgB;IACzB,CAAC;AAED,UAAM,CAAC,EAAE,KAAK,IAAI;AAQlB,UAAM,SAASJ,WAAU,KAAK,KAAK;AAEnC,WAAO;MACL;MACA;MACA,OAAO,QAAQ;IACjB;EACF;AACF;AG9kBA,SAAS,sBAAsB,SAAiB,qBAA6B;AAC3E,SAAO;IACL,MAAM;IACN,SAAS;IACT;IACA,mBAAmB;EACrB;AACF;AAMA,IAAM,sBAAsB;EAC1B,cAAc;IACZ,EAAE,MAAM,aAAa,MAAM,UAAU;IACrC,EAAE,MAAM,SAAS,MAAM,UAAU;IACjC,EAAE,MAAM,qBAAqB,MAAM,UAAU;IAC7C,EAAE,MAAM,gBAAgB,MAAM,UAAU;IACxC,EAAE,MAAM,aAAa,MAAM,UAAU;EACvC;AACF;AAOO,IAAM,YAAN,MAAgB;EACZ,YAAY;EACJ;;;;EAKjB,YAAY,YAAiC;AAC3C,QAAI;AACJ,QAAI,sBAAsB,YAAY;AACpC,eAASK,OAAM,UAAU;IAC3B,OAAO;AACL,eACE,WAAW,WAAW,IAAI,IAAI,aAAa,KAAK,UAAU;IAE9D;AACA,SAAK,WAAWC,qBAAoB,MAAM;EAC5C;;EAGA,IAAI,UAAkB;AACpB,WAAO,KAAK,SAAS;EACvB;;EAGA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,SAAS;EACvB;;EAGA,IAAI,UAA6B;AAC/B,WAAO,KAAK;EACd;;;;;;;EAQA,MAAM,iBACJ,QAK6B;AAC7B,UAAM,SAAS;MACb,OAAO;MACP,OAAO;IACT;AAEA,UAAM,YAAY,MAAM,KAAK,SAAS,cAAc;MAClD;MACA,OAAO;MACP,aAAa;MACb,SAAS;QACP,WAAW,OAAO;QAClB,OAAO,OAAO,OAAO,KAAK;QAC1B,mBAAmB,OAAO;QAC1B,cAAc,OAAO;QACrB,WAAW,OAAO;MACpB;IACF,CAAC;AAED,WAAO;MACL,WAAW,OAAO;MAClB,OAAO,OAAO;MACd,mBAAmB,OAAO;MAC1B,cAAc,OAAO;MACrB,WAAW,OAAO;MAClB;MACA,eAAe,KAAK,SAAS;MAC7B,SAAS,OAAO;MAChB,qBAAqB,OAAO;MAC5B,GAAI,OAAO,gBAAgB,EAAE,cAAc,OAAO,aAAa;IACjE;EACF;;;;;;;;;EAUA,OAAO,kBACL,OACA,UACiB;AACjB,WAAO;MACL,SAAS;MACT,YAAY;MACZ,WAAW,OAAO,WAAW;MAC7B,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,aAAa,OAAO;MAChE;MACA,WAAW,MAAM;MACjB,OAAO,MAAM;MACb,mBAAmB,MAAM,kBAAkB,SAAS;MACpD,cAAc,MAAM,aAAa,SAAS;MAC1C,WAAW,MAAM;MACjB,WAAW,MAAM;MACjB,eAAe,MAAM;MACrB,SAAS,MAAM;MACf,qBAAqB,MAAM;MAC3B,GAAI,MAAM,gBAAgB,EAAE,cAAc,MAAM,aAAa;IAC/D;EACF;AACF;AC9IA,SAAS,aAAa,KAAkC;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,aAAa,QAAQ,KAAK,GAAG,IAAI,MAAM,QAAQ,GAAG;AACxD,UAAM,OAAO,IAAI,IAAI,UAAU,EAAE,SAAS,YAAY;AACtD,WAAO,KAAK,SAAS,SAAS;EAChC,QAAQ;AACN,WAAO;EACT;AACF;AA6BA,eAAsB,iBACpB,WACA,gBACA,sBACA,qBAC4B;AAO5B,QAAM,mBACJ,CAAC,CAAC,cACD,UAAU,SAAS,YAAY,UAAU,SAAS;AACrD,QAAM,WAAW,QAAQ,IAAI,mBAAmB;AAChD,MACE,CAAC,oBACD,qBAAqB,qBAAqB,SAC1C,CAAC,YACD,aAAa,cAAc,GAC3B;AACA,UAAM,EAAE,uBAAAC,uBAAsB,IAAI,MAAM,OAAO,mCAAiB;AAChE,UAAM,EAAE,8BAA8B,kBAAkB,IACtD,MAAM,OAAO,+BAAa;AAE5B,UAAM,QAAQ,MAAMA,uBAAsB;MACxC,GAAI,qBAAqB,yBAAyB,SAC9C,EAAE,WAAW,oBAAoB,qBAAqB,IACtD,CAAC;IACP,CAAC;AAED,QAAI;AACF,aAAO;QACL,iBAAiB,6BAA6B,MAAM,UAAU;QAC9D,YAAY,kBAAkB,MAAM,UAAU;QAC9C,kBAAkB,MAAM;MAC1B;IACF,SAAS,KAAK;AAEZ,YAAM,MAAM,KAAK;AACjB,YAAM;IACR;EACF;AAEA,MAAI,CAAC,aAAa,UAAU,SAAS,UAAU;AAC7C,WAAO,CAAC;EACV;AAEA,MAAI,UAAU,SAAS,UAAU;AAC/B,UAAM;MACJ;MACA;MACA;IACF,IAAI,MAAM,OAAO,+BAAa;AAG9B,UAAM,iBAAiB,UAAU,UAAU;AAE3C,WAAO;MACL,iBAAiB,6BAA6B,UAAU,UAAU;MAClE,YAAY,kBAAkB,UAAU,UAAU;IACpD;EACF;AAEA,MAAI,UAAU,SAAS,WAAW;AAChC,UAAM,EAAE,sBAAsB,IAAI,MAAM,OAAO,gCAAc;AAC7D,UAAM,YAAY;MAChB,UAAU;MACV;MACA;IACF;AACA,WAAO;MACL,QAAQ,UAAU;MAClB,cAAc,UAAU;IAC1B;EACF;AAGA,QAAM,IAAI;IACR,4BAA6B,UAA+B,IAAI;EAClE;AACF;AVtIA,eAAsB,mBACpB,QACiC;AAKjC,QAAM,YAAY,MAAM;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP;MACE,GAAI,OAAO,qBAAqB,SAC5B,EAAE,kBAAkB,OAAO,iBAAiB,IAC5C,CAAC;MACL,GAAI,OAAO,yBAAyB,SAChC,EAAE,sBAAsB,OAAO,qBAAqB,IACpD,CAAC;IACP;EACF;AAGA,QAAM,kBAAkB,UAAU,UAAU,OAAO;AACnD,QAAM,wBAAwB,UAAU,gBAAgB,OAAO;AAG/D,QAAM,iBAAiB,oBAAoB,MAAM;AAKjD,MAAI,YAAqC;AACzC,MAAI,iBAAiB;AACnB,gBAAY,IAAI,iBAAiB;MAC/B,QAAQ;MACR,QAAQ,OAAO,aAAa;MAC5B,WAAW,OAAO,gBAAgB;MAClC,iBAAiB,UAAU;IAC7B,CAAC;AACD,UAAM,UAAU,QAAQ;EAC1B;AAGA,QAAM,gBACJ,aACA,IAAI,kBAAkB;IACpB,cAAc;IACd,SAAS,OAAO;IAChB,YAAY,OAAO;IACnB,YAAY,OAAO;IACnB,YAAY,UAAU;EACxB,CAAC;AAIH,MAAI,uBAAoD;AACxD,MAAI,OAAO,cAAc;AACvB,UAAM,YAAY,IAAI,UAAU,OAAO,aAAa;AACpD,2BAAuB,IAAI,qBAAqB;MAC9C;MACA,cAAc,OAAO;IACvB,CAAC;EACH;AAGA,QAAM,kBAA0C;IAC9C,aAAa,OAAO,cAAc,CAAC,GAAG,IAAI,CAAC,OAAO;MAChD,QAAQ,EAAE;MACV,UAAU,EAAE;MACZ,aAAa,EAAE,eAAe;IAChC,EAAE;IACF,cAAc,OAAO;IACrB,gBAAgB;IAChB,iBAAiB,OAAO;IACxB;IACA,eAAe,OAAO,QAAQ;IAC9B,aAAa,OAAO;IACpB,aAAa,OAAO;IACpB,kBAAkB;;EACpB;AAEA,QAAM,mBAAmB,IAAI;IAC3B;IACA,OAAO;IACP,OAAO;EACT;AAGA,mBAAiB,aAAa,aAAa;AAG3C,MAAI,sBAAsB;AACxB,qBAAiB,iBAAiB,oBAAoB;EACxD;AAMA,QAAM,mBAAmB,uBAAuB;IAC9C,WAAW,OAAO;IAClB;EACF,CAAC;AAED,SAAO;IACL;IACA;IACA;IACA,aAAa;IACb;IACA;;;IAGA,kBAAkB,UAAU;EAC9B;AACF;AW1GO,IAAM,eAAN,MAA0C;EACtC,YAAY;;EAEJ;EACT;;;;;;EAOR,YAAY,YAAwB,iBAA0B;AAC5D,QAAI,WAAW,WAAW,IAAI;AAC5B,YAAM,IAAI;QACR,qDAAqD,WAAW,MAAM;MACxE;IACF;AACA,SAAK,aAAa;AAClB,SAAK,oBAAoB;EAC3B;EAEQ,kBAA0B;AAChC,QAAI,KAAK,kBAAmB,QAAO,KAAK;AACxC,UAAM,KAAKJ,QAAQ,aAAa,KAAK,UAAU;AAC/C,SAAK,oBAAoBD,aAAa,IAAI,WAAW,EAAE,CAAC;AACxD,WAAO,KAAK;EACd;EAEA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,qBAAqB;EACnC;EAEA,MAAM,iBAAiB,QAQS;AAC9B,QAAI,OAAO,SAAS,cAAc,UAAU;AAC1C,YAAM,IAAI;QACR,4CAA4C,OAAO,SAAS,SAAS;MACvE;IACF;AAEA,UAAMM,UAAS,KAAK,gBAAgB;AAOpC,UAAM,UAAU;MACd,OAAO;MACP,OAAO,OAAO,KAAK;MACnB,OAAO;IACT;AAEA,UAAM,YAAYL,QAAQ,KAAK,SAAS,KAAK,UAAU;AACvD,UAAM,eAAe,OAAOE,OAAW,IAAI,WAAW,SAAS,CAAC;AAEhE,WAAO;MACL,WAAW,OAAO;MAClB,OAAO,OAAO;MACd,mBAAmB,OAAO;MAC1B,cAAc,OAAO;MACrB,WAAW,OAAO;MAClB,WAAW;MACX,eAAeG;MACf,SAAS;MACT,qBAAqB,OAAO,SAAS;MACrC,WAAW,OAAO;IACpB;EACF;EAEA,kBAAkB,OAA2B,UAAgC;AAG3E,UAAM,SAAS,MAAM,UAAU,WAAW,IAAI,IAC1C,MAAM,UAAU,MAAM,CAAC,IACvB,MAAM;AACV,UAAM,WAAW,WAAW;MAC1B,OAAO,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC;IAC3D;AACA,UAAM,kBAAkB,OAAO,KAAK,QAAQ,EAAE,SAAS,QAAQ;AAE/D,UAAM,QAA4B;MAChC,SAAS;MACT,YAAY;MACZ,WAAW,OAAO,WAAW;MAC7B,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,aAAa,OAAO;MAChE;;MAEA,gBAAgB,MAAM;MACtB,OAAO,MAAM;MACb,mBAAmB,MAAM,kBAAkB,SAAS;MACpD,WAAW;MACX,iBAAiB,KAAK,qBAAqB,MAAM;MACjD,WAAW,MAAM;IACnB;AACA,WAAO;EACT;AACF;AExBA,IAAI,iBAA4C;AAehD,eAAsB,iCAA8D;AAClF,MAAI,eAAgB,QAAO;AAE3B,QAAM,YAAY;AAElB,QAAM,MAAW,MAAM;;IAA0B;;AACjD,QAAM,SAA+B,aAAa,MAAM,IAAI,UAAU;AAWtE,QAAM,YAAa,YAChB;AACH,MAAI;AACJ,MAAI,OAAO,cAAc,YAAY;AACnC,cAAU,UAAU,SAAS;EAC/B,OAAO;AACL,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,QAAa;AACpD,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,KAAU;AACjD,cAAU;MACR,cAAc,YAAY,GAAG,EAAE,QAAQ,SAAS;IAClD,EAAE;EACJ;AACA,QAAM,gBAAgB,IAAI,IAAI,MAAM,OAAO;AAC3C,QAAM,cAAc,IAAI,IAAI,kCAAkC,aAAa,EACxE;AACH,QAAM,eAAe,IAAI,IAAI,sBAAsB,aAAa,EAAE;AAClE,QAAM,WAAW,IAAI,IAAI,yBAAyB,aAAa,EAAE;AAEjE,QAAM,CAAC,aAAa,cAAc,QAAQ,IAAI,MAAM,QAAQ,IAAI;IAC9D;;MAA0B;;IAC1B;;MAA0B;;IAC1B;;MAA0B;;EAC5B,CAAC;AAED,mBAAiB;IACf;IACA,UAAU,YAAY;IACtB,WAAW,aAAa;IACxB,WAAW,SAAS;EACtB;AACA,SAAO;AACT;AAWO,SAAS,sBACd,UACA,UACA,UACA,MACQ;AACR,SAAO,SAAS,KAAK,CAAC,UAAU,UAAU,IAAI,CAAC;AACjD;AAYO,SAAS,qBACd,UACA,gBACA,cACQ;AACR,QAAM,cAAc,eAAe,WAAW,YAAY;AAC1D,SAAO,SAAS,KAAK,CAAC,YAAY,CAAC,CAAC;AACtC;AAqBO,SAAS,gCACd,UACA,gBACA,kBACA,kBACA,cACQ;AACR,QAAM,IAAI,eAAe,WAAW,gBAAgB;AACpD,QAAM,IAAI,eAAe,WAAW,gBAAgB;AACpD,SAAO,SAAS,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC;AAC/C;AA4BA,eAAsB,6BAA6B,QAuBd;AACnC,QAAM,EAAE,QAAQ,UAAU,WAAW,UAAU,IAC7C,MAAM,+BAA+B;AAIvC,QAAM,SAAS,IAAI,OAAO,EAAE,SAAS,SAAS,CAAC;AAC/C,QAAM,kBACJ,OAAO,mBACP,OAAO,gBAAgB,OAAO,oBAAoB;AAEpD,QAAM,aAAa;IACjB;IACA,OAAO;IACP,OAAO;IACP,OAAO;EACT;AAKA,QAAM,mBACJ,OAAO,gBAAgB,OAAO,eAC1B;IACE;IACA;IACA,OAAO;IACP,OAAO;IACP,OAAO,gBAAgB;EACzB,IACA,qBAAqB,UAAU,WAAW,OAAO,YAAY;AAEnE,QAAM,UAAU,CAAC,YAAY,OAAO,OAAO,gBAAgB;AAC3D,QAAM,SAAS,OAAO,WAAW,SAAS,OAAO,oBAAoB;AAIrE,QAAM,EAAE,GAAG,EAAE,IAAI,UAAU,WAAW,OAAO,SAAS;AAEtD,QAAM,cAAc;IAClB,YAAY,WAAW,SAAS;IAChC,WAAW,EAAE,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,SAAS,EAAE;IAC9C,OAAO,OAAO,MAAM,SAAS;IAC7B;EACF;AACA,QAAM,YAAY,KAAK,UAAU,WAAW;AAC5C,QAAM,WAAW,OAAO,iBAAiB;AACzC,QAAM,QACJ,aAAa,WACT,OAAO,KAAK,WAAW,MAAM,EAAE,SAAS,QAAQ,IAChD;AAEN,SAAO;IACL,mBAAmB,WAAW,SAAS;IACvC;IACA,MAAM,OAAO,KAAK,SAAS;IAC3B;EACF;AACF;AC3UA,IAAM,4BAA4B;AASlC,eAAsB,qBACpB,YACA,cACA,YAA0B,OACT;AACjB,QAAM,QAAQ;AACd,QAAM,MAAM,MAAM,UAAU,YAAY;IACtC,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,EAAE,IAAI,aAAa,EAAE,CAAC;EACjE,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,qCAAqC,IAAI,MAAM,EAAE;EACnE;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;AACzC,UAAM,IAAI;MACR,uBAAuB,KAAK,OAAO,CAAC,GAAG,WAAW,SAAS;IAC7D;EACF;AACA,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,MAAI,CAAC,SAAS,MAAM,UAAU,2BAA2B;AACvD,UAAM,IAAI;MACR,cAAc,YAAY;IAC5B;EACF;AACA,SAAO,OAAO,MAAM,yBAAyB,CAAW;AAC1D;AFtBA,IAAM,wBAAwB;AAG9B,IAAM,qBAAqB;AAU3B,SAAS,eAAe,cAAsB,OAAuB;AACnE,QAAM,YAAY;IAChBX,QAAO,IAAI,YAAY,EAAE,OAAO,gBAAgB,YAAY,IAAI,KAAK,EAAE,CAAC;EAC1E;AACA,QAAM,OAAO,OAAO,OAAO,UAAU,MAAM,GAAG,EAAE,CAAC;AACjD,SAAO,SAAS,KAAK,KAAK;AAC5B;AA2CO,IAAM,aAAN,MAAwC;EACpC,YAAY;;EAEJ;EACT;EACS;;EAEA,eAAe,oBAAI,IAAoB;;;;;;;;;;EAWxD,YACE,YACA,iBACA,SACA;AACA,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,QAAI,SAAS,eAAe;AAC1B,WAAK,gBAAgB,QAAQ;IAC/B,WAAW,SAAS,YAAY;AAC9B,YAAM,MAAM,QAAQ;AACpB,WAAK,gBAAgB,CAAC,iBACpB,qBAAqB,KAAK,YAAY;IAC1C;EACF;;;;;;EAOA,MAAc,oBACZ,cAC6B;AAC7B,QAAI,KAAK,aAAa,IAAI,YAAY,GAAG;AACvC,aAAO,KAAK,aAAa,IAAI,YAAY;IAC3C;AACA,QAAI,CAAC,KAAK,cAAe,QAAO;AAChC,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,cAAc,YAAY;AAC1D,WAAK,aAAa,IAAI,cAAc,YAAY;AAChD,aAAO;IACT,QAAQ;AACN,aAAO;IACT;EACF;EAEA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,mBAAmB;EACjC;;EAGA,MAAc,mBACZ,sBACiB;AACjB,UAAM,EAAE,OAAO,IAAI,MAAM,+BAA+B;AACxD,WAAO,IAAI,OAAO,EAAE,SAAS,mBAAmB,CAAC,EAAE;MACjD;IACF;EACF;EAEA,MAAM,iBAAiB,QAiBS;AAC9B,QAAI,OAAO,SAAS,cAAc,QAAQ;AACxC,YAAM,IAAI;QACR,0CAA0C,OAAO,SAAS,SAAS;MACrE;IACF;AAKA,UAAM,eAAe,OAAO,aAAa,OAAO,SAAS;AACzD,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,iBAAiBE,0BAA0B,KAAK,UAAU;AAChE,UAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,UAAM,OAAO,eAAe,cAAc,OAAO,KAAK;AAKtD,UAAM,eACJ,KAAK,mBAAoB,MAAM,KAAK,mBAAmB,cAAc;AACvE,SAAK,kBAAkB;AAQvB,UAAM,aACJ,OAAO,aAAa,0BAA0B,KAAK,OAAO,SAAS,IAC/D,OAAO,YACP;AAcN,UAAM,eACJ,OAAO,gBAAiB,MAAM,KAAK,oBAAoB,YAAY;AACrE,QAAI,WAAW;AACf,QAAI,gBAAgB,QAAQ,eAAe,IAAI;AAC7C,UAAI,OAAO,oBAAoB,cAAc;AAC3C,cAAM,IAAI;UACR,wBAAwB,OAAO,iBAAiB,oCAC7B,YAAY;QACjC;MACF;AACA,iBAAW,eAAe,OAAO;IACnC;AAEA,UAAM,QAAQ,MAAM,6BAA6B;MAC/C;MACA,sBAAsB;MACtB,iBAAiB;;;;;MAKjB,UAAU,OAAO;MACjB;MACA;MACA,OAAO,OAAO,OAAO,KAAK;;;MAG1B,GAAI,aACA,EAAE,cAAc,cAAc,cAAc,WAAW,IACvD,CAAC;IACP,CAAC;AACD,SAAK,kBAAkB,MAAM;AAE7B,WAAO;MACL,WAAW;MACX,OAAO,OAAO;MACd,mBAAmB,OAAO;MAC1B,cAAc,OAAO;MACrB,WAAW,OAAO;;;MAGlB,WAAW,MAAM;MACjB,eAAe,MAAM;MACrB,SAAS;MACT,qBAAqB;MACrB,WAAW,OAAO;MAClB,MAAM;QACJ,mBAAmB,MAAM;QACzB,OAAO,MAAM;QACb,MAAM,MAAM;QACZ;MACF;IACF;EACF;EAEA,kBAAkB,OAA2B,UAAgC;AAC3E,QAAI,CAAC,MAAM,MAAM;AACf,YAAM,IAAI;QACR;MACF;IACF;AACA,UAAM,QAA0B;MAC9B,SAAS;MACT,YAAY;MACZ,WAAW,OAAO,WAAW;MAC7B,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,aAAa,OAAO;MAChE;MACA,cAAc,MAAM;MACpB,SAAS,MAAM,KAAK;MACpB,mBAAmB,MAAM,KAAK;MAC9B,OAAO,MAAM;MACb,OAAO,MAAM,KAAK;MAClB,MAAM,MAAM,KAAK;MACjB,mBAAmB,MAAM,kBAAkB,SAAS;;;;;;;MAOpD,iBAAiB,MAAM;MACvB,SAAS;IACX;AACA,WAAO;EACT;AACF;AGvQO,IAAM,iBAAN,MAAqB;EACT,WAAW,oBAAI,IAA6B;EAC5C,eAAe,oBAAI,IAAyB;EAC5C,eAAe,oBAAI,IAAoB;EACvC,eAAe,oBAAI,IAA6B;EAChD;EACA;EACA;EACT;;EAGS;EAEjB,YACE,WACA,OACA,QACA;AACA,SAAK,YAAY;AACjB,SAAK,QAAQ;AACb,SAAK,wBAAwB,QAAQ,kBAAkB;AACvD,SAAK,2BAA2B,QAAQ,qBAAqB;EAC/D;;;;EAKA,oBAAoB,WAAmB,QAA2B;AAChE,SAAK,aAAa,IAAI,WAAW,MAAM;EACzC;;;;EAKA,iBAAiB,QAAsC;AACrD,SAAK,gBAAgB;EACvB;;;;;EAMA,oBAAoB,WAAgC;AAClD,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,YAAY,SAAS,yBAAyB;IAChE;AAGA,UAAM,SAAS,KAAK,aAAa,IAAI,SAAS,SAAS;AACvD,QAAI,OAAQ,QAAO;AAGnB,QAAI,SAAS,cAAc,SAAS,KAAK,WAAW;AAClD,YAAM,YAAY,KAAK;AACvB,aAAO;QACL,WAAW;QACX,kBAAkB,UAAU;QAC5B,MAAM,iBAAiB,QAAQ;AAC7B,cAAI,OAAO,SAAS,cAAc;AAChC,kBAAM,IAAI,MAAM,uBAAuB;AACzC,iBAAO,UAAU,iBAAiB;YAChC,WAAW,OAAO;YAClB,OAAO,OAAO;YACd,mBAAmB,OAAO;YAC1B,cAAc,OAAO;YACrB,WAAW,OAAO;YAClB,SAAS,OAAO,SAAS;YACzB,qBAAqB,OAAO,SAAS;YACrC,cAAc,OAAO,SAAS;UAChC,CAAC;QACH;QACA,kBAAkB,OAAO,UAAU;AACjC,iBAAO,UAAU,kBAAkB,OAAO,QAAQ;QACpD;MACF;IACF;AAEA,UAAM,IAAI;MACR,wCAAwC,SAAS,SAAS;IAC5D;EACF;;;;;EAMA,MAAM,cACJ,QACA,aACiB;AAEjB,UAAM,WAAW,KAAK,aAAa,IAAI,MAAM;AAC7C,QAAI,SAAU,QAAO;AAGrB,UAAM,UAAU,KAAK,aAAa,IAAI,MAAM;AAC5C,QAAI,QAAS,QAAO;AAEpB,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI;QACR;MACF;IACF;AAEA,UAAM,eAAe,YAAY;AAC/B,UAAI;AAEF,cAAM,SAAS,MAAM,KAAK,cAAe,YAAY;UACnD;UACA,OAAO,YAAY;UACnB,OAAO,YAAY;UACnB,cAAc,YAAY;UAC1B,aAAa,YAAY;UACzB,gBACE,YAAY,kBAAkB,KAAK;UACrC,mBACE,YAAY,qBAAqB,KAAK;QAC1C,CAAC;AAED,aAAK,aAAa,OAAO,WAAW;UAClC,WAAW,YAAY;UACvB,SACE,OAAO,YAAY,YAAY,WAAW,YAAY,UAAU;UAClE,qBAAqB,YAAY,gBAAgB;UACjD,cAAc,YAAY;UAC1B,WAAW,YAAY;;;UAGvB,cAAc,OAAO;QACvB,CAAC;AACD,aAAK,aAAa,IAAI,QAAQ,OAAO,SAAS;AAC9C,eAAO,OAAO;MAChB,UAAA;AACE,aAAK,aAAa,OAAO,MAAM;MACjC;IACF,GAAG;AAEH,SAAK,aAAa,IAAI,QAAQ,WAAW;AACzC,WAAO;EACT;;;;EAKA,kBAAkB,QAAoC;AACpD,WAAO,KAAK,aAAa,IAAI,MAAM;EACrC;;;;;;;;;;EAWA,aACE,WACA,cAQA,eAAe,GACf,gBAAgB,IACV;AACN,UAAM,MAAM,cAAc,WAAW;AACrC,UAAM,SACJ,cAAc,uBACd;AAGF,QAAI,KAAK,OAAO;AACd,YAAM,YAAY,KAAK,MAAM,KAAK,SAAS;AAC3C,UAAI,WAAW;AACb,aAAK,SAAS,IAAI,WAAW;UAC3B,OAAO,UAAU;UACjB,kBAAkB,UAAU;UAC5B,WAAW,cAAc,aAAa;UACtC,SAAS;UACT,qBAAqB;UACrB,cAAc,cAAc;UAC5B,WAAW,cAAc;UACzB,cAAc,cAAc;QAC9B,CAAC;AACD;MACF;IACF;AAEA,SAAK,SAAS,IAAI,WAAW;MAC3B,OAAO;MACP,kBAAkB;MAClB,WAAW,cAAc,aAAa;MACtC,SAAS;MACT,qBAAqB;MACrB,cAAc,cAAc;MAC5B,WAAW,cAAc;MACzB,cAAc,cAAc;IAC9B,CAAC;EACH;;;;;;;;;;;EAYA,MAAM,iBACJ,WACA,kBAC6B;AAC7B,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;QACR,YAAY,SAAS;MACvB;IACF;AAEA,aAAS,SAAS;AAClB,aAAS,oBAAoB;AAG7B,QAAI,KAAK,OAAO;AACd,WAAK,MAAM,KAAK,WAAW;QACzB,OAAO,SAAS;QAChB,kBAAkB,SAAS;MAC7B,CAAC;IACH;AAGA,UAAM,SAAS,KAAK,aAAa,IAAI,SAAS,SAAS;AACvD,QAAI,UAAU,SAAS,cAAc,OAAO;AAC1C,UAAI,CAAC,SAAS,WAAW;AACvB,cAAM,IAAI;UACR,YAAY,SAAS,MAAM,SAAS,SAAS;QAE/C;MACF;AACA,YAAM,WAAW,KAAK,cAAc,QAAQ;AAC5C,aAAO,OAAO,iBAAiB;QAC7B;QACA,OAAO,SAAS;QAChB,mBAAmB,SAAS;QAC5B,cAAc;QACd,WACE;QACF,WAAW,SAAS;QACpB;;;;;QAKA,cAAc,SAAS;MACzB,CAAC;IACH;AAGA,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,mDAAmD;IACrE;AACA,WAAO,KAAK,UAAU,iBAAiB;MACrC;MACA,OAAO,SAAS;MAChB,mBAAmB,SAAS;MAC5B,cAAc;MACd,WACE;MACF,SAAS,SAAS;MAClB,qBAAqB,SAAS;MAC9B,cAAc,SAAS;IACzB,CAAC;EACH;EAEQ,cAAc,UAA0C;AAC9D,YAAQ,SAAS,WAAW;MAC1B,KAAK;AACH,eAAO,EAAE,WAAW,UAAU,WAAW,SAAS,oBAAoB;MACxE,KAAK;AACH,eAAO;UACL,WAAW;UACX,cAAc,SAAS;QACzB;MACF;AACE,eAAO;UACL,WAAW;UACX,SAAS,SAAS;UAClB,qBAAqB,SAAS;UAC9B,cAAc,SAAS;QACzB;IACJ;EACF;;;;EAKA,SAAS,WAA2B;AAClC,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,YAAY,SAAS,yBAAyB;IAChE;AACA,WAAO,SAAS;EAClB;;;;EAKA,oBAAoB,WAA2B;AAC7C,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,YAAY,SAAS,yBAAyB;IAChE;AACA,WAAO,SAAS;EAClB;;;;EAKA,qBAA+B;AAC7B,WAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;EACxC;;;;EAKA,WAAW,WAA4B;AACrC,WAAO,KAAK,SAAS,IAAI,SAAS;EACpC;AACF;AC1WO,IAAM,uBAAN,MAAmD;EACvC;EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW;EAClB;EAEA,KAAK,WAAmB,UAAmC;AACzD,UAAM,OAAO,KAAK,SAAS;AAC3B,SAAK,SAAS,IAAI;MAChB,OAAO,SAAS;MAChB,kBAAkB,SAAS,iBAAiB,SAAS;IACvD;AACA,SAAK,UAAU,IAAI;EACrB;EAEA,KAAK,WAAkD;AACrD,UAAM,OAAO,KAAK,SAAS;AAC3B,UAAM,QAAQ,KAAK,SAAS;AAC5B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO;MACL,OAAO,MAAM;MACb,kBAAkB,OAAO,MAAM,gBAAgB;IACjD;EACF;EAEA,OAAiB;AACf,WAAO,OAAO,KAAK,KAAK,SAAS,CAAC;EACpC;EAEA,OAAO,WAAyB;AAC9B,UAAM,OAAO,KAAK,SAAS;AAC3B,UAAM,EAAE,CAAC,SAAS,GAAG,GAAG,GAAG,KAAK,IAAI;AACpC,SAAK,UAAU,IAAI;EACrB;EAEQ,WAAsC;AAC5C,QAAI,CAAC,WAAW,KAAK,QAAQ,GAAG;AAC9B,aAAO,CAAC;IACV;AACA,UAAM,MAAM,aAAa,KAAK,UAAU,OAAO;AAC/C,WAAO,KAAK,MAAM,GAAG;EACvB;EAEQ,UAAU,MAAuC;AACvD,kBAAc,KAAK,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;EACrE;AACF;ArBWO,IAAM,aAAN,MAAiB;EACL;EACT,QAAgC;EACvB;EACT;;;;;;EAMA;EACA;;;;;;EAMA;EACA;EACS,mBAAmB,oBAAI,IAA6B;;;;;;;EAQrE,YAAY,QAA0B;AAEpC,mBAAe,MAAM;AAGrB,SAAK,SAAS,cAAc,MAAM;AAGlC,QAAI,KAAK,OAAO,eAAe;AAC7B,WAAK,YAAY,IAAI,UAAU,KAAK,OAAO,aAAa;IAC1D;EACF;;;;;;EAOA,OAAO,kBAA6D;AAClE,UAAM,YAAYU,mBAAkB;AACpC,UAAM,SAASC,eAAa,SAAS;AACrC,WAAO,EAAE,WAAW,OAAO;EAC7B;;;;;EAMA,eAAuB;AACrB,WAAOA,eAAa,KAAK,OAAO,SAAS;EAC3C;;;;;;EAOA,mBAAoD;AAClD,WAAO,iBAAiB,KAAK,MAAM;EACrC;;;;EAKA,gBAAoC;AAClC,WAAO,KAAK,WAAW;EACzB;;;;;;EAOA,mBAAuC;AACrC,WAAO,KAAK,cAAc;EAC5B;;;;;;EAOA,iBAAqC;AACnC,WAAO,KAAK,YAAY;EAC1B;;;;;;EAOA,MAAc,6BACZ,UACA,eAAe,GACA;AACf,QAAI,CAAC,KAAK,eAAgB;AAC1B,UAAM,WAAW,MAAM,mBAAmB,UAAU,YAAY;AAIhE,QAAI,SAAS,OAAO,WAAW;AAC7B,YAAM,OAAO,SAAS,OAAO,UAAU,MAAM,GAAG,EAAE;AAClD,WAAK,aAAa;AAClB,WAAK,eAAe,IAAI,aAAa,MAAM,SAAS,OAAO,SAAS;AACpE,WAAK,eAAe,oBAAoB,UAAU,KAAK,YAAY;IACrE;AAGA,QAAI,SAAS,KAAK,WAAW;AAC3B,WAAK,iBAAiB,SAAS,KAAK;AAMpC,WAAK,aAAa,IAAI;QACpB,SAAS,KAAK;QACd,SAAS,KAAK;QACd,KAAK,OAAO,aAAa,aACrB,EAAE,YAAY,KAAK,OAAO,YAAY,WAAW,IACjD;MACN;AACA,WAAK,eAAe,oBAAoB,QAAQ,KAAK,UAAU;IACjE;EACF;;;;;;;;;;;;;EAcA,MAAM,QAAkC;AACtC,QAAI,KAAK,UAAU,MAAM;AACvB,YAAM,IAAI,gBAAgB,0BAA0B,eAAe;IACrE;AAEA,QAAI;AAEF,UAAI,KAAK,WAAW;AAClB,cAAM,QAAQ,KAAK,OAAO,mBACtB,IAAI,qBAAqB,KAAK,OAAO,gBAAgB,IACrD;AACJ,aAAK,iBAAiB,IAAI,eAAe,KAAK,WAAW,KAAK;AAQ9D,YAAI,KAAK,OAAO,UAAU;AACxB,gBAAM,KAAK;YACT,KAAK,OAAO;YACZ,KAAK,OAAO,wBAAwB;UACtC;QACF;MACF;AAGA,YAAM,iBAAiB,MAAM,mBAAmB,KAAK,MAAM;AAE3D,YAAM;QACJ;QACA;QACA;QACA;QACA;MACF,IAAI;AAGJ,UAAI,KAAK,gBAAgB;AACvB,cAAM,KAAK,KAAK;AAChB,cAAM,cAAc,KAAK,aAAa;AAEtC,cAAM,kBAAkB,KAAK,uBAAuB;AACpD,yBAAiB;UACf,OAAO,WAAmB,WAAmB;AAE3C,gBAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B,iBAAG,aAAa,WAAW,eAAe;YAC5C;AAIA,kBAAM,QAAQ,MAAM,GAAG,iBAAiB,WAAW,MAAM;AACzD,kBAAM,SAAS,GAAG,oBAAoB,SAAS;AAC/C,mBAAO,OAAO,kBAAkB,OAAO,WAAW;UACpD;QACF;MACF;AAGA,YAAM,mBAAmB,MAAM,iBAAiB,UAAU;AAG1D,iBAAW,UAAU,kBAAkB;AACrC,YAAI,OAAO,mBAAmB,OAAO,mBAAmB;AACtD,gBAAM,YAAY,OAAO,gBAAgB,MAAM,GAAG,EAAE,CAAC,KAAK;AAC1D,gBAAM,QAAQ,OAAO,gBAAgB,MAAM,GAAG;AAC9C,gBAAM,UAAU,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI;AACpE,gBAAM,IAAI;AAIV,eAAK,iBAAiB,IAAI,OAAO,kBAAkB;YACjD,OAAO,OAAO;YACd;YACA,SAAS,MAAM,OAAO,IAAI,IAAI;YAC9B,mBAAmB,OAAO;YAC1B,cAAc,EAAE;YAChB,cAAc,EAAE;UAClB,CAAC;QACH,WACE,OAAO,oBACP,CAAC,KAAK,iBAAiB,IAAI,OAAO,gBAAgB,GAClD;AAKA,gBAAM,WAAW,OAAO;AAMxB,gBAAM,aAAa,SAAS,mBAAmB,CAAC;AAChD,gBAAM,YAAY,KAAK,OAAO,mBAAmB,CAAC;AAElD,gBAAM,eACJ,UAAU,KAAK,CAAC,MAAM,WAAW,SAAS,CAAC,CAAC,KAAK,UAAU,CAAC;AAC9D,cAAI,cAAc;AAChB,kBAAM,WAAW,SAAS,sBAAsB,YAAY;AAC5D,kBAAM,QAAQ,aAAa,MAAM,GAAG;AACpC,kBAAM,UACJ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI;AACtD,gBAAI,UAAU;AACZ,mBAAK,iBAAiB,IAAI,OAAO,kBAAkB;gBACjD,OAAO;gBACP,WAAW,MAAM,CAAC,KAAK;gBACvB,SAAS,MAAM,OAAO,IAAI,IAAI;gBAC9B,mBAAmB;gBACnB,cACE,SAAS,kBAAkB,YAAY,KACvC,KAAK,OAAO,kBAAkB,YAAY;gBAC5C,cACE,SAAS,gBAAgB,YAAY,KACrC,KAAK,OAAO,gBAAgB,YAAY;cAC5C,CAAC;YACH;UACF;QACF;AAEA,YACE,KAAK,kBACL,OAAO,aACP,CAAC,KAAK,eAAe,WAAW,OAAO,SAAS,GAChD;AACA,gBAAM,WAAW,KAAK,gBAAgB,OAAO,eAAe;AAC5D,eAAK,eAAe,aAAa,OAAO,WAAW,QAAQ;QAC7D;MACF;AAGA,UAAI,KAAK,kBAAkB,eAAe,sBAAsB;AAC9D,aAAK,eAAe;UAClB,eAAe;QACjB;AAOA,YAAI,KAAK,OAAO,iBAAiB,KAAK,YAAY;AAChD,yBAAe,qBAAqB,gBAAgB;YAClD,QAAQ,KAAK,OAAO,cAAc;YAClC,WAAW,KAAK,OAAO,cAAc;YACrC,WAAW,KAAK,OAAO,cAAc;YACrC,mBAAmB,KAAK,OAAO,cAAc;YAC7C,SAAS,KAAK,OAAO,cAAc;YACnC,SAAS,KAAK;UAChB,CAAC;QACH;AAaA,YAAI,KAAK,OAAO,eAAe,KAAK,gBAAgB;AAClD,yBAAe,qBAAqB,cAAc;YAChD,YAAY,KAAK,OAAO,YAAY;YACpC,cAAc,KAAK,OAAO,YAAY;YACtC,YAAY,KAAK;YACjB,GAAI,KAAK,OAAO,YAAY,sBAAsB,SAC9C,EAAE,mBAAmB,KAAK,OAAO,YAAY,kBAAkB,IAC/D,CAAC;YACL,GAAI,KAAK,OAAO,YAAY,YAAY,SACpC,EAAE,SAAS,KAAK,OAAO,YAAY,QAAQ,IAC3C,CAAC;YACL,GAAI,KAAK,OAAO,YAAY,YAAY,SACpC,EAAE,SAAS,KAAK,OAAO,YAAY,QAAQ,IAC3C,CAAC;YACL,GAAI,KAAK,OAAO,YAAY,cAAc,SACtC,EAAE,WAAW,KAAK,OAAO,YAAY,UAAU,IAC/C,CAAC;UACP,CAAC;QACH;MACF;AAGA,WAAK,QAAQ;QACX;QACA;QACA;QACA,iBAAiB,iBAAiB;QAClC,WAAW,aAAa;QACxB,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;MACjD;AAEA,aAAO;QACL,iBAAiB,iBAAiB;QAClC,MAAM;MACR;IACF,SAAS,OAAO;AACd,YAAM,IAAI;QACR;QACA;QACA,iBAAiB,QAAQ,QAAQ;MACnC;IACF;EACF;;;;;;;;;;;;EAaA,MAAM,aACJ,OACA,SAK6B;AAC7B,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,QAAI;AAEF,YAAM,WAAW,KAAK,OAAO,YAAY,KAAK;AAI9C,YAAM,mBAAmB;AACzB,YAAM,SACJ,SAAS,cAAc,SACnB,OAAO,QAAQ,SAAS,IACxB,OAAO,OAAO,SAAS,MAAM,IAAI,gBAAgB;AAGvD,YAAM,cACJ,SAAS,eAAe,KAAK,OAAO;AAEtC,UAAI,CAAC,KAAK,MAAM,WAAW;AACzB,cAAM,IAAI;UACR;UACA;QACF;MACF;AAGA,UAAI;AACJ,UAAI,SAAS,OAAO;AAIlB,uBAAe,KAAK,0BAA0B,QAAQ,KAAK;MAC7D,WAAW,KAAK,gBAAgB;AAE9B,cAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,cAAM,cAAc,KAAK,iBAAiB,IAAI,MAAM;AACpD,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI;YACR,qCAAqC,MAAM;YAC3C;UACF;QACF;AACA,cAAM,YAAY,MAAM,KAAK,eAAe;UAC1C;UACA;QACF;AACA,cAAM,QAAQ,MAAM,KAAK,eAAe;UACtC;UACA,OAAO,MAAM;QACf;AACA,cAAM,SAAS,KAAK,eAAe,oBAAoB,SAAS;AAChE,uBAAe,OAAO,kBAAkB,OAAO,KAAK,aAAa,CAAC;MACpE,OAAO;AACL,cAAM,IAAI;UACR;UACA;QACF;MACF;AAEA,YAAM,WAAW,MAAM,KAAK,MAAM,UAAU;QAC1C;UACE;UACA;UACA,MAAM;YACJ,oBAAoB,aAAa,WAAW,IAAI,WAAW,QAAQ;UACrE;QACF;QACA;MACF;AAEA,UAAI,CAAC,SAAS,UAAU;AACtB,eAAO;UACL,SAAS;UACT,OAAO,mBAAmB,SAAS,IAAI,MAAM,SAAS,OAAO;QAC/D;MACF;AAEA,aAAO;QACL,SAAS;QACT,SAAS,MAAM;QACf,MAAM,SAAS;MACjB;IACF,SAAS,OAAO;AACd,cAAQ;QACN;QACA,OAAO,KAAK;QACZ,iBAAiB,QAAQ,MAAM,QAAQ;MACzC;AACA,YAAM,IAAI;QACR;QACA;QACA,iBAAiB,QAAQ,QAAQ;MACnC;IACF;EACF;;;;;;;;;;;;;;;EAgBA,MAAM,eAAe,QAMM;AACzB,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,QAAI,CAAC,KAAK,MAAM,WAAW;AACzB,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,UAAM,eAAe,MAAM,KAAK;MAC9B,OAAO;MACP,OAAO;MACP,OAAO;IACT;AAEA,WAAO,KAAK,MAAM,UAAU;MAC1B;QACE,aAAa,OAAO;QACpB,QAAQ,OAAO,OAAO,MAAM;QAC5B,MAAM,SAAS,OAAO,QAAQ;QAC9B,SAAS,OAAO,WAAW;MAC7B;MACA;IACF;EACF;;;;;;;;;;;;;;;;;;;;;;;;EAyBQ,0BAA0B,OAAgC;AAChE,QAAI,KAAK,gBAAgB,WAAW,MAAM,SAAS,GAAG;AACpD,YAAM,SAAS,KAAK,eAAe,oBAAoB,MAAM,SAAS;AACtE,aAAO,OAAO,kBAAkB,OAAO,KAAK,aAAa,CAAC;IAC5D;AACA,WAAO,UAAU,kBAAkB,OAAO,KAAK,aAAa,CAAC;EAC/D;;;;;;EAOA,MAAc,2BACZ,aACA,QACA,eAEc;AACd,QAAI,eAAe;AACjB,aAAO,KAAK,0BAA0B,aAAa;IACrD;AACA,QAAI,KAAK,gBAAgB;AACvB,YAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,YAAM,cAAc,KAAK,iBAAiB,IAAI,MAAM;AACpD,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;UACR,qCAAqC,MAAM;UAC3C;QACF;MACF;AACA,YAAM,YAAY,MAAM,KAAK,eAAe;QAC1C;QACA;MACF;AACA,YAAM,QAAQ,MAAM,KAAK,eAAe;QACtC;QACA;MACF;AACA,YAAM,SAAS,KAAK,eAAe,oBAAoB,SAAS;AAChE,aAAO,OAAO,kBAAkB,OAAO,KAAK,aAAa,CAAC;IAC5D;AACA,UAAM,IAAI;MACR;MACA;IACF;EACF;;;;;;;;;;EAWA,MAAM,iBACJ,WACA,QAC6B;AAC7B,QAAI,CAAC,KAAK,gBAAgB;AACxB,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,WAAO,KAAK,eAAe,iBAAiB,WAAW,MAAM;EAC/D;;;;;;;;;;;;;;;;EAiBA,MAAM,YAAY,aAAuC;AACvD,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,QAAI,CAAC,KAAK,gBAAgB;AACxB,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,UAAM,OAAO,eAAe,KAAK,OAAO;AACxC,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,UAAM,SAAS,KAAK,cAAc,IAAI;AACtC,UAAM,cAAc,KAAK,iBAAiB,IAAI,MAAM;AACpD,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;QACR,qCAAqC,MAAM;QAC3C;MACF;IACF;AACA,WAAO,KAAK,eAAe,cAAc,QAAQ,WAAW;EAC9D;;;;EAKA,qBAA+B;AAC7B,WAAO,KAAK,gBAAgB,mBAAmB,KAAK,CAAC;EACvD;;;;EAKA,gBAAgB,WAA2B;AACzC,QAAI,CAAC,KAAK,eAAgB,OAAM,IAAI,MAAM,gCAAgC;AAC1E,WAAO,KAAK,eAAe,SAAS,SAAS;EAC/C;;;;EAKA,2BAA2B,WAA2B;AACpD,QAAI,CAAC,KAAK,eAAgB,OAAM,IAAI,MAAM,gCAAgC;AAC1E,WAAO,KAAK,eAAe,oBAAoB,SAAS;EAC1D;;;;;;EAOQ,cAAc,aAA6B;AAEjD,UAAM,WAAW,YAAY,MAAM,GAAG;AACtC,UAAM,cAAc,SAAS,SAAS,SAAS,CAAC,KAAK;AAGrD,QAAI,eAAe,KAAK,iBAAiB,IAAI,WAAW,GAAG;AACzD,aAAO;IACT;AAGA,eAAW,UAAU,KAAK,iBAAiB,KAAK,GAAG;AACjD,UACE,YAAY,SAAS,IAAI,MAAM,EAAE,KACjC,YAAY,SAAS,IAAI,OAAO,QAAQ,UAAU,EAAE,CAAC,EAAE,GACvD;AACA,eAAO;MACT;IACF;AAGA,UAAM,kBAAkB,KAAK,iBAAiB,KAAK,EAAE,KAAK;AAC1D,QAAI,CAAC,gBAAgB,QAAQ,gBAAgB;AAC3C,aAAO,gBAAgB;AAEzB,UAAM,IAAI;MACR,wCAAwC,WAAW;MACnD;IACF;EACF;;;;EAKQ,gBACN,iBAGY;AACZ,QAAI,CAAC,gBAAiB,QAAO;AAC7B,UAAM,QAAQ,gBAAgB,MAAM,GAAG;AACvC,UAAM,cAAc,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI;AACnD,UAAM,iBACJ,gBAAgB,SAAY,SAAS,aAAa,EAAE,IAAI;AAC1D,QAAI,MAAM,cAAc,EAAG,QAAO;AAClC,UAAM,sBAAsB,KAAK,OAAO,gBAAgB,eAAe;AACvE,QAAI,CAAC,oBAAqB,QAAO;AACjC,UAAM,eAAe,KAAK,OAAO,kBAAkB,eAAe;AAClE,WAAO,EAAE,SAAS,gBAAgB,qBAAqB,aAAa;EACtE;;;;EAKQ,yBAEM;AACZ,UAAM,SAAS,KAAK,OAAO;AAC3B,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,WAAO,KAAK,gBAAgB,OAAO,CAAC,CAAC;EACvC;;;;;;;;EASA,MAAM,YAAY,QAKS;AACzB,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,UAAM,YAAY;MAChB,aAAa,OAAO;MACpB,QAAQ,OAAO;MACf,MAAM,OAAO,QAAQ;IACvB;AAIA,QAAI,CAAC,OAAO,OAAO;AACjB,YAAM,IAAI;QACR;QACA;MACF;IACF;AACA,QAAI,CAAC,KAAK,MAAM,WAAW;AACzB,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,UAAM,eAAe,KAAK,0BAA0B,OAAO,KAAK;AAChE,WAAO,KAAK,MAAM,UAAU;MAC1B;MACA;IACF;EACF;;;;;;;;;;EAWA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,gBAAgB,sBAAsB,eAAe;IACjE;AAEA,UAAM,mBAAmB,KAAK,MAAM;AACpC,QAAI;AAEF,UAAI,KAAK,MAAM,WAAW;AACxB,cAAM,KAAK,MAAM,UAAU,WAAW;MACxC;AAKA,UAAI,kBAAkB;AACpB,cAAM,iBAAiB;MACzB;AAGA,WAAK,QAAQ;IACf,SAAS,OAAO;AACd,YAAM,IAAI;QACR;QACA;QACA,iBAAiB,QAAQ,QAAQ;MACnC;IACF;EACF;;;;EAKA,YAAqB;AACnB,WAAO,KAAK,UAAU;EACxB;;;;;;;EAQA,gBAAwB;AACtB,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,WAAO,KAAK,MAAM;EACpB;;;;;;;EAQA,qBAAqB;AACnB,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI;QACR;QACA;MACF;IACF;AAEA,WAAO,KAAK,MAAM,iBAAiB,mBAAmB;EACxD;AACF;AsCx6BA,IAAM,WAAW,KAAK;AACtB,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB,WAAW,WAAW,MAAM,KAAK,OAAO;AAO9D,IAAM,eAAe;AAyBrB,SAAS,aAAmB;AAE1B,QAAM,WACJ,WACA,SAAS;AACX,MAAI,CAAC,UAAU,MAAM;AACnB,UAAM,IAAI;MACR;IAEF;EACF;AACF;AAqDO,SAASC,iBACd,WACA,UACQ;AACR,aAAW;AACX,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GAAG;AACzD,UAAM,IAAI,MAAM,sDAAsD;EACxE;AACA,MACE,CAAC,aACD,OAAO,UAAU,SAAS,YAC1B,OAAO,UAAU,OAAO,YACxB,OAAO,UAAU,eAAe,YAChC,OAAO,UAAU,QAAQ,UACzB;AACA,UAAM,IAAI,MAAM,8CAA8C;EAChE;AAEA,QAAM,OAAO,OAAO,KAAK,UAAU,MAAM,QAAQ;AACjD,QAAM,KAAK,OAAO,KAAK,UAAU,IAAI,QAAQ;AAC7C,QAAM,aAAa,OAAO,KAAK,UAAU,YAAY,QAAQ;AAC7D,QAAM,MAAM,OAAO,KAAK,UAAU,KAAK,QAAQ;AAE/C,QAAM,MAAM,WAAW,UAAU,MAAM,gBAAgB;IACrD,GAAG;IACH,GAAG;IACH,GAAG;IACH,QAAQ;EACV,CAAC;AAED,MAAI;AACF,UAAM,WAAW,iBAAiB,eAAe,KAAK,IAAI;MACxD,eAAe;IACjB,CAAC;AACD,aAAS,WAAW,GAAG;AACvB,QAAI;AACF,YAAM,YAAY,OAAO,OAAO;QAC9B,SAAS,OAAO,UAAU;QAC1B,SAAS,MAAM;MACjB,CAAC;AACD,aAAO,UAAU,SAAS,MAAM;IAClC,QAAQ;AACN,YAAM,IAAI;QACR;MACF;IACF;EACF,UAAA;AACE,QAAI,KAAK,CAAC;EACZ;AACF;AAgDO,SAAS,aAAa,MAAc,UAA0B;AACnE,aAAW;AACX,QAAM,MAAMC,cAAa,MAAM,MAAM;AACrC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;EACzB,QAAQ;AACN,UAAM,IAAI,MAAM,oBAAoB,IAAI,oBAAoB;EAC9D;AACA,SAAOC,iBAAgB,QAAQ,QAAQ;AACzC;;;AyElOA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AA8Fd,SAAS,YAAoB;AAClC,SAAO,QAAQ,IAAI,kBAAkB,KAAKC,MAAK,QAAQ,GAAG,cAAc;AAC1E;AAGO,SAAS,oBAA4B;AAC1C,SAAOA,MAAK,UAAU,GAAG,aAAa;AACxC;AAGO,SAAS,eAAe,MAAgC;AAC7D,MAAI;AACF,WAAO,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM,IAAI;AAAA,MACR,mCAAmC,IAAI,KACrC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,MAAgC;AAC9D,QAAM,cAAc,QAAQ,IAAI,sBAAsB;AACtD,MAAI,YAAa,QAAO,YAAY,KAAK;AAEzC,MAAI,KAAK,cAAc;AACrB,UAAM,WAAW,QAAQ,IAAI,+BAA+B;AAC5D,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,aAAa,KAAK,cAAc,QAAQ;AAAA,EACjD;AAEA,MAAI,KAAK,SAAU,QAAO,KAAK,SAAS,KAAK;AAE7C,QAAM,IAAI;AAAA,IACR;AAAA,EAEF;AACF;AAQO,SAAS,cAAc,MAA8C;AAC1E,QAAM,WAAW,gBAAgB,IAAI;AAErC,QAAM,SAAS,QAAQ,IAAI,qBAAqB,KAAK,KAAK;AAC1D,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,WACJ,QAAQ,IAAI,uBAAuB,KACnC,KAAK,YACL;AACF,QAAM,gBAAgB,QAAQ,IAAI,mBAAmB,KAAK,KAAK;AAC/D,QAAM,WAAW;AAAA,IACf,QAAQ,IAAI,uBAAuB,KAAK,KAAK,YAAY;AAAA,EAC3D;AACA,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,cAAc,OAAO,KAAK,eAAe,GAAG;AAClD,QAAM,UAAW,QAAQ,IAAI,qBAAqB,KAAK,KAAK;AAK5D,QAAMC,SAAS,QAAQ,IAAI,mBAAmB,KAC5C,KAAK,SACL;AACF,QAAM,OAAO,KAAK,aAAaA,MAAK,KAAK,KAAK;AAS9C,QAAM,cAAc,OAAO,KAAK,wBAAwB,IAAI;AAC5D,QAAM,cACJ,KAAK,qBAAqB,gBAAgB,QAAQC,cAAa,MAAM;AAEvE,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI;AACJ,MAAI,eAAe;AACjB,gBAAY,EAAE,MAAM,UAAU,YAAY,cAAc;AACxD,uBAAmB;AACnB,sBAAkB;AAAA,EACpB,WAAW,aAAa;AACtB,gBAAY,EAAE,MAAM,SAAS;AAC7B,uBAAmB;AACnB,2BAAuB;AACvB,sBAAkB,uBAAuB,WAAW;AAAA,EACtD,OAAO;AACL,gBAAY,EAAE,MAAM,SAAS;AAC7B,uBAAmB;AAAA,EACrB;AAEA,QAAM,mBACJ,KAAK,oBAAoBH,MAAK,UAAU,GAAG,eAAe;AAC5D,QAAM,uBAAuBA,MAAK,UAAU,GAAG,oBAAoB;AAEnE,QAAM,mBAAqC;AAAA;AAAA,IAEzC,cAAc;AAAA,IACd;AAAA,IACA,sBAAsB,KAAK,wBAAwB;AAAA,IACnD,SAAS;AAAA,MACP,QAAQ,KAAK,OAAO,EAAE;AAAA,MACtB,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,WAAW;AAAA,MACX,YAAY;AAAA,IACd;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA,GAAI,yBAAyB,SAAY,EAAE,qBAAqB,IAAI,CAAC;AAAA,IACrE,oBAAoB;AAAA,IACpB,UAAU;AAAA;AAAA,IACV,YAAY,CAAC;AAAA,IACb;AAAA,IACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7B,GAAI,KAAK,kBAAkB,EAAE,iBAAiB,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACxE,GAAI,KAAK,sBACL,EAAE,qBAAqB,KAAK,oBAAoB,IAChD,CAAC;AAAA,IACL,GAAI,KAAK,kBAAkB,EAAE,iBAAiB,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACxE,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,IAClE,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC/D,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,IAClE,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,EAC9D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAI,oBAAoB,SAAY,EAAE,YAAY,gBAAgB,IAAI,CAAC;AAAA,IACvE;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAAE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAASC,cAAa,KAAsB;AAC1C,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE,SAAS,SAAS,SAAS;AAAA,EACjD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AChQO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACE,SACS,QACA,WACA,QACT;AACA,UAAM,OAAO;AAJJ;AACA;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EAChD,YACW,SACA,UACT;AACA,UAAM,iCAAiC,OAAO,EAAE;AAHvC;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AACF;AAWO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAA4B;AACtC,SAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,SAAK,YAAY,KAAK,aAAa;AACnC,SAAK,YAAY,KAAK,aAAa,WAAW;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,OAAyB;AAC7B,QAAI;AACF,YAAM,KAAK,QAAwB,OAAO,SAAS;AACnD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAI,eAAe,uBAAwB,QAAO;AAElD,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,SAAkC;AAChC,WAAO,KAAK,QAAwB,OAAO,SAAS;AAAA,EACtD;AAAA,EAEA,QAAQ,MAAgD;AACtD,WAAO,KAAK,QAAyB,QAAQ,YAAY,IAAI;AAAA,EAC/D;AAAA,EAEA,UAAU,MAAoD;AAC5D,WAAO,KAAK,QAA2B,QAAQ,cAAc,IAAI;AAAA,EACnE;AAAA,EAEA,OAAO,QAAqB,CAAC,GAA4B;AACvD,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,MAAM,MAAO,IAAG,IAAI,SAAS,MAAM,KAAK;AAC5C,QAAI,MAAM,WAAW,OAAW,IAAG,IAAI,UAAU,OAAO,MAAM,MAAM,CAAC;AACrE,QAAI,MAAM,UAAU,OAAW,IAAG,IAAI,SAAS,OAAO,MAAM,KAAK,CAAC;AAClE,UAAM,SAAS,GAAG,SAAS,IAAI,IAAI,GAAG,SAAS,CAAC,KAAK;AACrD,WAAO,KAAK,QAAwB,OAAO,UAAU,MAAM,EAAE;AAAA,EAC/D;AAAA,EAEA,YAAY,OAA2B,CAAC,GAAmC;AACzE,WAAO,KAAK,QAA+B,QAAQ,aAAa,IAAI;AAAA,EACtE;AAAA,EAEA,WAAsC;AACpC,WAAO,KAAK,QAA0B,OAAO,WAAW;AAAA,EAC1D;AAAA,EAEA,KAAK,MAA0C;AAC7C,WAAO,KAAK,QAAsB,QAAQ,SAAS,IAAI;AAAA,EACzD;AAAA,EAEA,MAAc,QACZ,QACA,MACA,MACY;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,SAAS;AACjE,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU,KAAK;AAAA,QAC9B;AAAA,QACA,SAAS,OAAO,EAAE,gBAAgB,mBAAmB,IAAI;AAAA,QACzD,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,QACpC,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,IAAI,uBAAuB,KAAK,SAAS,GAAG;AAAA,IACpD,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAEA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,OAAO,OAAO,SAAS,IAAI,IAAI;AACrC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAK,QAAQ,CAAC;AACpB,YAAM,IAAI;AAAA,QACR,EAAE,SAAS,QAAQ,IAAI,MAAM;AAAA,QAC7B,IAAI;AAAA,QACJ,EAAE,aAAa,IAAI,WAAW;AAAA,QAC9B,EAAE;AAAA,MACJ;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,MAAuB;AACvC,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AChJA,SAAS,aAAa;AACtB;AAAA,EACE,cAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,OACK;AACP,SAAS,SAAS,QAAAC,aAAY;AAC9B,SAAS,qBAAqB;AAKvB,SAAS,cAAsB;AACpC,SAAOC,MAAK,UAAU,GAAG,YAAY;AACvC;AAGO,SAAS,eAAe,KAAsB;AACnD,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC/C,MAAI;AAEF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AAEZ,WAAQ,IAA8B,SAAS;AAAA,EACjD;AACF;AAGO,SAAS,QAAQ,OAAO,YAAY,GAAkB;AAC3D,MAAI;AACF,UAAM,MAAM,SAASC,cAAa,MAAM,MAAM,EAAE,KAAK,GAAG,EAAE;AAC1D,WAAO,OAAO,UAAU,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,YAAY,OAAO,YAAY,GAAS;AACtD,QAAM,WAAW,QAAQ,IAAI;AAC7B,MACE,aAAa,QACb,aAAa,QAAQ,OACrB,eAAe,QAAQ,GACvB;AACA,UAAM,IAAI;AAAA,MACR,gDAAgD,QAAQ,8BAC3B,IAAI;AAAA,IACnC;AAAA,EACF;AACA,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,EAAAC,eAAc,MAAM,OAAO,QAAQ,GAAG,GAAG,EAAE,MAAM,IAAM,CAAC;AAC1D;AAGO,SAAS,YAAY,OAAO,YAAY,GAAS;AACtD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,QAAQ,QAAQ,KAAK;AACvB,QAAI;AACF,iBAAW,IAAI;AAAA,IACjB,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,OAAO,YAAY,GAAY;AAC7D,QAAM,MAAM,QAAQ,IAAI;AACxB,SAAO,QAAQ,QAAQ,eAAe,GAAG;AAC3C;AAgBO,SAAS,oBAAoB,OAA2B,CAAC,GAAW;AACzE,QAAM,QAAQ,KAAK,eAAe,mBAAmB;AACrD,QAAM,SAAS,KAAK,UAAU,UAAU;AACxC,YAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAGrC,QAAM,UAAUF,MAAK,QAAQ,YAAY;AAEzC,QAAM,MAAM,SAAS,SAAS,GAAG;AACjC,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,OAAO,KAAK,GAAG;AAAA,IACpD,UAAU;AAAA,IACV,OAAO,CAAC,UAAU,KAAK,GAAG;AAAA,IAC1B,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI;AAAA,EACrC,CAAC;AACD,QAAM,MAAM;AACZ,MAAI,MAAM,QAAQ,QAAW;AAC3B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AACA,SAAO,MAAM;AACf;AAGO,SAAS,qBAA6B;AAE3C,QAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,SAAOA,MAAK,MAAM,WAAW;AAC/B;AAOA,eAAsB,aACpB,SACA,YAAY,MACZ,aAAa,KACK;AAClB,QAAM,SAAS,IAAI,cAAc,EAAE,SAAS,WAAW,aAAa,EAAE,CAAC;AACvE,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI,MAAM,OAAO,KAAK,EAAG,QAAO;AAChC,UAAM,MAAM,UAAU;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;","names":["generateSecretKey","getPublicKey","decode","finalizeEvent","SimplePool","WebSocket","getPublicKey","SimplePool","getPublicKey","WebSocket","getPublicKey","isObject","PUBKEY_REGEX","isValidPubkey","chain","hexToBytes","clean","concatBytes","hexToBytes","concatBytes","checksum","sha256","getPublicKey","SimplePool","PUBKEY_REGEX","WebSocket","getPublicKey","isBytes","anumber","encode","decode","anumber","padding","isBytes","anumber","isBytes","sha256","sha256","wordlist","wordlist","hexToBytes","hexToBytes","_0n","_1n","_0n","_1n","gcd","_0n","_1n","_1n","_0n","_1n","_1n","_0n","_1n","window","Point","wbits","_0n","_2n","_0n","_1n","_3n","_4n","Point","endo","Point","getPublicKey","utils","randomBytes","hmac","_1n","r","s","hexToBytes","_2n","_0n","Point","_2n","_3n","sha256","sha256","hexToBytes","privateKeyToAccount","toHex","finalizeEvent","getPublicKey","randomBytes","writeFileSync","readFileSync","validateMnemonic","getPublicKey","hmac","sha512","ed25519","BASE58_ALPHABET","validateMnemonic","chain","generateSecretKey","toHex","textEncoder","mod","sha256","Field","hexToMinaBase58PrivateKey","STATE_MAP","chain","base58Encode","ed25519","status","toHex","privateKeyToAccount","startManagedAnonProxy","base58","generateSecretKey","getPublicKey","decryptMnemonic","readFileSync","decryptMnemonic","readFileSync","join","join","readFileSync","chain","isAnyoneHost","existsSync","readFileSync","writeFileSync","join","join","readFileSync","writeFileSync"]}
|