@toon-protocol/rig 3.6.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +112 -153
  2. package/dist/{chunk-HJDPM37T.js → chunk-7VCVR4GK.js} +12 -8
  3. package/dist/{chunk-HJDPM37T.js.map → chunk-7VCVR4GK.js.map} +1 -1
  4. package/dist/chunk-CDSFVZDV.js +290 -0
  5. package/dist/chunk-CDSFVZDV.js.map +1 -0
  6. package/dist/{chunk-B5ISARMU.js → chunk-GVXBUL3C.js} +15 -2
  7. package/dist/chunk-GVXBUL3C.js.map +1 -0
  8. package/dist/chunk-LTFMPOHU.js +194 -0
  9. package/dist/chunk-LTFMPOHU.js.map +1 -0
  10. package/dist/{chunk-2KJJ7LSO.js → chunk-NCEMQUPA.js} +40 -2
  11. package/dist/chunk-NCEMQUPA.js.map +1 -0
  12. package/dist/{chunk-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
  13. package/dist/chunk-YZRR2UO6.js.map +1 -0
  14. package/dist/cli/rig.js +631 -568
  15. package/dist/cli/rig.js.map +1 -1
  16. package/dist/index.d.ts +10 -115
  17. package/dist/index.js +16 -122
  18. package/dist/index.js.map +1 -1
  19. package/dist/{publisher-BFcnZeJI.d.ts → publisher-DMJ06cuL.d.ts} +98 -2
  20. package/dist/standalone/index.d.ts +241 -438
  21. package/dist/standalone/index.js +19 -11
  22. package/dist/standalone-mode-KTYRTUS6.js +390 -0
  23. package/dist/standalone-mode-KTYRTUS6.js.map +1 -0
  24. package/package.json +7 -5
  25. package/dist/chunk-2KJJ7LSO.js.map +0 -1
  26. package/dist/chunk-B5ISARMU.js.map +0 -1
  27. package/dist/chunk-IZ6KLYQ6.js +0 -724
  28. package/dist/chunk-IZ6KLYQ6.js.map +0 -1
  29. package/dist/chunk-LCYCKWAT.js.map +0 -1
  30. package/dist/chunk-PWNZWQOH.js +0 -404
  31. package/dist/chunk-PWNZWQOH.js.map +0 -1
  32. package/dist/chunk-UZZ2RN7A.js +0 -469
  33. package/dist/chunk-UZZ2RN7A.js.map +0 -1
  34. package/dist/network-bootstrap-27FYE3N7.js +0 -40
  35. package/dist/network-bootstrap-27FYE3N7.js.map +0 -1
  36. package/dist/standalone-mode-5MNNJC2D.js +0 -928
  37. package/dist/standalone-mode-5MNNJC2D.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/standalone-mode.ts","../src/standalone/notice.ts"],"sourcesContent":["/**\n * The embedded-client (standalone) publisher backing every paid `rig`\n * command: build a nonce-guarded {@link StandalonePublisher} from the\n * caller's own identity and config.\n *\n * Identity comes from the #248 precedence chain (`./identity.ts`:\n * RIG_MNEMONIC env → TOON_CLIENT_MNEMONIC env alias → project `.env` →\n * `~/.toon-client` keystore/config). The remaining config resolution\n * DUPLICATES the toon-clientd conventions\n * (`packages/client-mcp/src/daemon/config.ts`) the same way\n * `../standalone/nonce-guard.ts` does — this package must not import\n * `@toon-protocol/client-mcp` (circular; see that module's doc). Keep in sync:\n *\n * - state dir: `TOON_CLIENT_HOME`, else `~/.toon-client`; config `config.json`\n * - env overrides: `TOON_CLIENT_PROXY_URL`, `TOON_CLIENT_BTP_URL`,\n * `TOON_CLIENT_RELAY_URL`, `TOON_CLIENT_DESTINATION`,\n * `TOON_CLIENT_PUBLISH_DESTINATION`, `TOON_CLIENT_STORE_DESTINATION`,\n * `TOON_CLIENT_CHAIN`\n *\n * NETWORK BOOTSTRAP (#264): what explicit config does not pin is resolved\n * from the network itself — the payment peer's live kind:10032 announce on\n * the relay-origin, falling back to `@toon-protocol/core`'s committed\n * genesis peer seed. Uplink, channel anchor, publish/store routes, the\n * settlement chain and its TokenNetwork/token/RPC parameters all follow the\n * `explicit config > live announce > genesis seed` order documented in\n * `../standalone/network-bootstrap.ts`. The pure resolution lives in\n * {@link resolveNetworkTopology} (unit-testable without any network).\n *\n * PAID-WRITE TRANSPORT: with no explicit uplink, the resolution places BOTH\n * the official proxy (ILP-over-HTTP, which the x402 greeting bootstrap needs)\n * and the payment peer's announced BTP endpoint, and the embedded client is\n * built with `preferBtpForPaidWrites` so claims ride the authenticated BTP\n * session instead of an unauthenticated `POST /ilp` the edge answers 401.\n *\n * TOPOLOGY CACHE (#279): the resolved topology is persisted under\n * `TOON_CLIENT_HOME` (`../standalone/topology-cache.ts`) keyed by\n * relay-origin + identity + an explicit-config fingerprint, with a 15-min\n * TTL (`RIG_TOPOLOGY_TTL_MS` overrides; `0` disables). A cache hit skips\n * announce discovery and the funded-chain probes entirely; a cached\n * topology that then fails to BOOTSTRAP is invalidated and re-resolved\n * live in-process ({@link TopologyRecoveringPublisher}), so staleness costs\n * one failed attempt, never a broken run. Only paid-path resolutions\n * (`requireUplink !== false`) are written — a free-read topology may lack\n * an uplink and must not shadow the paid path's MissingUplinkError.\n *\n * CORE COEXISTENCE NOTE: rig performs discovery with ITS OWN\n * `@toon-protocol/core` (^2.0.x — live genesis seed), while the embedded\n * `@toon-protocol/client` keeps its internal core (^1.6.x) for its own\n * bootstrap/negotiation. The two never exchange class instances — rig feeds\n * the client plain config (`knownPeers`, settlement maps), so the version\n * split is safe by construction.\n *\n * This module statically imports `@toon-protocol/client` (heavy: viem,\n * noble, nostr-tools), so it must only ever be reached through the dynamic\n * import in `push.ts` (see `./standalone-context.ts`).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport {\n EvmSigner,\n deriveFullIdentity,\n deriveNostrKeyFromMnemonic,\n} from '@toon-protocol/client';\nimport {\n decodeEventFromToon,\n encodeEventToToon,\n resolveClientNetwork,\n} from '@toon-protocol/core';\nimport type {\n BlobUpload,\n FeeRates,\n GitObjectUpload,\n Publisher,\n PublishReceipt,\n UploadReceipt,\n} from '../publisher.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n ChannelMapStore,\n RIG_CHANNEL_MAP_FILENAME,\n type ChannelMapRecord,\n} from '../standalone/channel-map.js';\nimport {\n TOPOLOGY_CACHE_FILENAME,\n TOPOLOGY_TTL_ENV,\n TopologyCache,\n explicitConfigFingerprint,\n topologyCacheKey,\n topologyCacheTtlMs,\n} from '../standalone/topology-cache.js';\nimport {\n NOTICE_STORE_FILENAME,\n NoticeStore,\n showOperatorNoticeOnce,\n} from '../standalone/notice.js';\nimport {\n DISCOVERY_TIMEOUT_MS,\n MinaChannelUnderivableError,\n SolanaChannelUnderivableError,\n TokenNetworkUnderivableError,\n discoverAnnouncedPeers,\n evmChainIdOf,\n evmTokenBalance,\n genesisSeedPubkeys,\n loadGenesisSeed,\n pickPaymentPeer,\n resolveChainSettlement,\n selectSettlementChain,\n solanaTokenBalance,\n type AnnouncedPeer,\n type ChainSelection,\n type ChainSettlement,\n type ChannelRecordLike,\n type EvmBalanceProbe,\n type ExplicitChainConfig,\n type SolanaBalanceProbe,\n} from '../standalone/network-bootstrap.js';\nimport {\n StandalonePublisher,\n deriveRouteDestinations,\n type WalletViewFallback,\n} from '../standalone/standalone-publisher.js';\nimport { standaloneForced } from '../standalone/nonce-guard.js';\nimport { fetchRemoteState } from '../remote-state.js';\nimport { MinaZkAppStore } from '../standalone/mina-zkapp-store.js';\nimport { resolveIdentity } from './identity.js';\nimport type {\n StandaloneContext,\n StandaloneLoadOptions,\n} from './standalone-context.js';\n\n/** The subset of the shared client config file standalone mode consumes. */\nexport interface ClientConfigFile {\n network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';\n mnemonicAccountIndex?: number;\n btpUrl?: string;\n proxyUrl?: string;\n relayUrl?: string;\n destination?: string;\n publishDestination?: string;\n storeDestination?: string;\n /**\n * BTP ingress of the node terminating `storeDestination`, when that is a\n * different node than publishes go to. Normally discovered from that\n * node's own kind:10032 announce; pin it here (or via\n * `TOON_CLIENT_STORE_BTP_URL`) to skip discovery entirely.\n */\n storeBtpUrl?: string;\n feePerEvent?: string;\n channelStorePath?: string;\n /** Settlement chain: family (`evm`) or full id (`evm:31337`). */\n chain?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n}\n\n/** An identity was resolved, but there is no way to send paid writes. */\n/**\n * The official TOON relay implementation's client edge — the Rust connector\n * (connector #616: the parallel-fleet comparison concluded and the Rust\n * fleet serves the protocol's own `g.toon` namespace). This is rig's DEFAULT\n * HTTP uplink: paid writes go here unless an entry is explicitly configured\n * (`rig entry <url>` / `rig entry sandbox` / the TOON_CLIENT_* env\n * overrides). It is also the endpoint the channel bootstrap needs, since the\n * x402 greeting carries the channel-opening facts (connector #617) the\n * embedded client synthesizes a negotiation from — so a dead value here\n * fails a FRESH channel open even when paid writes ride BTP.\n *\n * BOTH the host and the path moved in the two-box cutover, and the old value\n * (`https://proxy.devnet.toonprotocol.dev/rust/ilp`) was dead on both counts:\n *\n * - Host: the apex was RETIRED and destroyed. `proxy.devnet.toonprotocol.dev`\n * still resolves but refuses connections; the relay box\n * (`g.toon.relay`) is where the publish route now lives.\n * - Path: `/rust/ilp` was a legacy apex path from the period when the Rust\n * and TS fleets ran side by side. The live edge serves the ILP-over-HTTP\n * ingress at plain `/ilp` (a GET answers `405 allow: POST`), and answers\n * `410 Gone` on `/rust/ilp` — the retirement is explicit, not incidental.\n *\n * This value is exactly what the relay's own live kind:10032 announce\n * advertises as its `httpEndpoint`, so discovery and this fallback agree.\n */\nexport const OFFICIAL_PROXY_URL =\n 'https://proxy.relay.devnet.toonprotocol.dev/ilp';\n/** The official relay-write route on that edge. */\nexport const OFFICIAL_PUBLISH_DESTINATION = 'g.toon.relay';\n\n/**\n * The BTP ingress paid writes ride when discovery does not name one — the\n * RELAY box of the two-box devnet (ILP address `g.toon.relay`), which is the\n * publish route every `rig push` terminates at.\n *\n * Deliberately NOT the genesis seed's `btpEndpoint`. `@toon-protocol/core`'s\n * committed seed still names the retired apex\n * (`wss://proxy.devnet.toonprotocol.dev/ilp/btp`), a host that resolves but\n * refuses connections since the two-box cutover — and because the embedded\n * client dials BTP during `start()`, handing it a dead endpoint would turn a\n * late 401 into rig refusing to start at all. That is what\n * {@link isBtpTransportError}'s degrade recovery guards against in general;\n * this constant keeps the common path off the dead box in the first place.\n */\nexport const OFFICIAL_BTP_URL =\n 'wss://proxy.relay.devnet.toonprotocol.dev/ilp/btp';\n\nexport class MissingUplinkError extends Error {\n constructor(configPath: string, relayUrl: string | undefined) {\n const discovered = relayUrl\n ? `no announce with a btp/http endpoint was found on ${relayUrl} and ` +\n 'the genesis seed has none; '\n : '';\n super(\n `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL ` +\n '(connector payment proxy) or TOON_CLIENT_BTP_URL, or add ' +\n `proxyUrl/btpUrl to ${configPath}`\n );\n this.name = 'MissingUplinkError';\n }\n}\n\nfunction configDir(env: NodeJS.ProcessEnv): string {\n return env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\nfunction readClientConfig(path: string): ClientConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as ClientConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pure topology resolution (#264) — exported for tests\n// ---------------------------------------------------------------------------\n\n/** A genesis-seed peer as this module consumes it (rig's core 3.x shape). */\nexport interface GenesisSeedLike {\n pubkey: string;\n relayUrl: string;\n ilpAddress: string;\n btpEndpoint: string;\n}\n\n/** Inputs to {@link resolveNetworkTopology} (side-effect free). */\nexport interface NetworkTopologyInputs {\n env: NodeJS.ProcessEnv;\n file: ClientConfigFile;\n /** For error messages (\"add X to <configPath>\"). */\n configPath: string;\n /** Resolved relay-origin (already precedence-resolved). */\n relayUrl: string;\n /** The discovered payment-peer announce, if any. */\n announce: AnnouncedPeer | undefined;\n /**\n * Every announce discovery returned, not just the picked payment peer.\n * The store node announces itself SEPARATELY from the publish node (on\n * this fleet, `g.toon.ario` alongside `g.toon.relay`), and its own\n * `btpEndpoint` is the only place its BTP ingress is published — the\n * payment peer's announce names the store ROUTE but not how to reach it.\n * Optional so existing callers (and every test that builds inputs by\n * hand) keep working: absent means \"no store uplink discovered\", which\n * degrades to the single-uplink behaviour that predates this field.\n */\n peers?: AnnouncedPeer[];\n /** The committed genesis seed entry, if any. */\n genesisSeed: GenesisSeedLike | undefined;\n identity: { mnemonic: string; accountIndex: number; pubkey: string };\n /**\n * #262 channel-map records for this identity (chain selection input).\n * A thunk so the (possibly corrupt-throwing) map read only happens when\n * chain selection actually needs it.\n */\n channelRecords: () => ChannelRecordLike[];\n /** EVM balance probe override (tests); default: raw `eth_call`. */\n probeBalance?: EvmBalanceProbe;\n /**\n * Solana balance probe override (tests); default: raw\n * `getTokenAccountsByOwner`.\n */\n probeSolanaBalance?: SolanaBalanceProbe;\n /**\n * When false (#263 free reads, e.g. `rig balance`), a missing uplink is\n * tolerated instead of throwing {@link MissingUplinkError}.\n */\n requireUplink?: boolean;\n warn: (line: string) => void;\n}\n\n/** The resolved payment topology feeding the embedded client config. */\nexport interface NetworkTopology {\n proxyUrl?: string;\n btpUrl?: string;\n /**\n * True when `btpUrl` came from DISCOVERY (the announce, or the\n * {@link OFFICIAL_BTP_URL} fallback) rather than explicit config. Only a\n * derived endpoint may be dropped by the unreachable-BTP degrade recovery\n * — an operator's explicit pin is never silently abandoned. Persisted with\n * the rest of the topology in the #279 cache.\n */\n btpUrlDerived?: boolean;\n /** Channel anchor / default ILP destination. */\n destination: string;\n publishDestination?: string;\n storeDestination?: string;\n /**\n * BTP ingress of the node that TERMINATES {@link storeDestination}, when\n * that is a different node from the publish peer.\n *\n * A payment-channel claim is only verifiable by the connector that holds\n * the channel: the claim names a channel id, and a connector with no\n * record of it refuses the packet outright (`F01 - claim rejected: names\n * a channel this connector has no record of`). On the two-box fleet the\n * store lives on its own node with its own settlement address, and the\n * publish node has NO route to it — its only route is its own publish\n * prefix — so a store write cannot be carried by the publish session and\n * has to reach the store node directly, on its own channel.\n *\n * Absent when the store terminates on the same node as publishes (the\n * single-box case), which needs no second uplink.\n */\n storeBtpUrl?: string;\n /** The peer the embedded client bootstraps + negotiates with. */\n knownPeers: { pubkey: string; relayUrl: string; btpEndpoint: string }[];\n /**\n * FLAT per-packet route prices the peer announces for the effective\n * publish/store destinations (kind:10032 `capabilities`; base-unit integer\n * strings — this document is JSON-cached). The connector rejects any\n * balance-proof claim advancing the channel by less than the destination\n * route's price (F06), so the publisher floors its per-packet fees here.\n */\n routePrices?: { publish?: string; store?: string };\n /** The selected settlement chain + rationale (absent: nothing known). */\n selection?: ChainSelection;\n supportedChains?: string[];\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n /**\n * Solana channel parameters derived for the (first) `solana:*` chain in\n * play — the config-ready `ToonClientConfig.solanaChannel` shape. Absent\n * when no Solana chain is supported or an explicit `solanaChannel` config\n * object already covers it (explicit rides through verbatim).\n */\n solanaChannel?: { rpcUrl: string; programId: string; tokenMint?: string };\n /**\n * Mina channel parameters derived for the (first) `mina:*` chain in play —\n * the config-ready `ToonClientConfig.minaChannel` shape. Absent when no Mina\n * chain is supported or an explicit `minaChannel` config object already\n * covers it (explicit rides through verbatim).\n */\n minaChannel?: {\n graphqlUrl: string;\n zkAppAddress: string;\n tokenId?: string;\n networkId?: 'devnet' | 'mainnet';\n };\n}\n\n/**\n * Build the embedded client's `minaChannel` from one resolved settlement — or\n * throw the actionable {@link MinaChannelUnderivableError} naming the missing\n * pieces. A Mina chain in play with no way to open its channel must fail HERE,\n * not later as the embedded client's generic \"Mina channel config not\n * provided\". The required pieces are `graphqlUrl` (from a core preset) and\n * `zkAppAddress` (from the live announce); `tokenId`/`networkId` ride through\n * when present (a Mina channel with no token id opens on native MINA).\n */\n/**\n * The `autoDeploy` block attached to a DERIVED `minaChannel` (an explicit\n * config object keeps the pin-a-zkApp semantics and never auto-deploys):\n * reuse the recorded per-pair zkApp when one exists, persist a fresh\n * deployment's key BEFORE the open proceeds (losing it strands the ~1.1 MINA\n * the deployment cost), and route the slow compile/deploy/inclusion progress\n * to the warn stream.\n */\nexport function buildMinaAutoDeploy(\n dir: string,\n identityPubkey: string,\n chain: string,\n warn: (line: string) => void\n): NonNullable<NonNullable<ToonClientConfig['minaChannel']>['autoDeploy']> {\n const store = MinaZkAppStore.forHome(dir);\n let existing: ReturnType<MinaZkAppStore['lookup']>;\n try {\n existing = store.lookup(identityPubkey, chain);\n } catch (err) {\n warn(\n `rig: unreadable Mina zkApp store at ${store.filePath} ` +\n `(${err instanceof Error ? err.message : String(err)}) — a fresh ` +\n 'zkApp may be deployed'\n );\n }\n return {\n ...(existing\n ? {\n deployed: {\n zkAppAddress: existing.zkAppAddress,\n zkAppPrivateKey: existing.zkAppPrivateKey,\n },\n }\n : {}),\n // Persist the zkApp key BEFORE the deploy tx is sent (bug #3): a crash or\n // failed cache-invalidation retry between send and confirmation then finds\n // this record next run and REDEPLOYS the same address, instead of paying\n // the ~1.1-MINA account-creation fee for a brand-new zkApp every attempt.\n onDeploying: (record) => {\n store.save({\n identity: identityPubkey,\n chain,\n zkAppAddress: record.zkAppAddress,\n zkAppPrivateKey: record.zkAppPrivateKey,\n feePayer: record.feePayer,\n deployedAt: new Date().toISOString(),\n source:\n 'rig auto-deploy (npm @toon-protocol/mina-zkapp build, pending)',\n });\n warn(\n `rig: recorded pending Mina zkApp ${record.zkAppAddress} before ` +\n `deploy — key saved to ${store.filePath} (reused on retry)`\n );\n },\n onDeployed: (record) => {\n store.save({\n identity: identityPubkey,\n chain,\n zkAppAddress: record.zkAppAddress,\n zkAppPrivateKey: record.zkAppPrivateKey,\n feePayer: record.feePayer,\n ...(record.deployTxHash !== undefined\n ? { deployTxHash: record.deployTxHash }\n : {}),\n ...(record.vkHash !== undefined ? { vkHash: record.vkHash } : {}),\n deployedAt: new Date().toISOString(),\n source: 'rig auto-deploy (npm @toon-protocol/mina-zkapp build)',\n });\n warn(\n `rig: deployed a dedicated Mina PaymentChannel zkApp ` +\n `${record.zkAppAddress} — key saved to ${store.filePath}`\n );\n },\n onProgress: (line) => warn(`rig: mina: ${line}`),\n };\n}\n\nfunction deriveMinaChannel(\n settlement: ChainSettlement,\n announce: AnnouncedPeer | undefined,\n relayUrl: string\n): NonNullable<NetworkTopology['minaChannel']> {\n const { rpcUrl, zkAppAddress, minaTokenId, networkId } = settlement;\n if (!rpcUrl || !zkAppAddress) {\n const missing = [\n ...(rpcUrl ? [] : ['graphqlUrl']),\n ...(zkAppAddress ? [] : ['zkAppAddress']),\n ];\n throw new MinaChannelUnderivableError(\n settlement.chain,\n missing,\n announce,\n relayUrl\n );\n }\n return {\n graphqlUrl: rpcUrl,\n zkAppAddress,\n ...(minaTokenId ? { tokenId: minaTokenId } : {}),\n ...(networkId ? { networkId } : {}),\n };\n}\n\n/**\n * Build the embedded client's `solanaChannel` from one resolved settlement —\n * or throw the actionable {@link SolanaChannelUnderivableError} naming the\n * missing pieces. A Solana chain in play with no way to open its channel must\n * fail HERE, not later as the embedded client's generic \"Solana channel\n * config not provided\".\n */\nfunction deriveSolanaChannel(\n settlement: ChainSettlement,\n announce: AnnouncedPeer | undefined,\n relayUrl: string\n): NonNullable<NetworkTopology['solanaChannel']> {\n const { rpcUrl, programId, tokenAddress } = settlement;\n if (!rpcUrl || !programId || !tokenAddress) {\n const missing = [\n ...(rpcUrl ? [] : ['rpcUrl']),\n ...(programId ? [] : ['programId']),\n ...(tokenAddress ? [] : ['tokenMint']),\n ];\n throw new SolanaChannelUnderivableError(\n settlement.chain,\n missing,\n announce,\n relayUrl\n );\n }\n return { rpcUrl, programId, tokenMint: tokenAddress };\n}\n\n/**\n * Resolve the payment topology per the #264 precedence order —\n * `explicit config > live announce > genesis seed` for every field, plus the\n * documented settlement-chain selection rule (see\n * `../standalone/network-bootstrap.ts`).\n *\n * @throws {MissingUplinkError} when no source yields an uplink.\n * @throws {TokenNetworkUnderivableError} when the selected EVM chain's\n * TokenNetwork cannot be derived from config, announce, or chain preset.\n */\nexport async function resolveNetworkTopology(\n inputs: NetworkTopologyInputs\n): Promise<NetworkTopology> {\n const { env, file, configPath, relayUrl, announce, genesisSeed, warn } =\n inputs;\n\n // ── Explicit config (always wins, per field) ─────────────────────────────\n const explicitProxyUrl = env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl;\n const explicitBtpUrl = env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl;\n const explicitDestination =\n env['TOON_CLIENT_DESTINATION'] ?? file.destination;\n const explicitPublish =\n env['TOON_CLIENT_PUBLISH_DESTINATION'] ?? file.publishDestination;\n const explicitStore =\n env['TOON_CLIENT_STORE_DESTINATION'] ?? file.storeDestination;\n const explicitChain = env['TOON_CLIENT_CHAIN'] ?? file.chain;\n const explicitMaps: ExplicitChainConfig = {\n ...(file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {}),\n ...(file.preferredTokens ? { preferredTokens: file.preferredTokens } : {}),\n ...(file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}),\n };\n\n // ── Uplink: explicit > THE OFFICIAL EDGE ─────────────────────────────────\n // The cutover (connector #616): the Rust connector is the official TOON\n // relay implementation, so with no explicit entry the uplink IS the\n // official edge — the live announce and the genesis seed no longer place\n // the uplink (they still inform the destination anchor, routes, prices\n // and bootstrap peers below). Opting onto another fleet is what\n // `rig entry <url>` / `rig entry sandbox` and the TOON_CLIENT_* env\n // overrides are for, and those are all EXPLICIT. `MissingUplinkError`\n // can no longer fire from here — an uplink always resolves.\n let proxyUrl = explicitProxyUrl;\n let btpUrl = explicitBtpUrl;\n if (!proxyUrl && !btpUrl) {\n proxyUrl = OFFICIAL_PROXY_URL;\n }\n\n // ── Paid-write BTP companion ─────────────────────────────────────────────\n // The proxy default above places an ILP-over-HTTP uplink and NOTHING else,\n // so the embedded client builds no BTP session at all and every paid write\n // goes out as `POST /ilp` — which the live edge answers `401 Unauthorized:\n // identity 'g.toon.client' failed to authenticate`, because the HTTP\n // one-shot carries only an unauthenticated `ILP-Peer-Id` header while the\n // BTP session authenticates on connect. Adopting the payment peer's\n // ANNOUNCED BTP endpoint ({@link OFFICIAL_BTP_URL} as the fallback — NOT\n // the genesis seed, which still names the retired apex) gives the client\n // both transports; `preferBtpForPaidWrites` (set alongside this in\n // `createStandaloneContext`) then sends claims over BTP first and keeps\n // HTTP as the transport's own fallback. The x402 greeting bootstrap\n // (connector #617) still rides the proxy, so the channel-open path is\n // unchanged.\n //\n // Only when NO uplink was configured explicitly: someone who pinned\n // `TOON_CLIENT_PROXY_URL` / `proxyUrl` pinned the transport too, and a\n // silently-added announce endpoint would route their paid writes off it.\n //\n // A derived endpoint is marked as such: it is a DISCOVERY guess, and the\n // client dials it during `start()`, so an unreachable one must be able to\n // degrade back to HTTP rather than stop rig from running. An explicit\n // `btpUrl` is an operator's pin and is never dropped behind their back.\n let btpUrlDerived = false;\n if (!explicitProxyUrl && !explicitBtpUrl) {\n btpUrl = announce?.info.btpEndpoint || OFFICIAL_BTP_URL;\n btpUrlDerived = true;\n }\n\n // ── Destination anchor + publish/store routes ────────────────────────────\n // The channel anchors at the peer's announced ilpAddress; publish/store\n // routes come from the announce's `routes` map. Explicit values always\n // win; with neither, the publisher's `<base>.relay.store` anchor-derivation\n // convention remains as the last-resort fallback (explicit anchors only).\n const destination =\n explicitDestination ??\n announce?.info.ilpAddress ??\n genesisSeed?.ilpAddress ??\n 'g.proxy';\n const publishDestination =\n explicitPublish ??\n announce?.routes?.publish ??\n (proxyUrl === OFFICIAL_PROXY_URL\n ? OFFICIAL_PUBLISH_DESTINATION\n : undefined);\n const storeDestination = explicitStore ?? announce?.routes?.store;\n\n // ── Store uplink ─────────────────────────────────────────────────────────\n // Resolved from the STORE node's own announce, matched by the ilpAddress it\n // publishes for itself — not from the payment peer's, which names the store\n // route but carries no way to reach it. Only when the store terminates\n // somewhere other than the publish peer: same-node stores ride the session\n // that is already open, and handing them a second identical uplink would\n // open a redundant on-chain channel against the same counterparty.\n //\n // No baked fallback here on purpose. `OFFICIAL_BTP_URL` can name the\n // publish edge because that is where an unconfigured rig must end up\n // anyway, but guessing a STORE endpoint would send claims — real money —\n // to a node nobody advertised. Undiscovered means undiscovered.\n const explicitStoreBtpUrl =\n env['TOON_CLIENT_STORE_BTP_URL'] ?? file.storeBtpUrl;\n let storeBtpUrl: string | undefined;\n if (storeDestination && storeDestination !== publishDestination) {\n const storePeer = inputs.peers?.find(\n (peer) => peer.info.ilpAddress === storeDestination\n );\n storeBtpUrl = explicitStoreBtpUrl || storePeer?.info.btpEndpoint || undefined;\n if (!storeBtpUrl && inputs.peers?.length) {\n warn(\n `rig: no announce found for the store route \"${storeDestination}\" — ` +\n 'store writes (git objects, the rig page) will be attempted on the ' +\n 'publish uplink and the terminating connector will refuse them if ' +\n 'it does not hold that channel'\n );\n }\n }\n\n // ── Route-price floors ───────────────────────────────────────────────────\n // The announce's `capabilities` carry a FLAT price per destination route,\n // and the connector gates every paid packet at it (a claim advancing the\n // channel by less is rejected — F06). Match the announced prices against\n // the EFFECTIVE publish/store destinations — including the publisher's\n // `<base>.relay.store` anchor-derivation fallback, so the floor applies\n // exactly when a paid write would actually target the priced route. An\n // unmatched destination gets no floor (behavior unchanged).\n let routePrices: NetworkTopology['routePrices'];\n {\n const derived = deriveRouteDestinations(destination);\n // Each node prices its OWN routes, so a price is looked up in the\n // announce of whichever peer terminates that address — the store route's\n // price lives in the store node's announce, not the payment peer's.\n // Falls back to the payment peer for addresses no discovered peer claims,\n // which is the single-node case and the pre-discovery behaviour.\n const priceOf = (address: string): string | undefined => {\n const owner =\n inputs.peers?.find((p) => p.info.ilpAddress === address) ?? announce;\n return (\n owner?.routePrices?.[address] ??\n owner?.capabilities?.find((c) => c.address === address)?.price\n );\n };\n const publishPrice = priceOf(publishDestination ?? derived.publish);\n const storePrice = priceOf(storeDestination ?? derived.store);\n if (publishPrice !== undefined || storePrice !== undefined) {\n routePrices = {\n ...(publishPrice !== undefined ? { publish: publishPrice } : {}),\n ...(storePrice !== undefined ? { store: storePrice } : {}),\n };\n }\n }\n\n // ── Known peer for the embedded client's own bootstrap ───────────────────\n // The client re-queries the peer's announce itself (its internal core) and\n // negotiates the settlement chain; rig just tells it WHO to bootstrap with.\n const knownPeers = announce\n ? [\n {\n pubkey: announce.pubkey,\n relayUrl,\n btpEndpoint: announce.info.btpEndpoint ?? '',\n },\n ]\n : genesisSeed\n ? [\n {\n pubkey: genesisSeed.pubkey,\n relayUrl: genesisSeed.relayUrl,\n btpEndpoint: genesisSeed.btpEndpoint,\n },\n ]\n : [];\n\n // ── Settlement chain + per-chain parameters ──────────────────────────────\n // NOTE: the `network` preset field is deliberately NOT forwarded to the\n // embedded client. `applyNetworkPresets` puts preset chains FIRST in\n // `supportedChains`, which is what steered devnet negotiation to the\n // unfunded public Solana preset (#260 root cause 4). The announce + the\n // rule below define the chain; presets only serve as per-chain parameter\n // fallbacks inside `resolveChainSettlement`.\n const announcedChains = announce?.info.supportedChains ?? [];\n const resolveSettlement = (chain: string) =>\n resolveChainSettlement(chain, explicitMaps, announce);\n\n let selection: ChainSelection | undefined;\n let supportedChains: string[] | undefined;\n let preferredTokens: Record<string, string> | undefined;\n let tokenNetworks: Record<string, string> | undefined;\n let chainRpcUrls: Record<string, string> | undefined;\n let solanaChannel: NetworkTopology['solanaChannel'];\n let minaChannel: NetworkTopology['minaChannel'];\n\n if (file.supportedChains?.length) {\n // Explicit chain list: pass through in order (its order IS the\n // negotiation preference), filling per-chain parameter gaps from\n // announce/presets.\n const listed = file.supportedChains;\n const kept: string[] = [];\n let firstDropError: unknown;\n preferredTokens = { ...file.preferredTokens };\n tokenNetworks = { ...file.tokenNetworks };\n chainRpcUrls = { ...file.chainRpcUrls };\n for (const chain of listed) {\n const s = resolveSettlement(chain);\n // Chain negotiation is an EXACT string intersection of the two chain\n // lists — a spelling difference for the same chain (config\n // `evm:base:31337` vs announced `evm:31337`) silently strands the\n // chain and negotiation falls through to whatever else matches. When\n // a listed EVM chain names the same numeric chain id as an announced\n // one, advertise the ANNOUNCED spelling (parameters resolved from the\n // configured spelling carry over).\n let key = chain;\n if (s.family === 'evm' && !announcedChains.includes(chain)) {\n const chainId = evmChainIdOf(chain);\n const equivalent =\n chainId !== undefined\n ? announcedChains.find((c) => evmChainIdOf(c) === chainId)\n : undefined;\n if (equivalent) {\n warn(\n `rig: aligning configured settlement chain \"${chain}\" to the ` +\n `announced spelling \"${equivalent}\" (same EVM chain id ` +\n `${chainId}) — chain negotiation matches identifiers exactly`\n );\n key = equivalent;\n }\n }\n if (kept.includes(key)) continue;\n if (s.tokenAddress && !preferredTokens[key]) {\n preferredTokens[key] = s.tokenAddress;\n }\n if (s.tokenNetwork && !tokenNetworks[key]) {\n tokenNetworks[key] = s.tokenNetwork;\n }\n if (s.rpcUrl && !chainRpcUrls[key]) {\n chainRpcUrls[key] = s.rpcUrl;\n }\n // Same fail-fast guarantee as the announce/selection path below: an\n // explicitly configured EVM chain whose TokenNetwork/RPC cannot be\n // derived must fail HERE with an actionable error, not later as the\n // embedded client's generic \"tokenNetwork address is required\".\n if (s.family === 'evm') {\n if (!tokenNetworks[key]) {\n throw new TokenNetworkUnderivableError(chain, announce, relayUrl);\n }\n if (!chainRpcUrls[key]) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${chain}\"` +\n ` — add chainRpcUrls[\"${chain}\"] to ${configPath}`\n );\n }\n }\n // A listed Solana chain is advertised to negotiation, which may pick\n // it — so its channel parameters must be derivable NOW. An explicit\n // solanaChannel config object covers it (rides through to the embedded\n // client verbatim); otherwise the first derivable Solana chain\n // provides the channel params, and an UNDERIVABLE one is dropped from\n // the advertised list (warned, with the remedy) instead of leaving a\n // chain in play that the on-chain open is guaranteed to die on.\n if (s.family === 'solana' && !file.solanaChannel) {\n if (solanaChannel) {\n // The embedded client carries ONE solanaChannel — a second Solana\n // chain cannot be honored with distinct params.\n kept.push(key);\n continue;\n }\n try {\n solanaChannel = deriveSolanaChannel(s, announce, relayUrl);\n } catch (err) {\n firstDropError ??= err;\n warn(\n `rig: dropping settlement chain ${chain} from the configured ` +\n `supportedChains — ${err instanceof Error ? err.message : String(err)}`\n );\n continue;\n }\n }\n // A listed Mina chain: same fail-fast contract as Solana. An explicit\n // minaChannel config covers it (rides through verbatim); otherwise the\n // first derivable Mina chain provides the channel params, and an\n // UNDERIVABLE one is dropped from the advertised list (warned) instead of\n // leaving a chain in play the on-chain open is guaranteed to die on.\n if (s.family === 'mina' && !file.minaChannel) {\n if (minaChannel) {\n kept.push(key);\n continue;\n }\n try {\n minaChannel = deriveMinaChannel(s, announce, relayUrl);\n } catch (err) {\n firstDropError ??= err;\n warn(\n `rig: dropping settlement chain ${chain} from the configured ` +\n `supportedChains — ${err instanceof Error ? err.message : String(err)}`\n );\n continue;\n }\n }\n kept.push(key);\n }\n if (kept.length === 0) {\n // Every listed chain was dropped — surface the first underivable\n // error instead of negotiating with an empty chain list.\n throw firstDropError;\n }\n supportedChains = kept;\n // The embedded client validates chainRpcUrls keys ⊆ supportedChains, so\n // entries for dropped/re-spelled chains must not survive; prune all\n // three chain-keyed maps to the advertised list for coherence.\n const advertised = new Set(kept);\n const prune = (map: Record<string, string>): Record<string, string> =>\n Object.fromEntries(\n Object.entries(map).filter(([chain]) => advertised.has(chain))\n );\n preferredTokens = prune(preferredTokens);\n tokenNetworks = prune(tokenNetworks);\n chainRpcUrls = prune(chainRpcUrls);\n selection = {\n chain: supportedChains[0] as string,\n reason: 'explicit',\n detail: 'supportedChains set by config',\n };\n } else if (explicitChain || announcedChains.length > 0) {\n // Selection rule: explicit > persisted channel > funded > first EVM.\n const { secretKey } = deriveNostrKeyFromMnemonic(\n inputs.identity.mnemonic,\n inputs.identity.accountIndex\n );\n const evmAddress = new EvmSigner(secretKey).address;\n // The Solana funded-chain probe needs the identity's base58 Solana\n // address, derived pre-client-start via the client's own SLIP-0010\n // helper (same paths the embedded client uses, so the probed address IS\n // the one the client will settle with). Only derived when a `solana:*`\n // chain is actually announced and no explicit chain pins selection\n // anyway; `deriveFullIdentity` degrades to an empty publicKey when the\n // Ed25519 deps are unavailable — then Solana chains are not probed.\n let solanaAddress: string | undefined;\n if (\n !explicitChain &&\n announcedChains.some((c) => c.startsWith('solana:'))\n ) {\n const fullIdentity = await deriveFullIdentity(\n inputs.identity.mnemonic,\n inputs.identity.accountIndex\n );\n solanaAddress = fullIdentity.solana.publicKey || undefined;\n // Only the address is needed here — drop the derived key material.\n fullIdentity.nostr.secretKey.fill(0);\n fullIdentity.evm.privateKey.fill(0);\n fullIdentity.solana.secretKey.fill(0);\n }\n selection = await selectSettlementChain({\n ...(explicitChain ? { explicitChain } : {}),\n announcedChains,\n records: inputs.channelRecords(),\n evmAddress,\n ...(solanaAddress ? { solanaAddress } : {}),\n resolveSettlement,\n probeBalance: inputs.probeBalance ?? evmTokenBalance,\n probeSolanaBalance: inputs.probeSolanaBalance ?? solanaTokenBalance,\n });\n const settlement = resolveSettlement(selection.chain);\n if (settlement.family === 'evm') {\n if (!settlement.tokenNetwork) {\n throw new TokenNetworkUnderivableError(\n selection.chain,\n announce,\n relayUrl\n );\n }\n if (!settlement.rpcUrl) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${selection.chain}\"` +\n ` — add chainRpcUrls[\"${selection.chain}\"] to ${configPath}`\n );\n }\n }\n // Same fail-fast for a selected Solana chain: its channel parameters\n // must be derivable now (an explicit solanaChannel config covers it).\n if (settlement.family === 'solana' && !file.solanaChannel) {\n solanaChannel = deriveSolanaChannel(settlement, announce, relayUrl);\n }\n // Same fail-fast for a selected Mina chain (an explicit minaChannel covers it).\n if (settlement.family === 'mina' && !file.minaChannel) {\n minaChannel = deriveMinaChannel(settlement, announce, relayUrl);\n }\n supportedChains = [selection.chain];\n preferredTokens = settlement.tokenAddress\n ? { [selection.chain]: settlement.tokenAddress }\n : undefined;\n tokenNetworks = settlement.tokenNetwork\n ? { [selection.chain]: settlement.tokenNetwork }\n : undefined;\n chainRpcUrls = settlement.rpcUrl\n ? { [selection.chain]: settlement.rpcUrl }\n : undefined;\n if (selection.reason !== 'explicit') {\n warn(\n `rig: settlement chain ${selection.chain} selected — ` +\n `${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains ` +\n 'in the client config) to override'\n );\n } else if (explicitChain && selection.chain !== explicitChain) {\n // The explicit pin was re-spelled to the announced chain id (chain\n // negotiation matches identifiers exactly) — say so, like the\n // configured-supportedChains path does.\n warn(\n `rig: aligning configured settlement chain \"${explicitChain}\" to ` +\n `the announced spelling \"${selection.chain}\" — ${selection.detail}`\n );\n }\n } else {\n warn(\n 'rig: no settlement chains are configured or announced — paid writes ' +\n 'will fail until a chain is configured (supportedChains) or the ' +\n `payment peer announces its chains on ${relayUrl}`\n );\n }\n\n const network = env['TOON_CLIENT_NETWORK'] ?? file.network;\n if (network && network !== 'custom' && !file.supportedChains?.length) {\n warn(\n `rig: ignoring the \"${network}\" network preset for settlement — the ` +\n \"settlement chain comes from the payment peer's announce and your \" +\n 'config, because preset chains can point at networks your wallet ' +\n 'has no funds on; set supportedChains explicitly to use preset chains'\n );\n }\n\n return {\n ...(proxyUrl ? { proxyUrl } : {}),\n ...(btpUrl ? { btpUrl, ...(btpUrlDerived ? { btpUrlDerived } : {}) } : {}),\n destination,\n ...(publishDestination ? { publishDestination } : {}),\n ...(storeDestination ? { storeDestination } : {}),\n ...(storeBtpUrl ? { storeBtpUrl } : {}),\n\n ...(routePrices ? { routePrices } : {}),\n knownPeers,\n ...(selection ? { selection } : {}),\n ...(supportedChains ? { supportedChains } : {}),\n ...(preferredTokens && Object.keys(preferredTokens).length > 0\n ? { preferredTokens }\n : {}),\n ...(tokenNetworks && Object.keys(tokenNetworks).length > 0\n ? { tokenNetworks }\n : {}),\n ...(chainRpcUrls && Object.keys(chainRpcUrls).length > 0\n ? { chainRpcUrls }\n : {}),\n ...(solanaChannel ? { solanaChannel } : {}),\n ...(minaChannel ? { minaChannel } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Embedded-client transport config\n// ---------------------------------------------------------------------------\n\n/** The transport slice of the embedded client's config. */\nexport type UplinkConfig = Pick<\n ToonClientConfig,\n | 'proxyUrl'\n | 'connectorUrl'\n | 'btpUrl'\n | 'btpAuthToken'\n | 'preferBtpForPaidWrites'\n>;\n\n/**\n * Turn a resolved topology's uplinks into the client's transport config.\n *\n * `preferBtpForPaidWrites` is the load-bearing part. The client's default\n * precedence is HTTP-first, so a proxy-configured client sends every paid\n * write as a `POST /ilp` one-shot carrying only an unauthenticated\n * `ILP-Peer-Id` header — which the live edge answers `401 Unauthorized:\n * identity 'g.toon.client' failed to authenticate`, making `rig push`\n * impossible. The flag (toon-client#482) swaps the order so claims ride the\n * BTP session, which authenticates on connect; the BTP transport keeps the\n * HTTP one as its OWN fallback, so a proxy+BTP topology loses nothing when\n * BTP is the broken side. Reads, the x402 greeting and the channel open are\n * untouched either way.\n *\n * The client's `requiredTransport` guard (toon-client#558) does not cover\n * this: no live kind:10032 announce carries that field, so the guard never\n * fires against the current fleet.\n */\nexport function resolveUplinkConfig(\n topo: Pick<NetworkTopology, 'proxyUrl' | 'btpUrl'>\n): UplinkConfig {\n return {\n // validateConfig requires connectorUrl OR proxyUrl; with BTP-only\n // config a dummy connectorUrl satisfies it (unused at runtime — same\n // convention as the daemon).\n ...(topo.proxyUrl\n ? { proxyUrl: topo.proxyUrl }\n : { connectorUrl: 'http://127.0.0.1:1' }),\n ...(topo.btpUrl\n ? {\n btpUrl: topo.btpUrl,\n btpAuthToken: '',\n preferBtpForPaidWrites: true,\n }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Bootstrap recovery (#279 cached topology; unreachable derived BTP uplink)\n// ---------------------------------------------------------------------------\n\n/**\n * Whether a start failure came from the BTP uplink itself — the websocket\n * would not open, or would not authenticate.\n *\n * `@toon-protocol/client` throws its own `BtpConnectionError`/`BtpAuthError`\n * for exactly these, but does not export them, so (like the channel\n * introspection in `../standalone/standalone-publisher.ts`) they are matched\n * structurally by `name`. Matching narrowly is the point: this decides\n * whether rig may drop the BTP leg, and a broad match would silently paper\n * over unrelated bootstrap failures.\n */\nexport function isBtpTransportError(err: unknown): boolean {\n const name = err instanceof Error ? err.name : '';\n return name === 'BtpConnectionError' || name === 'BtpAuthError';\n}\n\n/**\n * One registered, single-use bootstrap recovery: whether it could address a\n * given failure, what to tell the operator, and how to rebuild.\n */\nexport interface PublisherRecovery {\n /** True when this recovery could plausibly address `err`. */\n applies: (err: unknown) => boolean;\n /** Warned before the retry — says what is being changed, and why. */\n describe: (err: unknown) => string;\n rebuild: () => Promise<StandalonePublisher>;\n}\n\n/**\n * The bootstrap recoveries a standalone publisher runs with, in order (see\n * {@link TopologyRecoveringPublisher} for the retry contract).\n *\n * The recoveries share one mutable \"topology the inner publisher was built\n * from\", so they COMPOSE: the BTP degrade drops the BTP leg of whatever the\n * cache recovery re-resolved, never of the topology this was called with.\n *\n * @param reresolve present only for a CACHE-sourced publisher (it should\n * invalidate the entry and resolve live); absent means no cache recovery.\n */\nexport function bootstrapRecoveries(args: {\n topology: NetworkTopology;\n buildPublisher: (topo: NetworkTopology) => StandalonePublisher;\n reresolve?: () => Promise<NetworkTopology>;\n}): PublisherRecovery[] {\n let active = args.topology;\n const detail = (err: unknown): string =>\n err instanceof Error ? err.message : String(err);\n const recoveries: PublisherRecovery[] = [];\n\n // #279: a cache-sourced publisher whose bootstrap fails invalidates the\n // entry, re-resolves live (which re-writes the cache), and retries — so a\n // rotated peer endpoint costs one failed attempt, not a broken TTL.\n const reresolve = args.reresolve;\n if (reresolve) {\n recoveries.push({\n applies: () => true,\n describe: (err) =>\n 'rig: bootstrap with the cached network topology failed ' +\n `(${detail(err)}) — invalidating the cache and re-resolving live`,\n rebuild: async () => {\n active = await reresolve();\n return args.buildPublisher(active);\n },\n });\n }\n\n // A DERIVED BTP endpoint is a discovery guess, and the client dials it\n // during start() — so an unreachable one must not stop rig from running.\n // It drops back to the HTTP uplink, which is the behaviour placing the BTP\n // leg otherwise replaces. Gated on {@link isBtpTransportError} so an\n // unrelated bootstrap failure is never papered over, and on\n // `btpUrlDerived` so an operator's explicit `btpUrl` pin is never\n // abandoned behind their back.\n recoveries.push({\n applies: (err) => active.btpUrlDerived === true && isBtpTransportError(err),\n describe: (err) =>\n `rig: the discovered BTP uplink ${active.btpUrl} is unreachable ` +\n `(${detail(err)}) — continuing over the HTTP uplink alone; paid ` +\n 'writes an edge accepts only over BTP will still fail, so pin a ' +\n 'reachable endpoint with `rig entry <url>` if they do',\n rebuild: async () => {\n const { btpUrl: _btpUrl, btpUrlDerived: _derived, ...http } = active;\n active = http;\n return args.buildPublisher(active);\n },\n });\n\n return recoveries;\n}\n\n/** Structural check for a cached {@link NetworkTopology} document. */\nfunction isNetworkTopology(value: unknown): value is NetworkTopology {\n if (typeof value !== 'object' || value === null) return false;\n const t = value as Record<string, unknown>;\n return typeof t['destination'] === 'string' && Array.isArray(t['knownPeers']);\n}\n\n/**\n * Start failures that must NEVER trigger a cache-invalidation retry: they\n * are concurrency/state guards, not topology staleness — retrying would\n * bypass exactly what they protect.\n */\nconst NON_RECOVERABLE_START_ERRORS: ReadonlySet<string> = new Set([\n 'DaemonIdentityConflictError',\n 'StandaloneLockError',\n 'ChannelMapCorruptError',\n // An unfunded Mina fee payer is a wallet-funding problem, not topology\n // staleness — re-resolving the topology cannot fix it, so retrying would\n // just re-run the (already fail-fast) preflight for nothing.\n 'MinaFeePayerUnfundedError',\n]);\n\n/**\n * Publisher wrapper that retries a failed bootstrap (`start`/\n * `startClientOnly`) against a repaired publisher. Each registered\n * {@link PublisherRecovery} fires AT MOST ONCE, in order, and only when it\n * `applies` to the failure, so the retry count is bounded by the number of\n * recoveries — never a loop. Bootstrap failures are pre-payment by\n * construction (start() completes before any claim is signed), so a retry\n * can never double-pay. With no applicable recovery left, the failure passes\n * straight through.\n *\n * Two recoveries are registered by {@link createStandaloneContext}:\n *\n * - #279 cache invalidation: a publisher built from a CACHED topology\n * re-resolves LIVE, so a rotated peer endpoint costs one failed attempt\n * rather than a broken TTL.\n * - Unreachable DERIVED BTP uplink: discovery only guesses the peer's BTP\n * ingress, and the client dials it during `start()` — so a dead endpoint\n * would otherwise stop rig from running at all, which is strictly worse\n * than the HTTP-only behaviour it replaced. The BTP leg is dropped and\n * the run proceeds over HTTP. Never for an EXPLICIT `btpUrl`: that is an\n * operator's pin, and quietly abandoning it would hide a real\n * misconfiguration.\n */\nclass TopologyRecoveringPublisher implements Publisher {\n private inner: StandalonePublisher;\n private readonly recoveries: PublisherRecovery[];\n private readonly warn: (line: string) => void;\n\n constructor(\n inner: StandalonePublisher,\n recoveries: PublisherRecovery[],\n warn: (line: string) => void\n ) {\n this.inner = inner;\n this.recoveries = [...recoveries];\n this.warn = warn;\n }\n\n getPublicKey(): string {\n return this.inner.getPublicKey();\n }\n\n getFeeRates(): Promise<FeeRates> {\n return this.inner.getFeeRates();\n }\n\n /**\n * Run the bootstrap step, retrying against a repaired publisher for as long\n * as an unused recovery applies. The LAST attempt's error is what surfaces\n * — after a live re-resolution or an HTTP-only rebuild that is the\n * network's real state, strictly more actionable than the failure that\n * triggered the retry.\n */\n private async ensure(\n start: (p: StandalonePublisher) => Promise<void>\n ): Promise<StandalonePublisher> {\n for (;;) {\n try {\n await start(this.inner);\n return this.inner;\n } catch (err) {\n const name = err instanceof Error ? err.name : '';\n if (NON_RECOVERABLE_START_ERRORS.has(name)) throw err;\n // First applicable recovery wins, and is consumed either way — the\n // loop is bounded by the number registered.\n const index = this.recoveries.findIndex((r) => r.applies(err));\n if (index < 0) throw err;\n const [recovery] = this.recoveries.splice(index, 1) as [\n PublisherRecovery,\n ];\n this.warn(recovery.describe(err));\n const fresh = await recovery.rebuild();\n try {\n // Failed starts release their own lock/state; stop() is idempotent.\n await this.inner.stop();\n } catch {\n // best-effort teardown of the abandoned publisher\n }\n this.inner = fresh;\n }\n }\n }\n\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.publishEvent(event, relayUrls);\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.uploadGitObject(upload);\n }\n\n async uploadBlob(upload: BlobUpload): Promise<UploadReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.uploadBlob(upload);\n }\n\n // ── money lifecycle passthroughs (#263) — same recovery contract ─────────\n\n async openChannelExplicit(\n opts?: Parameters<StandalonePublisher['openChannelExplicit']>[0]\n ): ReturnType<StandalonePublisher['openChannelExplicit']> {\n const p = await this.ensure((x) => x.start());\n return p.openChannelExplicit(opts);\n }\n\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['closeRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.closeRecordedChannel(record);\n }\n\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['settleRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.settleRecordedChannel(record);\n }\n\n /** Free read on the unstarted client — no bootstrap, no recovery needed. */\n readWalletChainBalances(\n ...args: Parameters<StandalonePublisher['readWalletChainBalances']>\n ): ReturnType<StandalonePublisher['readWalletChainBalances']> {\n return this.inner.readWalletChainBalances(...args);\n }\n\n async stop(): Promise<void> {\n await this.inner.stop();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context factory\n// ---------------------------------------------------------------------------\n\n/**\n * #262 channel-map records for this identity, reduced to the slice chain\n * selection consumes (`closed` folds in the claim-watermark timers).\n */\nfunction chainRecordsFor(\n map: ChannelMapStore,\n identity: string\n): ChannelRecordLike[] {\n return map\n .list()\n .filter((r) => r.identity === identity)\n .map((r) => {\n const watermark = map.readWatermark(r.channelId);\n return {\n chain: r.chain,\n lastUsedAt: r.lastUsedAt,\n closed:\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined,\n };\n });\n}\n\n/**\n * Assemble an embedded-client standalone context: resolved identity + config\n * → network bootstrap (announce discovery / genesis seed) → ToonClientConfig\n * → nonce-guarded StandalonePublisher (guard + client start + channel open\n * happen lazily on the first paid call, or eagerly via the publisher's own\n * `start`).\n */\nexport async function createStandaloneContext(\n options: StandaloneLoadOptions\n): Promise<StandaloneContext> {\n const { env } = options;\n const warn = (line: string) => options.warn(line);\n const dir = configDir(env);\n const configPath = join(dir, 'config.json');\n const file = readClientConfig(configPath);\n const identity = await resolveIdentity(options);\n\n // Force-standalone override (RIG_STANDALONE / `rig --standalone`): when set,\n // the embedded publisher skips the same-identity daemon refusal so a running\n // toon-clientd never blocks the standalone path. The per-identity NonceLock\n // still guards against two standalone processes racing the claim watermark.\n const skipDaemonCheck = standaloneForced(env);\n if (skipDaemonCheck) {\n warn(\n 'rig: force-standalone (RIG_STANDALONE) — bypassing any running ' +\n 'toon-clientd; this rig process signs its own claims (stop the daemon ' +\n 'if it is also writing on this identity)'\n );\n }\n\n const genesisSeed = loadGenesisSeed();\n\n // ── Relay-origin ──────────────────────────────────────────────────────────\n // The relay the paid command resolved via `rig remote` (passed by the\n // command) is the user's clearest network statement; env/file follow, and\n // the genesis seed's relay is the out-of-the-box fallback.\n const relayUrl =\n options.relayUrl ??\n env['TOON_CLIENT_RELAY_URL'] ??\n file.relayUrl ??\n genesisSeed?.relayUrl ??\n // Last resort: the genesis seed normally supplies the relay, so this is\n // only reached when no seed/config/env relay is present. Point at the\n // shared public devnet relay rather than a dead local default.\n 'wss://relay-ws.devnet.toonprotocol.dev';\n\n // ── Peer→channel persistence (#262) ──────────────────────────────────────\n const channelStorePath = file.channelStorePath ?? join(dir, 'channels.json');\n // Sibling watermark file for the store leg's client — see the\n // `channelStorePath` note where the store client config is built.\n const storeChannelStorePath = channelStorePath.replace(\n /(\\.json)?$/,\n '.store.json'\n );\n const channelMap = new ChannelMapStore({\n mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),\n watermarkPath: channelStorePath,\n });\n\n // ── Topology cache (#279): keyed by relay-origin + identity + explicit\n // config; a hit skips discovery AND the pure-but-probing resolution below.\n const cache = new TopologyCache<NetworkTopology>({\n path: join(dir, TOPOLOGY_CACHE_FILENAME),\n ttlMs: topologyCacheTtlMs(env),\n validate: isNetworkTopology,\n });\n const cacheKey = topologyCacheKey({\n relayUrl,\n identity: identity.pubkey,\n fingerprint: explicitConfigFingerprint(\n env,\n file as Record<string, unknown>\n ),\n });\n\n // ── Operator notices (#78): seen-id persistence, sibling to the channel\n // map and topology cache above.\n const noticeStore = new NoticeStore({\n path: join(dir, NOTICE_STORE_FILENAME),\n });\n\n const resolveLiveTopology = async (): Promise<NetworkTopology> => {\n // ── Live announce discovery ────────────────────────────────────────────\n // Skipped when explicit config already pins the whole payment topology\n // (fully-configured setups keep their zero-roundtrip start and their\n // exact pre-#264 behavior). A listed `solana:*` chain WITHOUT explicit\n // channel parameters is not fully pinned: the announce is a needed\n // parameter source (program id / mint), and skipping discovery would\n // also skip the announced-spelling alignment that keeps the chain list\n // negotiable. Discovery failure is non-fatal: warn + genesis seed.\n const solanaExplicitlyComplete = (file.supportedChains ?? []).every(\n (chain) =>\n !chain.startsWith('solana:') ||\n Boolean(file.solanaChannel) ||\n Boolean(\n file.tokenNetworks?.[chain] &&\n file.preferredTokens?.[chain] &&\n file.chainRpcUrls?.[chain]\n )\n );\n // A listed `mina:*` chain WITHOUT an explicit `minaChannel` is not fully\n // pinned either: the announce is the authoritative source for the\n // deployment's zkApp (and token id), so discovery must run. Only an\n // explicit `minaChannel` config makes a Mina chain announce-independent.\n const minaExplicitlyComplete = (file.supportedChains ?? []).every(\n (chain) => !chain.startsWith('mina:') || Boolean(file.minaChannel)\n );\n // A config that pins a store route on a DIFFERENT node than it publishes\n // to is not fully explicit unless it also pins that node's uplink. The\n // store node holds its own channel, and a claim signed on the publish\n // channel is refused by it (F01) — so without a store uplink the store\n // route is unreachable, and only its own announce publishes one. Pinning\n // the publish transport says nothing about how to reach the store.\n const explicitStoreDest =\n env['TOON_CLIENT_STORE_DESTINATION'] ?? file.storeDestination;\n const explicitPublishDest =\n env['TOON_CLIENT_PUBLISH_DESTINATION'] ?? file.publishDestination;\n const storeUplinkKnown =\n !explicitStoreDest ||\n explicitStoreDest === explicitPublishDest ||\n Boolean(env['TOON_CLIENT_STORE_BTP_URL'] ?? file.storeBtpUrl);\n const fullyExplicit =\n Boolean(\n (env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl) ||\n (env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl)\n ) &&\n Boolean(env['TOON_CLIENT_DESTINATION'] ?? file.destination) &&\n Boolean(file.supportedChains?.length) &&\n storeUplinkKnown &&\n solanaExplicitlyComplete &&\n minaExplicitlyComplete;\n let announce: AnnouncedPeer | undefined;\n // Retained beyond the pick: the store node announces itself\n // separately, and only its own announce carries its BTP ingress.\n let discoveredPeers: AnnouncedPeer[] | undefined;\n if (!fullyExplicit) {\n try {\n const peers = await discoverAnnouncedPeers(relayUrl, {\n timeoutMs: DISCOVERY_TIMEOUT_MS,\n });\n discoveredPeers = peers;\n const seedPubkeys = genesisSeedPubkeys();\n announce = pickPaymentPeer(peers, seedPubkeys);\n if (!announce) {\n warn(\n `rig: no payment-peer announce (kind:10032) found on ${relayUrl} — ` +\n 'falling back to the genesis peer seed'\n );\n }\n // #78: prints once per notice id, and only when the picked payment\n // peer is a trusted (genesis-seed) announce — a no-op otherwise. No\n // extra round trip: this reuses the discovery above.\n showOperatorNoticeOnce(announce, seedPubkeys, noticeStore, warn);\n } catch (err) {\n warn(\n `rig: announce discovery on ${relayUrl} failed ` +\n `(${err instanceof Error ? err.message : String(err)}) — falling ` +\n 'back to the genesis peer seed'\n );\n }\n }\n\n // ── Topology resolution (pure; explicit > announce > genesis) ──────────\n const resolved = await resolveNetworkTopology({\n env,\n file,\n configPath,\n relayUrl,\n announce,\n ...(discoveredPeers ? { peers: discoveredPeers } : {}),\n genesisSeed,\n identity: {\n mnemonic: identity.mnemonic,\n accountIndex: identity.accountIndex,\n pubkey: identity.pubkey,\n },\n channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),\n ...(options.requireUplink !== undefined\n ? { requireUplink: options.requireUplink }\n : {}),\n warn,\n });\n // Cache only paid-path resolutions: a `requireUplink: false` free read\n // may resolve WITHOUT an uplink, and caching that would let a later paid\n // command skip past MissingUplinkError with a broken topology.\n if (options.requireUplink !== false) cache.write(cacheKey, resolved);\n return resolved;\n };\n\n const cached = cache.read(cacheKey);\n if (cached) {\n warn(\n `rig: network topology from cache (${Math.round(cached.ageMs / 1000)}s ` +\n `old; ${TOPOLOGY_TTL_ENV}=0 disables) — skipping announce discovery`\n );\n }\n const topology = cached?.topology ?? (await resolveLiveTopology());\n\n const eventFee = BigInt(file.feePerEvent ?? '1');\n\n const buildPublisher = (topo: NetworkTopology): StandalonePublisher => {\n const clientConfig: ToonClientConfig = {\n ...resolveUplinkConfig(topo),\n mnemonic: identity.mnemonic,\n mnemonicAccountIndex: identity.accountIndex,\n ilpInfo: {\n pubkey: '00'.repeat(32),\n ilpAddress: 'g.toon.client',\n btpEndpoint: topo.btpUrl ?? '',\n assetCode: 'USD',\n assetScale: 6,\n },\n toonEncoder: encodeEventToToon,\n toonDecoder: decodeEventFromToon,\n destinationAddress: topo.destination,\n // The embedded client bootstraps against the known peer above; its\n // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.\n relayUrl: '',\n knownPeers: topo.knownPeers,\n channelStorePath,\n ...(topo.supportedChains\n ? { supportedChains: topo.supportedChains }\n : {}),\n ...(file.settlementAddresses\n ? { settlementAddresses: file.settlementAddresses }\n : {}),\n ...(topo.preferredTokens\n ? { preferredTokens: topo.preferredTokens }\n : {}),\n ...(topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {}),\n ...(topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {}),\n // Solana channel params: an explicit config object wins verbatim; else\n // the topology's derivation (announce/presets — see resolveNetworkTopology).\n ...(file.solanaChannel\n ? { solanaChannel: file.solanaChannel }\n : topo.solanaChannel\n ? { solanaChannel: topo.solanaChannel }\n : {}),\n // Mina channel params: an explicit config object wins verbatim; else the\n // topology's derivation (announce zkApp/tokenId + preset graphqlUrl —\n // see resolveNetworkTopology / deriveMinaChannel).\n // An explicit config object keeps today's pin-a-zkApp semantics\n // verbatim. The DERIVED path additionally gets `autoDeploy`: the\n // announce/preset zkApp is shared and single-pair, so a fresh identity\n // can never open on it — the client checks on-chain ownership and\n // deploys a dedicated per-pair zkApp when needed, persisting the key\n // through the rig zkApp store (losing it would strand ~1.1 MINA).\n ...(file.minaChannel\n ? { minaChannel: file.minaChannel }\n : topo.minaChannel\n ? {\n minaChannel: {\n ...topo.minaChannel,\n autoDeploy: buildMinaAutoDeploy(\n dir,\n identity.pubkey,\n `mina:${topo.minaChannel.networkId ?? 'devnet'}`,\n warn\n ),\n },\n }\n : {}),\n };\n\n // Store leg (#96 follow-up): when the store route terminates on its own\n // node, it needs its own uplink and its own channel — a claim signed on\n // the publish channel is refused by the store connector, which has no\n // record of it (F01). Same identity and same channel store; only the\n // transport and the destination differ, so the two legs stay one wallet.\n const storeClientConfig: ToonClientConfig | undefined = topo.storeBtpUrl\n ? {\n ...clientConfig,\n proxyUrl: undefined,\n connectorUrl: 'http://127.0.0.1:1',\n btpUrl: topo.storeBtpUrl,\n btpAuthToken: '',\n preferBtpForPaidWrites: true,\n // Its OWN watermark file. A client's channel store and its\n // `.peers.json` binding sidecar are one unit that the client\n // rewrites wholesale, so pointing two live clients at one path has\n // them clobber each other's watermarks — observed as the publish\n // leg losing its channel (\"is not being tracked\", then F01) the\n // moment the store leg opened. Separate files; the legs stay\n // correlated through rig's own channel map, which is keyed by\n // anchor and records both.\n channelStorePath: storeChannelStorePath,\n ilpInfo: {\n ...clientConfig.ilpInfo,\n btpEndpoint: topo.storeBtpUrl,\n },\n destinationAddress: topo.storeDestination ?? topo.destination,\n }\n : undefined;\n\n return new StandalonePublisher({\n clientConfig,\n ...(storeClientConfig ? { storeClientConfig } : {}),\n eventFee,\n channelMap,\n warn,\n ...(skipDaemonCheck ? { skipDaemonCheck: true } : {}),\n ...(topo.publishDestination\n ? { publishDestination: topo.publishDestination }\n : {}),\n ...(topo.storeDestination\n ? { storeDestination: topo.storeDestination }\n : {}),\n // Route-price floors (F06): every per-packet claim must advance the\n // channel by at least the destination route's announced price.\n ...(topo.routePrices\n ? {\n routePrices: {\n ...(topo.routePrices.publish !== undefined\n ? { publish: BigInt(topo.routePrices.publish) }\n : {}),\n ...(topo.routePrices.store !== undefined\n ? { store: BigInt(topo.routePrices.store) }\n : {}),\n },\n }\n : {}),\n // `rig channel open --peer` (#263): anchor the channel (and its map\n // key) to an explicit peer destination instead of the configured\n // default.\n ...(options.channelDestination\n ? { channelDestination: options.channelDestination }\n : {}),\n // The peer's announce does not carry TokenNetwork/token parameters, so\n // the client's negotiation leaves them empty (#260 root cause 3) — the\n // publisher back-fills them from the derived per-chain maps before the\n // channel opens.\n ...(topo.tokenNetworks || topo.preferredTokens\n ? {\n negotiationFallbacks: {\n ...(topo.tokenNetworks\n ? { tokenNetworks: topo.tokenNetworks }\n : {}),\n ...(topo.preferredTokens\n ? { preferredTokens: topo.preferredTokens }\n : {}),\n },\n }\n : {}),\n });\n };\n\n const publisher = new TopologyRecoveringPublisher(\n buildPublisher(topology),\n bootstrapRecoveries({\n topology,\n buildPublisher,\n // Only a CACHE-sourced publisher gets the re-resolve recovery;\n // live-resolved ones have nothing staler to fall back from.\n ...(cached\n ? {\n reresolve: async () => {\n cache.invalidate(cacheKey);\n return resolveLiveTopology();\n },\n }\n : {}),\n }),\n warn\n );\n\n // Wallet-view preset defaults (#299): when the identity has no configured\n // Solana/Mina channel (the common single-EVM-chain case), `rig balance` still\n // shows those chains — 0 for an account not yet on-chain — by reading the\n // named network's public RPC/GraphQL. Resolved HERE and passed only to the\n // wallet-view read, NOT merged into the client's settlement config: setting\n // `network` there would expand `supportedChains` via applyNetworkPresets and\n // change chain negotiation, which this deliberately avoids.\n const walletViewNetwork = env['TOON_CLIENT_NETWORK'] ?? file.network;\n let walletViewFallback: WalletViewFallback | undefined;\n if (\n walletViewNetwork === 'devnet' ||\n walletViewNetwork === 'testnet' ||\n walletViewNetwork === 'mainnet'\n ) {\n const presets = resolveClientNetwork(walletViewNetwork);\n walletViewFallback = {\n ...(presets.solanaChannel\n ? { solanaChannel: presets.solanaChannel }\n : {}),\n ...(presets.minaChannel ? { minaChannel: presets.minaChannel } : {}),\n };\n }\n\n return {\n ownerPubkey: publisher.getPublicKey(),\n identitySource: identity.source,\n identitySourceLabel: identity.sourceLabel,\n publisher,\n defaultRelayUrls: [relayUrl],\n fetchRemote: (args) => fetchRemoteState(args),\n // Money lifecycle (#263): same guard/start/channel-map machinery as the\n // paid-write path, surfaced for fund/balance/channel open|close|settle.\n money: {\n openChannel: (opts) => publisher.openChannelExplicit(opts),\n closeChannel: (record) => publisher.closeRecordedChannel(record),\n settleChannel: (record) => publisher.settleRecordedChannel(record),\n walletChainBalances: () =>\n publisher.readWalletChainBalances(walletViewFallback),\n },\n stop: () => publisher.stop(),\n };\n}\n","/**\n * Operator notices (#78, part of toon-protocol/toon-meta#252): a kind:10032\n * announce's optional `notice` field (toon-protocol/toon#183) is rig's one\n * delivery channel to a human running this client — printed once per `id`,\n * ever, never once per run. Only an announce authored by a committed\n * genesis-seed pubkey (`genesisSeedPubkeys()` in `./network-bootstrap.ts`)\n * is trusted: anyone can publish a kind:10032, so an untrusted announcer's\n * notice — and its operator-controlled `url` — is never shown or fetched.\n *\n * BLOCKED ON A PUBLISH, NOT ON THIS TICKET. `IlpPeerInfo.notice` already\n * exists on `@toon-protocol/core`'s `main` (toon#183, closed), but no\n * published core version carries it yet: toon#184 (closed) landed the\n * release changeset but not the actual `npm publish` — registry `latest`\n * was still 3.3.0 as of 2026-08-12, and `parseIlpPeerInfo` there builds its\n * result field by field, so an announce's `notice` is dropped on the way\n * in. `AnnouncedPeer.info.notice` is therefore always `undefined` today\n * (see the widening note in `./network-bootstrap.ts`) and nothing below can\n * be exercised end-to-end against a real relay yet — the tests construct\n * `AnnouncedPeer` fixtures directly, the same way `pickPaymentPeer`'s own\n * tests do. Turning it on once core publishes takes a dependency bump\n * (`pnpm update @toon-protocol/core`; the `^3.2.0` range already accepts a\n * 3.4.x, but the lockfile pins 3.2.0) and no rig code changes.\n *\n * Seen-notice ids persist next to rig's other client state under\n * `TOON_CLIENT_HOME`, sibling to the channel map (`rig-channels.json`) and\n * topology cache (`rig-topology-cache.json`) — see `NoticeStore` below.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { AnnouncedPeer } from './network-bootstrap.js';\n\n/** Notice-store filename under the shared client state dir (`TOON_CLIENT_HOME`). */\nexport const NOTICE_STORE_FILENAME = 'rig-seen-notices.json';\n\n/** A validated, normalized `IlpPeerInfo.notice` (toon#183). */\nexport interface OperatorNotice {\n id: string;\n severity: 'info' | 'action-required';\n summary: string;\n url: string;\n}\n\n/**\n * Validate + normalize a raw `notice` value. Missing/wrong-typed\n * `id`/`summary`/`url` drops the notice entirely (`undefined`, and bootstrap\n * proceeds unaffected); an unrecognized `severity` degrades to `'info'`\n * rather than being rejected — mirrors core's own lenient-parsing contract\n * (toon#183) so an operator's typo can never cost this client its ability\n * to route. Unknown keys are ignored.\n */\nexport function normalizeNotice(value: unknown): OperatorNotice | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const { id, severity, summary, url } = value as Record<string, unknown>;\n if (typeof id !== 'string' || id.length === 0) return undefined;\n if (typeof summary !== 'string' || summary.length === 0) return undefined;\n if (typeof url !== 'string' || url.length === 0) return undefined;\n return {\n id,\n summary,\n url,\n severity: severity === 'action-required' ? 'action-required' : 'info',\n };\n}\n\n/**\n * The notice to trust from a bootstrap's chosen payment peer, or\n * `undefined`. `peer` is expected to be `pickPaymentPeer`'s result, which\n * already prefers an announce authored by a genesis-seed pubkey over any\n * other — so this is exactly \"is the payment peer we bootstrapped against a\n * seed peer\", not a second, independent selection (per the issue: reuse the\n * existing preference rather than inventing a new one). An announce from\n * any other pubkey never reaches `normalizeNotice`, regardless of what its\n * `notice` field contains.\n */\nexport function trustedNoticeFrom(\n peer: AnnouncedPeer | undefined,\n seedPubkeys: readonly string[]\n): OperatorNotice | undefined {\n if (!peer || !seedPubkeys.includes(peer.pubkey)) return undefined;\n return normalizeNotice(peer.info.notice);\n}\n\nconst ACTION_REQUIRED_BORDER = '='.repeat(70);\n\n/**\n * Render a notice for stderr. `url` is printed, never fetched — rendering\n * is the full extent of what rig does with it. `action-required` gets a\n * bordered, blank-line-padded block so it cannot be mistaken for routine\n * bootstrap chatter or scrolled past unnoticed; `info` stays a single line.\n */\nexport function formatNoticeLines(notice: OperatorNotice): string[] {\n if (notice.severity === 'action-required') {\n return [\n '',\n ACTION_REQUIRED_BORDER,\n `rig: ACTION REQUIRED — ${notice.summary}`,\n `rig: ${notice.url}`,\n ACTION_REQUIRED_BORDER,\n '',\n ];\n }\n return [`rig: notice — ${notice.summary} (${notice.url})`];\n}\n\ninterface NoticeStoreFile {\n version: 1;\n seenIds: string[];\n}\n\n/**\n * File-backed set of notice ids already shown (#78's \"once per id, ever,\n * across separate process runs\"). Mirrors `TopologyCache`'s best-effort\n * contract (`./topology-cache.ts`): a corrupt or unreadable file degrades to\n * \"nothing seen\" — the notice reprints — rather than a crash or, worse,\n * permanent silent suppression from a file the store can no longer read.\n */\nexport class NoticeStore {\n private readonly path: string;\n\n constructor(options: { path: string }) {\n this.path = options.path;\n }\n\n hasSeen(id: string): boolean {\n return this.readFile().seenIds.includes(id);\n }\n\n /** Best-effort; a failed write just means the notice reprints next run. */\n markSeen(id: string): void {\n const file = this.readFile();\n if (file.seenIds.includes(id)) return;\n try {\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(\n this.path,\n JSON.stringify(\n {\n version: 1,\n seenIds: [...file.seenIds, id],\n } satisfies NoticeStoreFile,\n null,\n 2\n ),\n { mode: 0o600 }\n );\n } catch {\n // Best-effort — see module doc.\n }\n }\n\n private readFile(): NoticeStoreFile {\n const empty: NoticeStoreFile = { version: 1, seenIds: [] };\n let raw: string;\n try {\n raw = readFileSync(this.path, 'utf8');\n } catch {\n return empty;\n }\n try {\n const parsed = JSON.parse(raw) as NoticeStoreFile;\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n parsed.version !== 1 ||\n !Array.isArray(parsed.seenIds) ||\n !parsed.seenIds.every((x) => typeof x === 'string')\n ) {\n return empty;\n }\n return parsed;\n } catch {\n return empty; // corrupt store = \"nothing seen\", never an error\n }\n }\n}\n\n/**\n * Show `peer`'s notice on stderr (via `warn`) and persist its id — but only\n * once, ever, and only when `peer` is a trusted (genesis-seed) announce. A\n * no-op when there is no peer, no notice, an untrusted announcer, or the id\n * has already been shown. Called from every bootstrapping command's single\n * announce-discovery point (`createStandaloneContext`), never on its own —\n * it costs no extra round trip beyond the discovery that already happened.\n */\nexport function showOperatorNoticeOnce(\n peer: AnnouncedPeer | undefined,\n seedPubkeys: readonly string[],\n store: NoticeStore,\n warn: (line: string) => void\n): void {\n const notice = trustedNoticeFrom(peer, seedPubkeys);\n if (!notice || store.hasSeen(notice.id)) return;\n for (const line of formatNoticeLines(notice)) warn(line);\n store.markSeen(notice.id);\n}\n"],"mappings":"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","names":["readFileSync","readFileSync"]}