@toon-protocol/core 3.1.1 → 3.1.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/dist/index.d.ts +7 -1
- package/dist/index.js +17 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/address/ilp-address-validation.ts","../src/address/derive-child-address.ts","../src/address/btp-prefix-exchange.ts","../src/address/address-assignment.ts","../src/address/address-registry.ts","../src/address/prefix-validation.ts","../src/chain/chain-id.ts","../src/events/swap-pair-validation.ts","../src/events/parsers.ts","../src/events/builders.ts","../src/events/nip40.ts","../src/events/seed-relay.ts","../src/events/service-discovery.ts","../src/events/attestation.ts","../src/events/dvm.ts","../src/events/workflow.ts","../src/events/swarm.ts","../src/events/prefix-claim.ts","../src/bootstrap/AttestationVerifier.ts","../src/events/attested-result-verifier.ts","../src/events/arweave-storage.ts","../src/events/reputation.ts","../src/discovery/NostrPeerDiscovery.ts","../src/discovery/genesis-peers.json","../src/discovery/GenesisPeerLoader.ts","../src/discovery/ArDrivePeerRegistry.ts","../src/discovery/SocialPeerDiscovery.ts","../src/discovery/seed-relay-discovery.ts","../src/fee/calculate-route-amount.ts","../src/fee/resolve-route-fees.ts","../src/settlement/settlement.ts","../src/settlement/hashes.ts","../src/settlement/base58.ts","../src/settlement/mina-key.ts","../src/bootstrap/BootstrapService.ts","../src/bootstrap/discovery-tracker.ts","../src/bootstrap/ilp-client.ts","../src/bootstrap/direct-ilp-client.ts","../src/bootstrap/direct-connector-admin.ts","../src/bootstrap/direct-channel-client.ts","../src/bootstrap/http-connector-admin.ts","../src/bootstrap/http-ilp-client.ts","../src/bootstrap/http-channel-client.ts","../src/bootstrap/AttestationBootstrap.ts","../src/utils/reject-code.ts","../src/compose.ts","../src/chain/usdc.ts","../src/chain/chain-config.ts","../src/chain/network-profile.ts","../src/x402/build-ilp-prepare.ts","../src/identity/kms-identity.ts","../src/build/nix-builder.ts","../src/build/pcr-validator.ts","../src/logger.ts","../src/ui/coordinate.ts","../src/index.ts"],"sourcesContent":["/**\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 httpEndpoint,\n supportsUpgrade,\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 Swap — 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 // ILP-over-HTTP advertisement (RFC-0035). Both optional; a node may advertise\n // an HTTP endpoint and/or whether it accepts an Upgrade to BTP.\n if (httpEndpoint !== undefined && typeof httpEndpoint !== 'string') {\n throw new InvalidEventError('Invalid field: httpEndpoint must be a string');\n }\n if (supportsUpgrade !== undefined && typeof supportsUpgrade !== 'boolean') {\n throw new InvalidEventError('Invalid field: supportsUpgrade must be a boolean');\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 ...(httpEndpoint !== undefined &&\n typeof httpEndpoint === 'string' && { httpEndpoint }),\n ...(supportsUpgrade !== undefined &&\n typeof supportsUpgrade === 'boolean' && { supportsUpgrade }),\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 { EXPIRATION_TAG } from './nip40.js';\nimport type { IlpPeerInfo } from '../types.js';\n\n/** Options controlling how a kind:10032 announcement event is built. */\nexport interface BuildIlpPeerInfoOptions {\n /**\n * NIP-40 time-to-live, in seconds. When set to a positive value, the event\n * carries an `[\"expiration\", created_at + ttlSeconds]` tag so a stale\n * announcement from an offline apex expires instead of lingering forever\n * (issue #261). Omit (or pass a non-positive value) for a non-expiring event.\n */\n ttlSeconds?: number;\n}\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 * @param options - Optional build options (e.g. a NIP-40 `ttlSeconds`)\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 options: BuildIlpPeerInfoOptions = {}\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 const createdAt = Math.floor(Date.now() / 1000);\n\n // NIP-40 expiration: a positive ttlSeconds turns the announcement into a\n // liveness signal — a live apex re-publishes before it elapses, and a dead\n // one expires so clients stop dialing its unreachable BTP endpoint (#261).\n const tags: string[][] = [];\n if (options.ttlSeconds !== undefined && options.ttlSeconds > 0) {\n tags.push([\n EXPIRATION_TAG,\n String(createdAt + Math.floor(options.ttlSeconds)),\n ]);\n }\n\n return finalizeEvent(\n {\n kind: ILP_PEER_INFO_KIND,\n content: JSON.stringify(effectiveInfo),\n tags,\n created_at: createdAt,\n },\n secretKey\n );\n}\n","/**\n * NIP-40 expiration-timestamp helpers.\n *\n * A kind:10032 announcement (`ILP_PEER_INFO_KIND`) is a replaceable event: a\n * relay keeps only the latest per author, so a stale announcement from an apex\n * that has since gone offline lingers forever and clients faithfully dial its\n * dead BTP endpoint (issue #261). Stamping the announcement with a NIP-40\n * `[\"expiration\", <unix-seconds>]` tag turns it into a liveness signal: a live\n * apex re-publishes before the tag elapses, and once it stops, NIP-40-aware\n * relays drop the event and discovery skips it.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\n\n/** Tag name carrying the NIP-40 expiration timestamp (unix seconds). */\nexport const EXPIRATION_TAG = 'expiration';\n\n/**\n * Read the NIP-40 expiration timestamp (unix seconds) from an event's tags.\n *\n * @returns The timestamp, or `undefined` when the event carries no valid\n * `expiration` tag (no tag, non-numeric, or negative value).\n */\nexport function getEventExpiration(event: NostrEvent): number | undefined {\n const tag = event.tags.find((t) => t[0] === EXPIRATION_TAG);\n if (!tag || tag[1] === undefined) return undefined;\n const ts = Number(tag[1]);\n if (!Number.isFinite(ts) || ts < 0) return undefined;\n return ts;\n}\n\n/**\n * Whether an event has expired per its NIP-40 `expiration` tag.\n *\n * Events with no (or malformed) expiration tag never expire — they return\n * `false`, preserving backward compatibility with announcements published\n * before TTLs existed.\n *\n * @param event - The event to test.\n * @param nowSeconds - Reference time in unix seconds (defaults to now).\n * Injectable for deterministic tests.\n */\nexport function isEventExpired(\n event: NostrEvent,\n nowSeconds: number = Math.floor(Date.now() / 1000)\n): boolean {\n const exp = getEventExpiration(event);\n return exp !== undefined && exp <= nowSeconds;\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 \"pubkey\": \"2813187eb66741f9509de2055161f328a0f04e01e1fc20188610b8dbd0591ea5\",\n \"relayUrl\": \"wss://relay-ws.devnet.toonprotocol.dev\",\n \"ilpAddress\": \"g.proxy\",\n \"btpEndpoint\": \"wss://proxy.devnet.toonprotocol.dev:443\"\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/**\n * Load and validate genesis peers from the bundled JSON file.\n *\n * The `TOON_GENESIS_PEERS` environment variable, when set, REPLACES the\n * bundled seed entirely (same JSON array format). Set it to `[]` to disable\n * bundled genesis peers — e.g. for private networks or hermetic tests.\n */\nfunction loadGenesisPeers(): GenesisPeer[] {\n const envOverride = process.env['TOON_GENESIS_PEERS'];\n if (envOverride !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(envOverride);\n } catch {\n console.warn('Failed to parse TOON_GENESIS_PEERS JSON:', envOverride);\n return [];\n }\n if (!Array.isArray(parsed)) {\n console.warn('TOON_GENESIS_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 TOON_GENESIS_PEERS entry:', entry);\n }\n }\n return deduplicateByPubkey(valid);\n }\n\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 { isEventExpired } from '../events/nip40.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 // Skip NIP-40-expired announcements: an apex that stopped re-publishing\n // is offline, and dialing its advertised BTP endpoint would fail (#261).\n if (isEventExpired(event)) {\n console.warn(\n `[SeedRelayDiscovery] Skipping expired kind:10032 announcement: ${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 — now re-exported from the dependency-light\n * leaf `@toon-protocol/settlement-digest`.\n *\n * These are the single source of truth for the byte/field layout that ALL\n * signers and verifiers across the monorepo depend on:\n * - the Swap-side signer (`packages/swap/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 Swap signer (Story 12.4) into `@toon-protocol/sdk`\n * (Story 12.6 AC-6), then relocated here to `@toon-protocol/core` so the client\n * could consume the canonical hashes without depending on the SDK. As of\n * connector#329 Phase 1 the digest itself lives in the `@toon-protocol/core`-free\n * leaf `@toon-protocol/settlement-digest` (`@noble/*`-only) so the connector's\n * off-chain verifier can share the SAME bytes without core's heavy transitive\n * tree. `@toon-protocol/core` (this file) and `@toon-protocol/sdk` re-export the\n * names unchanged, so Swap, the client, and existing SDK consumers are\n * unaffected — the public API is byte-identical to before.\n *\n * Any change to a hash layout must be made in `@toon-protocol/settlement-digest`\n * — it applies to every signer AND verifier at once; they cannot drift.\n *\n * NOTE: base58 (`./base58.ts`) and the Mina *key derivation* (`./mina-key.ts`)\n * intentionally REMAIN in `@toon-protocol/core` — they are not digests.\n *\n * @module\n */\n\nexport {\n // Byte helpers.\n hexToBytes,\n bigintToBytes32BE,\n concatBytes,\n // EVM v2 EIP-712 digest.\n eip712DomainSeparatorEvm,\n balanceProofHashEvm,\n coopCloseHashEvm,\n // Non-EVM message digests.\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n} from '@toon-protocol/settlement-digest';\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()` / `deriveSwapKeys()` 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/swap/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/**\n * Derive the Mina base58 (`B62…`) public key for a private-key scalar, using\n * the optional `mina-signer` peer dep.\n *\n * `deriveFullIdentity()` / `deriveSwapKeys()` emit only a keccak **hex\n * placeholder** for the Mina public key — they deliberately avoid pulling\n * Pallas curve math into derivation. That placeholder is unfundable and is\n * rejected by Mina GraphQL balance queries, so wallet views that display it\n * (e.g. a swap node's `/wallet/balances` Mina leg) show an unusable hex\n * string. This resolves the real, fundable `B62…` address when `mina-signer`\n * is installed.\n *\n * Returns `null` (not a throw) when `mina-signer` is absent — a missing\n * optional peer dep is not an error; callers fall back to the hex placeholder.\n * The B62 encoding is network-agnostic, so the chosen `network` is irrelevant.\n *\n * @param privateKey big-endian hex scalar (as derivation emits) or an\n * already-base58 `EK…` Mina private key.\n */\nexport async function deriveMinaPublicKeyBase58(\n privateKey: string\n): Promise<string | null> {\n let signerModule: unknown = null;\n try {\n // Dynamic specifier the TS compiler can't resolve at build time, so this\n // package type-checks without the optional `mina-signer` peer dep present.\n const specifier = 'mina-signer';\n signerModule = await import(/* @vite-ignore */ specifier);\n } catch {\n return null;\n }\n if (!signerModule) return null;\n const mod = signerModule as { default?: unknown };\n const ClientCtor = (mod.default ?? mod) as new (cfg: {\n network: 'mainnet' | 'testnet';\n }) => { derivePublicKey: (privateKey: string) => string };\n const client = new ClientCtor({ network: 'mainnet' });\n // mina-signer needs a base58check (`EK…`) private key; convert if hex.\n return client.derivePublicKey(hexToMinaBase58PrivateKey(privateKey));\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 expiresAt?: string;\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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt,\n }),\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 expiresAt?: string;\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. Honor a caller-supplied per-packet\n // expiry (ISO 8601); default to 30 seconds from now otherwise.\n const expiresAt = params.expiresAt\n ? new Date(params.expiresAt)\n : new Date(Date.now() + 30000);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new BootstrapError(\n `Invalid expiresAt: ${params.expiresAt} is not a parseable date`\n );\n }\n const result = await connector.sendPacket({\n destination: params.destination,\n amount,\n data,\n expiresAt,\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 expiresAt?: string;\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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt,\n }),\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 Swap 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). */\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. */\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 // Post-2026-07-19 public-chain cutover addresses (source of truth: toon-meta\n // docs/deployment.md). USDC is a 6-decimal mock with an ungated mint; the\n // retired e2e deployment (18-decimal USDC 0xac806…, TokenNetwork 0x47616F4b…,\n // registry 0xb9516c…) is still on-chain but MUST NOT be used.\n usdcAddress: '0x49beE1Bca5d15Fb0963117923403F9498119a9Ce',\n tokenNetworkAddress: '0x1E95493fEF46707E034b4a1945f25a8C76A1823D',\n registryAddress: '0xcC9079adE929b168B54145f6d25262b64FAB9D5b',\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: 'EdJxYPDxGvaJuu57DSUptf4soLv8enpdyQJJhHDLiydG',\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: 'B62qrH1As4odHiNyKpTZMHaM6tRs6gi5DJ53efZKQBtbaR5CUctbDs6',\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 TOON connector `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 (relay/swap), 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 relay 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 relay node uses Base; the\n * apex connector and Swap 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 → relay `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 relay 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 // Post-2026-07-19 public-chain cutover (source of truth: toon-meta\n // docs/deployment.md). USDC mint authority is the faucet treasury\n // AEPoA5xT…; the pre-cutover self-hosted-validator values (mint\n // 9FtYCX…, program EdJxYPD…) are retired and MUST NOT be used.\n usdcMint: 'xyc5J8MgKFiEN13PnfftdXxUzYH34FEvw1LCrFwN7in',\n programId: '2aEVJ8koKD8LTZrLRSGtAtU7LBt4e7QjjCgf1kzQ7Rip',\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 // Reconciled with e2e/testnets.json (#205): the previous address\n // (B62qjFgX…) is a bare, unfunded zkApp on-chain (balance 0, channel\n // nonceField 0) — usable for claim issuance only. This is the funded,\n // on-chain-settling PaymentChannel zkApp (balance ~4 MINA, nonceField 21\n // — the on-chain settle proven in #217), same VK hash 21482326…\n zkAppAddress: 'B62qrH1As4odHiNyKpTZMHaM6tRs6gi5DJ53efZKQBtbaR5CUctbDs6',\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 relay 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 → relay 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 relay 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 relay 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 '' → relay 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 /**\n * Per-packet expiry. When provided, it is propagated onto the produced\n * PREPARE (as an ISO 8601 string) so the transport sets exactly this\n * expiry on the wire. When omitted, the transport applies its own\n * default (typically derived from the request timeout, ~30s).\n */\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 * Packet expiry as an ISO 8601 string. Present only when the caller\n * supplied `expiresAt`; absent means the transport picks its default\n * (timeout-derived). Matches the connector's `POST /admin/ilp/send`\n * request field.\n */\n expiresAt?: 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 * passes through the destination, and — when supplied — serializes the\n * per-packet expiry to ISO 8601. 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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt.toISOString(),\n }),\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 * Pure helpers for NIP-on-TOON UI renderer resolution.\n *\n * An event may carry a `ui` tag whose value is an addressable coordinate\n * pointing at a `kind:31036` renderer event. The coordinate convention is:\n *\n * 31036:<renderer-author-pubkey>:<target-kind>\n *\n * where the trailing segment (the renderer event's `d` tag value) is the\n * kind of event the renderer knows how to render.\n *\n * These helpers are intentionally PURE: they parse coordinate strings and\n * select the latest addressable event from a set of candidates. The actual\n * resolution (relay query + cache) is client-local and lives outside core.\n *\n * Mirrors the style of `parseRepositoryReference` in `../nip34/types.ts`.\n */\n\nimport type { Event as NostrEvent } from 'nostr-tools/pure';\n\n/** The renderer event kind for NIP-on-TOON UI rendering. */\nexport const UI_RENDERER_KIND = 31036;\n\n/** Tag name carrying the UI renderer coordinate on a rendered event. */\nexport const UI_TAG = 'ui';\n\n/** Matches a lowercase 64-character hex pubkey. */\nconst HEX64_RE = /^[0-9a-f]{64}$/;\n\n/**\n * A parsed `ui` renderer coordinate.\n * Format: \"31036:<pubkey>:<targetKind>\"\n */\nexport interface UiCoordinate {\n /** Always {@link UI_RENDERER_KIND} (31036). */\n kind: typeof UI_RENDERER_KIND;\n /** The renderer author's 64-hex pubkey. */\n pubkey: string;\n /** The kind of event this renderer targets (the renderer's `d` value). */\n targetKind: number;\n}\n\n/**\n * Parse a `ui` renderer coordinate string of the form\n * `31036:<pubkey>:<targetKind>`.\n *\n * Returns `null` on any malformed input: wrong segment count, a leading kind\n * other than 31036, a pubkey that is not 64-hex, or a non-integer target kind.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param coord - The coordinate string (e.g. a `ui` tag value).\n * @returns The parsed {@link UiCoordinate}, or `null` if malformed.\n */\nexport function parseUiCoordinate(coord: string): UiCoordinate | null {\n if (typeof coord !== 'string') {\n return null;\n }\n const parts = coord.split(':');\n if (parts.length !== 3) {\n return null;\n }\n const [kindStr, pubkey, targetKindStr] = parts;\n if (\n kindStr === undefined ||\n pubkey === undefined ||\n targetKindStr === undefined\n ) {\n return null;\n }\n if (kindStr !== String(UI_RENDERER_KIND)) {\n return null;\n }\n if (!HEX64_RE.test(pubkey)) {\n return null;\n }\n if (!/^\\d+$/.test(targetKindStr)) {\n return null;\n }\n const targetKind = Number(targetKindStr);\n if (!Number.isInteger(targetKind)) {\n return null;\n }\n return {\n kind: UI_RENDERER_KIND,\n pubkey,\n targetKind,\n };\n}\n\n/**\n * Build a `ui` renderer coordinate string of the form\n * `31036:<pubkey>:<targetKind>`.\n *\n * The inverse of {@link parseUiCoordinate}. Returns `null` if the inputs are\n * invalid (pubkey not 64-hex, or target kind not a non-negative integer), so\n * that `build`→`parse` round-trips cleanly.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param ref - The renderer author pubkey and target kind.\n * @returns The coordinate string, or `null` if the inputs are invalid.\n */\nexport function buildUiCoordinate(ref: {\n pubkey: string;\n targetKind: number;\n}): string | null {\n const { pubkey, targetKind } = ref;\n if (typeof pubkey !== 'string' || !HEX64_RE.test(pubkey)) {\n return null;\n }\n if (!Number.isInteger(targetKind) || targetKind < 0) {\n return null;\n }\n return `${UI_RENDERER_KIND}:${pubkey}:${targetKind}`;\n}\n\n/**\n * Read the `ui` tag value off an event and parse it into a coordinate.\n *\n * Convenience wrapper around {@link parseUiCoordinate} that pulls the first\n * `ui` tag's value from `event.tags`. Returns `null` if the event has no\n * `ui` tag or the tag value is malformed.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param event - The event that may reference a renderer via a `ui` tag.\n * @returns The parsed {@link UiCoordinate}, or `null`.\n */\nexport function getUiCoordinate(event: NostrEvent): UiCoordinate | null {\n const value = event.tags.find((t) => t[0] === UI_TAG)?.[1];\n if (value === undefined) {\n return null;\n }\n return parseUiCoordinate(value);\n}\n\n/**\n * Select the latest addressable/replaceable event from a set of candidates\n * that share an addressable coordinate.\n *\n * Implements NIP-33 latest-wins: the event with the greatest `created_at`\n * wins. Ties on `created_at` are broken by the lexicographically lowest `id`\n * per the NIP-01 convention.\n *\n * Does not itself group by coordinate — callers pass the candidate set for a\n * single coordinate (e.g. the result of a relay query filtered by author +\n * kind + `#d`). Pure: no IO, no relay fetch.\n *\n * @param events - Candidate events sharing a coordinate.\n * @returns The latest event, or `undefined` for an empty input.\n */\nexport function selectLatestAddressable<T extends NostrEvent>(\n events: T[]\n): T | undefined {\n let latest: T | undefined;\n for (const event of events) {\n if (latest === undefined) {\n latest = event;\n continue;\n }\n if (event.created_at > latest.created_at) {\n latest = event;\n } else if (event.created_at === latest.created_at && event.id < latest.id) {\n latest = event;\n }\n }\n return latest;\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 type BuildIlpPeerInfoOptions,\n getEventExpiration,\n isEventExpired,\n EXPIRATION_TAG,\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 coopCloseHashEvm,\n eip712DomainSeparatorEvm,\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n base58Encode,\n base58Decode,\n hexToMinaBase58PrivateKey,\n deriveMinaPublicKeyBase58,\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-on-TOON UI renderer resolution (pure ui→kind:31036 coordinate helpers)\nexport {\n UI_RENDERER_KIND,\n UI_TAG,\n parseUiCoordinate,\n buildUiCoordinate,\n getUiCoordinate,\n selectLatestAddressable,\n type UiCoordinate,\n} from './ui/index.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"],"mappings":"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","names":["MAX_ILP_ADDRESS_LENGTH","isObject","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","HEX_64_REGEX","HEX_64_REGEX","finalizeEvent","finalizeEvent","HEX_64_REGEX","finalizeEvent","finalizeEvent","finalizeEvent","AttestationState","finalizeEvent","BASE64_REGEX","finalizeEvent","finalizeEvent","HEX_64_REGEX","finalizeEvent","PUBKEY_REGEX","PUBKEY_REGEX","isValidPubkey","valid","isValidPubkey","SimplePool","SimplePool","getPublicKey","getPublicKey","SimplePool","getPublicKey","WebSocket","getPublicKey","SimplePool","PUBKEY_REGEX","WebSocket","getPublicKey","getPublicKey","createHttpIlpClient","getPublicKey","path","getPublicKey","sha256","readFile","readFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/address/ilp-address-validation.ts","../src/address/derive-child-address.ts","../src/address/btp-prefix-exchange.ts","../src/address/address-assignment.ts","../src/address/address-registry.ts","../src/address/prefix-validation.ts","../src/chain/chain-id.ts","../src/events/swap-pair-validation.ts","../src/events/parsers.ts","../src/events/builders.ts","../src/events/nip40.ts","../src/events/seed-relay.ts","../src/events/service-discovery.ts","../src/events/attestation.ts","../src/events/dvm.ts","../src/events/workflow.ts","../src/events/swarm.ts","../src/events/prefix-claim.ts","../src/bootstrap/AttestationVerifier.ts","../src/events/attested-result-verifier.ts","../src/events/arweave-storage.ts","../src/events/reputation.ts","../src/discovery/NostrPeerDiscovery.ts","../src/discovery/genesis-peers.json","../src/discovery/GenesisPeerLoader.ts","../src/discovery/ArDrivePeerRegistry.ts","../src/discovery/SocialPeerDiscovery.ts","../src/discovery/seed-relay-discovery.ts","../src/fee/calculate-route-amount.ts","../src/fee/resolve-route-fees.ts","../src/settlement/settlement.ts","../src/settlement/hashes.ts","../src/settlement/base58.ts","../src/settlement/mina-key.ts","../src/bootstrap/BootstrapService.ts","../src/bootstrap/discovery-tracker.ts","../src/bootstrap/ilp-client.ts","../src/bootstrap/direct-ilp-client.ts","../src/bootstrap/direct-connector-admin.ts","../src/bootstrap/direct-channel-client.ts","../src/bootstrap/http-connector-admin.ts","../src/bootstrap/http-ilp-client.ts","../src/bootstrap/http-channel-client.ts","../src/bootstrap/AttestationBootstrap.ts","../src/utils/reject-code.ts","../src/compose.ts","../src/chain/usdc.ts","../src/chain/chain-config.ts","../src/chain/network-profile.ts","../src/x402/build-ilp-prepare.ts","../src/identity/kms-identity.ts","../src/build/nix-builder.ts","../src/build/pcr-validator.ts","../src/logger.ts","../src/ui/coordinate.ts","../src/index.ts"],"sourcesContent":["/**\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 httpEndpoint,\n supportsUpgrade,\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 Swap — 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 // ILP-over-HTTP advertisement (RFC-0035). Both optional; a node may advertise\n // an HTTP endpoint and/or whether it accepts an Upgrade to BTP.\n if (httpEndpoint !== undefined && typeof httpEndpoint !== 'string') {\n throw new InvalidEventError('Invalid field: httpEndpoint must be a string');\n }\n if (supportsUpgrade !== undefined && typeof supportsUpgrade !== 'boolean') {\n throw new InvalidEventError('Invalid field: supportsUpgrade must be a boolean');\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 ...(httpEndpoint !== undefined &&\n typeof httpEndpoint === 'string' && { httpEndpoint }),\n ...(supportsUpgrade !== undefined &&\n typeof supportsUpgrade === 'boolean' && { supportsUpgrade }),\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 { EXPIRATION_TAG } from './nip40.js';\nimport type { IlpPeerInfo } from '../types.js';\n\n/** Options controlling how a kind:10032 announcement event is built. */\nexport interface BuildIlpPeerInfoOptions {\n /**\n * NIP-40 time-to-live, in seconds. When set to a positive value, the event\n * carries an `[\"expiration\", created_at + ttlSeconds]` tag so a stale\n * announcement from an offline apex expires instead of lingering forever\n * (issue #261). Omit (or pass a non-positive value) for a non-expiring event.\n */\n ttlSeconds?: number;\n}\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 * @param options - Optional build options (e.g. a NIP-40 `ttlSeconds`)\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 options: BuildIlpPeerInfoOptions = {}\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 const createdAt = Math.floor(Date.now() / 1000);\n\n // NIP-40 expiration: a positive ttlSeconds turns the announcement into a\n // liveness signal — a live apex re-publishes before it elapses, and a dead\n // one expires so clients stop dialing its unreachable BTP endpoint (#261).\n const tags: string[][] = [];\n if (options.ttlSeconds !== undefined && options.ttlSeconds > 0) {\n tags.push([\n EXPIRATION_TAG,\n String(createdAt + Math.floor(options.ttlSeconds)),\n ]);\n }\n\n return finalizeEvent(\n {\n kind: ILP_PEER_INFO_KIND,\n content: JSON.stringify(effectiveInfo),\n tags,\n created_at: createdAt,\n },\n secretKey\n );\n}\n","/**\n * NIP-40 expiration-timestamp helpers.\n *\n * A kind:10032 announcement (`ILP_PEER_INFO_KIND`) is a replaceable event: a\n * relay keeps only the latest per author, so a stale announcement from an apex\n * that has since gone offline lingers forever and clients faithfully dial its\n * dead BTP endpoint (issue #261). Stamping the announcement with a NIP-40\n * `[\"expiration\", <unix-seconds>]` tag turns it into a liveness signal: a live\n * apex re-publishes before the tag elapses, and once it stops, NIP-40-aware\n * relays drop the event and discovery skips it.\n */\n\nimport type { NostrEvent } from 'nostr-tools/pure';\n\n/** Tag name carrying the NIP-40 expiration timestamp (unix seconds). */\nexport const EXPIRATION_TAG = 'expiration';\n\n/**\n * Read the NIP-40 expiration timestamp (unix seconds) from an event's tags.\n *\n * @returns The timestamp, or `undefined` when the event carries no valid\n * `expiration` tag (no tag, non-numeric, or negative value).\n */\nexport function getEventExpiration(event: NostrEvent): number | undefined {\n const tag = event.tags.find((t) => t[0] === EXPIRATION_TAG);\n if (!tag || tag[1] === undefined) return undefined;\n const ts = Number(tag[1]);\n if (!Number.isFinite(ts) || ts < 0) return undefined;\n return ts;\n}\n\n/**\n * Whether an event has expired per its NIP-40 `expiration` tag.\n *\n * Events with no (or malformed) expiration tag never expire — they return\n * `false`, preserving backward compatibility with announcements published\n * before TTLs existed.\n *\n * @param event - The event to test.\n * @param nowSeconds - Reference time in unix seconds (defaults to now).\n * Injectable for deterministic tests.\n */\nexport function isEventExpired(\n event: NostrEvent,\n nowSeconds: number = Math.floor(Date.now() / 1000)\n): boolean {\n const exp = getEventExpiration(event);\n return exp !== undefined && exp <= nowSeconds;\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 \"pubkey\": \"2813187eb66741f9509de2055161f328a0f04e01e1fc20188610b8dbd0591ea5\",\n \"relayUrl\": \"wss://relay-ws.devnet.toonprotocol.dev\",\n \"ilpAddress\": \"g.proxy\",\n \"btpEndpoint\": \"wss://proxy.devnet.toonprotocol.dev:443\"\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/**\n * Load and validate genesis peers from the bundled JSON file.\n *\n * The `TOON_GENESIS_PEERS` environment variable, when set, REPLACES the\n * bundled seed entirely (same JSON array format). Set it to `[]` to disable\n * bundled genesis peers — e.g. for private networks or hermetic tests.\n */\nfunction loadGenesisPeers(): GenesisPeer[] {\n const envOverride = process.env['TOON_GENESIS_PEERS'];\n if (envOverride !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(envOverride);\n } catch {\n console.warn('Failed to parse TOON_GENESIS_PEERS JSON:', envOverride);\n return [];\n }\n if (!Array.isArray(parsed)) {\n console.warn('TOON_GENESIS_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 TOON_GENESIS_PEERS entry:', entry);\n }\n }\n return deduplicateByPubkey(valid);\n }\n\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 { isEventExpired } from '../events/nip40.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 // Skip NIP-40-expired announcements: an apex that stopped re-publishing\n // is offline, and dialing its advertised BTP endpoint would fail (#261).\n if (isEventExpired(event)) {\n console.warn(\n `[SeedRelayDiscovery] Skipping expired kind:10032 announcement: ${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 — now re-exported from the dependency-light\n * leaf `@toon-protocol/settlement-digest`.\n *\n * These are the single source of truth for the byte/field layout that ALL\n * signers and verifiers across the monorepo depend on:\n * - the Swap-side signer (`packages/swap/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 Swap signer (Story 12.4) into `@toon-protocol/sdk`\n * (Story 12.6 AC-6), then relocated here to `@toon-protocol/core` so the client\n * could consume the canonical hashes without depending on the SDK. As of\n * connector#329 Phase 1 the digest itself lives in the `@toon-protocol/core`-free\n * leaf `@toon-protocol/settlement-digest` (`@noble/*`-only) so the connector's\n * off-chain verifier can share the SAME bytes without core's heavy transitive\n * tree. `@toon-protocol/core` (this file) and `@toon-protocol/sdk` re-export the\n * names unchanged, so Swap, the client, and existing SDK consumers are\n * unaffected — the public API is byte-identical to before.\n *\n * Any change to a hash layout must be made in `@toon-protocol/settlement-digest`\n * — it applies to every signer AND verifier at once; they cannot drift.\n *\n * NOTE: base58 (`./base58.ts`) and the Mina *key derivation* (`./mina-key.ts`)\n * intentionally REMAIN in `@toon-protocol/core` — they are not digests.\n *\n * @module\n */\n\nexport {\n // Byte helpers.\n hexToBytes,\n bigintToBytes32BE,\n concatBytes,\n // EVM v2 EIP-712 digest.\n eip712DomainSeparatorEvm,\n balanceProofHashEvm,\n coopCloseHashEvm,\n // Non-EVM message digests.\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n} from '@toon-protocol/settlement-digest';\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()` / `deriveSwapKeys()` 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/swap/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/**\n * Derive the Mina base58 (`B62…`) public key for a private-key scalar, using\n * the optional `mina-signer` peer dep.\n *\n * `deriveFullIdentity()` / `deriveSwapKeys()` emit only a keccak **hex\n * placeholder** for the Mina public key — they deliberately avoid pulling\n * Pallas curve math into derivation. That placeholder is unfundable and is\n * rejected by Mina GraphQL balance queries, so wallet views that display it\n * (e.g. a swap node's `/wallet/balances` Mina leg) show an unusable hex\n * string. This resolves the real, fundable `B62…` address when `mina-signer`\n * is installed.\n *\n * Returns `null` (not a throw) when `mina-signer` is absent — a missing\n * optional peer dep is not an error; callers fall back to the hex placeholder.\n * The B62 encoding is network-agnostic, so the chosen `network` is irrelevant.\n *\n * @param privateKey big-endian hex scalar (as derivation emits) or an\n * already-base58 `EK…` Mina private key.\n */\nexport async function deriveMinaPublicKeyBase58(\n privateKey: string\n): Promise<string | null> {\n let signerModule: unknown = null;\n try {\n // Dynamic specifier the TS compiler can't resolve at build time, so this\n // package type-checks without the optional `mina-signer` peer dep present.\n const specifier = 'mina-signer';\n signerModule = await import(/* @vite-ignore */ specifier);\n } catch {\n return null;\n }\n if (!signerModule) return null;\n const mod = signerModule as { default?: unknown };\n const ClientCtor = (mod.default ?? mod) as new (cfg: {\n network: 'mainnet' | 'testnet';\n }) => { derivePublicKey: (privateKey: string) => string };\n const client = new ClientCtor({ network: 'mainnet' });\n // mina-signer needs a base58check (`EK…`) private key; convert if hex.\n return client.derivePublicKey(hexToMinaBase58PrivateKey(privateKey));\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 expiresAt?: string;\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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt,\n }),\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 expiresAt?: string;\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. Honor a caller-supplied per-packet\n // expiry (ISO 8601); default to 30 seconds from now otherwise.\n const expiresAt = params.expiresAt\n ? new Date(params.expiresAt)\n : new Date(Date.now() + 30000);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new BootstrapError(\n `Invalid expiresAt: ${params.expiresAt} is not a parseable date`\n );\n }\n const result = await connector.sendPacket({\n destination: params.destination,\n amount,\n data,\n expiresAt,\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 expiresAt?: string;\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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt,\n }),\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 Swap 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). */\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. */\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 // The old `https://sepolia.base.org` is a stale-read load balancer: reads\n // work, but openChannel->setTotalDeposit fails with InvalidChannelState\n // (0xf806e9d9) because a follow-up read lands on a lagging replica. publicnode\n // is the working devnet/testnet default (source of truth: toon-meta\n // docs/deployment.md).\n rpcUrl: 'https://base-sepolia-rpc.publicnode.com',\n // Post-2026-07-19 public-chain cutover addresses (source of truth: toon-meta\n // docs/deployment.md). USDC is a 6-decimal mock with an ungated mint; the\n // retired e2e deployment (18-decimal USDC 0xac806…, TokenNetwork 0x47616F4b…,\n // registry 0xb9516c…) is still on-chain but MUST NOT be used.\n usdcAddress: '0x49beE1Bca5d15Fb0963117923403F9498119a9Ce',\n tokenNetworkAddress: '0x1E95493fEF46707E034b4a1945f25a8C76A1823D',\n registryAddress: '0xcC9079adE929b168B54145f6d25262b64FAB9D5b',\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: 'EdJxYPDxGvaJuu57DSUptf4soLv8enpdyQJJhHDLiydG',\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: 'B62qrH1As4odHiNyKpTZMHaM6tRs6gi5DJ53efZKQBtbaR5CUctbDs6',\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 TOON connector `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 (relay/swap), 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 relay 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 relay node uses Base; the\n * apex connector and Swap 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 → relay `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 relay 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 // Post-2026-07-19 public-chain cutover (source of truth: toon-meta\n // docs/deployment.md). USDC mint authority is the faucet treasury\n // AEPoA5xT…; the pre-cutover self-hosted-validator values (mint\n // 9FtYCX…, program EdJxYPD…) are retired and MUST NOT be used.\n usdcMint: 'xyc5J8MgKFiEN13PnfftdXxUzYH34FEvw1LCrFwN7in',\n programId: '2aEVJ8koKD8LTZrLRSGtAtU7LBt4e7QjjCgf1kzQ7Rip',\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 * Field id (decimal string) of the settlement token the zkApp channels are\n * denominated in. Required to read a custom-token balance on Mina (the GraphQL\n * `account(publicKey, token)` query); absent for the native-MINA case.\n */\n tokenId?: 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 // Post-2026-07-19 public-chain cutover (source of truth: toon-meta\n // docs/deployment.md). This is the CURRENT deployed PaymentChannel zkApp; the\n // previous address (B62qrH1As4…) is retired and MUST NOT be used. The channels\n // settle a custom USDC token, so the tokenId is required to read balances /\n // open channels against the right token.\n zkAppAddress: 'B62qmgPhv2Xo6QVEtwjLja8UZJUtu8yapRFAR6gaoGtbM9zE5hG7Tkf',\n tokenId:\n '9497120696276615621907376728658022802954262638363646162765282600447713419198',\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 ...(mina.tokenId && { tokenId: mina.tokenId }),\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 /**\n * Mina channel params (graphqlUrl + zkAppAddress + networkId [+ tokenId]), if\n * deployed. `tokenId` is the settlement-token Field id — present when the\n * deployed channels are denominated in a custom token (client reads the token\n * balance with it); absent for native MINA.\n */\n minaChannel?: {\n graphqlUrl: string;\n zkAppAddress: string;\n networkId: 'devnet' | 'mainnet';\n tokenId?: string;\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 ...(mina.tokenId && { tokenId: mina.tokenId }),\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 relay 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 → relay 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 relay 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 relay 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 '' → relay 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 /**\n * Per-packet expiry. When provided, it is propagated onto the produced\n * PREPARE (as an ISO 8601 string) so the transport sets exactly this\n * expiry on the wire. When omitted, the transport applies its own\n * default (typically derived from the request timeout, ~30s).\n */\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 * Packet expiry as an ISO 8601 string. Present only when the caller\n * supplied `expiresAt`; absent means the transport picks its default\n * (timeout-derived). Matches the connector's `POST /admin/ilp/send`\n * request field.\n */\n expiresAt?: 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 * passes through the destination, and — when supplied — serializes the\n * per-packet expiry to ISO 8601. 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 ...(params.expiresAt !== undefined && {\n expiresAt: params.expiresAt.toISOString(),\n }),\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 * Pure helpers for NIP-on-TOON UI renderer resolution.\n *\n * An event may carry a `ui` tag whose value is an addressable coordinate\n * pointing at a `kind:31036` renderer event. The coordinate convention is:\n *\n * 31036:<renderer-author-pubkey>:<target-kind>\n *\n * where the trailing segment (the renderer event's `d` tag value) is the\n * kind of event the renderer knows how to render.\n *\n * These helpers are intentionally PURE: they parse coordinate strings and\n * select the latest addressable event from a set of candidates. The actual\n * resolution (relay query + cache) is client-local and lives outside core.\n *\n * Mirrors the style of `parseRepositoryReference` in `../nip34/types.ts`.\n */\n\nimport type { Event as NostrEvent } from 'nostr-tools/pure';\n\n/** The renderer event kind for NIP-on-TOON UI rendering. */\nexport const UI_RENDERER_KIND = 31036;\n\n/** Tag name carrying the UI renderer coordinate on a rendered event. */\nexport const UI_TAG = 'ui';\n\n/** Matches a lowercase 64-character hex pubkey. */\nconst HEX64_RE = /^[0-9a-f]{64}$/;\n\n/**\n * A parsed `ui` renderer coordinate.\n * Format: \"31036:<pubkey>:<targetKind>\"\n */\nexport interface UiCoordinate {\n /** Always {@link UI_RENDERER_KIND} (31036). */\n kind: typeof UI_RENDERER_KIND;\n /** The renderer author's 64-hex pubkey. */\n pubkey: string;\n /** The kind of event this renderer targets (the renderer's `d` value). */\n targetKind: number;\n}\n\n/**\n * Parse a `ui` renderer coordinate string of the form\n * `31036:<pubkey>:<targetKind>`.\n *\n * Returns `null` on any malformed input: wrong segment count, a leading kind\n * other than 31036, a pubkey that is not 64-hex, or a non-integer target kind.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param coord - The coordinate string (e.g. a `ui` tag value).\n * @returns The parsed {@link UiCoordinate}, or `null` if malformed.\n */\nexport function parseUiCoordinate(coord: string): UiCoordinate | null {\n if (typeof coord !== 'string') {\n return null;\n }\n const parts = coord.split(':');\n if (parts.length !== 3) {\n return null;\n }\n const [kindStr, pubkey, targetKindStr] = parts;\n if (\n kindStr === undefined ||\n pubkey === undefined ||\n targetKindStr === undefined\n ) {\n return null;\n }\n if (kindStr !== String(UI_RENDERER_KIND)) {\n return null;\n }\n if (!HEX64_RE.test(pubkey)) {\n return null;\n }\n if (!/^\\d+$/.test(targetKindStr)) {\n return null;\n }\n const targetKind = Number(targetKindStr);\n if (!Number.isInteger(targetKind)) {\n return null;\n }\n return {\n kind: UI_RENDERER_KIND,\n pubkey,\n targetKind,\n };\n}\n\n/**\n * Build a `ui` renderer coordinate string of the form\n * `31036:<pubkey>:<targetKind>`.\n *\n * The inverse of {@link parseUiCoordinate}. Returns `null` if the inputs are\n * invalid (pubkey not 64-hex, or target kind not a non-negative integer), so\n * that `build`→`parse` round-trips cleanly.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param ref - The renderer author pubkey and target kind.\n * @returns The coordinate string, or `null` if the inputs are invalid.\n */\nexport function buildUiCoordinate(ref: {\n pubkey: string;\n targetKind: number;\n}): string | null {\n const { pubkey, targetKind } = ref;\n if (typeof pubkey !== 'string' || !HEX64_RE.test(pubkey)) {\n return null;\n }\n if (!Number.isInteger(targetKind) || targetKind < 0) {\n return null;\n }\n return `${UI_RENDERER_KIND}:${pubkey}:${targetKind}`;\n}\n\n/**\n * Read the `ui` tag value off an event and parse it into a coordinate.\n *\n * Convenience wrapper around {@link parseUiCoordinate} that pulls the first\n * `ui` tag's value from `event.tags`. Returns `null` if the event has no\n * `ui` tag or the tag value is malformed.\n *\n * Pure: no IO, no relay fetch.\n *\n * @param event - The event that may reference a renderer via a `ui` tag.\n * @returns The parsed {@link UiCoordinate}, or `null`.\n */\nexport function getUiCoordinate(event: NostrEvent): UiCoordinate | null {\n const value = event.tags.find((t) => t[0] === UI_TAG)?.[1];\n if (value === undefined) {\n return null;\n }\n return parseUiCoordinate(value);\n}\n\n/**\n * Select the latest addressable/replaceable event from a set of candidates\n * that share an addressable coordinate.\n *\n * Implements NIP-33 latest-wins: the event with the greatest `created_at`\n * wins. Ties on `created_at` are broken by the lexicographically lowest `id`\n * per the NIP-01 convention.\n *\n * Does not itself group by coordinate — callers pass the candidate set for a\n * single coordinate (e.g. the result of a relay query filtered by author +\n * kind + `#d`). Pure: no IO, no relay fetch.\n *\n * @param events - Candidate events sharing a coordinate.\n * @returns The latest event, or `undefined` for an empty input.\n */\nexport function selectLatestAddressable<T extends NostrEvent>(\n events: T[]\n): T | undefined {\n let latest: T | undefined;\n for (const event of events) {\n if (latest === undefined) {\n latest = event;\n continue;\n }\n if (event.created_at > latest.created_at) {\n latest = event;\n } else if (event.created_at === latest.created_at && event.id < latest.id) {\n latest = event;\n }\n }\n return latest;\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 type BuildIlpPeerInfoOptions,\n getEventExpiration,\n isEventExpired,\n EXPIRATION_TAG,\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 coopCloseHashEvm,\n eip712DomainSeparatorEvm,\n balanceProofHashSolana,\n minaHashToField,\n balanceProofFieldsMina,\n base58Encode,\n base58Decode,\n hexToMinaBase58PrivateKey,\n deriveMinaPublicKeyBase58,\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-on-TOON UI renderer resolution (pure ui→kind:31036 coordinate helpers)\nexport {\n UI_RENDERER_KIND,\n UI_TAG,\n parseUiCoordinate,\n buildUiCoordinate,\n getUiCoordinate,\n selectLatestAddressable,\n type UiCoordinate,\n} from './ui/index.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"],"mappings":"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","names":["MAX_ILP_ADDRESS_LENGTH","isObject","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","finalizeEvent","HEX_64_REGEX","HEX_64_REGEX","finalizeEvent","finalizeEvent","HEX_64_REGEX","finalizeEvent","finalizeEvent","finalizeEvent","AttestationState","finalizeEvent","BASE64_REGEX","finalizeEvent","finalizeEvent","HEX_64_REGEX","finalizeEvent","PUBKEY_REGEX","PUBKEY_REGEX","isValidPubkey","valid","isValidPubkey","SimplePool","SimplePool","getPublicKey","getPublicKey","SimplePool","getPublicKey","WebSocket","getPublicKey","SimplePool","PUBKEY_REGEX","WebSocket","getPublicKey","getPublicKey","createHttpIlpClient","getPublicKey","path","getPublicKey","sha256","readFile","readFile"]}
|