@toon-protocol/client-mcp 0.20.3 → 0.20.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LRHUY3PO.js → chunk-5EUJWW4T.js} +508 -4125
- package/dist/chunk-5EUJWW4T.js.map +1 -0
- package/dist/{chunk-LN2OF264.js → chunk-6GXZ4KRO.js} +257 -1552
- package/dist/chunk-6GXZ4KRO.js.map +1 -0
- package/dist/chunk-7ZUHCA5C.js +3950 -0
- package/dist/chunk-7ZUHCA5C.js.map +1 -0
- package/dist/{chunk-UITLRZ7O.js → chunk-JSL755E2.js} +2 -2
- package/dist/chunk-V7D5HJBT.js +1363 -0
- package/dist/chunk-V7D5HJBT.js.map +1 -0
- package/dist/{chunk-R3KZIPBQ.js → chunk-VXMZCGAT.js} +8 -6
- package/dist/{chunk-R3KZIPBQ.js.map → chunk-VXMZCGAT.js.map} +1 -1
- package/dist/daemon.js +5 -3
- package/dist/daemon.js.map +1 -1
- package/dist/{ed25519-VBPL32VX.js → ed25519-U6LNJH4K.js} +3 -2
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +5 -3
- package/dist/mcp.js.map +1 -1
- package/dist/mina-channel-deploy-5GPMFBGR-AFX3VUXM.js +12 -0
- package/dist/mina-channel-deploy-5GPMFBGR-AFX3VUXM.js.map +1 -0
- package/package.json +4 -4
- package/dist/chunk-LN2OF264.js.map +0 -1
- package/dist/chunk-LRHUY3PO.js.map +0 -1
- /package/dist/{chunk-UITLRZ7O.js.map → chunk-JSL755E2.js.map} +0 -0
- /package/dist/{ed25519-VBPL32VX.js.map → ed25519-U6LNJH4K.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon/first-run.ts","../src/relay-subscription.ts","../src/daemon/client-runner.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/constants.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/types.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/ForgejoClient.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/NIP34Handler.ts","../../rig/src/objects.ts","../../rig/src/publisher.ts","../../rig/src/nip34-events.ts","../../rig/src/remote-state.ts","../../arweave/src/gateways.ts","../../arweave/src/git-sha.ts","../../rig/src/repo-reader.ts","../../rig/src/object-fetch.ts","../../rig/src/read-pipeline.ts","../../rig/src/materialize.ts","../../rig/src/npub.ts","../../rig/src/routes.ts","../../rig/src/push.ts","../src/daemon/apex-channel-store.ts","../src/daemon/targets-store.ts","../src/daemon/apex-discovery.ts","../src/daemon/routes.ts"],"sourcesContent":["/**\n * First-run onboarding for `toon-clientd`.\n *\n * A brand-new user (`npx`/plugin install) has no identity and no config file.\n * Before the daemon resolves its config it calls {@link scaffoldFirstRun},\n * which makes a fresh install start with zero manual setup:\n *\n * 1. **Identity** — if no mnemonic source is configured (no\n * `TOON_CLIENT_MNEMONIC`, no `keystorePath`, no `mnemonic`), generate a\n * fresh BIP-39 mnemonic, encrypt it to `~/.toon-client/keystore.json`, and\n * record `keystorePath` (+ `keystoreAutoPassword`) in `config.json`. The\n * keystore is encrypted with `TOON_CLIENT_KEYSTORE_PASSWORD` when set, else\n * a default password so the identity survives restarts with no env var.\n * The mnemonic + derived addresses are printed ONCE for backup.\n * 2. **Transport scaffolding** — ensure `config.json` carries the transport\n * knobs (`btpUrl`/`relayUrl`) plus a `_help` block documenting them, so the\n * user can see what to point at.\n *\n * This is all idempotent: on later runs an identity already exists, so nothing\n * is regenerated and the config is left untouched.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { deriveFullIdentity, generateKeystore } from '@toon-protocol/client';\nimport {\n configDir,\n defaultConfigPath,\n readConfigFile,\n DEFAULT_KEYSTORE_PASSWORD,\n type DaemonConfigFile,\n} from './config.js';\n\n/** Default keystore path: `~/.toon-client/keystore.json`. */\nexport function defaultKeystorePath(): string {\n return join(configDir(), 'keystore.json');\n}\n\n/**\n * True when SOME mnemonic source is already configured: the env var, a keystore\n * path, or an inline mnemonic. When false the daemon would otherwise hard-fail\n * with \"No mnemonic configured\".\n */\nexport function hasConfiguredIdentity(file: DaemonConfigFile): boolean {\n return Boolean(\n process.env['TOON_CLIENT_MNEMONIC']?.trim() ||\n file.keystorePath ||\n file.mnemonic\n );\n}\n\n/** The `_help` block written into a scaffolded config to document transport. */\nconst CONFIG_HELP = {\n transport:\n 'Configure ONE uplink for paid writes: either `proxyUrl` (connector ' +\n 'payment-proxy over ILP-over-HTTP, e.g. https://proxy.devnet.toonprotocol.dev) ' +\n 'OR `btpUrl` (BTP WebSocket, e.g. ws://<host>:3000/btp). Reads are always ' +\n 'free over relayUrl.',\n proxyUrl:\n 'Connector-proxy base URL (deployed devnet/testnet edge). When set, paid ' +\n 'writes route through `POST /ilp` and no BTP socket is needed. Set ' +\n '`destination` to the apex ILP address (e.g. g.proxy for devnet).',\n faucetUrl:\n 'Devnet faucet base URL (e.g. https://faucet.devnet.toonprotocol.dev) used ' +\n 'to drip test funds before publishing.',\n btpUrl:\n 'BTP WebSocket URL of the apex/connector for paid writes. Optional when ' +\n '`proxyUrl` is set.',\n relayUrl:\n 'Relay WebSocket URL for FREE reads. Default ws://localhost:7100.',\n destination:\n 'Default ILP publish destination (apex address). Default g.proxy.',\n keystorePath:\n 'Auto-generated encrypted identity. Do not hand-edit; back up your seed phrase.',\n};\n\ninterface ScaffoldOptions {\n /** Config file path (defaults to `TOON_CLIENT_CONFIG` / `~/.toon-client/config.json`). */\n configPath?: string;\n /** Log sink (defaults to stderr, since stdout may carry MCP/JSON). */\n log?: (msg: string) => void;\n}\n\n/**\n * Generate + persist an identity and/or scaffold transport config on first run.\n * Safe to call on every startup — it only acts when something is missing.\n */\nexport async function scaffoldFirstRun(\n opts: ScaffoldOptions = {}\n): Promise<void> {\n const log = opts.log ?? ((m: string): void => console.error(m));\n const configPath =\n opts.configPath ?? process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath();\n\n // Ensure the config directory exists before writing the keystore/config.\n mkdirSync(dirname(configPath), { recursive: true });\n\n const file = readConfigFile(configPath);\n let updated: DaemonConfigFile = { ...file };\n let changed = false;\n\n // --- 1. Identity ----------------------------------------------------------\n if (!hasConfiguredIdentity(file)) {\n const keystorePath = defaultKeystorePath();\n const envPassword = process.env['TOON_CLIENT_KEYSTORE_PASSWORD'];\n const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;\n\n // Reuse an existing keystore file if one is already on disk (e.g. config\n // was deleted but the keystore survived); otherwise mint a new mnemonic.\n let mnemonic: string;\n if (existsSync(keystorePath)) {\n // Leave the existing keystore untouched; just relink it in config.\n mnemonic = '';\n log(\n `[toon-clientd] first run: relinking existing keystore at ${keystorePath}`\n );\n } else {\n const generated = generateKeystore(keystorePath, password);\n mnemonic = generated.mnemonic;\n }\n\n updated = {\n ...updated,\n keystorePath,\n // Only flag auto-password when WE chose it, so a user-imported keystore\n // (custom password) still requires the env var.\n ...(envPassword ? {} : { keystoreAutoPassword: true }),\n };\n changed = true;\n\n if (mnemonic) {\n await printNewIdentity(mnemonic, keystorePath, Boolean(envPassword), log);\n }\n }\n\n // --- 2. Transport scaffolding --------------------------------------------\n if (!existsSync(configPath)) {\n // Fresh install: surface the transport knobs with guidance.\n updated = {\n _help: CONFIG_HELP,\n proxyUrl: '',\n btpUrl: '',\n relayUrl: 'ws://localhost:7100',\n ...updated,\n } as DaemonConfigFile;\n changed = true;\n log(\n `[toon-clientd] wrote starter config at ${configPath} — set \"proxyUrl\" (connector proxy, ILP-over-HTTP) or \"btpUrl\" (BTP) to your apex before publishing.`\n );\n }\n\n if (changed) writeConfigFile(configPath, updated);\n}\n\n/** Derive + print the new identity's addresses and a one-time backup notice. */\nasync function printNewIdentity(\n mnemonic: string,\n keystorePath: string,\n hasEnvPassword: boolean,\n log: (msg: string) => void\n): Promise<void> {\n const id = await deriveFullIdentity(mnemonic);\n const lines = [\n '',\n '════════════════════════════════════════════════════════════════',\n ' TOON client: generated a new identity (first run)',\n '════════════════════════════════════════════════════════════════',\n ` Nostr pubkey : ${id.nostr.pubkey}`,\n ` EVM address : ${id.evm.address}`,\n ...(id.solana.publicKey ? [` Solana : ${id.solana.publicKey}`] : []),\n ...(id.mina.publicKey ? [` Mina : ${id.mina.publicKey}`] : []),\n '',\n ' Seed phrase (BACK THIS UP — shown only once):',\n ` ${mnemonic}`,\n '',\n ` Encrypted keystore: ${keystorePath}`,\n hasEnvPassword\n ? ' Encrypted with TOON_CLIENT_KEYSTORE_PASSWORD.'\n : ' Encrypted with the default password (set TOON_CLIENT_KEYSTORE_PASSWORD\\n' +\n ' + re-import to use your own). Identity reloads automatically on restart.',\n '════════════════════════════════════════════════════════════════',\n '',\n ];\n log(lines.join('\\n'));\n}\n\n/** Write the config file as pretty JSON with mode 0o600. */\nfunction writeConfigFile(path: string, file: DaemonConfigFile): void {\n writeFileSync(path, JSON.stringify(file, null, 2) + '\\n', {\n encoding: 'utf8',\n mode: 0o600,\n });\n}\n","/**\n * Persistent relay Nostr-WS subscription — the read half of the TOON\n * client, which `@toon-protocol/client` does not provide (its bootstrap only\n * issues one-shot WS queries and `DiscoveryTracker` is passive).\n *\n * Reads are FREE: this opens a long-lived NIP-01 connection to the relay,\n * keeps a bounded ring buffer of received events (de-duplicated by `event.id`),\n * and lets callers drain new events via a monotonic cursor. It auto-reconnects\n * with exponential backoff and re-issues every active REQ on reconnect.\n *\n * The WebSocket is injectable (`wsFactory`) so unit tests can drive the wire\n * protocol without a real relay; the default factory uses the `ws` package.\n */\n\nimport { createRequire } from 'node:module';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { NostrFilter } from './control-api.js';\n\n// ESM has no `require`; synchronously load the node-only `ws` dep for the default\n// WebSocket factory without forcing every caller (e.g. the browser-style\n// injected-factory path in tests) to pull it in.\nconst nodeRequire = createRequire(import.meta.url);\n\n/** Minimal WebSocket surface this module depends on (subset of `ws`). */\nexport interface MinimalWebSocket {\n send(data: string): void;\n close(): void;\n on(event: 'open' | 'close', cb: () => void): void;\n on(event: 'message', cb: (data: unknown) => void): void;\n on(event: 'error', cb: (err: unknown) => void): void;\n}\n\nexport type WebSocketFactory = (url: string) => MinimalWebSocket;\n\nexport interface RelaySubscriptionOptions {\n /** Relay WS URL, e.g. `ws://localhost:7100`. */\n relayUrl: string;\n /** Max events retained in the ring buffer (oldest evicted). Default 5000. */\n bufferSize?: number;\n /** Base reconnect delay, ms. Default 1000. */\n reconnectBaseMs?: number;\n /** Max reconnect delay, ms. Default 30000. */\n reconnectMaxMs?: number;\n /** Inject a WebSocket factory (tests / proxy customisation). */\n wsFactory?: WebSocketFactory;\n /**\n * Decode an `EVENT` payload that arrived as a string. The TOON relay sends\n * events TOON-encoded (key/value text) rather than as a JSON object, so the\n * daemon injects a TOON decoder here. When the payload is already a JSON\n * object it is used directly and this is not called.\n */\n decodeEvent?: (raw: string) => NostrEvent;\n /**\n * Invoked once per newly-buffered (de-duplicated) event. The daemon uses this\n * to feed a runner-level MERGED buffer across many relays — so a fan-out read\n * (`toon_read` with no relayUrl) draws from one ordered stream with a single\n * scalar cursor. The relay still keeps its own buffer for `bufferedCount` /\n * single-relay drains.\n */\n onEvent?: (subId: string, event: NostrEvent) => void;\n /** Optional logger. */\n logger?: (msg: string) => void;\n}\n\ninterface BufferedEvent {\n seq: number;\n subId: string;\n event: NostrEvent;\n}\n\n/** Result of {@link RelaySubscription.getEvents}. */\nexport interface DrainResult {\n events: NostrEvent[];\n cursor: number;\n hasMore: boolean;\n}\n\nconst DEFAULT_BUFFER = 5000;\nconst DEFAULT_BASE_MS = 1000;\nconst DEFAULT_MAX_MS = 30_000;\n\n/** Shared no-op used as the default logger. */\nconst noop = (): void => undefined;\n\nexport class RelaySubscription {\n private readonly relayUrl: string;\n private readonly bufferSize: number;\n private readonly reconnectBaseMs: number;\n private readonly reconnectMaxMs: number;\n private readonly log: (msg: string) => void;\n private readonly wsFactory: WebSocketFactory;\n private readonly decodeEvent?: (raw: string) => NostrEvent;\n private readonly onEvent?: (subId: string, event: NostrEvent) => void;\n\n /** Active subscriptions: subId -> filters (re-sent on every (re)connect). */\n private readonly subscriptions = new Map<string, NostrFilter[]>();\n\n /** Ring buffer of received events, ordered by ascending `seq`. */\n private buffer: BufferedEvent[] = [];\n /** De-dup index: event.id -> seq (kept in lockstep with the buffer). */\n private readonly seen = new Set<string>();\n private seqCounter = 0;\n private subIdCounter = 0;\n\n private ws: MinimalWebSocket | null = null;\n private connected = false;\n private closing = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(opts: RelaySubscriptionOptions) {\n this.relayUrl = opts.relayUrl;\n this.bufferSize = opts.bufferSize ?? DEFAULT_BUFFER;\n this.reconnectBaseMs = opts.reconnectBaseMs ?? DEFAULT_BASE_MS;\n this.reconnectMaxMs = opts.reconnectMaxMs ?? DEFAULT_MAX_MS;\n this.log = opts.logger ?? noop;\n this.wsFactory = opts.wsFactory ?? defaultWebSocketFactory();\n this.decodeEvent = opts.decodeEvent;\n this.onEvent = opts.onEvent;\n }\n\n /** Whether the underlying socket is currently open. */\n isConnected(): boolean {\n return this.connected;\n }\n\n /** Number of events currently held in the buffer. */\n bufferedCount(): number {\n return this.buffer.length;\n }\n\n /** Active subscription ids. */\n activeSubscriptions(): string[] {\n return [...this.subscriptions.keys()];\n }\n\n /** Open the connection (idempotent). */\n start(): void {\n this.closing = false;\n if (this.ws) return;\n this.open();\n }\n\n /**\n * Register a persistent subscription and (if connected) send the REQ.\n * Returns the subscription id (caller-supplied or generated).\n */\n subscribe(filters: NostrFilter | NostrFilter[], subId?: string): string {\n const id = subId ?? `sub-${++this.subIdCounter}`;\n const list = Array.isArray(filters) ? filters : [filters];\n this.subscriptions.set(id, list);\n if (this.connected) this.sendReq(id, list);\n return id;\n }\n\n /** Cancel a subscription and send CLOSE if connected. */\n unsubscribe(subId: string): void {\n if (!this.subscriptions.delete(subId)) return;\n if (this.connected) this.sendRaw(['CLOSE', subId]);\n }\n\n /**\n * Drain events newer than `cursor`. The cursor is the highest `seq` returned;\n * pass it back to fetch only events received since. Filtering by `subId`\n * restricts to one subscription.\n */\n getEvents(\n opts: { subId?: string; cursor?: number; limit?: number } = {}\n ): DrainResult {\n const after = opts.cursor ?? 0;\n const limit = opts.limit ?? 200;\n const matches = this.buffer.filter(\n (b) =>\n b.seq > after && (opts.subId === undefined || b.subId === opts.subId)\n );\n const page = matches.slice(0, limit);\n const hasMore = matches.length > page.length;\n const last = page.at(-1);\n const cursor = last ? last.seq : after;\n return { events: page.map((b) => b.event), cursor, hasMore };\n }\n\n /** Close the connection permanently and stop reconnecting. */\n close(): void {\n this.closing = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n /* ignore */\n }\n this.ws = null;\n }\n this.connected = false;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private open(): void {\n let ws: MinimalWebSocket;\n try {\n ws = this.wsFactory(this.relayUrl);\n } catch (err) {\n this.log(`[relay] connect failed: ${errMsg(err)}`);\n this.scheduleReconnect();\n return;\n }\n this.ws = ws;\n\n ws.on('open', () => {\n this.connected = true;\n this.reconnectAttempts = 0;\n this.log(`[relay] connected to ${this.relayUrl}`);\n // Re-issue every active subscription.\n for (const [id, filters] of this.subscriptions) this.sendReq(id, filters);\n });\n\n ws.on('message', (data: unknown) => this.handleMessage(data));\n\n ws.on('error', (err: unknown) => {\n this.log(`[relay] socket error: ${errMsg(err)}`);\n });\n\n ws.on('close', () => {\n this.connected = false;\n this.ws = null;\n if (!this.closing) {\n this.log('[relay] disconnected; scheduling reconnect');\n this.scheduleReconnect();\n }\n });\n }\n\n private scheduleReconnect(): void {\n if (this.closing || this.reconnectTimer) return;\n const delay = Math.min(\n this.reconnectMaxMs,\n this.reconnectBaseMs * 2 ** this.reconnectAttempts\n );\n this.reconnectAttempts += 1;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (!this.closing) this.open();\n }, delay);\n // Don't keep the event loop alive solely for a reconnect timer.\n (this.reconnectTimer as { unref?: () => void }).unref?.();\n }\n\n private sendReq(subId: string, filters: NostrFilter[]): void {\n this.sendRaw(['REQ', subId, ...filters]);\n }\n\n private sendRaw(message: unknown[]): void {\n if (!this.ws || !this.connected) return;\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n this.log(`[relay] send failed: ${errMsg(err)}`);\n }\n }\n\n private handleMessage(data: unknown): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(toText(data));\n } catch {\n return;\n }\n if (!Array.isArray(parsed) || parsed.length === 0) return;\n const type = parsed[0];\n switch (type) {\n case 'EVENT': {\n const subId = typeof parsed[1] === 'string' ? parsed[1] : '';\n const event = this.parseEventPayload(parsed[2]);\n if (event && typeof event.id === 'string')\n this.bufferEvent(subId, event);\n break;\n }\n case 'EOSE':\n // End of stored events — nothing to do; we keep streaming live events.\n break;\n case 'CLOSED':\n this.log(\n `[relay] subscription closed by relay: ${String(parsed[2] ?? '')}`\n );\n break;\n case 'NOTICE':\n this.log(`[relay] NOTICE: ${String(parsed[1] ?? '')}`);\n break;\n default:\n break;\n }\n }\n\n /**\n * Normalise an `EVENT` payload to a NostrEvent. Standard relays send a JSON\n * object; the TOON relay sends a TOON-encoded string, decoded via the injected\n * `decodeEvent`. Returns undefined when it can't be parsed.\n */\n private parseEventPayload(raw: unknown): NostrEvent | undefined {\n if (\n raw &&\n typeof raw === 'object' &&\n typeof (raw as NostrEvent).id === 'string'\n ) {\n return raw as NostrEvent;\n }\n if (typeof raw === 'string' && this.decodeEvent) {\n try {\n return this.decodeEvent(raw);\n } catch (err) {\n this.log(`[relay] event decode failed: ${errMsg(err)}`);\n return undefined;\n }\n }\n return undefined;\n }\n\n private bufferEvent(subId: string, event: NostrEvent): void {\n if (this.seen.has(event.id)) return; // de-dup by event.id\n this.seen.add(event.id);\n this.buffer.push({ seq: ++this.seqCounter, subId, event });\n if (this.buffer.length > this.bufferSize) {\n const evicted = this.buffer.shift();\n if (evicted) this.seen.delete(evicted.event.id);\n }\n // Mirror into the runner-level merged buffer (cross-relay fan-out reads).\n this.onEvent?.(subId, event);\n }\n}\n\nfunction toText(data: unknown): string {\n if (typeof data === 'string') return data;\n if (data instanceof Uint8Array) return Buffer.from(data).toString('utf8');\n if (Buffer.isBuffer(data)) return data.toString('utf8');\n return String(data);\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Default factory backed by the `ws` package.\n */\nfunction defaultWebSocketFactory(): WebSocketFactory {\n return (url: string): MinimalWebSocket => {\n const WebSocketImpl = nodeRequire('ws') as new (\n address: string\n ) => MinimalWebSocket;\n return new WebSocketImpl(url);\n };\n}\n","/**\n * ClientRunner — the daemon's connection owner. The TOON client is 1-to-MANY:\n * it can write through several apexes (each a `ToonClient` + BTP session +\n * payment channel) and read from several relays (each a `RelaySubscription`).\n *\n * • Writes go through BTP, never to a relay directly — `publish`/`swap` select\n * an apex (default: the config-seeded one).\n * • Reads FAN OUT — `subscribe`/`getEvents` apply across every relay and merge\n * into one ordered stream with a single scalar cursor (the runner owns the\n * merged buffer; each `RelaySubscription` mirrors new events into it).\n *\n * Targets are added at runtime (`addRelay`/`addApex`), persisted to\n * `targets.json`, replayed on the next boot, and removable. The config-seeded\n * relay + apex are the permanent DEFAULT targets and cannot be removed.\n *\n * Each apex bootstraps asynchronously and non-blocking: the connection comes up\n * in the background. Until ready, writes against it report `bootstrapping` so\n * tools surface \"retry\".\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, resolve, sep } from 'node:path';\nimport type { NostrEvent, EventTemplate } from 'nostr-tools/pure';\nimport { generateSecretKey } from 'nostr-tools/pure';\nimport { decodeEventFromToon } from '@toon-protocol/core';\nimport {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport { arweaveUrls } from '@toon-protocol/arweave';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport {\n extractArweaveTxId,\n fundWallet as faucetFund,\n mintExecutionCondition,\n ingestAndReveal,\n buildSwapSettlements,\n InMemoryReceivedClaimStore,\n JsonFileReceivedClaimStore,\n InMemoryPreimageRetentionStore,\n type FaucetChain,\n type ReceivedClaimEntry,\n type ReceivedClaimStore,\n type PreimageRetentionStore,\n type RevealFn,\n} from '@toon-protocol/client';\nimport {\n loadMinaSignerClient,\n type SettlementBundle,\n} from '@toon-protocol/sdk';\nimport {\n GitRepoReader,\n buildComment,\n buildIssue,\n buildPatch,\n buildStatus,\n executePush,\n fetchRemoteState,\n planPush,\n type Publisher,\n type PublishReceipt,\n type PushPlan,\n type PushResult,\n type RemoteState,\n type StatusKind,\n type UnsignedEvent,\n type UploadReceipt,\n type GitObjectUpload,\n} from '@toon-protocol/rig';\nimport { streamSwap } from '@toon-protocol/sdk/swap';\nimport {\n AdaptiveDeltaController,\n JsonFileSwapControllerStateStore,\n type PacketProgress,\n} from '@toon-protocol/sdk';\nimport { RelaySubscription } from '../relay-subscription.js';\nimport type {\n AddApexRequest,\n AddApexResponse,\n ApexTargetStatus,\n BalanceInfo,\n BalancesResponse,\n ChannelDepositRequest,\n ChannelDepositResponse,\n CloseChannelRequest,\n CloseChannelResponse,\n SettleChannelRequest,\n SettleChannelResponse,\n ChannelsResponse,\n ChainStatus,\n EventsResponse,\n FundStatusResponse,\n FundWalletRequest,\n FundWalletResponse,\n GitCommentRequest,\n GitEstimateRequest,\n GitEstimateResponse,\n GitEventResponse,\n GitFeeEstimate,\n GitIssueRequest,\n GitPatchRequest,\n GitPushRequest,\n GitPushResponse,\n GitRepoAddr,\n GitStatusRequest,\n HttpFetchPaidRequest,\n HttpFetchPaidResponse,\n NostrFilter,\n PublishResponse,\n PublishUnsignedRequest,\n RelayTargetStatus,\n SettlementChain,\n StatusResponse,\n SubscribeRequest,\n SubscribeResponse,\n SwapControllerParams,\n SwapPacketOutcome,\n SwapResponse,\n SwapClaim,\n ListSwapClaimsResponse,\n ReceivedClaimInfo,\n SettleSwapClaimsRequest,\n SettleSwapClaimsResponse,\n SwapSettlementResult,\n TargetsResponse,\n UploadMediaRequest,\n UploadMediaResponse,\n} from '../control-api.js';\nimport type {\n EventsQuery,\n PublishRequest,\n SwapRequest,\n} from '../control-api.js';\nimport {\n configDir,\n type ApexNegotiationConfig,\n type ResolvedDaemonConfig,\n} from './config.js';\nimport {\n loadApexChannel,\n saveApexChannel,\n type PersistedChannelContext,\n} from './apex-channel-store.js';\nimport {\n loadTargets,\n removeApexTarget,\n removeRelayTarget,\n saveApexTarget,\n saveRelayTarget,\n type PersistedApexTarget,\n} from './targets-store.js';\nimport { discoverApex } from './apex-discovery.js';\n\n/** The subset of `ToonClient` the runner depends on. */\nexport interface ToonClientLike {\n start(): Promise<{ peersDiscovered: number; mode: string }>;\n stop(): Promise<void>;\n getPublicKey(): string;\n getEvmAddress(): string | undefined;\n getSolanaAddress(): string | undefined;\n getMinaAddress(): string | undefined;\n getNetworkStatus(): { evm: string; solana: string; mina: string } | undefined;\n publishEvent(\n event: NostrEvent,\n options?: {\n destination?: string;\n claim?: unknown;\n ilpAmount?: bigint;\n /** HTTP request-target the payment-proxy replays (default '/write';\n * '/store' routes to the Arweave store/DVM backend). */\n proxyPath?: string;\n }\n ): Promise<{\n success: boolean;\n eventId?: string;\n data?: string;\n error?: string;\n }>;\n signBalanceProof(channelId: string, amount: bigint): Promise<unknown>;\n /**\n * Sign an unsigned event template with the daemon-held Nostr key (the key\n * never leaves the daemon). Backs the `publish-unsigned` / `upload-media`\n * paths so a UI/agent supplies only the event shell.\n */\n signEvent(template: EventTemplate): NostrEvent | Promise<NostrEvent>;\n /**\n * Upload bytes to Arweave via the kind:5094 blob-storage DVM (single-packet),\n * returning the Arweave tx id. Reuses the client's claim/channel plumbing.\n */\n uploadBlob(params: {\n blobData: Uint8Array;\n contentType?: string;\n bid?: string;\n destination?: string;\n ilpAmount?: bigint;\n }): Promise<{\n success: boolean;\n txId?: string;\n eventId?: string;\n error?: string;\n }>;\n openChannel(destination?: string): Promise<string>;\n getTrackedChannels(): string[];\n getChannelNonce(channelId: string): number;\n getChannelCumulativeAmount(channelId: string): bigint;\n getChannelDepositTotal(channelId: string): bigint;\n getBalances(): Promise<BalanceInfo[]>;\n depositToChannel(\n channelId: string,\n amount: string\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }>;\n closeChannel(channelId: string): Promise<{\n channelId: string;\n txHash?: string;\n closedAt: string;\n settleableAt: string;\n }>;\n settleChannel(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string }>;\n getChannelCloseState(\n channelId: string\n ): 'open' | 'closing' | 'settleable' | 'settled';\n getSettleableAt(channelId: string): bigint | undefined;\n /**\n * Re-read a resumed channel's on-chain deposit (persisted state omits it).\n * Optional so lightweight fakes need not implement it; the real ToonClient\n * does. Best-effort — callers await + catch.\n */\n rehydrateChannelDeposit?(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined>;\n sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n claim?: unknown;\n /**\n * Sender-chosen 32-byte execution condition (toon-client#350). The\n * transport puts it on the PREPARE and verifies the FULFILL preimage\n * (`sha256(fulfillment) == condition`); absent/all-zero = legacy packet.\n */\n executionCondition?: Uint8Array;\n /** Explicit ILP expiry; defaults to `now + timeout` in the transport. */\n expiresAt?: Date;\n }): Promise<{\n accepted: boolean;\n data?: string;\n code?: string;\n message?: string;\n }>;\n /**\n * Payment-aware HTTP fetch: issue the request and, on a `402 Payment\n * Required`, transparently pay over TOON and retry, returning the settled Web\n * `Response`. Pinned to the `ToonClient.h402Fetch` shape (issue #50).\n */\n h402Fetch(\n url: string,\n opts?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string | Uint8Array;\n timeout?: number;\n destination?: string;\n }\n ): Promise<Response>;\n /**\n * Submit a receive-side swap settlement bundle on-chain (#352). EVM only;\n * env-gated on `chainRpcUrls[bundle.chain]`. Optional so lightweight fakes\n * need not implement it — the runner surfaces a result-shaped\n * `SUBMISSION_UNAVAILABLE` when absent.\n */\n settleSwapBundle?(\n bundle: SettlementBundle\n ): Promise<{ txHash: string; status?: 'success' | 'reverted' }>;\n}\n\n/** A started managed proxy: just the teardown handle the runner needs. */\n/** Builds a `ToonClient` (or a fake) for a given resolved client config. */\nexport type CreateClient = (config: ToonClientConfig) => ToonClientLike;\n\n/** Builds a `RelaySubscription` for a given relay URL. */\nexport type CreateRelay = (opts: {\n relayUrl: string;\n onEvent: (subId: string, event: NostrEvent) => void;\n logger?: (msg: string) => void;\n}) => RelaySubscription;\n\nexport interface ClientRunnerDeps {\n config: ResolvedDaemonConfig;\n /** Factory producing the (real or fake) ToonClient for a client config. */\n createClient: CreateClient;\n /** Factory producing a relay subscription (defaults to the real one). */\n createRelay?: CreateRelay;\n logger?: (msg: string) => void;\n /** Path to the dynamic-targets store (tests override). */\n targetsPath?: string;\n /**\n * Test seams for the `/git/*` pipeline (default: the real\n * @toon-protocol/rig implementations). `fetchRemoteState` opens relay\n * WebSockets, so tests inject a canned reader instead of hitting the network.\n */\n gitDeps?: {\n fetchRemoteState?: typeof fetchRemoteState;\n createRepoReader?: (repoPath: string) => GitRepoReader;\n };\n}\n\n/** One apex write target: a BTP session + its payment channel + settlement. */\ninterface ApexConnection {\n btpUrl: string;\n client: ToonClientLike;\n negotiation?: ApexNegotiationConfig;\n childPeers: string[];\n destination: string;\n chain: SettlementChain;\n /** Per-apex channel-store path (distinct so parallel apexes don't race it). */\n channelStorePath: string;\n feePerEvent: bigint;\n apexChannelId?: string;\n ready: boolean;\n bootstrapping: boolean;\n /** In-flight bootstrap, so concurrent callers await the same work (not re-run). */\n bootstrapPromise?: Promise<void>;\n lastError?: string;\n isDefault: boolean;\n}\n\n/** A runner-level merged read-buffer entry, tagged with its source relay. */\ninterface MergedEvent {\n seq: number;\n relayUrl: string;\n subId: string;\n event: NostrEvent;\n}\n\nconst MERGED_BUFFER = 5000;\n\n/**\n * Per-attempt bound for an on-chain balance read, kept WELL under the control\n * client's `/balances` timeout (12s) so a stalled provider fast-fails inside the\n * daemon instead of letting the whole control request hang to the wire timeout\n * (#199). With {@link BALANCES_READ_ATTEMPTS} the worst case stays under 12s.\n */\nconst BALANCES_READ_TIMEOUT_MS = 5_000;\n/** Bounded retry for a transient provider stall on a balance read (#199). */\nconst BALANCES_READ_ATTEMPTS = 2;\n\n/**\n * In-memory record of one background faucet drip. Structurally identical to the\n * wire {@link FundWalletResponse} snapshot the daemon returns, so a job can be\n * handed back verbatim.\n */\ntype FundJob = FundWalletResponse;\n\nexport class ClientRunner {\n private readonly config: ResolvedDaemonConfig;\n private readonly createClient: CreateClient;\n private readonly createRelay: CreateRelay;\n private readonly log: (msg: string) => void;\n private readonly targetsPath?: string;\n\n /** Remote-state reader for `/git/*` (injectable — opens relay sockets). */\n private readonly fetchGitRemoteState: typeof fetchRemoteState;\n /** Local-repo reader factory for `/git/*` (injectable for tests). */\n private readonly createRepoReader: (repoPath: string) => GitRepoReader;\n\n /**\n * Identity-level chain-read client. Reading your OWN on-chain wallet balance is\n * a pure (wallet keys + chain RPC) operation that has nothing to do with the\n * ILP/payment peer, so it lives at the daemon level rather than inside an apex.\n * Built once from the daemon's own `toonClientConfig` (the same keys + chain\n * RPC config every apex shares) and REUSED as the default apex's client, so a\n * funded apex's `start()` (which derives Solana/Mina keys) also benefits this\n * reader. `getBalances` uses it directly, so balances work even with zero\n * apexes registered (follow-up to #199/#200).\n */\n private readonly identityClient: ToonClientLike;\n\n private readonly startedAt = Date.now();\n\n /** Apex write targets, keyed by btpUrl. */\n private readonly apexes = new Map<string, ApexConnection>();\n /** Relay read targets, keyed by relayUrl. */\n private readonly relays = new Map<string, RelaySubscription>();\n\n /**\n * Durable store for VERIFIED received swap claims (chain-B watermarks,\n * #352) — file-backed when the config names a path (production), in-memory\n * otherwise (manually-built test configs). Claims survive a daemon restart.\n */\n private readonly receivedClaimStore: ReceivedClaimStore;\n\n /**\n * Async faucet drip jobs, keyed by chain. A drip is launched in the background\n * (the Mina faucet legitimately takes ~75s — longer than the MCP host's ~60s\n * tool-call budget) and its terminal state is observed via {@link getFundStatus}\n * / re-reading balances rather than by blocking the caller.\n */\n private readonly fundJobs = new Map<FaucetChain, FundJob>();\n\n /** Runner-level merged read buffer across all relays (de-duped by event.id). */\n private merged: MergedEvent[] = [];\n private readonly mergedSeen = new Set<string>();\n private mergedSeq = 0;\n\n /**\n * Fan-out subscriptions (no relayUrl restriction): replayed onto relays added\n * later so a new relay immediately participates in existing reads.\n */\n private readonly fanoutSubs = new Map<string, NostrFilter[]>();\n private subIdCounter = 0;\n\n private readonly defaultBtpUrl: string;\n private readonly defaultRelayUrl: string;\n\n private stopped = false;\n private started = false;\n\n constructor(deps: ClientRunnerDeps) {\n this.config = deps.config;\n this.createClient = deps.createClient;\n this.log = deps.logger ?? ((): void => undefined);\n if (deps.targetsPath !== undefined) this.targetsPath = deps.targetsPath;\n this.fetchGitRemoteState =\n deps.gitDeps?.fetchRemoteState ?? fetchRemoteState;\n this.createRepoReader =\n deps.gitDeps?.createRepoReader ??\n ((repoPath) => new GitRepoReader(repoPath));\n this.defaultBtpUrl = deps.config.toonClientConfig.btpUrl ?? '';\n this.defaultRelayUrl = deps.config.relayUrl;\n this.receivedClaimStore = deps.config.receivedClaimStorePath\n ? new JsonFileReceivedClaimStore(deps.config.receivedClaimStorePath)\n : new InMemoryReceivedClaimStore();\n\n this.createRelay =\n deps.createRelay ??\n ((opts) =>\n new RelaySubscription({\n relayUrl: opts.relayUrl,\n ...(opts.logger ? { logger: opts.logger } : {}),\n onEvent: opts.onEvent,\n // The TOON relay sends events TOON-encoded (text) on reads, not JSON.\n decodeEvent: (raw) =>\n decodeEventFromToon(new TextEncoder().encode(raw)),\n }));\n\n // Build the permanent config-seeded default relay + apex up front (not yet\n // started/bootstrapped) so `bootstrap()` works standalone (the daemon and\n // tests both rely on constructing then awaiting bootstrap()).\n this.registerRelay(this.defaultRelayUrl);\n // Build the identity-level read client ONCE and reuse it as the default\n // apex's client (same keys + chain RPC config), so on-chain balance reads\n // never depend on an apex existing.\n this.identityClient = this.createClient(this.config.toonClientConfig);\n const defaultApex = this.makeApex({\n btpUrl: this.defaultBtpUrl,\n client: this.identityClient,\n ...(this.config.apex ? { negotiation: this.config.apex } : {}),\n childPeers: this.config.apexChildPeers ?? [],\n destination: this.config.destination,\n chain: this.config.chain,\n channelStorePath:\n this.config.toonClientConfig.channelStorePath ??\n this.apexChannelStorePathFor(this.defaultBtpUrl),\n feePerEvent: this.config.feePerEvent,\n isDefault: true,\n });\n this.apexes.set(defaultApex.btpUrl, defaultApex);\n }\n\n /**\n * Start the live connections: the shared read proxy, every relay socket, the\n * default apex bootstrap (non-blocking), then replay persisted dynamic\n * targets. Returns immediately; apexes become ready asynchronously.\n */\n start(): void {\n if (this.started) return;\n this.started = true;\n for (const relay of this.relays.values()) relay.start();\n void this.bootstrap();\n this.replayPersistedTargets();\n }\n\n /** Await the default apex's bootstrap (kicking it off if not already running). */\n bootstrap(): Promise<void> {\n // Read-only daemon (no proxy/BTP uplink): never bootstrap an apex — there is\n // no write transport and FREE reads run off the relay subscription (#69).\n if (!this.config.hasUplink) return Promise.resolve();\n const apex = this.apexes.get(this.defaultBtpUrl);\n if (!apex) return Promise.resolve();\n return this.bootstrapApex(apex);\n }\n\n // ── Relays (reads) ─────────────────────────────────────────────────────────\n\n /**\n * Build + register a relay (idempotent by URL), wiring its events into the\n * merged buffer and replaying active fan-out subscriptions. Does NOT start the\n * socket — callers start it (so construction stays side-effect-free for tests).\n */\n private registerRelay(relayUrl: string): RelaySubscription {\n const existing = this.relays.get(relayUrl);\n if (existing) return existing;\n const relay = this.createRelay({\n relayUrl,\n logger: this.log,\n onEvent: (subId, event) => this.pushMerged(relayUrl, subId, event),\n });\n this.relays.set(relayUrl, relay);\n // A new relay joins every active fan-out subscription.\n for (const [subId, filters] of this.fanoutSubs)\n relay.subscribe(filters, subId);\n return relay;\n }\n\n /**\n * Add a relay read target at runtime. Persisted unless `persist` is false.\n */\n async addRelay(relayUrl: string, persist = true): Promise<void> {\n if (this.relays.has(relayUrl)) return;\n const relay = this.registerRelay(relayUrl);\n relay.start();\n if (persist) saveRelayTarget(relayUrl, this.targetsPath);\n }\n\n /** Remove a relay read target. The config-seeded default cannot be removed. */\n removeRelay(relayUrl: string): void {\n if (relayUrl === this.defaultRelayUrl) {\n throw new TargetError('Cannot remove the default (config-seeded) relay.');\n }\n const relay = this.relays.get(relayUrl);\n if (!relay) throw new TargetError(`No such relay: ${relayUrl}`);\n relay.close();\n this.relays.delete(relayUrl);\n // Drop its events from the merged buffer (and dedup index).\n this.merged = this.merged.filter((m) => {\n if (m.relayUrl === relayUrl) {\n this.mergedSeen.delete(m.event.id);\n return false;\n }\n return true;\n });\n removeRelayTarget(relayUrl, this.targetsPath);\n }\n\n /** Mirror a newly-buffered relay event into the merged cross-relay buffer. */\n private pushMerged(relayUrl: string, subId: string, event: NostrEvent): void {\n if (this.mergedSeen.has(event.id)) return;\n this.mergedSeen.add(event.id);\n this.merged.push({ seq: ++this.mergedSeq, relayUrl, subId, event });\n if (this.merged.length > MERGED_BUFFER) {\n const evicted = this.merged.shift();\n if (evicted) this.mergedSeen.delete(evicted.event.id);\n }\n }\n\n /**\n * Register a free-read subscription. With no `relayUrl` it FANS OUT across\n * every relay (and onto relays added later); with one it targets that relay.\n */\n subscribe(req: SubscribeRequest): SubscribeResponse {\n const subId = req.subId ?? `sub-${++this.subIdCounter}`;\n const filters = Array.isArray(req.filters) ? req.filters : [req.filters];\n const targets = req.relayUrl ? [req.relayUrl] : [...this.relays.keys()];\n if (req.relayUrl && !this.relays.has(req.relayUrl)) {\n throw new TargetError(`No such relay: ${req.relayUrl}`);\n }\n if (!req.relayUrl) this.fanoutSubs.set(subId, filters);\n for (const url of targets) this.relays.get(url)?.subscribe(filters, subId);\n return { subId, relays: targets };\n }\n\n /**\n * One-shot free read: subscribe the given filter(s) across all relays, wait a\n * bounded window for the relay(s) to deliver, then return every buffered event\n * matching the filter (matched by content, not subId — so events already\n * buffered by other subscriptions are included despite the global dedup).\n *\n * Backs the apps `toon_query` tool the generative-UI runtime calls to resolve\n * a ViewSpec node's data bind.\n */\n async query(\n filters: NostrFilter | NostrFilter[],\n timeoutMs = 1200\n ): Promise<NostrEvent[]> {\n const list = Array.isArray(filters) ? filters : [filters];\n const subId = `q-${++this.subIdCounter}`;\n const targets = [...this.relays.keys()];\n for (const url of targets) this.relays.get(url)?.subscribe(list, subId);\n await delay(timeoutMs);\n for (const url of targets) this.relays.get(url)?.unsubscribe(subId);\n return this.merged\n .map((m) => m.event)\n .filter((event) => list.some((f) => matchesFilter(event, f)));\n }\n\n /** Drain merged events newer than the cursor (free read), optionally scoped. */\n getEvents(query: EventsQuery): EventsResponse {\n const after = query.cursor ?? 0;\n const limit = query.limit ?? 200;\n const matches = this.merged.filter(\n (m) =>\n m.seq > after &&\n (query.subId === undefined || m.subId === query.subId) &&\n (query.relayUrl === undefined || m.relayUrl === query.relayUrl)\n );\n const page = matches.slice(0, limit);\n const hasMore = matches.length > page.length;\n const last = page.at(-1);\n return {\n events: page.map((m) => m.event),\n cursor: last ? last.seq : after,\n hasMore,\n };\n }\n\n // ── Apexes (writes) ──────────────────────────────────────────────────────\n\n private makeApex(init: {\n btpUrl: string;\n client: ToonClientLike;\n negotiation?: ApexNegotiationConfig;\n childPeers: string[];\n destination: string;\n chain: SettlementChain;\n channelStorePath: string;\n feePerEvent: bigint;\n isDefault: boolean;\n }): ApexConnection {\n return {\n ...init,\n ready: false,\n bootstrapping: false,\n };\n }\n\n /**\n * Bootstrap one apex (memoized): start, inject negotiation, open/resume the\n * channel, route child peers. Concurrent callers await the same in-flight\n * work rather than re-running it.\n */\n private bootstrapApex(apex: ApexConnection): Promise<void> {\n if (apex.ready) return Promise.resolve();\n if (!apex.bootstrapPromise) {\n apex.bootstrapPromise = this.doBootstrapApex(apex);\n }\n return apex.bootstrapPromise;\n }\n\n private async doBootstrapApex(apex: ApexConnection): Promise<void> {\n apex.bootstrapping = true;\n try {\n // PROXY mode (no BTP discovery): if no negotiation was supplied via config,\n // discover the apex's settlement params from its kind:10032 on the default\n // relay before opening the channel (#69). Config-supplied negotiation wins.\n // In BTP mode the legacy bootstrap path handles discovery, so skip this.\n if (!apex.negotiation && this.config.proxyUrl) {\n await this.discoverApexNegotiation(apex);\n }\n await apex.client.start();\n this.injectApexNegotiation(apex);\n // PROXY mode: resume a previously-opened channel up front, else DEFER the\n // on-chain open to the first write / `POST /channels` so the wallet can be\n // funded AFTER the daemon starts (the fund→open→publish demo flow, #69).\n // The apex is \"ready\" once negotiation is in place — `openChannel` /\n // `publish` open lazily and idempotently via the ChannelManager.\n // BTP mode keeps the historical eager open at bootstrap.\n const deferOpen = Boolean(this.config.proxyUrl);\n apex.apexChannelId = await this.openOrResumeApexChannel(apex, {\n resumeOnly: deferOpen,\n });\n this.routeChildPeersThroughApexChannel(apex);\n apex.ready = true;\n apex.lastError = undefined;\n this.log(\n `[runner] apex ${apex.btpUrl || apex.destination} ready; channel ${\n apex.apexChannelId ?? '(deferred — open on first write)'\n }`\n );\n } catch (err) {\n apex.lastError = err instanceof Error ? err.message : String(err);\n this.log(\n `[runner] apex ${apex.btpUrl} bootstrap failed: ${apex.lastError}`\n );\n } finally {\n apex.bootstrapping = false;\n }\n }\n\n /**\n * Add an apex write target. Settlement params are discovered by reading the\n * apex's kind:10032 off the given relay (added first if unknown). Persisted.\n */\n async addApex(req: AddApexRequest): Promise<AddApexResponse> {\n await this.addRelay(req.relayUrl); // ensure + persist the discovery relay\n const relay = this.relays.get(req.relayUrl);\n if (!relay) throw new TargetError(`Relay unavailable: ${req.relayUrl}`);\n\n const discovered = await discoverApex({\n relay,\n ilpAddress: req.ilpAddress,\n ...(req.pubkey ? { pubkey: req.pubkey } : {}),\n ...(req.chain ? { chain: req.chain } : {}),\n ...(req.childPeers ? { childPeers: req.childPeers } : {}),\n });\n\n const feePerEvent =\n req.feePerEvent !== undefined\n ? BigInt(req.feePerEvent)\n : this.config.feePerEvent;\n\n await this.instantiateApex(\n {\n btpUrl: discovered.btpUrl,\n negotiation: discovered.negotiation,\n ...(discovered.apexChildPeers\n ? { apexChildPeers: discovered.apexChildPeers }\n : {}),\n feePerEvent: req.feePerEvent ?? feePerEvent.toString(),\n discoveredFrom: req.relayUrl,\n },\n true\n );\n\n const apex = this.apexes.get(discovered.btpUrl);\n if (!apex) {\n throw new TargetError(\n `Apex ${discovered.btpUrl} failed to register after discovery.`\n );\n }\n return {\n btpUrl: apex.btpUrl,\n destination: apex.destination,\n chain: apex.chain,\n ready: apex.ready,\n };\n }\n\n /** Build + register + bootstrap an apex from a (persisted) target record. */\n private async instantiateApex(\n target: PersistedApexTarget,\n persist: boolean\n ): Promise<void> {\n if (this.apexes.has(target.btpUrl)) return;\n const clientConfig = this.deriveApexClientConfig(\n target.btpUrl,\n target.negotiation.destination\n );\n const apex = this.makeApex({\n btpUrl: target.btpUrl,\n client: this.createClient(clientConfig),\n negotiation: target.negotiation,\n childPeers: target.apexChildPeers ?? [],\n destination: target.negotiation.destination,\n chain: target.negotiation.chain,\n channelStorePath: this.apexChannelStorePathFor(target.btpUrl),\n feePerEvent: BigInt(target.feePerEvent ?? this.config.feePerEvent),\n isDefault: false,\n });\n this.apexes.set(apex.btpUrl, apex);\n if (persist) saveApexTarget(target, this.targetsPath);\n await this.bootstrapApex(apex);\n }\n\n /** Remove an apex write target. The config-seeded default cannot be removed. */\n async removeApex(btpUrl: string): Promise<void> {\n if (btpUrl === this.defaultBtpUrl) {\n throw new TargetError('Cannot remove the default (config-seeded) apex.');\n }\n const apex = this.apexes.get(btpUrl);\n if (!apex) throw new TargetError(`No such apex: ${btpUrl}`);\n try {\n await apex.client.stop();\n } catch (err) {\n this.log(\n `[runner] apex ${btpUrl} stop error: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n this.apexes.delete(btpUrl);\n removeApexTarget(btpUrl, this.targetsPath);\n }\n\n /** Derive a per-apex ToonClientConfig from the default (shared identity/transport). */\n private deriveApexClientConfig(\n btpUrl: string,\n destination: string\n ): ToonClientConfig {\n const base = this.config.toonClientConfig;\n // A DISCOVERED apex (e.g. the store DVM at `wss://proxy.store…:443`) lives on\n // a different connector than the default `proxyUrl`. In direct/HTTP transport\n // every paid packet POSTs to `proxyUrl`, so without a per-apex override the\n // discovered apex's packets go to the DEFAULT connector — which has no route\n // to its ILP prefix (F02 \"No route to destination\"). Derive the apex's own\n // HTTP `/ilp` base from its BTP url so its packets reach the right connector.\n const derivedProxyUrl = btpUrl\n .replace(/^wss:\\/\\//, 'https://')\n .replace(/^ws:\\/\\//, 'http://')\n .replace(/:443(\\/|$)/, '$1')\n .replace(/\\/btp\\/?$/, '')\n .replace(/\\/$/, '');\n return {\n ...base,\n ...(derivedProxyUrl ? { proxyUrl: derivedProxyUrl } : {}),\n btpUrl,\n destinationAddress: destination,\n // Distinct nonce-watermark store per apex so parallel ChannelManagers in\n // this process never race a shared channels.json.\n channelStorePath: this.apexChannelStorePathFor(btpUrl),\n ilpInfo: { ...base.ilpInfo, btpEndpoint: btpUrl },\n };\n }\n\n private apexChannelStorePathFor(btpUrl: string): string {\n return `${configDir()}/channels-${sanitize(btpUrl)}.json`;\n }\n\n // ── Persisted-target replay ────────────────────────────────────────────────\n\n private replayPersistedTargets(): void {\n let store;\n try {\n store = loadTargets(this.targetsPath);\n } catch (err) {\n this.log(\n `[runner] failed to load targets store: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n for (const r of store.relays) {\n if (r.relayUrl === this.defaultRelayUrl) continue;\n void this.addRelay(r.relayUrl, false).catch((err) =>\n this.log(`[runner] replay relay ${r.relayUrl} failed: ${errMsg(err)}`)\n );\n }\n for (const a of store.apexes) {\n if (a.btpUrl === this.defaultBtpUrl) continue;\n void this.instantiateApex(a, false).catch((err) =>\n this.log(`[runner] replay apex ${a.btpUrl} failed: ${errMsg(err)}`)\n );\n }\n }\n\n // ── Channel / negotiation helpers (per-apex) ───────────────────────────────\n\n /**\n * Open the apex channel — or, on a restart, RESUME the existing one.\n *\n * With `resumeOnly`, only a persisted channel is resumed (no on-chain open);\n * returns undefined when none exists so the caller can defer the open to the\n * first write (funded-after-start demo flow, #69).\n */\n private async openOrResumeApexChannel(\n apex: ApexConnection,\n opts: { resumeOnly?: boolean } = {}\n ): Promise<string | undefined> {\n const { destination, chain } = apex;\n const { apexChannelStorePath } = this.config;\n const saved = loadApexChannel(apexChannelStorePath, destination, chain);\n const cm = (\n apex.client as unknown as {\n channelManager?: {\n trackChannel?: (id: string, ctx: PersistedChannelContext) => void;\n };\n }\n ).channelManager;\n\n if (saved && cm && typeof cm.trackChannel === 'function') {\n cm.trackChannel(saved.channelId, saved.context);\n // Persisted channel state omits the on-chain deposit, so re-read it from\n // chain — otherwise the wallet shows 0 spendable on a funded channel.\n if (saved.context.chainType === 'evm') {\n await apex.client\n .rehydrateChannelDeposit?.(saved.channelId, {\n chain: `evm:${saved.context.chainId}`,\n tokenNetworkAddress: saved.context.tokenNetworkAddress,\n })\n .catch((err) =>\n this.log(\n `[runner] deposit re-hydrate for ${saved.channelId} failed: ${errMsg(err)}`\n )\n );\n }\n this.log(\n `[runner] resumed apex channel ${saved.channelId} (deposit re-read)`\n );\n return saved.channelId;\n }\n\n if (opts.resumeOnly) return undefined;\n\n const channelId = await apex.client.openChannel(destination);\n this.persistApexChannel(apex, channelId);\n return channelId;\n }\n\n /**\n * Persist a (lazily- or eagerly-) opened apex channel so a restart RESUMES it\n * (tracked, no re-deposit) rather than opening a second on-chain channel.\n * No-op when the apex carries no negotiation (nothing to key the store on).\n */\n private persistApexChannel(apex: ApexConnection, channelId: string): void {\n const a = apex.negotiation;\n if (!a) return;\n saveApexChannel(\n this.config.apexChannelStorePath,\n apex.destination,\n apex.chain,\n {\n channelId,\n context: {\n chainType: a.chain,\n chainId: a.chainId,\n tokenNetworkAddress: a.tokenNetwork ?? '',\n ...(a.tokenAddress ? { tokenAddress: a.tokenAddress } : {}),\n recipient: a.settlementAddress,\n },\n }\n );\n }\n\n /**\n * Discover the apex's settlement negotiation from its kind:10032 on the\n * default relay and attach it to the apex (proxy-mode fallback when no config\n * negotiation was supplied, #69). Throws ApexDiscoveryError on timeout/missing\n * settlement params so the apex's `lastError` reports exactly what is missing.\n */\n private async discoverApexNegotiation(apex: ApexConnection): Promise<void> {\n const relay = this.relays.get(this.defaultRelayUrl);\n if (!relay) {\n throw new TargetError(\n `Cannot discover apex \"${apex.destination}\": default relay ` +\n `${this.defaultRelayUrl} is not registered.`\n );\n }\n relay.start();\n const discovered = await discoverApex({\n relay,\n ilpAddress: apex.destination,\n chain: apex.chain,\n ...(apex.childPeers.length > 0 ? { childPeers: apex.childPeers } : {}),\n });\n apex.negotiation = discovered.negotiation;\n if (discovered.apexChildPeers) apex.childPeers = discovered.apexChildPeers;\n this.log(\n `[runner] discovered apex negotiation for \"${apex.destination}\" ` +\n `(chain ${discovered.negotiation.chainKey}, settle ` +\n `${discovered.negotiation.settlementAddress})`\n );\n }\n\n /** Inject the apex settlement negotiation directly into its ToonClient. */\n private injectApexNegotiation(apex: ApexConnection): void {\n const a = apex.negotiation;\n if (!a) return;\n const negotiations = (\n apex.client as unknown as { peerNegotiations?: Map<string, unknown> }\n ).peerNegotiations;\n if (!(negotiations instanceof Map)) {\n throw new Error(\n 'ToonClient.peerNegotiations layout changed — cannot inject apex negotiation'\n );\n }\n negotiations.set(a.peerId, {\n chain: a.chainKey,\n chainType: a.chain,\n chainId: a.chainId,\n settlementAddress: a.settlementAddress,\n tokenAddress: a.tokenAddress,\n tokenNetwork: a.tokenNetwork,\n });\n this.log(`[runner] injected apex negotiation for peer \"${a.peerId}\"`);\n }\n\n /** Route apex CHILD peers (store/swap) through the SAME apex payment channel. */\n private routeChildPeersThroughApexChannel(apex: ApexConnection): void {\n const a = apex.negotiation;\n if (!a || !apex.apexChannelId || apex.childPeers.length === 0) return;\n const client = apex.client as unknown as {\n peerNegotiations?: Map<string, unknown>;\n channelManager?: { peerChannels?: Map<string, string> };\n };\n const negotiations = client.peerNegotiations;\n const peerChannels = client.channelManager?.peerChannels;\n if (!(negotiations instanceof Map) || !(peerChannels instanceof Map)) {\n this.log(\n '[runner] cannot route child peers — ToonClient internals layout changed'\n );\n return;\n }\n for (const peer of apex.childPeers) {\n if (peer === a.peerId) continue;\n negotiations.set(peer, {\n chain: a.chainKey,\n chainType: a.chain,\n chainId: a.chainId,\n settlementAddress: a.settlementAddress,\n tokenAddress: a.tokenAddress,\n tokenNetwork: a.tokenNetwork,\n });\n peerChannels.set(peer, apex.apexChannelId);\n this.log(\n `[runner] routed child peer \"${peer}\" through apex channel ${apex.apexChannelId}`\n );\n }\n }\n\n // ── Status ─────────────────────────────────────────────────────────────────\n\n private defaultApex(): ApexConnection | undefined {\n return this.apexes.get(this.defaultBtpUrl);\n }\n\n /** Whether any apex has finished bootstrapping. */\n isReady(): boolean {\n return [...this.apexes.values()].some((a) => a.ready);\n }\n\n isBootstrapping(): boolean {\n return [...this.apexes.values()].some((a) => a.bootstrapping);\n }\n\n getStatus(): StatusResponse {\n const apex = this.defaultApex();\n const client = apex?.client;\n const net = client?.getNetworkStatus();\n const network: ChainStatus[] | undefined = net\n ? (['evm', 'solana', 'mina'] as const).map((c) => ({\n chain: c,\n ready: net[c] === 'configured',\n detail: net[c],\n }))\n : undefined;\n const relay = this.relays.get(this.defaultRelayUrl);\n return {\n uptimeMs: Date.now() - this.startedAt,\n bootstrapping: apex?.bootstrapping ?? false,\n ready: apex?.ready ?? false,\n settlementChain: this.config.chain,\n feePerEvent: (apex?.feePerEvent ?? this.config.feePerEvent).toString(),\n identity: {\n nostrPubkey: safe(() => client?.getPublicKey()) ?? '',\n evmAddress: safe(() => client?.getEvmAddress()),\n solanaAddress: safe(() => client?.getSolanaAddress()),\n minaAddress: safe(() => client?.getMinaAddress()),\n },\n transport: {\n type: 'direct',\n ...(apex ? { btpUrl: apex.btpUrl } : {}),\n },\n relay: {\n url: this.defaultRelayUrl,\n connected: relay?.isConnected() ?? false,\n buffered: relay?.bufferedCount() ?? 0,\n subscriptions: relay?.activeSubscriptions() ?? [],\n },\n ...(network ? { network } : {}),\n ...(apex?.lastError ? { lastError: apex.lastError } : {}),\n // Advertise the optional-route surface this daemon build serves so a\n // version-skewed rig CLI can capability-gate the `/git/*` write path\n // BEFORE delegating (an old daemon lacking these routes 404s otherwise —\n // #306). Static: these routes are always registered by this build.\n capabilities: ['git'],\n };\n }\n\n /**\n * Drip devnet test funds to a wallet from the configured faucet. Defaults the\n * chain to the active settlement chain and the address to this client's own\n * address on that chain, so a no-arg call funds the caller's own wallet\n * (the typical \"fund me before I open a channel\" flow). The daemon holds the\n * faucet URL + the keys, so the MCP caller never needs either.\n */\n fundWallet(req: FundWalletRequest = {}): FundWalletResponse {\n const faucetUrl = this.config.faucetUrl;\n if (!faucetUrl) {\n throw new InvalidPayloadError(\n 'no faucet configured — set faucetUrl in the daemon config (or the ' +\n 'TOON_CLIENT_FAUCET_URL env var) to fund wallets.'\n );\n }\n const chain: FaucetChain = req.chain ?? this.config.chain;\n const client = this.defaultApex()?.client;\n const address =\n req.address ??\n safe(() =>\n chain === 'evm'\n ? client?.getEvmAddress()\n : chain === 'solana'\n ? client?.getSolanaAddress()\n : client?.getMinaAddress()\n );\n if (!address) {\n throw new InvalidPayloadError(\n `no ${chain} address available to fund — pass an explicit address ` +\n `(this client has no ${chain} key configured).`\n );\n }\n\n // Idempotent: a drip already in flight for this chain returns its snapshot\n // rather than launching a second faucet call (a re-click / poll mustn't\n // double-drip).\n const existing = this.fundJobs.get(chain);\n if (existing && existing.status === 'pending') {\n return { ...existing };\n }\n\n // The drip is ASYNC: launch the faucet call in the background and return a\n // 'pending' snapshot immediately. The Mina faucet mints native MINA + USDC\n // on a slow-settling chain and legitimately takes ~75s — longer than the MCP\n // host's ~60s tool-call budget and the control client's wire timeout — so a\n // blocking call surfaces a working drip as a misleading relay/apex timeout\n // (#199-class). The daemon happily waits the full chain-aware faucet budget\n // in the background; the caller observes the result via getFundStatus /\n // re-reading balances.\n const job: FundJob = {\n chain,\n address,\n faucetUrl,\n status: 'pending',\n startedAt: Date.now(),\n };\n this.fundJobs.set(chain, job);\n\n // The drip runs in the BACKGROUND, so there is no caller to protect from a\n // slow faucet — use a GENEROUS timeout. The faucet client default (30s for\n // evm/solana) is tuned for a synchronous call and falsely aborts a drip that\n // succeeds server-side a bit later: e.g. a loaded EVM faucet answers >30s but\n // the tx still lands, so the job would report `error` while the balance\n // actually went up — causing a misleading failure + double-fund risk. Await\n // the real outcome instead (config `faucetTimeoutMs` still overrides).\n const faucetTimeout =\n this.config.faucetTimeoutMs ?? (chain === 'mina' ? 130_000 : 90_000);\n void faucetFund(faucetUrl, address, chain, { timeout: faucetTimeout })\n .then(({ response }) => {\n job.status = 'success';\n job.response = response;\n job.finishedAt = Date.now();\n this.log(`[runner] faucet drip succeeded: ${chain} → ${address}`);\n })\n .catch((err: unknown) => {\n // The background promise must never become an unhandled rejection.\n try {\n const msg = errMsg(err);\n // A timeout is NOT a definitive failure — the on-chain drip may still\n // settle after the client gives up (observed on EVM). Mark it as a\n // distinct, non-terminal-sounding state and advise re-checking\n // balances before re-funding, rather than asserting it failed.\n const timedOut = /timed out|timeout|aborted/i.test(msg);\n job.status = timedOut ? 'timeout' : 'error';\n job.error = timedOut\n ? `${msg} — the on-chain drip may still have settled; re-check balances before re-funding.`\n : msg;\n job.finishedAt = Date.now();\n this.log(\n `[runner] faucet drip ${timedOut ? 'timed out' : 'failed'}: ${chain} → ${address}: ${msg}`\n );\n } catch {\n // Swallow — recording the failure must not itself reject.\n }\n });\n\n return { ...job };\n }\n\n /**\n * Snapshots of tracked faucet drip jobs — all of them, or just the one for\n * `chain`. Lets a caller poll for the terminal state of an async drip without\n * re-dripping.\n */\n getFundStatus(chain?: FaucetChain): FundStatusResponse {\n const jobs = chain\n ? this.fundJobs.has(chain)\n ? [{ ...this.fundJobs.get(chain)! }]\n : []\n : [...this.fundJobs.values()].map((j) => ({ ...j }));\n return { jobs };\n }\n\n /** Full registry of relay + apex targets with per-target status. */\n getTargets(): TargetsResponse {\n const relays: RelayTargetStatus[] = [...this.relays.entries()].map(\n ([relayUrl, r]) => ({\n relayUrl,\n connected: r.isConnected(),\n buffered: r.bufferedCount(),\n subscriptions: r.activeSubscriptions(),\n isDefault: relayUrl === this.defaultRelayUrl,\n })\n );\n const apexes: ApexTargetStatus[] = [...this.apexes.values()].map((a) => ({\n btpUrl: a.btpUrl,\n destination: a.destination,\n chain: a.chain,\n ready: a.ready,\n bootstrapping: a.bootstrapping,\n ...(a.apexChannelId ? { channelId: a.apexChannelId } : {}),\n ...(a.lastError ? { lastError: a.lastError } : {}),\n isDefault: a.isDefault,\n }));\n return { relays, apexes };\n }\n\n // ── Paid operations ──────────────────────────────────────────────────────\n\n /**\n * Lazily open the apex channel on first paid write (deferred at bootstrap so\n * the wallet can be funded after start, #69) and persist it for resume.\n */\n private async ensureApexChannel(\n apex: ApexConnection,\n destination?: string\n ): Promise<string> {\n let channelId = apex.apexChannelId;\n if (!channelId) {\n channelId = await apex.client.openChannel(destination);\n if (!destination || destination === apex.destination) {\n apex.apexChannelId = channelId;\n this.persistApexChannel(apex, channelId);\n }\n }\n return channelId;\n }\n\n /** Pay-to-write a single event through the selected (or default) apex. */\n async publish(req: PublishRequest): Promise<PublishResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n const channelId = await this.ensureApexChannel(apex, req.destination);\n const fee = req.fee !== undefined ? BigInt(req.fee) : apex.feePerEvent;\n const claim = await apex.client.signBalanceProof(channelId, fee);\n // Relay writes default to the configured publish destination (e.g.\n // g.proxy.relay) — NOT the apex anchor, which on the devnet proxy is\n // g.proxy.relay.store and would forward a /write to the store (→ 404). An\n // explicit per-call destination still wins. The claim is pre-signed on the\n // apex channel, so the destination is pure routing (settlement is unaffected).\n const result = await apex.client.publishEvent(req.event, {\n destination: req.destination ?? this.config.publishDestination,\n claim,\n ilpAmount: fee,\n });\n if (!result.success) {\n throw new PublishRejectedError(result.error ?? 'relay rejected event');\n }\n return {\n eventId: result.eventId ?? req.event.id,\n ...(result.data !== undefined ? { data: result.data } : {}),\n channelId,\n nonce: apex.client.getChannelNonce(channelId),\n feePaid: fee.toString(),\n channelBalanceAfter: this.channelAvailable(apex, channelId),\n };\n }\n\n /**\n * Available (spendable) balance for a channel after a write — locked collateral\n * minus cumulative spent, clamped at 0. Same math as {@link getChannels}; used\n * to report a truthful post-write balance in publish/upload receipts. Returns\n * undefined if the channel isn't tracked on this apex (balance unknown).\n */\n private channelAvailable(\n apex: ApexConnection,\n channelId: string\n ): string | undefined {\n if (!apex.client.getTrackedChannels().includes(channelId)) return undefined;\n const cumulative = apex.client.getChannelCumulativeAmount(channelId);\n const depositTotal = apex.client.getChannelDepositTotal(channelId);\n const available =\n depositTotal > cumulative ? depositTotal - cumulative : 0n;\n return available.toString();\n }\n\n /**\n * Build, sign (with the daemon-held key), and pay-to-write an event. The\n * caller supplies only the event shell; the private key never leaves the\n * daemon. Payloads are MODEL-AUTHORED → validated server-side here (the model\n * is not a security boundary). Replaceable kinds (0/3) merge the latest known\n * event's tags before signing.\n */\n async publishUnsigned(req: PublishUnsignedRequest): Promise<PublishResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n const template = this.buildTemplate(apex, req);\n const signed = await apex.client.signEvent(template);\n return this.publish({\n event: signed,\n ...(req.destination ? { destination: req.destination } : {}),\n ...(req.fee ? { fee: req.fee } : {}),\n ...(req.btpUrl ? { btpUrl: req.btpUrl } : {}),\n });\n }\n\n /**\n * Upload media to Arweave (kind:5094 blob DVM, single-packet) then sign+publish\n * a media event referencing the resulting URL. One spendy operation, two steps,\n * entirely server-side.\n */\n async uploadMedia(req: UploadMediaRequest): Promise<UploadMediaResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n // Source the bytes from EXACTLY ONE of inline base64 or an on-disk path.\n // `filePath` lets agent callers skip materializing the whole payload as a\n // tool argument (it never touches the model context); `dataBase64` stays for\n // back-compat. Both-or-neither is a payload error.\n const hasData = typeof req.dataBase64 === 'string' && req.dataBase64 !== '';\n const hasPath = typeof req.filePath === 'string' && req.filePath !== '';\n if (hasData === hasPath) {\n throw new InvalidPayloadError(\n 'exactly one of dataBase64 (base64 media bytes) | filePath (absolute path) is required.'\n );\n }\n const blobData = hasPath\n ? await this.readUploadFile(req.filePath as string)\n : new Uint8Array(Buffer.from(req.dataBase64 as string, 'base64'));\n const fee = req.fee !== undefined ? BigInt(req.fee) : apex.feePerEvent;\n // ── Leg 1: Arweave blob upload ──────────────────────────────────────────\n // Blob storage terminates at the store/DVM backend (POST /store → Arweave),\n // so it routes to the configured store destination (e.g. g.proxy.store,\n // derived from the `….relay.store` anchor by #143). This makes uploads work\n // via the default apex without the caller hand-passing a store `btpUrl`. A\n // failure here is distinct from the kind:1-equivalent publish below; label\n // it so the UI/agent can tell the upload leg apart from the publish leg.\n const upload = await apex.client.uploadBlob({\n blobData,\n destination: this.config.storeDestination,\n ...(req.mime ? { contentType: req.mime } : {}),\n ilpAmount: fee,\n });\n if (!upload.success || !upload.txId) {\n throw new PublishRejectedError(\n `Arweave upload leg failed (store ${this.config.storeDestination}): ${upload.error ?? 'blob upload rejected'}`\n );\n }\n const { url, fallbacks } = arweaveUrls(\n upload.txId,\n this.config.arweaveGateways\n );\n const kind = req.kind ?? 1063;\n const signed = await apex.client.signEvent({\n kind,\n created_at: nowSeconds(),\n tags: this.buildMediaTags(kind, url, fallbacks, req),\n content: req.caption ?? '',\n });\n // ── Leg 2: publish the NIP-94/NIP-68 reference event ────────────────────\n // The reference event is a normal Nostr write, so it must publish through a\n // RELAY apex, not the store/DVM. `this.publish` routes it to the configured\n // publish destination (e.g. g.proxy.relay) — the exact path #143 made kind:1\n // work. Omit `btpUrl` so it uses the default (relay) apex. Label any failure\n // here as the post-upload publish leg (the blob already stored OK).\n let pub: PublishResponse;\n try {\n pub = await this.publish({\n event: signed,\n ...(req.fee ? { fee: req.fee } : {}),\n });\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new PublishRejectedError(\n `kind:${kind} publish leg failed after upload (blob stored at ${url}): ${detail}`\n );\n }\n // An upload pays TWICE — the blob (leg 1) and the reference event (leg 2) —\n // so the truthful total is the sum, not just the publish leg's fee. The\n // post-write balance from the (last) publish leg is already current.\n const feePaid = (fee + BigInt(pub.feePaid)).toString();\n return { ...pub, feePaid, url, txId: upload.txId };\n }\n\n /**\n * Read media bytes off disk for an upload `filePath`. The path is resolved\n * and, when an `uploadAllowedRoot` is configured, must resolve inside it —\n * bounding which filesystem locations the daemon reads on an agent's behalf.\n * A missing/unreadable file (or an out-of-bounds path) surfaces as an\n * `InvalidPayloadError` (HTTP 400), not an unhandled crash.\n */\n private async readUploadFile(filePath: string): Promise<Uint8Array> {\n const resolved = resolve(filePath);\n const root = this.config.uploadAllowedRoot;\n if (root && resolved !== root && !resolved.startsWith(root + sep)) {\n throw new InvalidPayloadError(\n `filePath must resolve inside the configured upload root (${root}).`\n );\n }\n try {\n const buf = await readFile(resolved);\n return new Uint8Array(buf);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new InvalidPayloadError(\n `failed to read filePath ${resolved}: ${detail}`\n );\n }\n }\n\n /** Validate + assemble a signable event template (with replaceable merge). */\n private buildTemplate(\n apex: ApexConnection,\n req: PublishUnsignedRequest\n ): EventTemplate {\n if (!Number.isInteger(req.kind) || req.kind < 0 || req.kind > 65535) {\n throw new InvalidPayloadError('kind must be an integer in [0, 65535].');\n }\n if (req.content !== undefined && typeof req.content !== 'string') {\n throw new InvalidPayloadError('content must be a string.');\n }\n const tags = normalizeTags(req.tags);\n const content = req.content ?? '';\n\n // Replaceable kinds: merge the latest known self-authored event so a single\n // \"follow X\" / profile edit doesn't clobber prior tags. Best-effort from the\n // read buffer (v1 — concurrent edits can still race; see plan risk #6).\n if (req.kind === 0 || req.kind === 3) {\n const prior = this.latestSelfReplaceable(apex, req.kind);\n if (prior) {\n return {\n kind: req.kind,\n created_at: nowSeconds(),\n tags: mergeTags(prior.tags, tags),\n content: content !== '' ? content : prior.content,\n };\n }\n }\n return { kind: req.kind, created_at: nowSeconds(), tags, content };\n }\n\n /** Latest self-authored event of `kind` currently in the merged read buffer. */\n private latestSelfReplaceable(\n apex: ApexConnection,\n kind: number\n ): NostrEvent | undefined {\n const pubkey = safe(() => apex.client.getPublicKey());\n if (!pubkey) return undefined;\n let latest: NostrEvent | undefined;\n for (const m of this.merged) {\n if (m.event.kind !== kind || m.event.pubkey !== pubkey) continue;\n if (!latest || m.event.created_at > latest.created_at) latest = m.event;\n }\n return latest;\n }\n\n /**\n * Tags for a published media event referencing an Arweave URL. `url` is the\n * primary gateway; `fallbacks` are mirror URLs for the same tx id on other\n * gateways, emitted so readers can fail over if the primary is unreachable.\n */\n private buildMediaTags(\n kind: number,\n url: string,\n fallbacks: string[],\n req: UploadMediaRequest\n ): string[][] {\n const mime = req.mime ?? 'application/octet-stream';\n const extra = normalizeTags(req.tags);\n if (kind === 1063) {\n // NIP-94 file metadata: separate url/m tags, mirrors as `fallback` tags.\n return [\n ['url', url],\n ['m', mime],\n ...fallbacks.map((f) => ['fallback', f]),\n ...extra,\n ];\n }\n // NIP-68/71 picture/video + NIP-92 inline note: a single `imeta` tag with\n // the primary `url` first and the remaining gateways as `fallback` mirrors.\n return [\n [\n 'imeta',\n `url ${url}`,\n `m ${mime}`,\n ...fallbacks.map((f) => `fallback ${f}`),\n ],\n ...extra,\n ];\n }\n\n /** Open (or return) a payment channel on the selected (or default) apex. */\n async openChannel(\n destination?: string,\n btpUrl?: string\n ): Promise<{ channelId: string }> {\n const apex = this.selectApex(btpUrl);\n this.assertApexReady(apex);\n const channelId = await apex.client.openChannel(\n destination ?? apex.destination\n );\n if (!destination || destination === apex.destination) {\n const firstOpen = apex.apexChannelId !== channelId;\n apex.apexChannelId = channelId;\n // Persist the (possibly lazily-opened) apex channel for restart-resume.\n if (firstOpen) this.persistApexChannel(apex, channelId);\n }\n return { channelId };\n }\n\n /** List tracked channels across ALL apexes with nonce + cumulative amount. */\n getChannels(): ChannelsResponse {\n const seen = new Set<string>();\n const channels: ChannelsResponse['channels'] = [];\n for (const apex of this.apexes.values()) {\n for (const channelId of apex.client.getTrackedChannels()) {\n if (seen.has(channelId)) continue;\n seen.add(channelId);\n const cumulative = apex.client.getChannelCumulativeAmount(channelId);\n const depositTotal = apex.client.getChannelDepositTotal(channelId);\n // Available (spendable) balance = locked collateral − cumulative spent.\n // Clamp at 0 so an over-spend estimate never surfaces as negative.\n const available =\n depositTotal > cumulative ? depositTotal - cumulative : 0n;\n const settleableAt = apex.client.getSettleableAt(channelId);\n channels.push({\n channelId,\n nonce: apex.client.getChannelNonce(channelId),\n cumulativeAmount: cumulative.toString(),\n depositTotal: depositTotal.toString(),\n availableBalance: available.toString(),\n closeState: apex.client.getChannelCloseState(channelId),\n ...(settleableAt !== undefined\n ? { settleableAt: settleableAt.toString() }\n : {}),\n });\n }\n }\n return { channels };\n }\n\n /**\n * On-chain wallet balances. The wallet is identity-level (same keys across\n * apexes), so this reads from the daemon's {@link identityClient} — NOT an apex\n * — and therefore works even with zero apexes / no payment peer configured\n * (reading your own balance is a pure wallet-keys + chain-RPC operation).\n * Per-chain reads are best-effort inside the client (a failing chain is simply\n * omitted).\n *\n * Each underlying read hits per-chain RPC providers that can stall\n * indefinitely on devnet (a provider being `detail: \"configured\"` in\n * toon_status means it is WIRED, not that its RPC is live). A stall here used\n * to block the whole control request until the client aborted, surfacing as a\n * misleading \"relay/apex unreachable\" timeout (#199). Bound each attempt well\n * under the control API timeout and retry once so a single transient\n * provider stall FAST-FAILS with an honest \"balances handler / provider\n * stalled\" error instead of hanging.\n */\n async getBalances(): Promise<BalancesResponse> {\n let lastErr: unknown;\n for (let attempt = 1; attempt <= BALANCES_READ_ATTEMPTS; attempt++) {\n try {\n const balances = (await withTimeout(\n this.identityClient.getBalances(),\n BALANCES_READ_TIMEOUT_MS,\n `chain balance read timed out after ${BALANCES_READ_TIMEOUT_MS}ms`\n )) as BalanceInfo[];\n return { balances };\n } catch (err) {\n lastErr = err;\n }\n }\n throw new BalancesUnavailableError(\n `the balances control handler's chain RPC/provider read did not return ` +\n `(${BALANCES_READ_ATTEMPTS} attempts, ${BALANCES_READ_TIMEOUT_MS}ms each) — ` +\n `the on-chain provider stalled, not the relay or apex. Retry shortly.`,\n lastErr instanceof Error ? lastErr.message : undefined\n );\n }\n\n /**\n * Deposit additional collateral into an open channel. Routes to the apex whose\n * client tracks the channel (each apex client opens/tracks its own channels);\n * the client signs its own on-chain tx.\n */\n async depositToChannel(\n req: ChannelDepositRequest\n ): Promise<ChannelDepositResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.depositToChannel(req.channelId, req.amount)\n );\n }\n\n /** Close a channel to begin the settlement grace period (withdraw, step 1). */\n async closeChannel(req: CloseChannelRequest): Promise<CloseChannelResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.closeChannel(req.channelId)\n );\n }\n\n /**\n * Settle a closed channel to release collateral (withdraw, step 2). The client\n * enforces the `now >= settleableAt` guard and throws a retryable error if\n * called early; `mapError` maps that to HTTP 425.\n */\n async settleChannel(\n req: SettleChannelRequest\n ): Promise<SettleChannelResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.settleChannel(req.channelId)\n );\n }\n\n /** Run `fn` against the apex client that tracks `channelId`, else throw. */\n private async withTrackingApex<T>(\n channelId: string,\n fn: (client: ApexConnection['client']) => Promise<T>\n ): Promise<T> {\n for (const apex of this.apexes.values()) {\n if (apex.client.getTrackedChannels().includes(channelId)) {\n return fn(apex.client);\n }\n }\n throw new Error(`Channel \"${channelId}\" is not tracked by any apex.`);\n }\n\n /**\n * Swap source→target asset against a swap peer via the selected apex.\n *\n * sdk ≥2.0.0 (the `mill`→`swap` vocabulary rename, toon commit `af4cd24`):\n * `streamSwap` takes `swapPubkey`/`swapIlpAddress` and accumulated claims\n * carry `swapSignerAddress`. The rename has NO wire back-compat — a\n * pre-rename (sdk ≤1.x) swap peer still emits `millSignerAddress` in its\n * FULFILL settlement metadata, which `decodeFulfillMetadata` silently drops\n * as an unknown field. That skew would otherwise surface only much later as\n * `MISSING_SETTLEMENT_METADATA` in `buildSettlementTx`, so we detect it\n * here (accepted claims with no `swapSignerAddress`) and surface a loud\n * `warning` on the response at swap time (#349).\n *\n * With `req.senderConditions` set, every swap packet is sent with a FRESH\n * sender-minted execution condition (`C_i = sha256(P_i)`, one per packet —\n * toon-client#350, rolling-swap spec §3 R1/R2) and the transport verifies\n * each FULFILL's preimage; a mismatch counts the packet failed. This\n * requires a maker/connector implementing the sender-chosen fulfillment\n * contract (connector#309) — the deployed claim-issuing mill cannot satisfy\n * it — so it is opt-in and the default stays the legacy zero condition.\n *\n * Sender-side rolling-swap defenses (#351, sdk ≥2.1.0, spec §5/§6):\n *\n * - **Hard floor** — `req.minExchangeRate`, or derived\n * `pair.rate × (1 − floorBps/10000)` from `req.floorBps` /\n * `swapDefaults.floorBps`. A below-floor packet records `BELOW_FLOOR` and\n * halts the stream (`abortReason: 'below-floor'`); the armed floor is\n * echoed on the response so hosts can show the guaranteed worst case.\n * - **Adaptive controller** — `req.controller` (or\n * `swapDefaults.controller` when the request pins no `packetCount`)\n * replaces the static even split with `AdaptiveDeltaController` δ/W\n * sizing; per-(source chain, maker, pair) state persists in\n * `swap-controller-state.json` beside the daemon's channel stores. The\n * controller is efficiency-only — it can never relax the floor.\n * - **Telemetry** — `onPacket` is always wired: per-packet outcomes,\n * rejections, and a realized-rate summary land on the response, and each\n * accepted packet is logged. Everything else is strictly opt-in: with no\n * new params and no `swapDefaults`, the `streamSwap` call is the legacy\n * request (no floor, no controller, no expiry stamping, no signal).\n * - **Abort** — `req.timeoutMs` arms an `AbortSignal`; on expiry in-flight\n * packets drain and the partial fill is reported exactly (partial\n * `claims`, cumulatives, `state`/`abortReason`).\n */\n async swap(req: SwapRequest): Promise<SwapResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n if (req.controller && req.packetCount !== undefined) {\n throw new InvalidPayloadError(\n '`controller` and `packetCount` are mutually exclusive: the adaptive ' +\n 'controller replaces the static even split with dynamic δ/W sizing.'\n );\n }\n const defaults = this.config.swapDefaults;\n // Floor precedence: explicit rate → per-request bps → daemon-default bps.\n const minExchangeRate =\n req.minExchangeRate ??\n deriveFloorRate(req.pair.rate, req.floorBps ?? defaults?.floorBps);\n const packetExpiryMs = req.packetExpiryMs ?? defaults?.packetExpiryMs;\n // Controller precedence: per-request params → daemon default — but an\n // explicit packetCount on the request always pins the legacy even split.\n const controllerParams =\n req.controller ??\n (req.packetCount === undefined ? defaults?.controller : undefined);\n const controller = controllerParams\n ? await this.createSwapController(req, controllerParams)\n : undefined;\n\n // Per-packet telemetry: collected for the response and logged. The\n // callback must never throw — streamSwap treats a throwing onPacket as a\n // stop signal, and telemetry must not be able to halt the stream.\n const packets: SwapPacketOutcome[] = [];\n let packetsTruncated = false;\n const onPacket = (p: PacketProgress): void => {\n try {\n this.log(\n `[runner] swap packet ${p.index}: ${p.sourceAmount} → ` +\n `${p.targetAmount} (rate ${p.effectiveRate.toFixed(6)}, ` +\n `deviation ${p.rateDeviation.toFixed(6)}` +\n (p.rate ? `, tape ${p.rate}` : '') +\n ')'\n );\n if (packets.length >= SWAP_PACKETS_RESPONSE_LIMIT) {\n packetsTruncated = true;\n return;\n }\n packets.push({\n index: p.index,\n sourceAmount: p.sourceAmount.toString(),\n targetAmount: p.targetAmount.toString(),\n effectiveRate: p.effectiveRate,\n rateDeviation: p.rateDeviation,\n ...(p.rate !== undefined ? { rate: p.rate } : {}),\n ...(p.rateTimestamp !== undefined\n ? { rateTimestamp: p.rateTimestamp }\n : {}),\n });\n } catch {\n // Swallow: telemetry failures must not stop the stream.\n }\n };\n\n const senderSecretKey = generateSecretKey();\n // Session-scoped preimage retention (#360): populated only on the\n // sender-chosen path, consumed by the leg-B reveal in `ingestAndReveal`.\n const preimages = new InMemoryPreimageRetentionStore();\n const swapClient = req.senderConditions\n ? this.withSenderConditions(apex.client, preimages)\n : apex.client;\n const result = await streamSwap({\n client: swapClient as unknown as Parameters<\n typeof streamSwap\n >[0]['client'],\n swapPubkey: req.swapPubkey,\n swapIlpAddress: req.destination,\n pair: req.pair,\n senderSecretKey,\n chainRecipient: req.chainRecipient,\n totalAmount: BigInt(req.amount),\n // EXACTLY ONE of controller / packetCount (sdk contract).\n ...(controller ? { controller } : { packetCount: req.packetCount ?? 1 }),\n onPacket,\n ...(minExchangeRate !== undefined ? { minExchangeRate } : {}),\n ...(packetExpiryMs !== undefined ? { packetExpiryMs } : {}),\n ...(req.timeoutMs !== undefined\n ? { signal: AbortSignal.timeout(req.timeoutMs) }\n : {}),\n });\n const firstReject = result.rejections[0];\n\n // #352/#360: receipt-time verification + durable ingestion composed\n // ATOMICALLY with the leg-B reveal. Every FULFILLed claim with settlement\n // metadata is verified (signature against the maker's advertised/pinned\n // signer, recipient, chain, nonce/cumulative monotonicity vs the persisted\n // watermark) and, only if it passes, persisted as the channel's\n // highest-nonce watermark; the sender then reveals the retained preimage\n // `P_i` to commit leg A. If the reveal is withheld or fails, the watermark\n // advance is ROLLED BACK so it tracks only revealed packets (engine R8: the\n // maker reuses the rolled-back nonce for the next fill, which must not be\n // falsely rejected as non-monotonic). A claim that fails verification is\n // NEVER revealed and NEVER counted. Claims MISSING settlement metadata take\n // the legacy #349 path (warning, not persisted) unchanged.\n //\n // In the deployed single-round-trip model the FULFILL already resolved\n // leg A inline at `sendSwapPacket` time (the transport verified `P_i`), so\n // the reveal here commits every verified claim — this wires the atomic seam\n // and the retained preimages without withholding. The withhold/rollback\n // branch is exercised by a maker-driven leg-B (spec §3.2) and covered by\n // `ingestAndReveal`'s unit tests.\n const expectedChain = req.pair.to.chain;\n const minaSignerClient = expectedChain.startsWith('mina')\n ? await loadMinaSignerClient()\n : undefined;\n const reveal: RevealFn = () => ({ decision: 'revealed' });\n const ingest = await ingestAndReveal({\n claims: result.claims,\n expectedChain,\n chainRecipient: req.chainRecipient,\n ...(req.swapSignerAddress\n ? { expectedSignerAddress: req.swapSignerAddress }\n : {}),\n // v2 EIP-712 receive-side verify (#365): EVM claims are domain-separated\n // over `(chainId, verifyingContract)`, so the receive path needs the same\n // `tokenNetworks` (chain key → RollingSwapChannel address) map the settle\n // path already threads, or an EVM claim is rejected MISSING_CHAIN_CONFIG.\n ...(this.config.toonClientConfig.tokenNetworks\n ? { tokenNetworks: this.config.toonClientConfig.tokenNetworks }\n : {}),\n store: this.receivedClaimStore,\n ...(minaSignerClient ? { minaSignerClient } : {}),\n preimages,\n reveal,\n });\n preimages.clear();\n const verifiedSet = new Set(ingest.revealed.map((v) => v.claim));\n const rejectionByClaim = new Map(\n ingest.rejected.map((r) => [r.claim, r] as const)\n );\n for (const r of ingest.rejected) {\n this.log(\n `[runner] swap: REJECTED received claim (packet ${r.claim.packetIndex}, ` +\n `channel ${r.claim.channelId ?? '?'}): ${r.code} — ${r.message}`\n );\n }\n\n const claims = result.claims.map((c): SwapClaim => {\n const rejection = rejectionByClaim.get(c);\n return {\n sourceAmount: c.sourceAmount.toString(),\n targetAmount: c.targetAmount.toString(),\n claim: Buffer.from(c.claimBytes).toString('base64'),\n ...(c.channelId ? { channelId: c.channelId } : {}),\n ...(c.recipient ? { recipient: c.recipient } : {}),\n ...(c.swapSignerAddress\n ? { swapSignerAddress: c.swapSignerAddress }\n : {}),\n ...(c.claimId ? { claimId: c.claimId } : {}),\n ...(c.nonce ? { nonce: c.nonce } : {}),\n ...(c.cumulativeAmount ? { cumulativeAmount: c.cumulativeAmount } : {}),\n ...(verifiedSet.has(c) ? { verified: true } : {}),\n ...(rejection\n ? {\n verified: false,\n verificationError: {\n code: rejection.code,\n message: rejection.message,\n },\n }\n : {}),\n };\n });\n\n // Wire-rename skew guard (#349): claims were FULFILLed but none carries\n // the swapSignerAddress settlement metadata — the signature of a\n // pre-rename swap peer (emits `millSignerAddress`, silently dropped by\n // sdk ≥2's decodeFulfillMetadata). Settlement of these claims WILL fail\n // with MISSING_SETTLEMENT_METADATA; say so now instead of then.\n const missingSettlementSigner =\n claims.length > 0 && claims.every((c) => !c.swapSignerAddress);\n if (missingSettlementSigner) {\n this.log(\n '[runner] swap: accepted claims are missing swapSignerAddress ' +\n 'settlement metadata — swap peer is likely pre-rename (sdk <2.0.0)'\n );\n }\n const realizedRate = computeRealizedRate(\n result.cumulativeSource,\n result.cumulativeTarget,\n req.pair\n );\n const warnings: string[] = [];\n if (missingSettlementSigner) {\n warnings.push(\n 'Accepted claims are missing `swapSignerAddress` settlement ' +\n 'metadata, so settling them will fail with ' +\n 'MISSING_SETTLEMENT_METADATA. The swap peer is likely running ' +\n 'a pre-rename SDK (<2.0.0, emits `millSignerAddress`, which ' +\n 'sdk ≥2 silently drops). Upgrade the swap peer before settling.'\n );\n }\n const firstRejected = ingest.rejected[0];\n if (firstRejected) {\n warnings.push(\n `${ingest.rejected.length} received claim(s) FAILED verification and ` +\n `were NOT counted as value received (first: ${firstRejected.code} — ` +\n `${firstRejected.message}). See per-claim verificationError.`\n );\n }\n\n const hadIngestibleClaims =\n ingest.revealed.length + ingest.rejected.length > 0;\n return {\n // A swap only counts as accepted when it yielded a VERIFIED+REVEALED\n // claim (or a legacy no-metadata claim, whose path is unchanged).\n // FULFILLed packets whose claims all failed verification are a failed\n // swap, loudly.\n accepted: ingest.revealed.length + ingest.legacy.length > 0,\n packetsAccepted: result.claims.length,\n claims,\n cumulativeSource: result.cumulativeSource.toString(),\n cumulativeTarget: result.cumulativeTarget.toString(),\n state: result.state,\n abortReason: result.abortReason,\n ...(packets.length > 0 ? { packets } : {}),\n ...(packetsTruncated ? { packetsTruncated } : {}),\n ...(result.rejections.length > 0\n ? {\n rejections: result.rejections.map((r) => ({\n packetIndex: r.packetIndex,\n sourceAmount: r.sourceAmount.toString(),\n code: r.code,\n message: r.message,\n })),\n }\n : {}),\n ...(realizedRate !== undefined ? { realizedRate } : {}),\n ...(minExchangeRate !== undefined ? { minExchangeRate } : {}),\n ...(firstReject\n ? { code: firstReject.code, message: firstReject.message }\n : {}),\n ...(warnings.length > 0 ? { warning: warnings.join('\\n') } : {}),\n ...(hadIngestibleClaims\n ? {\n claimsVerified: ingest.revealed.length,\n claimsRejected: ingest.rejected.length,\n valueReceived: ingest.valueRevealed.toString(),\n }\n : {}),\n };\n }\n\n // ── Received swap claims: persistence + settlement surfaces (#352) ─────────\n\n /** List the persisted received-claim watermarks (`GET /swap/claims`). */\n listSwapClaims(): ListSwapClaimsResponse {\n return {\n claims: this.receivedClaimStore.list().map(toReceivedClaimInfo),\n };\n }\n\n /**\n * Build (and, where chain plumbing is configured, submit) on-chain\n * settlements for persisted received claims (`POST /swap/settle`).\n *\n * Per (chain, channelId) the persisted entry IS the highest-nonce\n * watermark, so N received advances redeem as ONE close. Result-shaped\n * throughout: a channel that cannot build or submit reports `error` instead\n * of failing the batch. Submission is the env-gated seam — it requires the\n * identity client's EVM plumbing plus `chainRpcUrls[chain]`; without them\n * the built (re-verified) tx is returned as `unsignedTx` for an external\n * signer. Solana submission and the Mina receive-side co-sign path are\n * explicit follow-ups (spec §9 dependency 2).\n */\n async settleSwapClaims(\n req: SettleSwapClaimsRequest = {}\n ): Promise<SettleSwapClaimsResponse> {\n const submit = req.submit !== false;\n const entries = this.receivedClaimStore\n .list()\n .filter((e) => (req.chain ? e.chain === req.chain : true))\n .filter((e) => (req.channelId ? e.channelId === req.channelId : true));\n\n const results: SwapSettlementResult[] = [];\n const pending: ReceivedClaimEntry[] = [];\n for (const entry of entries) {\n if (\n entry.settledNonce !== undefined &&\n entry.settledNonce >= entry.nonce\n ) {\n results.push({\n chain: entry.chain,\n channelId: entry.channelId,\n built: false,\n submitted: false,\n error: {\n code: 'ALREADY_SETTLED',\n message: `watermark nonce ${entry.nonce} was already settled (tx ${entry.settleTxHash ?? 'unknown'})`,\n },\n });\n continue;\n }\n pending.push(entry);\n }\n\n const minaSignerClient = pending.some((e) => e.chain.startsWith('mina'))\n ? await loadMinaSignerClient()\n : undefined;\n const builds = buildSwapSettlements({\n entries: pending,\n ...(this.config.toonClientConfig.tokenNetworks\n ? { tokenNetworks: this.config.toonClientConfig.tokenNetworks }\n : {}),\n // Re-verify the stored watermark's signature at settle time\n // (defense-in-depth over the store file). The published v2 sdk\n // (`@toon-protocol/sdk@^3`) verifies EVM claims against the SAME v2\n // EIP-712 domain-separated digest the receive-side used (#365), so a\n // valid v2 signature verifies correctly here — `buildSwapSettlements`\n // threads `chainId` + `verifyingContract` (from `tokenNetworks`) into the\n // sdk signer config so the EIP-712 domain is reconstructed.\n verifySignatures: true,\n ...(minaSignerClient ? { minaSignerClient } : {}),\n });\n\n for (const [i, build] of builds.entries()) {\n const entry = pending[i];\n if (!entry) continue; // builds is index-aligned with pending\n if (!build.bundle) {\n results.push({\n chain: build.chain,\n channelId: build.channelId,\n built: false,\n submitted: false,\n ...(build.error ? { error: build.error } : {}),\n });\n continue;\n }\n const bundle = build.bundle;\n const base: SwapSettlementResult = {\n chain: build.chain,\n channelId: build.channelId,\n built: true,\n submitted: false,\n nonce: bundle.nonce,\n cumulativeAmount: bundle.cumulativeAmount,\n unsignedTx: Buffer.from(bundle.unsignedTxBytes).toString('base64'),\n };\n if (!submit) {\n results.push(base);\n continue;\n }\n if (bundle.chainKind === 'solana') {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNSUPPORTED',\n message:\n 'Solana settlement submission is not wired yet (bundle carries a serialized Message; follow-up under toon-meta#145).',\n },\n });\n continue;\n }\n if (bundle.chainKind === 'mina') {\n // Mina receive-side redemption (#357): the client produces the\n // recipient's co-signature and drives the dual-party `claimFromChannel`\n // (o1js proving) via `settleSwapBundle`. Fails closed with a stable\n // MinaSettlementError code (e.g. NO_GRAPHQL_CONFIGURED,\n // MINA_MAKER_COSIGN_REQUIRED) surfaced as SUBMISSION_FAILED.\n if (!this.identityClient.settleSwapBundle) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNAVAILABLE',\n message: 'The active client does not implement settleSwapBundle.',\n },\n });\n continue;\n }\n try {\n const submitted = await this.identityClient.settleSwapBundle(bundle);\n const latest =\n this.receivedClaimStore.load(entry.chain, entry.channelId) ?? entry;\n this.receivedClaimStore.save({\n ...latest,\n settledAt: Date.now(),\n settledNonce: BigInt(bundle.nonce),\n settleTxHash: submitted.txHash,\n });\n this.log(\n `[runner] swap settle: submitted ${bundle.chain}/${bundle.channelId} ` +\n `nonce ${bundle.nonce} cumulative ${bundle.cumulativeAmount} → ${submitted.txHash}`\n );\n results.push({\n ...base,\n submitted: true,\n txHash: submitted.txHash,\n ...(submitted.status ? { txStatus: submitted.status } : {}),\n });\n } catch (err) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n continue;\n }\n const rpcUrl = this.config.toonClientConfig.chainRpcUrls?.[bundle.chain];\n if (!rpcUrl) {\n results.push({\n ...base,\n error: {\n code: 'NO_RPC_CONFIGURED',\n message: `No RPC URL configured for \"${bundle.chain}\" (chainRpcUrls) — returning the built tx unsubmitted.`,\n },\n });\n continue;\n }\n if (!this.identityClient.settleSwapBundle) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNAVAILABLE',\n message: 'The active client does not implement settleSwapBundle.',\n },\n });\n continue;\n }\n try {\n const submitted = await this.identityClient.settleSwapBundle(bundle);\n // Mark the watermark settled so a re-run skips it.\n const latest =\n this.receivedClaimStore.load(entry.chain, entry.channelId) ?? entry;\n this.receivedClaimStore.save({\n ...latest,\n settledAt: Date.now(),\n settledNonce: BigInt(bundle.nonce),\n settleTxHash: submitted.txHash,\n });\n this.log(\n `[runner] swap settle: submitted ${bundle.chain}/${bundle.channelId} ` +\n `nonce ${bundle.nonce} cumulative ${bundle.cumulativeAmount} → ${submitted.txHash}`\n );\n results.push({\n ...base,\n submitted: true,\n txHash: submitted.txHash,\n ...(submitted.status ? { txStatus: submitted.status } : {}),\n });\n } catch (err) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n }\n return { results };\n }\n\n /**\n * Wrap an apex client so each `sendSwapPacket` call carries a freshly\n * minted sender-chosen execution condition (one preimage per packet, spec\n * R1 — reuse would let an observer of packet *i* fulfill packet *i+1*).\n * `streamSwap` calls `sendSwapPacket` once per packet, in strictly\n * increasing `packetIndex` order, so minting here yields exactly one\n * condition per packet and the local counter tracks `packetIndex`. The\n * transports verify each FULFILL's preimage against the condition and fail\n * the packet on mismatch.\n *\n * Preimage retention (toon-client#360, spec §3.2 leg-B reveal): every minted\n * `P_i` is retained in `preimages`, keyed by `packetIndex` — the identifier\n * shared with `AccumulatedClaim.packetIndex` — so the receive-side reveal\n * step (`ingestAndReveal`) can correlate and consume the preimage for the\n * claim it commits. Retention is session-scoped (one store per `swap()`\n * call); a fresh stream mints fresh preimages.\n */\n private withSenderConditions(\n client: ToonClientLike,\n preimages: PreimageRetentionStore\n ): ToonClientLike {\n const wrapped: ToonClientLike = Object.create(client) as ToonClientLike;\n let packetIndex = 0;\n wrapped.sendSwapPacket = (params) => {\n const { preimage, condition } = mintExecutionCondition();\n preimages.retain({\n packetIndex: packetIndex++,\n preimage,\n condition,\n retainedAt: Date.now(),\n });\n return client.sendSwapPacket({\n ...params,\n executionCondition: condition,\n });\n };\n return wrapped;\n }\n\n /**\n * Build the adaptive δ/W controller for one swap session (#351, spec §6).\n * State is keyed per-(source chain, maker, pair) and persisted in the\n * daemon's data dir via the sdk's atomic JSON-file store, so ramp/trust\n * survives across swaps and daemon restarts.\n */\n private async createSwapController(\n req: SwapRequest,\n params: SwapControllerParams\n ): Promise<AdaptiveDeltaController> {\n if (\n typeof params.advertisedSpread !== 'number' ||\n !(params.advertisedSpread > 0)\n ) {\n throw new InvalidPayloadError(\n 'controller.advertisedSpread must be a positive fraction (e.g. ' +\n '0.004 = 40 bps): ε is denominated off the half-spread and the sdk ' +\n 'deliberately has no default.'\n );\n }\n const store = new JsonFileSwapControllerStateStore(\n this.swapControllerStatePath()\n );\n return AdaptiveDeltaController.create({\n makerPubkey: req.swapPubkey,\n pair: req.pair,\n advertisedSpread: params.advertisedSpread,\n ...(params.maxPacketAmount !== undefined\n ? { maxPacketAmount: BigInt(params.maxPacketAmount) }\n : {}),\n ...(params.minPacketAmount !== undefined\n ? { minPacketAmount: BigInt(params.minPacketAmount) }\n : {}),\n ...(params.maxWindow !== undefined\n ? { maxWindow: params.maxWindow }\n : {}),\n ...(params.cleanStreakLength !== undefined\n ? { cleanStreakLength: params.cleanStreakLength }\n : {}),\n ...(params.coldStartDivisor !== undefined\n ? { coldStartDivisor: params.coldStartDivisor }\n : {}),\n ...(params.ewmaAlpha !== undefined\n ? { ewmaAlpha: params.ewmaAlpha }\n : {}),\n store,\n });\n }\n\n /**\n * Controller-state file path: resolved config value, or the same\n * `<configDir>` the other daemon stores live in (`channels.json`,\n * `apex-channels.json`) for manually-built configs.\n */\n private swapControllerStatePath(): string {\n return (\n this.config.swapControllerStatePath ??\n join(configDir(), 'swap-controller-state.json')\n );\n }\n\n /**\n * Payment-aware HTTP fetch through an apex's client. The client issues the\n * request and, on `402 Payment Required`, pays over TOON and retries; we\n * translate the resulting Web `Response` into the wire envelope.\n */\n async httpFetchPaid(\n req: HttpFetchPaidRequest\n ): Promise<HttpFetchPaidResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const res = await apex.client.h402Fetch(req.url, {\n ...(req.method ? { method: req.method } : {}),\n ...(req.headers ? { headers: req.headers } : {}),\n ...(req.body !== undefined ? { body: req.body } : {}),\n ...(req.timeout !== undefined ? { timeout: req.timeout } : {}),\n });\n return {\n status: res.status,\n headers: Object.fromEntries(res.headers.entries()),\n body: await res.text(),\n };\n }\n\n // ── Git write path (/git/*, epic #222 ticket #227) ────────────────────────\n\n /**\n * The daemon `Publisher` implementation (see @toon-protocol/rig) for one\n * apex. Maps the interface onto the runner's production paid-write\n * machinery:\n *\n * - `getFeeRates`: flat `apex.feePerEvent` per publish + the network\n * per-byte upload rate.\n * - `uploadGitObject`: kind:5094 store write with Git-SHA/Git-Type/Repo\n * tags (the proven seed-pipeline shape), signed with the daemon key,\n * paid via signBalanceProof on the apex channel, routed to the store\n * destination (`POST /store`); the Arweave txId is decoded from the\n * FULFILL HTTP envelope.\n * - `publishEvent`: sign with the daemon key + the standard paid publish\n * path (signBalanceProof → publishEvent → feePaid). The daemon owns its\n * write routing (config-seeded relay via the apex), so the advisory\n * `relayUrls` list is not consulted here — remote-state reads DO use it.\n */\n private gitPublisher(apex: ApexConnection): Publisher {\n return {\n getFeeRates: async () => ({\n uploadFeePerByte: UPLOAD_FEE_PER_BYTE,\n eventFee: apex.feePerEvent,\n }),\n uploadGitObject: (upload) => this.gitUploadObject(apex, upload),\n publishEvent: (event) => this.gitPublishEvent(apex, event),\n };\n }\n\n /** Upload one git object body as a paid kind:5094 store write. */\n private async gitUploadObject(\n apex: ApexConnection,\n upload: GitObjectUpload\n ): Promise<UploadReceipt> {\n const channelId = await this.ensureApexChannel(apex);\n const fee = BigInt(upload.body.length) * UPLOAD_FEE_PER_BYTE;\n const claim = await apex.client.signBalanceProof(channelId, fee);\n const signed = await apex.client.signEvent({\n kind: 5094,\n content: '',\n tags: [\n ['i', upload.body.toString('base64'), 'blob'],\n ['bid', fee.toString(), 'usdc'],\n ['output', 'application/octet-stream'],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n created_at: nowSeconds(),\n });\n const result = await apex.client.publishEvent(signed, {\n destination: this.config.storeDestination,\n claim,\n ilpAmount: fee,\n // The store/DVM backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload failed (store ` +\n `${this.config.storeDestination}): ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.data) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload FULFILL carried no data — expected the Arweave tx ID`\n );\n }\n let txId: string;\n try {\n txId = extractArweaveTxId(result.data);\n } catch (err) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload: ${errMsg(err)}`\n );\n }\n return { txId, feePaid: fee };\n }\n\n /** Sign (daemon key) + pay-to-publish one NIP-34 event via the apex. */\n private async gitPublishEvent(\n apex: ApexConnection,\n event: UnsignedEvent\n ): Promise<PublishReceipt> {\n const signed = await apex.client.signEvent(event);\n const pub = await this.publish({\n event: signed,\n ...(apex.btpUrl ? { btpUrl: apex.btpUrl } : {}),\n });\n return { eventId: pub.eventId, feePaid: BigInt(pub.feePaid) };\n }\n\n /**\n * Plan a push: read the local repo + the remote NIP-34 state, classify ref\n * updates, compute the object delta, and price it. Shared by\n * estimate (returns the plan) and push (executes it).\n */\n private async planGitPush(\n apex: ApexConnection,\n req: GitEstimateRequest\n ): Promise<{\n plan: PushPlan;\n remoteState: RemoteState;\n repoReader: GitRepoReader;\n relayUrls: string[];\n publisher: Publisher;\n }> {\n await assertRepoPath(req.repoPath);\n if (typeof req.repoId !== 'string' || req.repoId === '') {\n throw new InvalidPayloadError('repoId is required.');\n }\n const relayUrls =\n req.relayUrls && req.relayUrls.length > 0\n ? req.relayUrls\n : [this.defaultRelayUrl];\n // Pushes publish kind:30617/30618 signed by the daemon key, so the daemon\n // identity IS the repo owner whose remote state we read.\n const ownerPubkey = apex.client.getPublicKey();\n const repoReader = this.createRepoReader(req.repoPath);\n const remoteState = await this.fetchGitRemoteState({\n relayUrls,\n ownerPubkey,\n repoId: req.repoId,\n });\n const publisher = this.gitPublisher(apex);\n const feeRates = await publisher.getFeeRates();\n const plan = await planPush({\n repoReader,\n remoteState,\n feeRates,\n repoId: req.repoId,\n ...(req.refspecs !== undefined ? { refs: req.refspecs } : {}),\n ...(req.force !== undefined ? { force: req.force } : {}),\n ...(req.announcement !== undefined\n ? { announcement: req.announcement }\n : {}),\n });\n return { plan, remoteState, repoReader, relayUrls, publisher };\n }\n\n /** Plan + price a push WITHOUT paying (backs `POST /git/estimate`). */\n async gitEstimate(req: GitEstimateRequest): Promise<GitEstimateResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const { plan } = await this.planGitPush(apex, req);\n return serializePushPlan(plan);\n }\n\n /** Plan + EXECUTE a push: paid uploads + paid publishes (`POST /git/push`). */\n async gitPush(req: GitPushRequest): Promise<GitPushResponse> {\n if (req.confirm !== true) {\n throw new InvalidPayloadError(\n 'a push uploads objects to Arweave and publishes events — permanent ' +\n 'and paid. Run /git/estimate first, then set confirm: true to proceed.'\n );\n }\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const { plan, remoteState, repoReader, relayUrls, publisher } =\n await this.planGitPush(apex, req);\n const result = await executePush({\n plan,\n publisher,\n remoteState,\n repoReader,\n relayUrls,\n });\n return serializePushResult(plan, result);\n }\n\n /** Build, sign, and pay-to-publish a kind:1621 issue. */\n async gitIssue(req: GitIssueRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.title, 'title');\n assertNonEmptyString(req.body, 'body');\n const event = buildIssue(\n addr.ownerPubkey,\n addr.repoId,\n req.title,\n req.body,\n req.labels ?? []\n );\n return this.gitPublishSigned(event);\n }\n\n /** Build, sign, and pay-to-publish a kind:1622 comment on an issue/patch. */\n async gitComment(req: GitCommentRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.rootEventId, 'rootEventId');\n assertNonEmptyString(req.body, 'body');\n const event = buildComment(\n addr.ownerPubkey,\n addr.repoId,\n req.rootEventId,\n req.parentAuthorPubkey ?? addr.ownerPubkey,\n req.body,\n req.marker ?? 'root'\n );\n return this.gitPublishSigned(event);\n }\n\n /**\n * Build, sign, and pay-to-publish a kind:1617 patch. Content is either the\n * supplied `patchText` or real `git format-patch --stdout <range>` output\n * from a local repository — exactly one source must be given.\n */\n async gitPatch(req: GitPatchRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.title, 'title');\n const hasText = typeof req.patchText === 'string' && req.patchText !== '';\n const hasRange =\n typeof req.repoPath === 'string' &&\n req.repoPath !== '' &&\n typeof req.range === 'string' &&\n req.range !== '';\n if (hasText === hasRange) {\n throw new InvalidPayloadError(\n 'exactly one of patchText | repoPath+range is required.'\n );\n }\n let content: string;\n if (hasRange) {\n await assertRepoPath(req.repoPath as string);\n content = await this.createRepoReader(req.repoPath as string).formatPatch(\n req.range as string\n );\n if (content === '') {\n throw new InvalidPayloadError(\n `range ${JSON.stringify(req.range)} selects no commits — nothing to publish.`\n );\n }\n } else {\n content = req.patchText as string;\n }\n const event = buildPatch(\n addr.ownerPubkey,\n addr.repoId,\n req.title,\n req.commits ?? [],\n req.branch,\n content,\n // PR body → `description` tag; never the content (git am safety, #280).\n typeof req.description === 'string' && req.description !== ''\n ? req.description\n : undefined\n );\n return this.gitPublishSigned(event);\n }\n\n /** Build, sign, and pay-to-publish a kind:1630-1633 status event. */\n async gitStatus(req: GitStatusRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.targetEventId, 'targetEventId');\n const kind = STATUS_KIND_BY_VALUE[req.status];\n if (kind === undefined) {\n throw new InvalidPayloadError(\n 'status must be one of open | applied | closed | draft.'\n );\n }\n const event = buildStatus(req.targetEventId, kind, req.targetPubkey);\n // NIP-34 status events also carry the repo `a` tag so readers can scope\n // a status stream to the repository without resolving the target first.\n event.tags.push([\n 'a',\n `${REPOSITORY_ANNOUNCEMENT_KIND}:${addr.ownerPubkey}:${addr.repoId}`,\n ]);\n return this.gitPublishSigned(event);\n }\n\n /** Sign a built NIP-34 event with the daemon key and pay-to-publish it. */\n private async gitPublishSigned(\n event: UnsignedEvent\n ): Promise<GitEventResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const signed = await apex.client.signEvent(event);\n const pub = await this.publish({ event: signed });\n return { ...pub, kind: event.kind };\n }\n\n /** Graceful teardown: close every relay + stop every apex client. */\n async stop(): Promise<void> {\n if (this.stopped) return;\n this.stopped = true;\n for (const relay of this.relays.values()) relay.close();\n for (const apex of this.apexes.values()) {\n try {\n await apex.client.stop();\n } catch (err) {\n this.log(`[runner] client stop error (${apex.btpUrl}): ${errMsg(err)}`);\n }\n }\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private selectApex(btpUrl?: string): ApexConnection {\n if (btpUrl) {\n const apex = this.apexes.get(btpUrl);\n if (!apex) throw new TargetError(`No such apex: ${btpUrl}`);\n return apex;\n }\n const def = this.defaultApex();\n if (!def) throw new NotReadyError('No apex configured.');\n return def;\n }\n\n private assertApexReady(apex: ApexConnection): void {\n // FREE reads need no uplink; a write does. Reject early with an actionable\n // message rather than letting the apex sit forever un-bootstrapped (#69).\n if (!this.config.hasUplink) {\n throw new TargetError(\n 'No write uplink configured — this daemon is read-only. Set ' +\n 'TOON_CLIENT_PROXY_URL (connector proxy) or TOON_CLIENT_BTP_URL to ' +\n 'enable paid writes.'\n );\n }\n if (!apex.ready) {\n throw new NotReadyError(\n apex.bootstrapping\n ? 'Apex is still bootstrapping (transport/channel coming up) — retry shortly.'\n : (apex.lastError ?? 'Apex is not ready.')\n );\n }\n }\n}\n\n/** Thrown by paid-write operations while the target apex is not yet ready. */\nexport class NotReadyError extends Error {\n readonly retryable = true;\n constructor(message: string) {\n super(message);\n this.name = 'NotReadyError';\n }\n}\n\n/** Thrown when the relay/connector rejects a paid write. */\nexport class PublishRejectedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PublishRejectedError';\n }\n}\n\n/** Thrown for invalid target add/remove/select operations (maps to HTTP 400/404). */\nexport class TargetError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TargetError';\n }\n}\n\n/** Thrown when a model-authored publish/upload payload fails validation (HTTP 400). */\nexport class InvalidPayloadError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidPayloadError';\n }\n}\n\n/**\n * Thrown when the on-chain balance read stalls past its per-call provider\n * timeout (after the bounded retry). Retryable, and explicitly attributed to the\n * balances handler / chain provider — NOT the relay/apex — so the user-facing\n * message names the real failing subsystem (#199). Maps to HTTP 504.\n */\nexport class BalancesUnavailableError extends Error {\n readonly retryable = true;\n /** The underlying provider error message, when one was captured. */\n readonly providerError?: string;\n constructor(message: string, providerError?: string) {\n super(message);\n this.name = 'BalancesUnavailableError';\n if (providerError !== undefined) this.providerError = providerError;\n }\n}\n\n/**\n * Upload price per git-object body byte, micro-USDC. Matches the network\n * default `basePricePerByte` (10) the ToonClient prices writes with and the\n * seed pipeline bids (`bytes × 10`); the bid tag, the signed claim, and the\n * ILP amount all use this same figure so the pre-push estimate is exactly\n * what a push pays.\n */\nconst UPLOAD_FEE_PER_BYTE = 10n;\n\n/** NIP-34 status kinds by wire value (`GitStatusRequest.status`). */\nconst STATUS_KIND_BY_VALUE: Record<string, StatusKind> = {\n open: STATUS_OPEN_KIND,\n applied: STATUS_APPLIED_KIND,\n closed: STATUS_CLOSED_KIND,\n draft: STATUS_DRAFT_KIND,\n};\n\n/** Validate that `repoPath` names an existing directory (a git repo check\n * proper happens on first plumbing call — a non-repo dir surfaces as a\n * GitError the routes map to 400). */\nasync function assertRepoPath(repoPath: unknown): Promise<void> {\n if (typeof repoPath !== 'string' || repoPath === '') {\n throw new InvalidPayloadError('repoPath is required.');\n }\n let stats;\n try {\n stats = await stat(resolve(repoPath));\n } catch {\n throw new InvalidPayloadError(`repoPath does not exist: ${repoPath}`);\n }\n if (!stats.isDirectory()) {\n throw new InvalidPayloadError(`repoPath is not a directory: ${repoPath}`);\n }\n}\n\nfunction assertNonEmptyString(value: unknown, what: string): void {\n if (typeof value !== 'string' || value === '') {\n throw new InvalidPayloadError(`${what} is required.`);\n }\n}\n\n/** Validate a NIP-34 repo address (owner pubkey + repo id). */\nfunction validateRepoAddr(addr: GitRepoAddr | undefined): GitRepoAddr {\n if (\n !addr ||\n typeof addr.ownerPubkey !== 'string' ||\n !/^[0-9a-f]{64}$/.test(addr.ownerPubkey)\n ) {\n throw new InvalidPayloadError(\n 'repoAddr.ownerPubkey must be a 64-char lowercase hex Nostr pubkey.'\n );\n }\n if (typeof addr.repoId !== 'string' || addr.repoId === '') {\n throw new InvalidPayloadError('repoAddr.repoId is required.');\n }\n return addr;\n}\n\n/** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */\nfunction serializePushPlan(plan: PushPlan): GitEstimateResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n newRefs: plan.newRefs,\n headSymref: plan.headSymref,\n objects: plan.objects,\n knownShaToTxId: Object.fromEntries(plan.knownShaToTxId),\n announceNeeded: plan.announceNeeded,\n announcement: plan.announcement,\n estimate: serializeFeeEstimate(plan),\n };\n}\n\nfunction serializeFeeEstimate(plan: PushPlan): GitFeeEstimate {\n return {\n objectCount: plan.estimate.objectCount,\n totalObjectBytes: plan.estimate.totalObjectBytes,\n uploadFee: plan.estimate.uploadFee.toString(),\n eventCount: plan.estimate.eventCount,\n eventFees: plan.estimate.eventFees.toString(),\n totalFee: plan.estimate.totalFee.toString(),\n };\n}\n\n/** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */\nfunction serializePushResult(\n plan: PushPlan,\n result: PushResult\n): GitPushResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n uploads: result.uploads.map((u) => ({\n sha: u.sha,\n txId: u.txId,\n feePaid: u.feePaid.toString(),\n skipped: u.skipped,\n })),\n announceReceipt: result.announceReceipt\n ? {\n eventId: result.announceReceipt.eventId,\n feePaid: result.announceReceipt.feePaid.toString(),\n }\n : null,\n refsReceipt: {\n eventId: result.refsReceipt.eventId,\n feePaid: result.refsReceipt.feePaid.toString(),\n },\n arweaveMap: Object.fromEntries(result.arweaveMap),\n totalFeePaid: result.totalFeePaid.toString(),\n estimate: serializeFeeEstimate(plan),\n };\n}\n\n/** Current time in whole seconds (Nostr `created_at` unit). */\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** Resolve after `ms` (bounded wait for relay delivery in `query`). */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Race a promise against a timeout, rejecting with `message` if it does not\n * settle in `ms`. The underlying work is NOT cancelled (it may complete in the\n * background) — this just bounds how long the caller waits, so a stalled chain\n * RPC fast-fails instead of blocking the control request (#199).\n */\nfunction withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n message: string\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(message)), ms);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n });\n}\n\n/** NIP-01 filter match (kinds/authors/ids/since/until + `#<letter>` tag filters). */\nfunction matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n if (filter.ids && !filter.ids.includes(event.id)) return false;\n if (filter.kinds && !filter.kinds.includes(event.kind)) return false;\n if (filter.authors && !filter.authors.includes(event.pubkey)) return false;\n if (filter.since !== undefined && event.created_at < filter.since)\n return false;\n if (filter.until !== undefined && event.created_at > filter.until)\n return false;\n for (const [key, values] of Object.entries(filter)) {\n if (!key.startsWith('#') || !Array.isArray(values)) continue;\n const letter = key.slice(1);\n const hit = event.tags.some(\n (t) => t[0] === letter && t[1] !== undefined && values.includes(t[1])\n );\n if (!hit) return false;\n }\n return true;\n}\n\n/** Validate that `raw` is an array of string arrays, returning it typed. */\nfunction normalizeTags(raw: unknown): string[][] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw))\n throw new InvalidPayloadError('tags must be an array.');\n return raw.map((tag, i) => {\n if (!Array.isArray(tag) || !tag.every((x) => typeof x === 'string')) {\n throw new InvalidPayloadError(`tags[${i}] must be an array of strings.`);\n }\n return tag as string[];\n });\n}\n\n/** Append `additions` to `base`, de-duping whole tags (for replaceable merges). */\nfunction mergeTags(base: string[][], additions: string[][]): string[][] {\n const seen = new Set(base.map((t) => JSON.stringify(t)));\n const out = [...base];\n for (const tag of additions) {\n const key = JSON.stringify(tag);\n if (!seen.has(key)) {\n seen.add(key);\n out.push(tag);\n }\n }\n return out;\n}\n\nfunction safe<T>(fn: () => T): T | undefined {\n try {\n return fn();\n } catch {\n return undefined;\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Filesystem-safe slug for a per-apex channel-store filename. */\nfunction sanitize(s: string): string {\n return s\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n}\n\n/**\n * Cap on per-packet outcomes echoed on a `SwapResponse`. Adaptive sizing can\n * schedule an unbounded packet count (δ_min defaults to 1 micro-unit); the\n * cumulative totals stay exact, only the per-packet echo is truncated.\n */\nconst SWAP_PACKETS_RESPONSE_LIMIT = 500;\n\n/**\n * Derive the hard floor from the advertised rate (#351, rolling-swap spec §5:\n * `minExchangeRate = R₀ × (1 − tolerance)`): exact decimal-string\n * `rate × (10000 − floorBps) / 10000` — no float round-trip, so the floor is\n * bit-stable for the sdk's BigInt comparison. Returns `undefined` when no\n * tolerance is configured. Exported for tests.\n */\nexport function deriveFloorRate(\n rate: string,\n floorBps: number | undefined\n): string | undefined {\n if (floorBps === undefined) return undefined;\n if (!Number.isInteger(floorBps) || floorBps < 0 || floorBps >= 10_000) {\n throw new InvalidPayloadError(\n `floorBps must be an integer in [0, 10000), got ${String(floorBps)}.`\n );\n }\n const m = /^(\\d+)(?:\\.(\\d+))?$/.exec(rate.trim());\n if (!m) {\n throw new InvalidPayloadError(\n `pair.rate \"${rate}\" is not a plain positive decimal — cannot derive ` +\n 'a floor from floorBps; pass minExchangeRate explicitly.'\n );\n }\n const [, intDigits = '', fracDigits = ''] = m;\n const digits = intDigits + fracDigits;\n const scale = fracDigits.length + 4; // ×(10000−bps) adds 4 decimal places\n const scaled = (BigInt(digits) * BigInt(10_000 - floorBps))\n .toString()\n .padStart(scale + 1, '0');\n const intPart = scaled.slice(0, -scale);\n const fracPart = scaled.slice(-scale).replace(/0+$/, '');\n return fracPart ? `${intPart}.${fracPart}` : intPart;\n}\n\n/**\n * Realized-rate summary: delivered/spent in WHOLE units, adjusted for the\n * pair's asset scales (display-only `number`, same convention as the sdk's\n * `PacketProgress.effectiveRate`). `undefined` when nothing was filled.\n */\nfunction computeRealizedRate(\n cumulativeSource: bigint,\n cumulativeTarget: bigint,\n pair: SwapRequest['pair']\n): number | undefined {\n if (cumulativeSource <= 0n) return undefined;\n return (\n (Number(cumulativeTarget) / Number(cumulativeSource)) *\n 10 ** (pair.from.assetScale - pair.to.assetScale)\n );\n}\n\n/** Map a persisted received-claim entry onto the wire shape (#352). */\nfunction toReceivedClaimInfo(e: ReceivedClaimEntry): ReceivedClaimInfo {\n return {\n chain: e.chain,\n channelId: e.channelId,\n nonce: e.nonce.toString(),\n cumulativeAmount: e.cumulativeAmount.toString(),\n recipient: e.recipient,\n swapSignerAddress: e.swapSignerAddress,\n receivedAt: e.receivedAt,\n updatedAt: e.updatedAt,\n ...(e.settledAt !== undefined ? { settledAt: e.settledAt } : {}),\n ...(e.settledNonce !== undefined\n ? { settledNonce: e.settledNonce.toString() }\n : {}),\n ...(e.settleTxHash !== undefined ? { settleTxHash: e.settleTxHash } : {}),\n };\n}\n","/**\n * NIP-34: Git Stuff\n * https://github.com/nostr-protocol/nips/blob/master/34.md\n *\n * Event kinds for decentralized code collaboration via Nostr.\n */\n\n/**\n * Repository Announcement (kind 30617)\n * Replaceable event announcing a Git repository's existence.\n * Contains clone URLs, maintainers, and metadata.\n */\nexport const REPOSITORY_ANNOUNCEMENT_KIND = 30617;\n\n/**\n * Patch (kind 1617)\n * Direct patch submission for files under 60KB.\n * Contains git format-patch output and commit metadata.\n */\nexport const PATCH_KIND = 1617;\n\n/**\n * Pull Request (kind 1618)\n * Larger submissions or branch-based changes.\n * References remote repository and commit range.\n */\nexport const PULL_REQUEST_KIND = 1618;\n\n/**\n * Pull Request Status Update (kind 1619)\n * Updates to existing pull requests.\n */\nexport const PR_STATUS_UPDATE_KIND = 1619;\n\n/**\n * Issue (kind 1621)\n * Bug reports and feature requests in Markdown format.\n */\nexport const ISSUE_KIND = 1621;\n\n/**\n * Status: Open (kind 1630)\n */\nexport const STATUS_OPEN_KIND = 1630;\n\n/**\n * Status: Applied/Merged (kind 1631)\n */\nexport const STATUS_APPLIED_KIND = 1631;\n\n/**\n * Status: Closed (kind 1632)\n */\nexport const STATUS_CLOSED_KIND = 1632;\n\n/**\n * Status: Draft (kind 1633)\n */\nexport const STATUS_DRAFT_KIND = 1633;\n\n/**\n * All NIP-34 event kinds\n */\nexport const NIP34_EVENT_KINDS = [\n REPOSITORY_ANNOUNCEMENT_KIND,\n PATCH_KIND,\n PULL_REQUEST_KIND,\n PR_STATUS_UPDATE_KIND,\n ISSUE_KIND,\n STATUS_OPEN_KIND,\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n] as const;\n\n/**\n * Check if an event kind is a NIP-34 event\n */\nexport function isNIP34Event(kind: number): boolean {\n return (NIP34_EVENT_KINDS as readonly number[]).includes(kind);\n}\n","import type { Event as NostrEvent } from 'nostr-tools/pure';\n\n/**\n * Helper to get tag value by name\n */\nexport function getTag(event: NostrEvent, tagName: string): string | undefined {\n return event.tags.find((t) => t[0] === tagName)?.[1];\n}\n\n/**\n * Helper to get all tag values by name\n */\nexport function getTags(event: NostrEvent, tagName: string): string[] {\n return event.tags\n .filter((t) => t[0] === tagName)\n .map((t) => t[1])\n .filter((v): v is string => v !== undefined);\n}\n\n/**\n * Repository Announcement Event (Kind 30617)\n */\nexport interface RepositoryAnnouncement extends NostrEvent {\n kind: 30617;\n tags: (\n | ['d', string] // repository identifier\n | ['name', string] // human-readable name\n | ['description', string] // repository description\n | ['web', string] // browsing URL\n | ['clone', string] // clone URL\n | ['relays', ...string[]] // preferred relays\n | ['r', string, 'euc'] // earliest unique commit\n | ['maintainers', ...string[]]\n )[];\n}\n\n/**\n * Patch Event (Kind 1617)\n */\nexport interface PatchEvent extends NostrEvent {\n kind: 1617;\n content: string; // git format-patch output\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['r', string] // earliest unique commit\n | ['p', string] // repository owner pubkey\n | ['commit', string] // current commit SHA\n | ['parent-commit', string] // parent commit SHA\n | ['commit-pgp-sig', string] // optional PGP signature\n | ['committer', string] // optional committer info\n | ['t', 'root' | 'reply']\n )[];\n}\n\n/**\n * Pull Request Event (Kind 1618)\n */\nexport interface PullRequestEvent extends NostrEvent {\n kind: 1618;\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['r', string] // earliest unique commit\n | ['p', string] // repository owner pubkey\n | ['clone', string] // contributor's clone URL\n | ['c', string] // commit tip SHA\n | ['merge-base', string] // merge base commit\n | ['subject', string] // PR title\n | ['t', 'root' | 'reply']\n )[];\n}\n\n/**\n * Issue Event (Kind 1621)\n */\nexport interface IssueEvent extends NostrEvent {\n kind: 1621;\n content: string; // Markdown issue body\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['p', string] // repository owner pubkey\n | ['subject', string] // issue title\n | ['t', string]\n )[];\n}\n\n/**\n * Status Event (Kinds 1630-1633)\n */\nexport interface StatusEvent extends NostrEvent {\n kind: 1630 | 1631 | 1632 | 1633; // open, applied, closed, draft\n tags: (\n | ['e', string] // event being updated (patch, PR, issue)\n | ['p', string]\n )[];\n}\n\n/**\n * Union type of all NIP-34 events\n */\nexport type NIP34Event =\n | RepositoryAnnouncement\n | PatchEvent\n | PullRequestEvent\n | IssueEvent\n | StatusEvent;\n\n/**\n * Parse repository reference from 'a' tag\n * Format: \"30617:pubkey:repo-id\"\n */\nexport interface RepositoryReference {\n kind: 30617;\n pubkey: string;\n repoId: string;\n}\n\nexport function parseRepositoryReference(aTag: string): RepositoryReference {\n const parts = aTag.split(':');\n if (parts.length !== 3 || parts[0] !== '30617' || !parts[1] || !parts[2]) {\n throw new Error(`Invalid repository reference format: ${aTag}`);\n }\n return {\n kind: 30617,\n pubkey: parts[1],\n repoId: parts[2],\n };\n}\n\n/**\n * Extract commit message from git format-patch content\n */\nexport function extractCommitMessage(patchContent: string): string {\n const lines = patchContent.split('\\n');\n const subjectLine = lines.find((line) => line.startsWith('Subject:'));\n if (!subjectLine) {\n return 'Applied patch from Nostr';\n }\n // Remove \"Subject: [PATCH]\" prefix\n return subjectLine\n .replace(/^Subject:\\s*\\[PATCH\\]\\s*/, '')\n .replace(/^Subject:\\s*/, '');\n}\n","/**\n * Forgejo API Client\n *\n * Wrapper around Forgejo REST API for repository and issue management.\n * Uses fetch API for HTTP requests (compatible with gitea-js SDK structure).\n */\n\nexport interface ForgejoConfig {\n /** Base URL of Forgejo instance (e.g., \"http://forgejo:3000\") */\n baseUrl: string;\n /** API token for authentication */\n token: string;\n /** Default owner/organization for repositories */\n defaultOwner?: string;\n}\n\nexport interface CreateRepositoryOptions {\n name: string;\n description?: string;\n private?: boolean;\n auto_init?: boolean;\n default_branch?: string;\n}\n\nexport interface CreatePullRequestOptions {\n owner: string;\n repo: string;\n title: string;\n head: string; // source branch\n base: string; // target branch\n body?: string;\n}\n\nexport interface CreateIssueOptions {\n owner: string;\n repo: string;\n title: string;\n body?: string;\n labels?: number[]; // label IDs\n}\n\nexport interface ForgejoRepository {\n id: number;\n name: string;\n full_name: string;\n description: string;\n html_url: string;\n clone_url: string;\n ssh_url: string;\n}\n\nexport interface ForgejoPullRequest {\n id: number;\n number: number;\n title: string;\n html_url: string;\n state: 'open' | 'closed';\n}\n\nexport interface ForgejoIssue {\n id: number;\n number: number;\n title: string;\n html_url: string;\n state: 'open' | 'closed';\n}\n\nexport interface CreateOrUpdateFileOptions {\n owner: string;\n repo: string;\n filepath: string;\n content: string; // base64 encoded\n message: string;\n branch?: string;\n sha?: string; // required for updates\n}\n\nexport interface ForgejoFileResponse {\n content: {\n name: string;\n path: string;\n sha: string;\n };\n commit: {\n sha: string;\n };\n}\n\n/**\n * Forgejo API Client\n */\nexport class ForgejoClient {\n private baseUrl: string;\n private token: string;\n private defaultOwner?: string;\n\n constructor(config: ForgejoConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.token = config.token;\n this.defaultOwner = config.defaultOwner;\n }\n\n /**\n * Make an authenticated API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}/api/v1${path}`;\n const headers: Record<string, string> = {\n Authorization: `token ${this.token}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Forgejo API error (${response.status}): ${errorText}`);\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Create a new repository\n */\n async createRepository(\n options: CreateRepositoryOptions\n ): Promise<ForgejoRepository> {\n const owner = this.defaultOwner;\n if (!owner) {\n throw new Error('No default owner configured for repository creation');\n }\n\n return this.request<ForgejoRepository>('POST', '/user/repos', {\n name: options.name,\n description: options.description || '',\n private: options.private ?? false,\n auto_init: options.auto_init ?? true,\n default_branch: options.default_branch || 'main',\n });\n }\n\n /**\n * Create a pull request\n */\n async createPullRequest(\n options: CreatePullRequestOptions\n ): Promise<ForgejoPullRequest> {\n return this.request<ForgejoPullRequest>(\n 'POST',\n `/repos/${options.owner}/${options.repo}/pulls`,\n {\n title: options.title,\n head: options.head,\n base: options.base,\n body: options.body || '',\n }\n );\n }\n\n /**\n * Create an issue\n */\n async createIssue(options: CreateIssueOptions): Promise<ForgejoIssue> {\n return this.request<ForgejoIssue>(\n 'POST',\n `/repos/${options.owner}/${options.repo}/issues`,\n {\n title: options.title,\n body: options.body || '',\n labels: options.labels,\n }\n );\n }\n\n /**\n * Get repository information\n */\n async getRepository(owner: string, repo: string): Promise<ForgejoRepository> {\n return this.request<ForgejoRepository>('GET', `/repos/${owner}/${repo}`);\n }\n\n /**\n * Check if repository exists\n */\n async repositoryExists(owner: string, repo: string): Promise<boolean> {\n try {\n await this.getRepository(owner, repo);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get clone URL for internal use (within Docker network)\n */\n getInternalCloneUrl(owner: string, repo: string): string {\n // Use internal Docker network URL\n return `${this.baseUrl}/${owner}/${repo}.git`;\n }\n\n /**\n * Get clone URL for external use\n */\n getExternalCloneUrl(repo: ForgejoRepository): string {\n return repo.clone_url;\n }\n\n /**\n * Create or update a file in a repository\n */\n async createOrUpdateFile(\n options: CreateOrUpdateFileOptions\n ): Promise<ForgejoFileResponse> {\n const path = `/repos/${options.owner}/${options.repo}/contents/${options.filepath}`;\n const body: Record<string, unknown> = {\n content: options.content,\n message: options.message,\n };\n\n if (options.branch) {\n body['branch'] = options.branch;\n }\n if (options.sha) {\n body['sha'] = options.sha;\n }\n\n // Use PUT for updates (when SHA provided), POST for creates\n const method = options.sha ? 'PUT' : 'POST';\n return this.request<ForgejoFileResponse>(method, path, body);\n }\n\n /**\n * Create a new branch\n */\n async createBranch(\n owner: string,\n repo: string,\n branchName: string,\n fromBranch = 'main'\n ): Promise<void> {\n // Create the new branch from the source branch\n // Forgejo API: POST /repos/{owner}/{repo}/branches\n await this.request('POST', `/repos/${owner}/${repo}/branches`, {\n new_branch_name: branchName,\n old_branch_name: fromBranch,\n });\n }\n\n /**\n * Get file content from repository\n */\n async getFileContent(\n owner: string,\n repo: string,\n filepath: string,\n branch?: string\n ): Promise<{ content: string; sha: string } | null> {\n try {\n const path = `/repos/${owner}/${repo}/contents/${filepath}${branch ? `?ref=${branch}` : ''}`;\n const response = await this.request<{\n content: string;\n sha: string;\n }>('GET', path);\n return response;\n } catch {\n return null;\n }\n }\n}\n","/**\n * NIP-34 Handler\n *\n * Processes NIP-34 events (Git stuff on Nostr) and executes corresponding\n * Git operations on a Forgejo instance.\n *\n * Flow:\n * 1. TOON receives NIP-34 event via ILP payment\n * 2. BLS validates payment and stores event\n * 3. BLS calls NIP34Handler.handleEvent()\n * 4. Handler maps event to Git operation\n * 5. Operation executes on Forgejo\n */\n\nimport type { Event as NostrEvent } from 'nostr-tools/pure';\nimport {\n ForgejoClient,\n type CreateRepositoryOptions,\n} from './ForgejoClient.js';\nimport {\n isNIP34Event,\n REPOSITORY_ANNOUNCEMENT_KIND,\n PATCH_KIND,\n PULL_REQUEST_KIND,\n ISSUE_KIND,\n} from './constants.js';\nimport {\n getTag,\n parseRepositoryReference,\n extractCommitMessage,\n type NIP34Event,\n} from './types.js';\n\nexport interface NIP34Config {\n /** Forgejo base URL (e.g., \"http://forgejo:3000\") */\n forgejoUrl: string;\n /** Forgejo API token */\n forgejoToken: string;\n /** Default owner/org for repositories */\n defaultOwner: string;\n /** Git commit identity configuration */\n gitConfig?: {\n userName: string;\n userEmail: string;\n };\n /** Enable verbose logging */\n verbose?: boolean;\n}\n\nexport interface HandleEventResult {\n success: boolean;\n operation:\n | 'repository'\n | 'patch'\n | 'pull_request'\n | 'issue'\n | 'status'\n | 'unsupported';\n message: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * NIP-34 Event Handler\n *\n * Maps Nostr events to Git operations on Forgejo.\n */\nexport class NIP34Handler {\n private forgejo: ForgejoClient;\n private verbose: boolean;\n private defaultOwner: string;\n\n constructor(config: NIP34Config) {\n this.forgejo = new ForgejoClient({\n baseUrl: config.forgejoUrl,\n token: config.forgejoToken,\n defaultOwner: config.defaultOwner,\n });\n\n this.defaultOwner = config.defaultOwner;\n this.verbose = config.verbose ?? false;\n }\n\n /**\n * Handle a NIP-34 event\n *\n * This is the main entry point called by the BLS after storing an event.\n */\n async handleEvent(event: NostrEvent): Promise<HandleEventResult> {\n // Check if this is a NIP-34 event\n if (!isNIP34Event(event.kind)) {\n return {\n success: false,\n operation: 'unsupported',\n message: `Not a NIP-34 event (kind ${event.kind})`,\n };\n }\n\n this.log(\n `Handling NIP-34 event: kind=${event.kind} id=${event.id.substring(0, 8)}`\n );\n\n try {\n switch (event.kind) {\n case REPOSITORY_ANNOUNCEMENT_KIND:\n return await this.handleRepositoryAnnouncement(event as NIP34Event);\n\n case PATCH_KIND:\n return await this.handlePatch(event as NIP34Event);\n\n case PULL_REQUEST_KIND:\n return await this.handlePullRequest(event as NIP34Event);\n\n case ISSUE_KIND:\n return await this.handleIssue(event as NIP34Event);\n\n default:\n // Status events (1630-1633) - not yet implemented\n return {\n success: true,\n operation: 'status',\n message: `Status event kind ${event.kind} received (not yet implemented)`,\n };\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.log(`Error handling event: ${message}`);\n return {\n success: false,\n operation: this.getOperationType(event.kind),\n message: `Failed to process event: ${message}`,\n };\n }\n }\n\n /**\n * Handle Repository Announcement (kind 30617)\n *\n * Creates a new repository in Forgejo.\n */\n private async handleRepositoryAnnouncement(\n event: NIP34Event\n ): Promise<HandleEventResult> {\n const repoId = getTag(event, 'd');\n const name = getTag(event, 'name') || repoId;\n const description = getTag(event, 'description');\n\n if (!repoId) {\n return {\n success: false,\n operation: 'repository',\n message: 'Missing required \"d\" tag (repository identifier)',\n };\n }\n\n // Extract just the repository name (remove owner prefix if present)\n // e.g., \"admin/repo-name\" -> \"repo-name\"\n const repoName = repoId.includes('/')\n ? (repoId.split('/').pop() ?? repoId)\n : repoId;\n\n this.log(`Creating repository: ${repoName}`);\n\n const options: CreateRepositoryOptions = {\n name: repoName,\n description: description || name,\n private: false,\n auto_init: true,\n };\n\n const repo = await this.forgejo.createRepository(options);\n\n this.log(`Repository created: ${repo.html_url}`);\n\n return {\n success: true,\n operation: 'repository',\n message: `Repository \"${repoName}\" created`,\n metadata: {\n repoId: repoName,\n htmlUrl: repo.html_url,\n cloneUrl: repo.clone_url,\n },\n };\n }\n\n /**\n * Handle Patch (kind 1617)\n *\n * Applies a patch to a repository via Forgejo API and creates a pull request.\n */\n private async handlePatch(event: NIP34Event): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const patchContent = event.content;\n\n if (!aTag) {\n return {\n success: false,\n operation: 'patch',\n message: 'Missing required \"a\" tag (repository reference)',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n // Check if repository exists\n const exists = await this.forgejo.repositoryExists(owner, repoName);\n if (!exists) {\n return {\n success: false,\n operation: 'patch',\n message: `Repository ${owner}/${repoName} does not exist`,\n };\n }\n\n this.log(`Applying patch to ${owner}/${repoName}`);\n\n // Parse patch to extract file changes\n const patchInfo = this.parsePatch(patchContent);\n if (!patchInfo) {\n return {\n success: false,\n operation: 'patch',\n message: 'Failed to parse patch content',\n };\n }\n\n const patchBranch = `patch-${event.id.substring(0, 8)}`;\n const commitMessage = extractCommitMessage(patchContent);\n\n try {\n // Create a new branch via API\n await this.forgejo.createBranch(owner, repoName, patchBranch, 'main');\n\n // Apply each file change via API\n for (const file of patchInfo.files) {\n const content = Buffer.from(file.content).toString('base64');\n\n // Check if file exists on the branch to get its SHA for updates\n const existingFile = await this.forgejo.getFileContent(\n owner,\n repoName,\n file.path,\n patchBranch\n );\n\n await this.forgejo.createOrUpdateFile({\n owner,\n repo: repoName,\n filepath: file.path,\n content,\n message: commitMessage,\n branch: patchBranch,\n sha: existingFile?.sha, // Include SHA if file exists (for update)\n });\n }\n\n // Create pull request via API\n const pr = await this.forgejo.createPullRequest({\n owner,\n repo: repoName,\n title: commitMessage,\n head: patchBranch,\n base: 'main',\n body: `Patch from Nostr event: ${event.id}\\n\\nAuthor: ${event.pubkey}`,\n });\n\n this.log(`Pull request created: ${pr.html_url}`);\n\n return {\n success: true,\n operation: 'patch',\n message: `Patch applied and PR #${pr.number} created`,\n metadata: {\n branch: patchBranch,\n prNumber: pr.number,\n prUrl: pr.html_url,\n },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n operation: 'patch',\n message: `Failed to apply patch: ${message}`,\n };\n }\n }\n\n /**\n * Handle Pull Request (kind 1618)\n *\n * Creates an issue documenting the pull request (simplified approach without git).\n */\n private async handlePullRequest(\n event: NIP34Event\n ): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const cloneUrl = getTag(event, 'clone');\n const commitTip = getTag(event, 'c');\n const subject = getTag(event, 'subject');\n\n if (!aTag || !cloneUrl || !commitTip) {\n return {\n success: false,\n operation: 'pull_request',\n message: 'Missing required tags: a, clone, c',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n this.log(`Creating PR issue for ${owner}/${repoName} from ${cloneUrl}`);\n\n // Create an issue documenting the pull request\n const issueBody = `\n**Pull Request from Nostr**\n\nA pull request was submitted via NIP-34 event.\n\n**Details:**\n- Source Repository: ${cloneUrl}\n- Commit: ${commitTip}\n- Nostr Event: ${event.id}\n- Author: ${event.pubkey}\n\n**To apply this pull request:**\n1. Clone the source repository: \\`git clone ${cloneUrl}\\`\n2. Fetch the specific commit: \\`git fetch origin ${commitTip}\\`\n3. Create a branch: \\`git checkout -b pr-${event.id.substring(0, 8)} ${commitTip}\\`\n4. Push to this repository and create a PR manually\n\n${event.content}\n`;\n\n const issue = await this.forgejo.createIssue({\n owner,\n repo: repoName,\n title: subject || `Pull Request from ${event.pubkey.substring(0, 8)}`,\n body: issueBody,\n });\n\n this.log(`PR issue created: ${issue.html_url}`);\n\n return {\n success: true,\n operation: 'pull_request',\n message: `PR documented as issue #${issue.number}`,\n metadata: {\n issueNumber: issue.number,\n issueUrl: issue.html_url,\n },\n };\n }\n\n /**\n * Handle Issue (kind 1621)\n *\n * Creates an issue in Forgejo.\n */\n private async handleIssue(event: NIP34Event): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const subject = getTag(event, 'subject');\n const body = event.content;\n\n if (!aTag || !subject) {\n return {\n success: false,\n operation: 'issue',\n message: 'Missing required tags: a, subject',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n this.log(`Creating issue in ${owner}/${repoName}`);\n\n const issue = await this.forgejo.createIssue({\n owner,\n repo: repoName,\n title: subject,\n body: `${body}\\n\\n---\\nSubmitted via Nostr event: ${event.id}\\nAuthor: ${event.pubkey}`,\n });\n\n this.log(`Issue created: ${issue.html_url}`);\n\n return {\n success: true,\n operation: 'issue',\n message: `Issue #${issue.number} created`,\n metadata: {\n issueNumber: issue.number,\n issueUrl: issue.html_url,\n },\n };\n }\n\n /**\n * Get operation type from event kind\n */\n private getOperationType(\n kind: number\n ):\n | 'repository'\n | 'patch'\n | 'pull_request'\n | 'issue'\n | 'status'\n | 'unsupported' {\n switch (kind) {\n case REPOSITORY_ANNOUNCEMENT_KIND:\n return 'repository';\n case PATCH_KIND:\n return 'patch';\n case PULL_REQUEST_KIND:\n return 'pull_request';\n case ISSUE_KIND:\n return 'issue';\n case 1630:\n case 1631:\n case 1632:\n case 1633:\n return 'status';\n default:\n return 'unsupported';\n }\n }\n\n /**\n * Parse git format-patch output to extract file changes\n */\n private parsePatch(\n patchContent: string\n ): { files: { path: string; content: string }[] } | null {\n try {\n const files: { path: string; content: string }[] = [];\n\n // Simple parser for git format-patch\n // Look for diff --git lines to identify files\n const diffPattern = /diff --git a\\/(.*?) b\\/(.*?)$/gm;\n const matches = [...patchContent.matchAll(diffPattern)];\n\n if (matches.length === 0) {\n return null;\n }\n\n for (const match of matches) {\n const filePath = match[2]; // Use the \"b/\" path (new file)\n if (!filePath) continue; // Skip if no path found\n\n // Extract the patch content for this file\n // For simplicity, we'll extract everything after the diff header\n // In a full implementation, we'd properly parse hunks and apply them\n const fileStart = match.index ?? 0;\n const nextMatch = matches[matches.indexOf(match) + 1];\n const fileEnd = nextMatch?.index ?? patchContent.length;\n const filePatch = patchContent.substring(fileStart, fileEnd);\n\n // For now, extract content lines (lines starting with +)\n // This is a simplified approach - a full parser would handle hunks properly\n const contentLines = filePatch\n .split('\\n')\n .filter((line) => line.startsWith('+') && !line.startsWith('+++'))\n .map((line) => line.substring(1));\n\n if (contentLines.length > 0) {\n files.push({\n path: filePath,\n content: contentLines.join('\\n'),\n });\n }\n }\n\n return files.length > 0 ? { files } : null;\n } catch (error) {\n this.log(\n `Error parsing patch: ${error instanceof Error ? error.message : 'Unknown'}`\n );\n return null;\n }\n }\n\n /**\n * Log message if verbose mode is enabled\n */\n private log(message: string): void {\n if (this.verbose) {\n console.log(`[NIP34] ${message}`);\n }\n }\n}\n","/**\n * Pure git object construction and SHA-1 envelope hashing.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/git-builder.ts` (#223) —\n * the proven seed pipeline builders, now the core of the Git-to-TOON write\n * path. Everything here is pure: no network, no signing, no payments.\n * Upload/publish lives with the Publisher (#226).\n *\n * Git object format: `<type> <size>\\0<content>`. The SHA-1 is computed over\n * the full envelope (header + NUL + content); the `body` (content only) is\n * what gets uploaded to Arweave.\n */\n\nimport { createHash } from 'node:crypto';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** All git object types TOON can carry. */\nexport type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';\n\nexport interface GitObject {\n /** SHA-1 hex digest computed over full git envelope */\n sha: string;\n /** Full git object (header + null + content) */\n buffer: Buffer;\n /** Body only (content after the null byte) — this is what gets uploaded */\n body: Buffer;\n}\n\n/**\n * Maximum uploadable git object body size: 95KB safety margin under the\n * 100KB free tier (R10-005). Larger objects are a hard error in v1.\n */\nexport const MAX_OBJECT_SIZE = 95 * 1024;\n\n/**\n * Git's universal empty-blob SHA-1: the object for a zero-byte file.\n *\n * It is the ONLY zero-byte object git can produce (trees, commits, and tags\n * are never empty) and it always has this exact constant value. The store\n * rejects a zero-byte kind:5094 blob upload as malformed (F00), so `rig` never\n * uploads this object — it is skipped on push and synthesized locally on\n * clone/fetch (both keyed off an ACTUAL zero-length body, never a heuristic).\n */\nexport const EMPTY_BLOB_SHA = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';\n\n// ---------------------------------------------------------------------------\n// Envelope hashing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a raw object body in the git envelope (`<type> <size>\\0`) and compute\n * its SHA-1. This is exactly what `git hash-object -t <type>` does.\n */\nexport function hashGitObject(type: GitObjectType, body: Buffer): GitObject {\n const header = Buffer.from(`${type} ${body.length}\\0`);\n const fullObject = Buffer.concat([header, body]);\n const sha = createHash('sha1').update(fullObject).digest('hex');\n return { sha, buffer: fullObject, body };\n}\n\n// ---------------------------------------------------------------------------\n// Git object construction\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a git blob object and compute its SHA-1.\n *\n * Format: blob <size>\\0<content>\n * SHA is over the full envelope; body is content only (for upload).\n */\nexport function createGitBlob(content: string): GitObject {\n return hashGitObject('blob', Buffer.from(content, 'utf-8'));\n}\n\n/**\n * Construct a git tree object from sorted entries.\n *\n * Format: tree <size>\\0<entries>\n * Each entry: <mode> <name>\\0<20-byte-raw-sha1>\n * Entries MUST be sorted by name (byte-wise).\n */\nexport function createGitTree(\n entries: { mode: string; name: string; sha: string }[]\n): GitObject {\n // Git sorts tree entries by raw byte order (NOT locale-aware)\n const sorted = [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0\n );\n\n const entryBuffers: Buffer[] = [];\n for (const entry of sorted) {\n const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\\0`);\n // Raw 20-byte SHA-1 (NOT hex)\n const rawSha = Buffer.from(entry.sha, 'hex');\n entryBuffers.push(Buffer.concat([modeAndName, rawSha]));\n }\n\n return hashGitObject('tree', Buffer.concat(entryBuffers));\n}\n\n/**\n * Construct a git commit object.\n *\n * Format: commit <size>\\0tree <tree-sha>\\n[parent ...]\\nauthor ...\\ncommitter ...\\n\\n<message>\n * Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.\n */\nexport function createGitCommit(opts: {\n treeSha: string;\n parentSha?: string;\n authorName: string;\n authorPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `tree ${opts.treeSha}`,\n ...(opts.parentSha ? [`parent ${opts.parentSha}`] : []),\n `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('commit', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n\n/**\n * Construct an annotated git tag object.\n *\n * Format: tag <size>\\0object <sha>\\ntype <type>\\ntag <name>\\ntagger ...\\n\\n<message>\n * The tagged object is usually a commit, but git allows tagging any object\n * type (including another tag).\n */\nexport function createGitTag(opts: {\n /** SHA-1 of the object being tagged (hex, 40 chars) */\n objectSha: string;\n /** Type of the tagged object (usually 'commit') */\n objectType: GitObjectType;\n /** Tag name, e.g. 'v1.0.0' */\n tagName: string;\n taggerName: string;\n taggerPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `object ${opts.objectSha}`,\n `type ${opts.objectType}`,\n `tag ${opts.tagName}`,\n `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('tag', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n","/**\n * Publisher — the paid-write seam between push planning (this package) and\n * the two transport implementations that follow it in epic #222:\n *\n * 1. daemon (#227): client-mcp ControlClient → toon-clientd loopback\n * /git/* routes, backed by `ClientRunner.publish`/`uploadBlob` (the\n * production paid-publish path — apex channel, signBalanceProof, flat\n * per-event fee).\n * 2. standalone (#228): an embedded ToonClient constructed from a\n * mnemonic, uploading git objects as kind:5094 store writes with\n * Git-SHA/Git-Type/Repo tags (the proven seed-pipeline shape) and\n * publishing NIP-34 events through the relay.\n *\n * The interface is deliberately minimal so both fit:\n *\n * - `uploadGitObject` takes the raw object body (content only — no git\n * envelope header; the store re-derives the SHA envelope from the\n * Git-Type/Git-SHA tags) and returns the Arweave txId plus the fee paid.\n * - `publishEvent` takes an UNSIGNED event template — signing stays with\n * the implementation (the daemon-held key never leaves the daemon; the\n * standalone impl signs with its own keypair). Relay URLs are plural\n * from day one (forward-compat for parked #84/#92; size 1 today).\n * - `getFeeRates` exposes what `planPush` needs for the pre-push estimate:\n * a per-byte upload rate (seed pipeline bids bytes × rate) and a flat\n * per-event publish fee (the daemon charges `apex.feePerEvent` regardless\n * of event size).\n *\n * All fees are bigint in the smallest asset unit (matches `@toon-protocol/client`\n * channel math); HTTP transports serialize them as strings and convert at\n * the boundary.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { GitObjectType } from './objects.js';\n\n/** A git object queued for upload to Arweave via the paid store path. */\nexport interface GitObjectUpload {\n /** Full 40-hex SHA-1 (over the git envelope — see `hashGitObject`). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no `<type> <size>\\0` header). */\n body: Buffer;\n /** Repository identifier — becomes the `Repo` tag on the store write. */\n repoId: string;\n /**\n * Path the blob was reached by in the tree (#368), if known. Its extension\n * derives the `Content-Type` sent in the store write's `output` tag so a\n * gateway serves the blob as its real media type instead of\n * `application/octet-stream`. Absent (or a non-blob object) → octet-stream.\n */\n path?: string;\n}\n\n/**\n * A NON-git blob queued for upload to Arweave with an explicit `Content-Type`\n * (#368): the ar.io path manifest that turns a pushed repo into a permaweb\n * site. Unlike {@link GitObjectUpload} it carries no `Git-SHA`/`Git-Type`\n * tags, so the store stores the raw bytes verbatim (no git-envelope\n * re-derivation) — exactly what a manifest needs.\n */\nexport interface BlobUpload {\n /** Raw bytes to store. */\n body: Buffer;\n /** MIME type sent in the store write's `output` tag. */\n contentType: string;\n /** Optional repository identifier for provenance (`Repo` tag). */\n repoId?: string;\n}\n\n/** Receipt for one uploaded git object. */\nexport interface UploadReceipt {\n /** Arweave transaction ID the object is retrievable under. */\n txId: string;\n /** Fee paid for this upload, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Receipt for one published Nostr event. */\nexport interface PublishReceipt {\n /** Event ID as accepted by the relay(s). */\n eventId: string;\n /** Fee paid for this publish, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Fee rates used by `planPush` for the pre-push estimate. */\nexport interface FeeRates {\n /** Upload cost per body byte (smallest asset unit). */\n uploadFeePerByte: bigint;\n /**\n * Flat cost per published event (smallest asset unit). Implementations\n * already fold any per-packet route-price floor into this flat value, so\n * estimates using it match the claims actually signed.\n */\n eventFee: bigint;\n /**\n * FLAT minimum per upload claim (smallest asset unit): the store\n * destination's announced route price. The connector gates every paid\n * packet at the destination route's price — a balance-proof claim\n * advancing the channel by less is rejected (F06) — so each per-upload fee\n * is `max(bytes × uploadFeePerByte, minUploadFee)`. Absent: no floor\n * (pre-floor behavior, e.g. when the peer announces no capability prices).\n */\n minUploadFee?: bigint;\n}\n\n/**\n * Per-upload fee: `bytes × ratePerByte`, floored at `minFee` (the\n * destination's announced flat route price — see\n * {@link FeeRates.minUploadFee}). The single shared implementation keeps\n * every estimate site equal to what the publisher actually claims.\n */\nexport function flooredUploadFee(\n bytes: number,\n ratePerByte: bigint,\n minFee?: bigint\n): bigint {\n const fee = BigInt(bytes) * ratePerByte;\n const min = minFee ?? 0n;\n return fee > min ? fee : min;\n}\n\n/**\n * Paid transport for `executePush` — implemented by the daemon route\n * handlers (#227) and the standalone embedded client (#228).\n *\n * Implementations must be safe to call sequentially (uploads are issued one\n * at a time in dependency-safe order) and should throw on any failure —\n * `executePush` does not retry; a crashed push is resumed by re-planning\n * (content-addressed uploads make the retry idempotent).\n */\nexport interface Publisher {\n /** Current fee rates for estimation (may be queried before every plan). */\n getFeeRates(): Promise<FeeRates>;\n /** Upload one git object body to Arweave; paid. */\n uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;\n /**\n * Upload one raw blob (no git envelope) with an explicit `Content-Type`;\n * paid. Used by `rig site` (#368) for the ar.io path manifest. Optional so\n * transports that only move git objects (and pre-#368 test fakes) need not\n * implement it — `rig site` requires it and errors clearly when absent.\n */\n uploadBlob?(upload: BlobUpload): Promise<UploadReceipt>;\n /**\n * Sign (implementation-held key) and pay-to-publish one event to the\n * given relay(s).\n */\n publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt>;\n}\n","/**\n * Pure NIP-34 event builders for the Git-to-TOON write path.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/event-builders.ts` (#223).\n * All builders return UnsignedEvent — the caller signs with their keypair\n * via finalizeEvent() and publishes through a Publisher (#226). Tag\n * structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.\n */\n\nimport {\n ISSUE_KIND,\n PATCH_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport type {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n} from '@toon-protocol/core/nip34';\n\n// Kinds not (yet) exported by @toon-protocol/core/nip34:\n/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */\nexport const REPOSITORY_STATE_KIND = 30618;\n/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */\nexport const COMMENT_KIND = 1622;\n\n// ---------------------------------------------------------------------------\n// UnsignedEvent type (subset of nostr-tools — no id, sig, or pubkey)\n// ---------------------------------------------------------------------------\n\nexport interface UnsignedEvent {\n kind: number;\n content: string;\n tags: string[][];\n created_at: number;\n}\n\n// ---------------------------------------------------------------------------\n// kind:30617 — Repository Announcement (+ maintainer authority, #287)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared maintainers: one multi-valued tag\n * `[\"maintainers\", \"<hex-pubkey>\", \"<hex-pubkey>\", …]` on the kind:30617\n * announcement (mirrors the spec's multi-valued `relays` tag). The repo\n * OWNER — the announcement event's own pubkey — is ALWAYS an implicit\n * maintainer and need not be listed. Consumers derive an issue/PR's status\n * ONLY from kind:1630-1633 events signed by owner ∪ maintainers (#287): the\n * relay is permissionless, so this is the CONSUMER-side authority filter.\n */\nexport const MAINTAINERS_TAG = 'maintainers';\n\nconst HEX64 = /^[0-9a-f]{64}$/;\n\n/**\n * Collect the declared maintainer pubkeys (lowercased hex) from a kind:30617\n * event's tags. Tolerant of repeated `maintainers` tags and non-hex noise —\n * only 64-char hex values survive. Does NOT include the owner (implicit).\n */\nexport function parseMaintainers(tags: string[][]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== MAINTAINERS_TAG) continue;\n for (const value of tag.slice(1)) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n out.push(hex);\n }\n }\n }\n return out;\n}\n\n/**\n * The set of pubkeys whose kind:1630-1633 status events are authoritative for\n * a repo: the owner (always) ∪ the declared maintainers (from the 30617's\n * `maintainers` tag). All values are lowercased hex.\n */\nexport function authorizedStatusAuthors(\n ownerPubkey: string,\n repoAnnouncementTags: string[][]\n): Set<string> {\n return new Set([\n ownerPubkey.toLowerCase(),\n ...parseMaintainers(repoAnnouncementTags),\n ]);\n}\n\n/**\n * Build a kind:30617 repository announcement event.\n *\n * @param repoId - Repository identifier (d tag)\n * @param name - Human-readable repository name\n * @param description - Repository description\n * @param maintainers - Optional declared maintainer pubkeys (hex). Emitted as\n * a single `[\"maintainers\", …]` tag when non-empty; duplicate and non-64-hex\n * values are dropped. The owner (the signer) is an implicit maintainer and\n * need not be listed — if passed it is emitted, which is harmless since the\n * owner is authorized regardless. See {@link MAINTAINERS_TAG}.\n */\nexport function buildRepoAnnouncement(\n repoId: string,\n name: string,\n description: string,\n maintainers: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['d', repoId],\n ['name', name],\n ['description', description],\n ];\n const declared: string[] = [];\n const seen = new Set<string>();\n for (const value of maintainers) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n declared.push(hex);\n }\n }\n if (declared.length > 0) {\n tags.push([MAINTAINERS_TAG, ...declared]);\n }\n return {\n kind: REPOSITORY_ANNOUNCEMENT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:30618 — Repository Refs/State\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:30618 repository refs/state event.\n *\n * @param repoId - Repository identifier (d tag, matches kind:30617)\n * @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })\n * @param arweaveMap - Map of git SHAs to Arweave transaction IDs\n */\nexport function buildRepoRefs(\n repoId: string,\n refs: Record<string, string>,\n arweaveMap: Record<string, string> = {}\n): UnsignedEvent {\n const tags: string[][] = [['d', repoId]];\n\n // Add ref tags\n for (const [refPath, commitSha] of Object.entries(refs)) {\n tags.push(['r', refPath, commitSha]);\n }\n\n // Default HEAD to first ref (typically refs/heads/main)\n const firstRef = Object.keys(refs)[0];\n if (firstRef) {\n tags.push(['HEAD', `ref: ${firstRef}`]);\n }\n\n // Add arweave SHA-to-txId mapping tags\n for (const [sha, txId] of Object.entries(arweaveMap)) {\n tags.push(['arweave', sha, txId]);\n }\n\n return {\n kind: REPOSITORY_STATE_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1621 — Issue\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1621 issue event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Issue title (subject tag)\n * @param body - Issue body (Markdown content)\n * @param labels - Optional labels (t tags)\n */\nexport function buildIssue(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n body: string,\n labels: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ...labels.map((label) => ['t', label]),\n ];\n\n return {\n kind: ISSUE_KIND,\n content: body,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1622 — Comment (on issue or PR)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1622 comment event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param issueOrPrEventId - Event ID of the issue or PR being commented on\n * @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author\n * @param body - Comment body (Markdown content)\n * @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')\n */\nexport function buildComment(\n repoOwnerPubkey: string,\n repoId: string,\n issueOrPrEventId: string,\n authorPubkey: string,\n body: string,\n marker: 'root' | 'reply' = 'reply'\n): UnsignedEvent {\n return {\n kind: COMMENT_KIND,\n content: body,\n tags: [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['e', issueOrPrEventId, '', marker],\n ['p', authorPubkey],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1617 — Patch / PR\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1617 patch event.\n *\n * The PR body/description travels in a dedicated `description` tag, NEVER in\n * `content` (#280): `content` is real `git format-patch` output that readers\n * pipe straight into `git am`, and git's patch-format detection hard-fails on\n * any leading prose (verified: \"Patch format detection failed.\"). The tag\n * route keeps `git am` consumption intact while `rig pr show` and the\n * rig-web/views `parsePR` renderers surface the description.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Patch/PR title (subject tag)\n * @param commits - Array of { sha, parentSha } for commit and parent-commit tags\n * @param branchTag - Branch name for the t tag\n * @param content - Real `git format-patch` text (NIP-34 patch body); defaults\n * to '' for callers that only reference commits by tag\n * @param description - PR body/cover text (`description` tag) — kept out of\n * `content` so `git am` still applies it\n */\nexport function buildPatch(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n commits: { sha: string; parentSha: string }[],\n branchTag?: string,\n content = '',\n description?: string\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ];\n\n if (description !== undefined && description !== '') {\n tags.push(['description', description]);\n }\n\n for (const commit of commits) {\n tags.push(['commit', commit.sha]);\n tags.push(['parent-commit', commit.parentSha]);\n }\n\n if (branchTag) {\n tags.push(['t', branchTag]);\n }\n\n return {\n kind: PATCH_KIND,\n content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1630-1633 — Status\n// ---------------------------------------------------------------------------\n\n/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */\nexport type StatusKind =\n | typeof STATUS_OPEN_KIND\n | typeof STATUS_APPLIED_KIND\n | typeof STATUS_CLOSED_KIND\n | typeof STATUS_DRAFT_KIND;\n\n/**\n * Build a status event (kind 1630-1633).\n *\n * @param targetEventId - Event ID of the patch, PR, or issue being updated\n * @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)\n * @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)\n */\nexport function buildStatus(\n targetEventId: string,\n statusKind: StatusKind,\n targetPubkey?: string\n): UnsignedEvent {\n const tags: string[][] = [['e', targetEventId]];\n if (targetPubkey) {\n tags.push(['p', targetPubkey]);\n }\n return {\n kind: statusKind,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Remote repository state reader — the \"what does the remote have?\" half of\n * `rig push` (epic #222, ticket #225).\n *\n * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:\n * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,\n * `arweave` tags → git SHA → Arweave txId hints.\n * - kind:30617 (repository announcement): presence = the repo exists on\n * TOON (first-push detection) + name/description/relays metadata.\n *\n * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest\n * event per (kind, author, d) survives, but we still pick the newest across\n * relays (highest created_at, ties broken by lowest id per NIP-01).\n *\n * SHAs missing from the `arweave` tag map can be resolved via the shared\n * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module\n * the rig SPA uses) through {@link RemoteState.resolveMissing}.\n *\n * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):\n * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a\n * double-JSON-encoded string (devnet relay quirk: a JSON string containing\n * the event JSON), or as a TOON-encoded string. All three are tolerated.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport {\n resolveGitSha,\n seedShaCache,\n shaCacheKey,\n} from '@toon-protocol/arweave';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\n\nimport { REPOSITORY_STATE_KIND, parseMaintainers } from './nip34-events.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A signed Nostr event as seen on a relay (read side). */\nexport interface NostrEvent {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** NIP-01 subscription filter (only the fields rig's readers send). */\nexport interface NostrFilter {\n ids?: string[];\n kinds?: number[];\n authors?: string[];\n '#d'?: string[];\n /** Repo address tag filter, e.g. `30617:<owner>:<repoId>` (#278 tracker). */\n '#a'?: string[];\n /** Event-reference tag filter (#278 tracker: statuses + comments). */\n '#e'?: string[];\n limit?: number;\n}\n\n/**\n * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket\n * global (Node >= 22 / undici, browsers) and by the `ws` package.\n */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: string, listener: (event: never) => void): void;\n}\n\n/** Factory for WebSocket connections (injectable for tests / `ws` fallback). */\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport interface FetchRemoteStateOptions {\n /**\n * Relay WebSocket URLs to query. Plural from day one (forward-compat with\n * multi-relay routing); size 1 is typical today. Results are merged and the\n * newest replaceable event across all relays wins.\n */\n relayUrls: string[];\n /** Repository owner's pubkey (hex) — the author of 30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */\n timeoutMs?: number;\n /**\n * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.\n * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;\n * injectable for tests.\n */\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n /** WebSocket constructor override (defaults to the global WebSocket). */\n webSocketFactory?: WebSocketFactory;\n}\n\n/** Remote repository state assembled from relay events. */\nexport interface RemoteState {\n /** True when a kind:30617 announcement exists (false ⇒ first push). */\n announced: boolean;\n /** Ref map from the latest kind:30618: refname → commit SHA. */\n refs: Map<string, string>;\n /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */\n headSymref: string | null;\n /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */\n shaToTxId: Map<string, string>;\n /** The latest kind:30618 event, or null if the repo has no state yet. */\n refsEvent: NostrEvent | null;\n /** The latest kind:30617 event, or null if the repo is unannounced. */\n announceEvent: NostrEvent | null;\n /** Repository name from the announcement `name` tag. */\n name: string | null;\n /** Announcement `description` tag (falls back to event content). */\n description: string | null;\n /** Relay URLs advertised in the announcement `relays` tag(s). */\n relays: string[];\n /**\n * Declared maintainer pubkeys (hex) from the announcement `maintainers`\n * tag (#287). Does NOT include the owner (an implicit maintainer). Empty\n * when unannounced or owner-only.\n */\n maintainers: string[];\n /**\n * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when\n * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve\n * nowhere are omitted from the returned map.\n */\n resolveMissing(shas: string[]): Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// Relay query (NIP-01 REQ → EVENT* → EOSE)\n// ---------------------------------------------------------------------------\n\n/** Validate that a relay URL uses a WebSocket scheme (mirrors rig's url-utils). */\nfunction isValidRelayUrl(url: string): boolean {\n return /^wss?:\\/\\//i.test(url);\n}\n\n/** WHATWG WebSocket OPEN ready state. */\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string): WebSocketLike {\n const ctor = (\n globalThis as { WebSocket?: new (url: string) => WebSocketLike }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) — pass webSocketFactory'\n );\n }\n return new ctor(url);\n}\n\n/**\n * Decode a relay EVENT payload, tolerating every encoding seen in the wild:\n * inline object (standard NIP-01), double-JSON-encoded string (devnet relay\n * serves the event as a JSON string containing the event JSON), or a\n * TOON-encoded string (rig's `decodeToonMessage` path).\n */\nfunction decodeEventPayload(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object') {\n return payload as NostrEvent;\n }\n if (typeof payload !== 'string') {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object') {\n return parsed as NostrEvent;\n }\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/**\n * Query one relay: send a REQ, collect EVENTs until EOSE, then CLOSE.\n * Mirrors rig's `queryRelay` (partial results on timeout / early close).\n * Exported for reuse by the network-bootstrap discovery (kind:10032).\n */\nexport function queryRelay(\n relayUrl: string,\n filter: NostrFilter,\n timeoutMs: number,\n webSocketFactory: WebSocketFactory\n): Promise<NostrEvent[]> {\n return new Promise((resolve, reject) => {\n const events: NostrEvent[] = [];\n const subId = `git-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let ws: WebSocketLike;\n // eslint-disable-next-line prefer-const -- assigned after `settle` is defined\n let timeoutHandle: ReturnType<typeof setTimeout>;\n let settled = false;\n\n const settle = (outcome: 'resolve' | 'reject', error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n try {\n if (ws.readyState === WS_OPEN) {\n ws.send(JSON.stringify(['CLOSE', subId]));\n }\n ws.close();\n } catch {\n // Ignore close errors\n }\n if (outcome === 'reject') {\n reject(error);\n } else {\n resolve(events);\n }\n };\n\n if (!isValidRelayUrl(relayUrl)) {\n reject(\n new Error(\n `Invalid relay URL protocol (must be ws:// or wss://): ${relayUrl}`\n )\n );\n return;\n }\n\n try {\n ws = webSocketFactory(relayUrl);\n } catch (err) {\n reject(new Error(`Failed to connect to relay ${relayUrl}: ${String(err)}`));\n return;\n }\n\n timeoutHandle = setTimeout(() => {\n // Resolve with whatever we collected so far (partial results)\n settle('resolve');\n }, timeoutMs);\n\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.addEventListener('message', (msgEvent: { data?: unknown }) => {\n try {\n const msg = JSON.parse(String(msgEvent.data)) as unknown[];\n if (!Array.isArray(msg) || msg.length < 2) return;\n\n const msgType = msg[0];\n if (msgType === 'EVENT' && msg[1] === subId && msg[2] !== undefined) {\n const event = decodeEventPayload(msg[2]);\n if (event) events.push(event);\n } else if (msgType === 'EOSE' && msg[1] === subId) {\n settle('resolve');\n }\n } catch {\n // Ignore parse errors for individual messages\n }\n });\n\n ws.addEventListener('error', (event: { message?: unknown }) => {\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n settle(\n 'reject',\n new Error(`WebSocket error connecting to ${relayUrl}: ${detail}`)\n );\n });\n\n ws.addEventListener('close', () => {\n // If we haven't settled yet, resolve with what we have\n settle('resolve');\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// NIP-33 replaceable selection + tag parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Pick the winning replaceable event: highest created_at, ties broken by\n * lowest id (NIP-01 replaceable-event convention).\n */\nfunction latestReplaceable(events: NostrEvent[]): NostrEvent | null {\n let winner: NostrEvent | null = null;\n for (const event of events) {\n if (\n winner === null ||\n event.created_at > winner.created_at ||\n (event.created_at === winner.created_at && event.id < winner.id)\n ) {\n winner = event;\n }\n }\n return winner;\n}\n\n/** Get the first value for a tag name. */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n const tag = tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/** Maximum number of refs parsed from a single kind:30618 event (mirrors views). */\nconst MAX_REFS_PER_EVENT = 1000;\n\n/** Symref prefix used in `HEAD` tags: `[\"HEAD\", \"ref: refs/heads/main\"]`. */\nconst SYMREF_PREFIX = 'ref: ';\n\ninterface ParsedRefs {\n refs: Map<string, string>;\n headSymref: string | null;\n shaToTxId: Map<string, string>;\n}\n\n/** Parse a kind:30618 event's `r` / `HEAD` / `arweave` tags. */\nfunction parseRefsEvent(event: NostrEvent): ParsedRefs {\n const refs = new Map<string, string>();\n const shaToTxId = new Map<string, string>();\n let headSymref: string | null = null;\n\n for (const tag of event.tags) {\n const [tagName, v1, v2] = tag;\n if (tagName === 'r' && v1 && v2) {\n if (v1 === 'HEAD' && v2.startsWith(SYMREF_PREFIX)) {\n // Alternate symref spelling: [\"r\", \"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v2.slice(SYMREF_PREFIX.length);\n continue;\n }\n if (refs.size >= MAX_REFS_PER_EVENT) continue;\n refs.set(v1, v2);\n } else if (tagName === 'HEAD' && v1?.startsWith(SYMREF_PREFIX)) {\n // NIP-34 symref tag: [\"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v1.slice(SYMREF_PREFIX.length);\n } else if (tagName === 'arweave' && v1 && v2) {\n shaToTxId.set(v1, v2);\n }\n }\n\n return { refs, headSymref, shaToTxId };\n}\n\n// ---------------------------------------------------------------------------\n// fetchRemoteState\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the remote repository state from the relay(s).\n *\n * Sends one REQ per relay for kind:30617 + kind:30618 with\n * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),\n * and reduces to the latest replaceable event per kind.\n *\n * Resolves as long as at least one relay answers; throws only when every\n * relay fails. Events from other authors / repos are ignored (defense\n * against misbehaving relays that over-return).\n */\nexport async function fetchRemoteState(\n options: FetchRemoteStateOptions\n): Promise<RemoteState> {\n const {\n relayUrls,\n ownerPubkey,\n repoId,\n timeoutMs = 10000,\n resolveSha = resolveGitSha,\n webSocketFactory = defaultWebSocketFactory,\n } = options;\n\n if (relayUrls.length === 0) {\n throw new Error('fetchRemoteState: relayUrls must not be empty');\n }\n if (!ownerPubkey) {\n throw new Error('fetchRemoteState: ownerPubkey is required');\n }\n if (!repoId) {\n throw new Error('fetchRemoteState: repoId is required');\n }\n\n const filter: NostrFilter = {\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [ownerPubkey],\n '#d': [repoId],\n };\n\n const results = await Promise.allSettled(\n relayUrls.map((url) => queryRelay(url, filter, timeoutMs, webSocketFactory))\n );\n\n const failures: string[] = [];\n const byId = new Map<string, NostrEvent>();\n for (const result of results) {\n if (result.status === 'rejected') {\n failures.push(String((result.reason as Error)?.message ?? result.reason));\n continue;\n }\n for (const event of result.value) {\n // Only trust events from the repo owner for this repo — relays are\n // untrusted and may over-return.\n if (event.pubkey !== ownerPubkey) continue;\n if (getTagValue(event.tags, 'd') !== repoId) continue;\n if (typeof event.id === 'string' && !byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n }\n\n if (failures.length === relayUrls.length) {\n throw new Error(\n `fetchRemoteState: all ${relayUrls.length} relay(s) failed: ${failures.join('; ')}`\n );\n }\n\n const events = [...byId.values()];\n const refsEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_STATE_KIND)\n );\n const announceEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND)\n );\n\n const { refs, headSymref, shaToTxId } = refsEvent\n ? parseRefsEvent(refsEvent)\n : {\n refs: new Map<string, string>(),\n headSymref: null,\n shaToTxId: new Map<string, string>(),\n };\n\n // Seed the shared resolver cache so later resolveGitSha calls (here or in\n // any other consumer of @toon-protocol/arweave) skip GraphQL for known SHAs.\n if (shaToTxId.size > 0) {\n seedShaCache(\n [...shaToTxId].map(\n ([sha, txId]) => [shaCacheKey(sha, repoId), txId] as [string, string]\n )\n );\n }\n\n // Announcement metadata (mirrors views' parseRepoAnnouncement defaults).\n const name = announceEvent\n ? (getTagValue(announceEvent.tags, 'name') ?? null)\n : null;\n const description = announceEvent\n ? (getTagValue(announceEvent.tags, 'description') ?? announceEvent.content)\n : null;\n // NIP-34 announcement relays tag is multi-valued: [\"relays\", url1, url2, …]\n const relays: string[] = [];\n if (announceEvent) {\n for (const tag of announceEvent.tags) {\n if (tag[0] === 'relays') {\n relays.push(...tag.slice(1).filter((url) => url.length > 0));\n }\n }\n }\n // Declared maintainers (#287): the `maintainers` tag on the 30617. Owner is\n // an implicit maintainer and is NOT listed here.\n const maintainers = announceEvent\n ? parseMaintainers(announceEvent.tags)\n : [];\n\n const resolveMissing = async (\n shas: string[]\n ): Promise<Map<string, string>> => {\n const resolved = new Map<string, string>();\n const missing: string[] = [];\n for (const sha of new Set(shas)) {\n const known = shaToTxId.get(sha);\n if (known !== undefined) {\n resolved.set(sha, known);\n } else {\n missing.push(sha);\n }\n }\n const lookups = await Promise.all(\n missing.map(\n async (sha) => [sha, await resolveSha(sha, repoId)] as const\n )\n );\n for (const [sha, txId] of lookups) {\n if (txId) resolved.set(sha, txId);\n }\n return resolved;\n };\n\n return {\n announced: announceEvent !== null,\n refs,\n headSymref,\n shaToTxId,\n refsEvent,\n announceEvent,\n name,\n description,\n relays,\n maintainers,\n resolveMissing,\n };\n}\n","/**\n * Arweave gateway redundancy — single source of truth.\n *\n * Media bytes are content-addressed by Arweave tx id, so every gateway serves\n * the same bytes. This module owns the ordered gateway preference list and the\n * URL helpers used on BOTH sides of the wire:\n * - upload (client-mcp daemon): stamp a primary `url` + `fallback` mirrors.\n * - render (views/rig browser): re-point imeta URLs + fail over on error.\n *\n * Previously hand-duplicated in `views`, `rig`, and `client-mcp`; those now all\n * import from here. The default list can be overridden per call (e.g. from a\n * daemon env var) — pass `gateways` to the helpers.\n */\n\n/** Ordered Arweave gateways to try (primary first, then fallbacks). */\nexport const ARWEAVE_GATEWAYS = [\n 'https://ar-io.dev',\n 'https://arweave.net',\n 'https://permagate.io',\n] as const;\n\n/** Timeout for individual Arweave fetch requests in milliseconds. */\nexport const ARWEAVE_FETCH_TIMEOUT_MS = 15000;\n\n/** Arweave transaction IDs are 43-character base64url strings. */\nconst TX_ID_RE = /^[a-zA-Z0-9_-]{43}$/;\n\n/** Hosts recognized as Arweave gateways (path-addressed). */\nconst ARWEAVE_HOST_RE =\n /(^|\\.)(arweave\\.net|ar-io\\.dev|permagate\\.io|g8way\\.io|ar\\.io)$/i;\n\n/**\n * Extract an Arweave tx id from a media URL, or null if it is not\n * Arweave-addressable. Handles `ar://<txid>` and path-style\n * `https://<gateway>/<txid>`.\n *\n * Sandbox-subdomain URLs (`https://<txid>.<gateway>`) are deliberately NOT\n * decoded: tx ids are case-sensitive base64url, but `URL` (and DNS) lower-case\n * the hostname, which would corrupt the id. Real gateway sandboxing uses a\n * base32 label, not the raw id — the canonical id always travels in the path.\n */\nexport function arweaveTxId(rawUrl: string): string | null {\n const ar = /^ar:\\/\\/([a-zA-Z0-9_-]{43})(?:[/?#]|$)/.exec(rawUrl);\n if (ar?.[1]) return ar[1];\n\n let u: URL;\n try {\n u = new URL(rawUrl);\n } catch {\n return null;\n }\n // Only re-point hosts we actually recognize as Arweave gateways, so a stray\n // 43-char path segment on some other CDN is never misread as a tx id.\n if (!ARWEAVE_HOST_RE.test(u.hostname)) return null;\n\n // Path style: https://arweave.net/<txid>\n const seg = u.pathname.split('/').find(Boolean);\n if (seg && TX_ID_RE.test(seg)) return seg;\n\n return null;\n}\n\n/**\n * Primary URL + fallback mirror URLs for an Arweave tx id, one per gateway in\n * preference order. Used by the upload path to stamp `imeta` `url` + `fallback`.\n */\nexport function arweaveUrls(\n txId: string,\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): { url: string; fallbacks: string[] } {\n const all = (gateways.length ? gateways : ARWEAVE_GATEWAYS).map(\n (g) => `${g}/${txId}`\n );\n const [url, ...fallbacks] = all;\n return { url: url ?? `${ARWEAVE_GATEWAYS[0]}/${txId}`, fallbacks };\n}\n\n/**\n * Ordered candidate URLs for a media URL. Arweave-addressable URLs expand to the\n * full gateway-preference list (primary first); anything else is returned\n * unchanged. `extraFallbacks` (e.g. publisher-supplied `imeta` mirrors) are\n * appended last, de-duplicated. Used by the render path to fail over on error.\n */\nexport function arweaveGatewayCandidates(\n rawUrl: string,\n extraFallbacks: string[] = [],\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): string[] {\n const txId = arweaveTxId(rawUrl);\n const candidates = txId\n ? (gateways.length ? gateways : ARWEAVE_GATEWAYS).map((g) => `${g}/${txId}`)\n : [rawUrl];\n const seen = new Set(candidates);\n for (const f of extraFallbacks) {\n if (f && !seen.has(f)) {\n seen.add(f);\n candidates.push(f);\n }\n }\n return candidates;\n}\n","/**\n * Git-SHA → Arweave transaction ID resolution.\n *\n * Git objects uploaded to Arweave are tagged with `Git-SHA` (the object's\n * SHA-1) and `Repo` (the repository identifier, matching the NIP-34 `d` tag).\n * This module resolves a git SHA to its Arweave tx id via the Arweave GraphQL\n * gateway, with a bounded in-memory cache that can be pre-seeded from relay\n * state (kind:30618 `arweave` tags) to skip the GraphQL indexing delay.\n *\n * Extracted verbatim from rig's `web/arweave-client.ts` (#225) so the browser\n * SPA (rig-web) and the Node write path (@toon-protocol/rig) share ONE resolver.\n * Uses only WHATWG fetch + AbortSignal.timeout — browser and Node compatible.\n */\n\nimport { ARWEAVE_FETCH_TIMEOUT_MS } from './gateways.js';\n\n/** Arweave GraphQL endpoint used for Git-SHA tag lookups. */\nconst ARWEAVE_GRAPHQL_URL = 'https://arweave.net/graphql';\n\n/** Maximum number of entries in the SHA-to-txId cache to prevent unbounded memory growth. */\nconst SHA_CACHE_MAX_SIZE = 10000;\n\n/** In-memory cache for SHA-to-txId resolution. Bounded to prevent memory leaks. */\nconst shaToTxIdCache = new Map<string, string>();\n\n/**\n * Validate a git SHA-1 hash format (40-character hex string).\n */\nfunction isValidGitSha(sha: string): boolean {\n return /^[0-9a-f]{40}$/i.test(sha);\n}\n\n/**\n * Sanitize a string for safe inclusion in a GraphQL query.\n * Removes characters that could break out of a GraphQL string literal,\n * including backticks which some GraphQL parsers may interpret.\n */\nfunction sanitizeGraphQLValue(value: string): string {\n // eslint-disable-next-line no-control-regex -- intentional: strip control chars for GraphQL safety\n return value.replace(/[\"\\\\\\n\\r\\u0000-\\u001f`]/g, '');\n}\n\n/**\n * Build the cache key used by {@link resolveGitSha} / {@link seedShaCache}.\n *\n * The cache is keyed on `\"sha:repo\"` so the same SHA in different repos\n * resolves independently (uploads are tagged per-repo).\n */\nexport function shaCacheKey(sha: string, repo: string): string {\n return `${sha}:${repo}`;\n}\n\n/**\n * Clear the SHA-to-txId cache. Used for test isolation.\n */\nexport function clearShaCache(): void {\n shaToTxIdCache.clear();\n}\n\n/**\n * Pre-seed the SHA-to-txId cache with known mappings.\n *\n * Used when txId mappings are available from relay events (e.g., kind:30618\n * `arweave` tags) to avoid the GraphQL indexing delay after Turbo/Irys uploads.\n *\n * @param mappings - Map of \"sha:repo\" cache keys to Arweave transaction IDs\n */\nexport function seedShaCache(\n mappings: Map<string, string> | [string, string][]\n): void {\n const entries = mappings instanceof Map ? mappings.entries() : mappings;\n for (const [key, txId] of entries) {\n if (shaToTxIdCache.size >= SHA_CACHE_MAX_SIZE) {\n const firstKey = shaToTxIdCache.keys().next().value;\n if (firstKey !== undefined) {\n shaToTxIdCache.delete(firstKey);\n }\n }\n shaToTxIdCache.set(key, txId);\n }\n}\n\n/** Arweave transaction IDs are 43-character base64url strings. */\nconst ARWEAVE_TX_ID_RE = /^[a-zA-Z0-9_-]{43}$/;\n\n/**\n * Validate an Arweave transaction ID format.\n * Arweave tx IDs are 43-character base64url-encoded strings.\n */\nexport function isValidArweaveTxId(txId: string): boolean {\n return ARWEAVE_TX_ID_RE.test(txId);\n}\n\n/**\n * Resolve a git SHA to an Arweave transaction ID via GraphQL.\n *\n * Queries the Arweave GraphQL endpoint for transactions tagged with\n * the given Git-SHA and Repo values. Results are cached in-memory.\n *\n * @param sha - Git object SHA-1 hash (hex)\n * @param repo - Repository identifier (matches d tag)\n * @returns Arweave transaction ID, or null if not found\n */\nexport async function resolveGitSha(\n sha: string,\n repo: string\n): Promise<string | null> {\n // Validate SHA format to prevent injection of arbitrary strings into GraphQL\n if (!isValidGitSha(sha)) {\n return null;\n }\n\n const cacheKey = shaCacheKey(sha, repo);\n const cached = shaToTxIdCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const safeSha = sanitizeGraphQLValue(sha);\n const safeRepo = sanitizeGraphQLValue(repo);\n const query = `query {\n transactions(tags: [\n { name: \"Git-SHA\", values: [\"${safeSha}\"] },\n { name: \"Repo\", values: [\"${safeRepo}\"] }\n ]) {\n edges { node { id } }\n }\n}`;\n\n try {\n const response = await fetch(ARWEAVE_GRAPHQL_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query }),\n signal: AbortSignal.timeout(ARWEAVE_FETCH_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n return null;\n }\n\n const json = (await response.json()) as {\n data?: {\n transactions?: {\n edges?: { node?: { id?: string } }[];\n };\n };\n };\n\n const edges = json.data?.transactions?.edges;\n if (!edges || edges.length === 0) {\n return null;\n }\n\n const txId = edges[0]?.node?.id;\n if (!txId || !isValidArweaveTxId(txId)) {\n return null;\n }\n\n // Evict oldest entries if cache exceeds max size\n if (shaToTxIdCache.size >= SHA_CACHE_MAX_SIZE) {\n const firstKey = shaToTxIdCache.keys().next().value;\n if (firstKey !== undefined) {\n shaToTxIdCache.delete(firstKey);\n }\n }\n shaToTxIdCache.set(cacheKey, txId);\n return txId;\n } catch {\n return null;\n }\n}\n","/**\n * GitRepoReader — read a local repository via `execFile` git plumbing.\n *\n * Real git gives perfect fidelity (packfiles, delta chains, exotic history)\n * with zero new deps — no isomorphic-git. Everything here is read-only and\n * injection-safe:\n *\n * - child processes are spawned with `execFile`/`spawn` and argument\n * ARRAYS — never a shell, never string interpolation;\n * - every caller-supplied revision/range is validated against strict\n * regexes that (among other things) reject a leading `-`, so a value\n * like `--upload-pack=…` can never be parsed as an option;\n * - `--` terminators are appended where git supports them so nothing\n * user-supplied can be re-interpreted as a pathspec.\n *\n * Push planning/publishing live in follow-up tickets of epic\n * toon-client#222 — this module only reads.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { GitObjectType } from './objects.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Generous cap for plumbing stdout (rev-list on big repos, format-patch). */\nconst MAX_BUFFER = 256 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single ref from `git for-each-ref` (branches + tags). */\nexport interface GitRef {\n /** Full refname, e.g. `refs/heads/main` or `refs/tags/v1.0.0`. */\n refname: string;\n /**\n * SHA the ref points at. For annotated tags this is the TAG object's SHA\n * (the peeled commit is in {@link peeledSha}); for branches and\n * lightweight tags it is the commit SHA.\n */\n sha: string;\n /** Type of the referenced object: `commit`, or `tag` for annotated tags. */\n type: GitObjectType;\n /** For annotated tags: the peeled (target) object SHA. */\n peeledSha?: string;\n}\n\n/** Result of {@link GitRepoReader.listRefs}. */\nexport interface RepoRefs {\n /**\n * Full refname HEAD points at (e.g. `refs/heads/main`), or `undefined`\n * when HEAD is detached.\n */\n head?: string;\n refs: GitRef[];\n}\n\n/** One object streamed out of `git cat-file --batch`. */\nexport interface ReadGitObject {\n /** Full 40-hex SHA-1. */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** Result of {@link GitRepoReader.readObjects}. */\nexport interface ReadObjectsResult {\n /** Objects found, in input order (minus missing ones). */\n objects: ReadGitObject[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** One object from `rev-list --objects`: SHA plus the path it was reached by. */\nexport interface ObjectWithPath {\n /** Full 40-hex SHA-1. */\n sha: string;\n /**\n * Path the object was first reached by (blobs and non-root trees);\n * `undefined` for commits, root trees, and tag objects.\n */\n path?: string;\n}\n\n/** One object's metadata from `cat-file --batch-check`. */\nexport interface ObjectStat {\n sha: string;\n type: GitObjectType;\n /** Object body size in bytes (content only, no envelope header). */\n size: number;\n}\n\n/** Result of {@link GitRepoReader.statObjects}. */\nexport interface StatObjectsResult {\n /** Stats found, in input order (minus missing ones). */\n objects: ObjectStat[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** Error from a git child process, carrying exit code and stderr. */\nexport class GitError extends Error {\n constructor(\n message: string,\n /** Process exit code (undefined when the process failed to spawn). */\n public readonly exitCode: number | undefined,\n /** Captured stderr, trimmed. */\n public readonly stderr: string\n ) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Argument validation (injection defense)\n// ---------------------------------------------------------------------------\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * One revision token: a SHA prefix (4–40 hex) or a refname-ish word with an\n * optional `^`/`~<n>` ancestry suffix. Must start with an alphanumeric, so a\n * leading `-` (option injection) is impossible; `@{…}`, whitespace, and other\n * revspec exotica are deliberately rejected.\n */\nconst REV_TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*(?:[~^][0-9]*)*$/;\n\nfunction isValidRevision(rev: string): boolean {\n if (rev.length === 0 || rev.length > 1024) return false;\n if (!REV_TOKEN_RE.test(rev)) return false;\n // Refname rules git enforces that our charset alone doesn't:\n if (rev.includes('..')) return false; // range separator / invalid in refnames\n if (rev.endsWith('.lock') || rev.endsWith('/') || rev.endsWith('.')) return false;\n return true;\n}\n\nfunction assertRevision(rev: string, what: string): void {\n if (!isValidRevision(rev)) {\n throw new Error(\n `${what} is not a valid git revision (got ${JSON.stringify(rev)}); ` +\n 'expected a SHA or simple refname — options/ranges are rejected'\n );\n }\n}\n\nfunction assertFullSha(sha: string, what: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(\n `${what} is not a full 40-hex SHA-1 (got ${JSON.stringify(sha)})`\n );\n }\n}\n\n/**\n * A revision range for format-patch: `<rev>`, `<rev>..<rev>`, or\n * `<rev>...<rev>` where each side passes {@link isValidRevision}.\n */\nfunction assertRange(range: string, what: string): void {\n const parts = range.split(/\\.{2,3}/);\n const separators = range.match(/\\.{2,3}/g) ?? [];\n const ok =\n parts.length <= 2 &&\n separators.length === parts.length - 1 &&\n parts.every((p) => isValidRevision(p));\n if (!ok) {\n throw new Error(\n `${what} is not a valid revision range (got ${JSON.stringify(range)}); ` +\n 'expected <rev>, <rev>..<rev>, or <rev>...<rev>'\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// cat-file --batch incremental parser\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: ReadonlySet<string> = new Set(['blob', 'tree', 'commit', 'tag']);\n\n/**\n * Incremental parser for `git cat-file --batch` output:\n * `<sha> <type> <size>\\n<body>\\n` per found object, `<name> missing\\n` for\n * absent ones. Bodies are raw bytes (possibly binary) and may be split\n * across arbitrary chunk boundaries, so parsing is strictly size-driven.\n */\nclass BatchParser {\n private buf: Buffer = Buffer.alloc(0);\n private pending: { sha: string; type: GitObjectType; size: number } | null = null;\n\n readonly objects: ReadGitObject[] = [];\n readonly missing: string[] = [];\n\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /** True when no partially-parsed record remains. */\n isComplete(): boolean {\n return this.pending === null && this.buf.length === 0;\n }\n\n private drain(): void {\n for (;;) {\n if (this.pending) {\n // Need body + trailing LF before the record is complete.\n const needed = this.pending.size + 1;\n if (this.buf.length < needed) return;\n const body = Buffer.from(this.buf.subarray(0, this.pending.size));\n this.objects.push({ sha: this.pending.sha, type: this.pending.type, body });\n this.buf = this.buf.subarray(needed);\n this.pending = null;\n continue;\n }\n\n const nl = this.buf.indexOf(0x0a);\n if (nl === -1) return;\n const header = this.buf.subarray(0, nl).toString('utf-8');\n this.buf = this.buf.subarray(nl + 1);\n\n const [name, second, third] = header.split(' ');\n if (name && second === 'missing' && third === undefined) {\n this.missing.push(name);\n continue;\n }\n if (name && second && third !== undefined && OBJECT_TYPES.has(second)) {\n const size = Number.parseInt(third, 10);\n if (Number.isSafeInteger(size) && size >= 0) {\n this.pending = { sha: name, type: second as GitObjectType, size };\n continue;\n }\n }\n throw new GitError(\n `unexpected cat-file --batch header: ${JSON.stringify(header)}`,\n undefined,\n ''\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// GitRepoReader\n// ---------------------------------------------------------------------------\n\n/**\n * Read-only view of a local git repository via git plumbing commands.\n *\n * All methods throw {@link GitError} when the underlying git process fails\n * unexpectedly, and plain `Error` when a caller-supplied argument fails\n * validation (before any process is spawned).\n */\nexport class GitRepoReader {\n constructor(\n /** Absolute or relative path to the repository worktree (or .git dir). */\n public readonly repoPath: string\n ) {}\n\n /** Run git with argument-array safety; resolves stdout as UTF-8. */\n private async git(\n args: string[],\n opts: { allowExitCodes?: number[] } = {}\n ): Promise<{ stdout: string; exitCode: number }> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: this.repoPath,\n maxBuffer: MAX_BUFFER,\n encoding: 'utf-8',\n });\n return { stdout, exitCode: 0 };\n } catch (err) {\n const e = err as NodeJS.ErrnoException & {\n code?: number | string;\n stdout?: string;\n stderr?: string;\n };\n const exitCode = typeof e.code === 'number' ? e.code : undefined;\n if (exitCode !== undefined && opts.allowExitCodes?.includes(exitCode)) {\n return { stdout: e.stdout ?? '', exitCode };\n }\n throw new GitError(\n `git ${args[0]} failed${exitCode !== undefined ? ` (exit ${exitCode})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`,\n exitCode,\n (e.stderr ?? '').trim()\n );\n }\n }\n\n /**\n * List all branches and tags plus the symbolic HEAD.\n *\n * Annotated tags report the tag object's SHA/type with the peeled target\n * in `peeledSha`. A detached HEAD is tolerated (`head` is `undefined`).\n */\n async listRefs(): Promise<RepoRefs> {\n const format = '%(refname)%00%(objectname)%00%(objecttype)%00%(*objectname)';\n const [refsRes, headRes] = await Promise.all([\n this.git(['for-each-ref', `--format=${format}`, 'refs/heads', 'refs/tags']),\n // Exit 1 = detached HEAD (or unborn branch pointer oddities) — tolerated.\n this.git(['symbolic-ref', '--quiet', 'HEAD'], { allowExitCodes: [1] }),\n ]);\n\n const refs: GitRef[] = [];\n for (const line of refsRes.stdout.split('\\n')) {\n if (!line) continue;\n const [refname, sha, objecttype, peeled] = line.split('\\0');\n if (!refname || !sha || !objecttype || !OBJECT_TYPES.has(objecttype)) {\n throw new GitError(`unexpected for-each-ref line: ${JSON.stringify(line)}`, undefined, '');\n }\n refs.push({\n refname,\n sha,\n type: objecttype as GitObjectType,\n ...(peeled ? { peeledSha: peeled } : {}),\n });\n }\n\n const head = headRes.exitCode === 0 ? headRes.stdout.trim() || undefined : undefined;\n return { head, refs };\n }\n\n /**\n * SHAs of every object reachable from `want` but not from `have`\n * (`git rev-list --objects <want…> --not <have…>`), i.e. the push delta.\n *\n * Haves that don't exist locally (e.g. remote tips we never fetched) are\n * filtered out first via one `cat-file --batch-check` pass — rev-list\n * would otherwise die on them.\n */\n async objectsBetween(want: string[], have: string[]): Promise<string[]> {\n const objects = await this.objectsBetweenWithPaths(want, have);\n return objects.map((o) => o.sha);\n }\n\n /**\n * Like {@link objectsBetween} but keeps the path each object was reached\n * by (`rev-list --objects` emits `<sha> <path>` for blobs and non-root\n * trees) — used by push planning to report actionable oversize errors.\n */\n async objectsBetweenWithPaths(\n want: string[],\n have: string[]\n ): Promise<ObjectWithPath[]> {\n for (const w of want) assertRevision(w, 'want');\n for (const h of have) assertRevision(h, 'have');\n if (want.length === 0) return [];\n\n const knownHaves = await this.filterExisting(have);\n\n const args = ['rev-list', '--objects', ...want];\n if (knownHaves.length > 0) args.push('--not', ...knownHaves);\n args.push('--'); // nothing user-supplied can become a pathspec\n const { stdout } = await this.git(args);\n\n const objects: ObjectWithPath[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n // `--objects` lines are `<sha>` or `<sha> <path>`.\n const spaceIdx = line.indexOf(' ');\n if (spaceIdx === -1) {\n objects.push({ sha: line });\n } else {\n const path = line.slice(spaceIdx + 1);\n objects.push({\n sha: line.slice(0, spaceIdx),\n ...(path ? { path } : {}),\n });\n }\n }\n return objects;\n }\n\n /**\n * List every blob (file) reachable from a ref's root tree, recursively,\n * with the path it is served at (#368: the ar.io site manifest join key).\n * Uses `git ls-tree -r -z` — NUL-terminated records so binary/spaced paths\n * survive verbatim, and no path quoting to undo. Submodule (`commit`)\n * gitlink entries and directories are excluded; only real file blobs remain.\n */\n async listBlobs(rev: string): Promise<{ path: string; sha: string }[]> {\n assertRevision(rev, 'ref');\n const { stdout } = await this.git(['ls-tree', '-r', '-z', rev, '--']);\n const blobs: { path: string; sha: string }[] = [];\n for (const record of stdout.split('\\0')) {\n if (!record) continue;\n // `<mode> SP <type> SP <sha> TAB <path>`\n const tab = record.indexOf('\\t');\n if (tab === -1) {\n throw new GitError(\n `unexpected ls-tree record: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n const meta = record.slice(0, tab);\n const path = record.slice(tab + 1);\n const [, type, sha] = meta.split(' ');\n if (type !== 'blob') continue; // trees are flattened by -r; skip gitlinks\n if (!sha || !FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `unexpected ls-tree object id: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n blobs.push({ path, sha });\n }\n return blobs;\n }\n\n /** Run git feeding `input` on stdin; resolves collected stdout bytes. */\n private runWithStdin(args: string[], input: string): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const child = spawn('git', args, {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const out: Buffer[] = [];\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => out.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git ${args[0]}: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new GitError(`git ${args[0]} failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n resolve(Buffer.concat(out));\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(input);\n child.stdin.end();\n });\n }\n\n /** Of the given revisions, keep only those resolvable locally. */\n private async filterExisting(revs: string[]): Promise<string[]> {\n if (revs.length === 0) return [];\n const { missing } = await this.batchCheck(revs);\n const missingSet = new Set(missing);\n return revs.filter((r) => !missingSet.has(r));\n }\n\n /** One `cat-file --batch-check` pass; returns names reported missing. */\n private async batchCheck(names: string[]): Promise<{ missing: string[] }> {\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n names.join('\\n') + '\\n'\n );\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (line.endsWith(' missing')) missing.push(line.slice(0, -' missing'.length));\n }\n return { missing };\n }\n\n /**\n * Object metadata (type + body size) for a batch of SHAs via one\n * `cat-file --batch-check` pass — no bodies are read. Missing objects are\n * reported, not thrown.\n */\n async statObjects(shas: string[]): Promise<StatObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n shas.join('\\n') + '\\n'\n );\n\n const objects: ObjectStat[] = [];\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (!line) continue;\n if (line.endsWith(' missing')) {\n missing.push(line.slice(0, -' missing'.length));\n continue;\n }\n const [sha, type, sizeStr] = line.split(' ');\n const size = Number.parseInt(sizeStr ?? '', 10);\n if (\n !sha ||\n !type ||\n !OBJECT_TYPES.has(type) ||\n !Number.isSafeInteger(size) ||\n size < 0\n ) {\n throw new GitError(\n `unexpected cat-file --batch-check line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n objects.push({ sha, type: type as GitObjectType, size });\n }\n return { objects, missing };\n }\n\n /**\n * Read raw object bodies via a single streaming `git cat-file --batch`\n * child process. Bodies may be binary and are parsed size-driven across\n * chunk boundaries. Missing objects are reported, not thrown.\n */\n async readObjects(shas: string[]): Promise<ReadObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const parser = new BatchParser();\n await new Promise<void>((resolve, reject) => {\n const child = spawn('git', ['cat-file', '--batch'], {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n let parseError: Error | null = null;\n\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.stdout.on('data', (chunk: Buffer) => {\n if (parseError) return;\n try {\n parser.push(chunk);\n } catch (err) {\n parseError = err as Error;\n child.kill();\n }\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git cat-file: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (parseError) return reject(parseError);\n if (code !== 0) {\n return reject(\n new GitError(`git cat-file --batch failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n if (!parser.isComplete()) {\n return reject(\n new GitError('git cat-file --batch output ended mid-record', code ?? undefined, stderr.trim())\n );\n }\n resolve();\n });\n\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' will surface the error.\n });\n child.stdin.write(shas.join('\\n') + '\\n');\n child.stdin.end();\n });\n\n return { objects: parser.objects, missing: parser.missing };\n }\n\n /**\n * `git merge-base --is-ancestor <a> <b>` — true when `a` is an ancestor\n * of `b` (fast-forward check / force detection). Exit codes other than\n * 0/1 (e.g. unknown revisions) throw.\n */\n async isAncestor(a: string, b: string): Promise<boolean> {\n assertRevision(a, 'ancestor candidate');\n assertRevision(b, 'descendant candidate');\n const { exitCode } = await this.git(\n ['merge-base', '--is-ancestor', a, b],\n { allowExitCodes: [1] }\n );\n return exitCode === 0;\n }\n\n /**\n * `git format-patch --stdout <range>` — the full mbox-formatted patch\n * series text (empty string when the range selects no commits).\n */\n async formatPatch(range: string): Promise<string> {\n assertRange(range, 'range');\n const { stdout } = await this.git(['format-patch', '--stdout', range, '--']);\n return stdout;\n }\n\n /**\n * Parent SHAs for a batch of commit SHAs via one\n * `git rev-list --no-walk=unsorted --parents` pass. Root commits map to an\n * empty array. Used to derive the kind:1617 `commit`/`parent-commit` tag\n * pairs for exactly the commits a format-patch series carries.\n */\n async commitParents(shas: string[]): Promise<Map<string, string[]>> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return new Map();\n const { stdout } = await this.git([\n 'rev-list',\n '--no-walk=unsorted',\n '--parents',\n ...shas,\n '--',\n ]);\n const parents = new Map<string, string[]>();\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const [sha, ...rest] = line.split(' ');\n if (!sha || !FULL_SHA_RE.test(sha) || rest.some((p) => !FULL_SHA_RE.test(p))) {\n throw new GitError(\n `unexpected rev-list --parents line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n parents.set(sha, rest);\n }\n return parents;\n }\n\n /**\n * Resolve a ref/revision to a full SHA via `git rev-parse --verify`.\n * Throws {@link GitError} when the name doesn't resolve.\n */\n async resolveRef(name: string): Promise<string> {\n assertRevision(name, 'ref name');\n const { stdout } = await this.git(['rev-parse', '--verify', '--quiet', name]);\n const sha = stdout.trim();\n if (!FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `rev-parse --verify returned unexpected output for ${JSON.stringify(name)}: ${JSON.stringify(sha)}`,\n undefined,\n ''\n );\n }\n return sha;\n }\n}\n","/**\n * The Git-from-TOON READ pipeline core (#278): download git object bodies\n * from Arweave gateways, verify them against their SHA-1, and walk the\n * object graph to prove a ref closure is complete.\n *\n * This is the CLI counterpart of rig-web's proven browser read path\n * (`web/arweave-client.ts` + `web/git-objects.ts` + the commit walker) — the\n * logic is mirrored, not imported, because the SPA package is not a library.\n *\n * Everything here is FREE (reads only: Arweave gateway GETs + the GraphQL\n * Git-SHA resolver) and pure of git — materializing objects into a real\n * repository lives in ./materialize.ts.\n *\n * INTEGRITY IS NON-NEGOTIABLE: an Arweave upload stores the object BODY\n * (content after the envelope NUL). Re-wrapping the body as each of the four\n * git object types and comparing the envelope SHA-1 against the expected SHA\n * both AUTHENTICATES the bytes and DISCOVERS the object's type in one step —\n * a body that matches under no type is rejected as corrupt/tampered, never\n * written.\n */\n\nimport {\n ARWEAVE_FETCH_TIMEOUT_MS,\n ARWEAVE_GATEWAYS,\n isValidArweaveTxId,\n} from '@toon-protocol/arweave';\nimport { hashGitObject, type GitObjectType } from './objects.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A downloaded git object: SHA-verified body + the type that verified it. */\nexport interface FetchedObject {\n /** Full 40-hex SHA-1 (verified against the body). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** WHATWG-fetch seam (injectable for tests). */\nexport type FetchLike = (\n url: string,\n init?: { signal?: AbortSignal }\n) => Promise<{\n ok: boolean;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\nexport interface GatewayFetchOptions {\n /** Ordered gateway base URLs (default: the shared preference list). */\n gateways?: readonly string[];\n /** fetch implementation (default: global fetch). */\n fetchFn?: FetchLike;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n}\n\nexport interface DownloadOptions extends GatewayFetchOptions {\n /** Maximum concurrent gateway downloads (default {@link DEFAULT_CONCURRENCY}). */\n concurrency?: number;\n /** Progress callback, called once per finished object. */\n onObject?: (done: number, total: number) => void;\n}\n\n/** Result of {@link downloadGitObjects}. */\nexport interface DownloadResult {\n /** SHA → verified object, for every SHA that could be downloaded. */\n objects: Map<string, FetchedObject>;\n /** SHAs whose txId 404'd/errored on EVERY gateway (propagation lag). */\n unavailable: { sha: string; txId: string }[];\n}\n\n/** Default parallel-download cap. */\nexport const DEFAULT_CONCURRENCY = 8;\n\n/**\n * A downloaded body did not hash to its expected SHA under ANY git object\n * type — corrupt or tampered content. The clone/fetch pipelines treat this\n * as a hard failure: nothing is written.\n */\nexport class ObjectIntegrityError extends Error {\n constructor(\n /** The objects that failed verification. */\n public readonly objects: { sha: string; txId: string }[]\n ) {\n super(\n `${objects.length} downloaded object(s) failed SHA-1 verification — ` +\n 'the gateway content does not match the announced git SHA(s): ' +\n objects.map((o) => `${o.sha} (tx ${o.txId})`).join(', ') +\n '. Refusing to write corrupt/tampered objects.'\n );\n this.name = 'ObjectIntegrityError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Verification (SHA check == type discovery)\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: readonly GitObjectType[] = [\n 'blob',\n 'tree',\n 'commit',\n 'tag',\n];\n\n/**\n * Verify a downloaded body against its expected SHA-1 by trying the four git\n * envelope types. Returns the verified object, or null when no type matches\n * (corrupt/tampered bytes).\n */\nexport function verifyObjectBody(\n expectedSha: string,\n bytes: Uint8Array\n): FetchedObject | null {\n const body = Buffer.from(bytes);\n for (const type of OBJECT_TYPES) {\n if (hashGitObject(type, body).sha === expectedSha) {\n return { sha: expectedSha, type, body };\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway download (fallback chain, mirrors rig-web's fetchArweaveObject)\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch raw bytes for an Arweave tx id, trying each gateway in preference\n * order. Returns null when every gateway fails (404 / error / timeout).\n */\nexport async function fetchTxBytes(\n txId: string,\n options: GatewayFetchOptions = {}\n): Promise<Uint8Array | null> {\n if (!isValidArweaveTxId(txId)) return null;\n const gateways = options.gateways ?? ARWEAVE_GATEWAYS;\n const fetchFn = options.fetchFn ?? (fetch as FetchLike);\n const timeoutMs = options.timeoutMs ?? ARWEAVE_FETCH_TIMEOUT_MS;\n\n for (const gateway of gateways) {\n try {\n const response = await fetchFn(`${gateway}/${txId}`, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) continue;\n return new Uint8Array(await response.arrayBuffer());\n } catch {\n // Network error, timeout, or other failure — try the next gateway.\n }\n }\n return null;\n}\n\n/**\n * Download + verify a batch of git objects (sha → Arweave txId) with a\n * concurrency cap and per-gateway fallback.\n *\n * Objects that 404 on every gateway are reported in `unavailable` (Arweave\n * propagation lag — the caller decides whether that is fatal). Objects whose\n * bytes fail SHA-1 verification throw {@link ObjectIntegrityError}: corrupt\n * content is NEVER returned.\n */\nexport async function downloadGitObjects(\n entries: Iterable<[sha: string, txId: string]>,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const queue = [...entries];\n const total = queue.length;\n const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY);\n\n const objects = new Map<string, FetchedObject>();\n const unavailable: { sha: string; txId: string }[] = [];\n const corrupt: { sha: string; txId: string }[] = [];\n let done = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const next = queue.shift();\n if (!next) return;\n const [sha, txId] = next;\n const bytes = await fetchTxBytes(txId, options);\n if (bytes === null) {\n unavailable.push({ sha, txId });\n } else {\n const verified = verifyObjectBody(sha, bytes);\n if (verified === null) {\n corrupt.push({ sha, txId });\n } else {\n objects.set(sha, verified);\n }\n }\n done += 1;\n options.onObject?.(done, total);\n }\n };\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, total) }, () => worker())\n );\n\n if (corrupt.length > 0) throw new ObjectIntegrityError(corrupt);\n return { objects, unavailable };\n}\n\n// ---------------------------------------------------------------------------\n// Object-graph references (mirrors rig-web's git-objects parsing)\n// ---------------------------------------------------------------------------\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/** Submodule (gitlink) tree-entry mode: references a commit in ANOTHER repo. */\nconst GITLINK_MODE = '160000';\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0');\n return hex;\n}\n\n/**\n * SHAs an object references inside the SAME repository:\n * - commit → its tree + parents\n * - tree → entry SHAs, EXCEPT gitlinks (mode 160000: submodule commits\n * live in another repository and are never present — git fsck\n * skips them too)\n * - tag → the tagged object\n * - blob → nothing\n */\nexport function referencedShas(object: FetchedObject): string[] {\n switch (object.type) {\n case 'blob':\n return [];\n case 'tree': {\n const refs: string[] = [];\n const data = object.body;\n let offset = 0;\n while (offset < data.length) {\n const spaceIdx = data.indexOf(0x20, offset);\n if (spaceIdx === -1) break;\n const mode = data.subarray(offset, spaceIdx).toString('utf-8');\n const nulIdx = data.indexOf(0x00, spaceIdx + 1);\n if (nulIdx === -1 || nulIdx + 21 > data.length) break;\n const sha = bytesToHex(data.subarray(nulIdx + 1, nulIdx + 21));\n if (mode !== GITLINK_MODE) refs.push(sha);\n offset = nulIdx + 21;\n }\n return refs;\n }\n case 'commit': {\n const refs: string[] = [];\n const text = object.body.toString('utf-8');\n const headerEnd = text.indexOf('\\n\\n');\n const header = headerEnd === -1 ? text : text.slice(0, headerEnd);\n for (const line of header.split('\\n')) {\n if (line.startsWith('tree ')) refs.push(line.slice(5).trim());\n else if (line.startsWith('parent ')) refs.push(line.slice(7).trim());\n }\n return refs.filter((sha) => FULL_SHA_RE.test(sha));\n }\n case 'tag': {\n const text = object.body.toString('utf-8');\n const match = /^object ([0-9a-f]{40})$/m.exec(text);\n return match?.[1] ? [match[1]] : [];\n }\n }\n}\n\n/** Result of {@link walkClosure}. */\nexport interface ClosureResult {\n /** Every SHA reachable from the tips that lives in `objects`. */\n reachable: Set<string>;\n /** Reachable SHAs found NEITHER in `objects` nor in `presentLocally`. */\n missing: string[];\n}\n\n/**\n * Walk the object graph from the ref tips over the downloaded object set and\n * report which reachable SHAs are missing. `presentLocally` marks SHAs that\n * already exist in the destination repository — the walk does not descend\n * into them (a consistent local repo carries its own closure; the same\n * assumption `git fetch` makes).\n */\nexport function walkClosure(\n tips: Iterable<string>,\n objects: ReadonlyMap<string, FetchedObject>,\n presentLocally: ReadonlySet<string> = new Set()\n): ClosureResult {\n const reachable = new Set<string>();\n const missing = new Set<string>();\n const stack = [...new Set(tips)];\n\n while (stack.length > 0) {\n const sha = stack.pop() as string;\n if (reachable.has(sha) || missing.has(sha)) continue;\n if (presentLocally.has(sha)) continue; // local closure assumed complete\n const object = objects.get(sha);\n if (!object) {\n missing.add(sha);\n continue;\n }\n reachable.add(sha);\n for (const ref of referencedShas(object)) {\n if (!reachable.has(ref) && !missing.has(ref)) stack.push(ref);\n }\n }\n\n return { reachable, missing: [...missing] };\n}\n","/**\n * The shared clone/fetch object-collection engine (#278).\n *\n * Given the remote's ref tips and its kind:30618 sha→Arweave-txId map, gather\n * every object the refs need:\n *\n * 1. download the mapped objects the destination repo doesn't already have\n * (parallel, gateway fallback chain, SHA-verified — ./object-fetch.ts);\n * 2. walk the object graph from the tips (./object-fetch.ts walkClosure) —\n * the local repository's objects count as present (git fetch's own\n * assumption: a consistent repo carries its own closure);\n * 3. SHAs the map doesn't cover are resolved through the Arweave GraphQL\n * Git-SHA resolver (RemoteState.resolveMissing) and downloaded, looping\n * until the closure is complete or no progress can be made.\n *\n * The result separates FATAL gaps (reachable objects that could not be\n * obtained — usually Arweave gateway propagation lag, 10–20 min for fresh\n * pushes) from harmless ones (mapped-but-unreachable objects, e.g. history\n * that was force-pushed away). Corrupt objects throw ObjectIntegrityError\n * from the download layer and never surface here.\n */\n\nimport {\n downloadGitObjects,\n walkClosure,\n type DownloadOptions,\n type FetchedObject,\n} from './object-fetch.js';\nimport { EMPTY_BLOB_SHA } from './objects.js';\n\n/** A reachable object that could not be obtained. */\nexport interface MissingObject {\n sha: string;\n /** The txId that failed on every gateway, or null when no txId resolved. */\n txId: string | null;\n}\n\nexport interface CollectRepoObjectsOptions extends DownloadOptions {\n /** Ref tip SHAs (commits or annotated tags) the closure must reach. */\n tips: string[];\n /** kind:30618 `arweave` tag map: git SHA → Arweave txId. */\n shaToTxId: ReadonlyMap<string, string>;\n /** GraphQL fallback for SHAs the map doesn't cover (RemoteState.resolveMissing). */\n resolveMissing: (shas: string[]) => Promise<Map<string, string>>;\n /** SHAs already present in the destination repository (fetch delta). */\n presentLocally?: ReadonlySet<string>;\n}\n\nexport interface CollectRepoObjectsResult {\n /** Verified objects to write, keyed by SHA. */\n objects: Map<string, FetchedObject>;\n /** Reachable SHAs that could not be obtained — FATAL for clone/fetch. */\n missing: MissingObject[];\n /** Mapped-but-unreachable SHAs that failed to download — warn only. */\n skippedUnavailable: { sha: string; txId: string }[];\n}\n\n/** Iteration cap: closure depth of NEW unmapped SHAs per pass; generous. */\nconst MAX_PASSES = 64;\n\n/** Collect (download + verify + close over) the objects the ref tips need. */\nexport async function collectRepoObjects(\n options: CollectRepoObjectsOptions\n): Promise<CollectRepoObjectsResult> {\n const { tips, shaToTxId, resolveMissing } = options;\n const present = options.presentLocally ?? new Set<string>();\n\n /** All txId knowledge: the 30618 map + GraphQL-resolved additions. */\n const txIds = new Map<string, string>(shaToTxId);\n /** SHAs we already asked the GraphQL resolver about (avoid re-queries). */\n const resolverAsked = new Set<string>();\n /** SHAs whose download failed on every gateway. */\n const undownloadable = new Map<string, string>();\n const objects = new Map<string, FetchedObject>();\n\n // Pass 0: bulk-download everything the map covers that isn't local yet.\n const initial: [string, string][] = [];\n for (const [sha, txId] of txIds) {\n if (!present.has(sha)) initial.push([sha, txId]);\n }\n const bulk = await downloadGitObjects(initial, options);\n for (const [sha, object] of bulk.objects) objects.set(sha, object);\n for (const { sha, txId } of bulk.unavailable) undownloadable.set(sha, txId);\n\n // Iterate: close over the tips; resolve + download whatever is still open.\n for (let pass = 0; pass < MAX_PASSES; pass++) {\n const closure = walkClosure(tips, objects, present);\n const open = closure.missing.filter((sha) => !undownloadable.has(sha));\n if (open.length === 0) break;\n\n // Resolve txIds for open SHAs we haven't asked the resolver about.\n const toResolve = open.filter(\n (sha) => !txIds.has(sha) && !resolverAsked.has(sha)\n );\n for (const sha of toResolve) resolverAsked.add(sha);\n if (toResolve.length > 0) {\n const resolved = await resolveMissing(toResolve);\n for (const [sha, txId] of resolved) txIds.set(sha, txId);\n }\n\n // Download open SHAs that now have a txId and no failed attempt yet.\n const batch: [string, string][] = [];\n for (const sha of open) {\n const txId = txIds.get(sha);\n if (txId !== undefined && !objects.has(sha)) batch.push([sha, txId]);\n }\n if (batch.length === 0) break; // no progress possible\n const result = await downloadGitObjects(batch, options);\n for (const [sha, object] of result.objects) objects.set(sha, object);\n for (const { sha, txId } of result.unavailable)\n undownloadable.set(sha, txId);\n if (result.objects.size === 0) break; // every attempt failed — stop\n }\n\n // The git empty blob is never uploaded (the store rejects zero-byte\n // content; `rig push` skips it), so a tree that references it reports it\n // \"missing\" here even though it is a git constant. Synthesize it locally —\n // a zero-byte blob body always hashes to EMPTY_BLOB_SHA — instead of\n // erroring, so an empty file reconstructs bit-identically (git fsck clean).\n // Keyed off the EXACT constant SHA: the honest lag-error still fires for any\n // genuinely-missing non-empty object.\n let finalClosure = walkClosure(tips, objects, present);\n if (\n finalClosure.missing.includes(EMPTY_BLOB_SHA) &&\n !present.has(EMPTY_BLOB_SHA)\n ) {\n objects.set(EMPTY_BLOB_SHA, {\n sha: EMPTY_BLOB_SHA,\n type: 'blob',\n body: Buffer.alloc(0),\n });\n finalClosure = walkClosure(tips, objects, present);\n }\n\n // Final accounting.\n const missing: MissingObject[] = finalClosure.missing.map((sha) => ({\n sha,\n txId: txIds.get(sha) ?? null,\n }));\n const reachable = finalClosure.reachable;\n const skippedUnavailable = [...undownloadable]\n .filter(\n ([sha]) => !reachable.has(sha) && !finalClosure.missing.includes(sha)\n )\n .map(([sha, txId]) => ({ sha, txId }));\n\n return { objects, missing, skippedUnavailable };\n}\n\n/**\n * The honest propagation-lag error text: which SHAs are unobtainable and why\n * retrying later is the expected remedy.\n */\nexport function missingObjectsMessage(\n missing: MissingObject[],\n context: string\n): string {\n const listed = missing\n .slice(0, 20)\n .map(\n (m) =>\n ` ${m.sha}${m.txId ? ` (tx ${m.txId})` : ' (no Arweave tx found)'}`\n )\n .join('\\n');\n const more =\n missing.length > 20 ? `\\n … and ${missing.length - 20} more` : '';\n return (\n `${context}: ${missing.length} required object(s) could not be downloaded:\\n` +\n `${listed}${more}\\n` +\n 'Recently pushed objects can take 10-20 minutes to become fetchable from ' +\n 'Arweave gateways — if this repo was just pushed, retry in a few minutes. ' +\n 'Nothing was written.'\n );\n}\n","/**\n * Materialize downloaded git objects into a REAL repository (#278).\n *\n * Objects are written through git's own plumbing — `git hash-object -w\n * --stdin -t <type>` with the raw body on stdin — so git computes, validates\n * (syntax checks for trees/commits/tags), stores (loose object + zlib), and\n * RETURNS the SHA. The returned SHA is compared against the expected one:\n * a second, independent integrity gate after ./object-fetch.ts's envelope\n * verification. Refs land via `git update-ref`, HEAD via `git symbolic-ref`.\n *\n * Same injection posture as GitRepoReader: child processes use\n * `execFile`/`spawn` with argument ARRAYS (never a shell), and refnames are\n * validated with `git check-ref-format` semantics before use.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FetchedObject } from './object-fetch.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** A written object's SHA disagreed with what `git hash-object` computed. */\nexport class ObjectWriteMismatchError extends Error {\n constructor(\n public readonly expectedSha: string,\n public readonly writtenSha: string\n ) {\n super(\n `git hash-object wrote ${writtenSha} where ${expectedSha} was expected — ` +\n 'object content does not round-trip; aborting'\n );\n this.name = 'ObjectWriteMismatchError';\n }\n}\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * Conservative refname validation (superset-safe subset of\n * `git check-ref-format`): must start `refs/`, no component may start with\n * `-` or `.`, no `..`, no control/space/git-special characters, no trailing\n * `/`, `.`, or `.lock`. Rejecting odd-but-legal names is fine — these\n * refnames come from relay events, and a hostile relay must not be able to\n * smuggle options or path traversal into git invocations.\n */\nexport function isSafeRefname(refname: string): boolean {\n if (!refname.startsWith('refs/') || refname.length > 1024) return false;\n // eslint-disable-next-line no-control-regex -- explicit control-char reject\n if (/[\\u0000-\\u0020~^:?*[\\\\\\u007f]/.test(refname)) return false;\n if (refname.includes('..') || refname.includes('@{')) return false;\n if (refname.endsWith('/') || refname.endsWith('.')) return false;\n for (const part of refname.split('/')) {\n if (part === '' || part.startsWith('.') || part.startsWith('-'))\n return false;\n if (part.endsWith('.lock')) return false;\n }\n return true;\n}\n\nfunction assertSafeRefname(refname: string): void {\n if (!isSafeRefname(refname)) {\n throw new Error(\n `unsafe ref name from remote state: ${JSON.stringify(refname)} — refusing`\n );\n }\n}\n\nfunction assertFullSha(sha: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(`not a full 40-hex SHA-1: ${JSON.stringify(sha)}`);\n }\n}\n\n/** Run git with argument-array safety in `repoPath`. */\nexport async function runGit(\n repoPath: string,\n args: string[]\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: repoPath,\n encoding: 'utf-8',\n maxBuffer: 64 * 1024 * 1024,\n });\n return stdout;\n } catch (err) {\n const e = err as {\n code?: number | string;\n stderr?: string;\n message?: string;\n };\n throw new Error(\n `git ${args[0]} failed${typeof e.code === 'number' ? ` (exit ${e.code})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`\n );\n }\n}\n\n/**\n * Write one object via `git hash-object -w --stdin -t <type>` (binary-safe\n * stdin) and verify the SHA git computed matches the expected one.\n */\nexport async function writeGitObject(\n repoPath: string,\n object: FetchedObject\n): Promise<void> {\n assertFullSha(object.sha);\n const written = await new Promise<string>((resolve, reject) => {\n const child = spawn(\n 'git',\n ['hash-object', '-w', '--stdin', '-t', object.type],\n { cwd: repoPath, stdio: ['pipe', 'pipe', 'pipe'] }\n );\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf-8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new Error(`failed to spawn git hash-object: ${err.message}`));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new Error(`git hash-object failed (exit ${code}): ${stderr.trim()}`)\n );\n }\n resolve(stdout.trim());\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(object.body);\n child.stdin.end();\n });\n if (written !== object.sha) {\n throw new ObjectWriteMismatchError(object.sha, written);\n }\n}\n\n/** Write a batch of verified objects into the repository (sequential). */\nexport async function writeGitObjects(\n repoPath: string,\n objects: Iterable<FetchedObject>\n): Promise<number> {\n let count = 0;\n for (const object of objects) {\n await writeGitObject(repoPath, object);\n count += 1;\n }\n return count;\n}\n\n/** `git update-ref <refname> <sha>` with refname/SHA validation. */\nexport async function updateRef(\n repoPath: string,\n refname: string,\n sha: string\n): Promise<void> {\n assertSafeRefname(refname);\n assertFullSha(sha);\n await runGit(repoPath, ['update-ref', refname, sha]);\n}\n\n/** Point HEAD at a branch via `git symbolic-ref HEAD <refname>`. */\nexport async function setHeadSymref(\n repoPath: string,\n refname: string\n): Promise<void> {\n assertSafeRefname(refname);\n await runGit(repoPath, ['symbolic-ref', 'HEAD', refname]);\n}\n","/**\n * Minimal bech32 npub encoding/decoding for Nostr pubkeys (BIP-173 / NIP-19),\n * dependency-free. Mirrors rig-web's proven `web/npub.ts` — the CLI must not\n * import from the SPA package, so the ~100 lines live here too (#278: `rig\n * clone` accepts `<owner-npub-or-hex>/<repo-id>` addresses).\n */\n\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\nfunction bech32Polymod(values: number[]): number {\n const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\n let chk = 1;\n for (const v of values) {\n const b = chk >> 25;\n chk = ((chk & 0x1ffffff) << 5) ^ v;\n for (let i = 0; i < 5; i++) {\n chk ^= (b >> i) & 1 ? (GEN[i] as number) : 0;\n }\n }\n return chk;\n}\n\nfunction bech32HrpExpand(hrp: string): number[] {\n const ret: number[] = [];\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) >> 5);\n }\n ret.push(0);\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) & 31);\n }\n return ret;\n}\n\nfunction bech32CreateChecksum(hrp: string, data: number[]): number[] {\n const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);\n const polymod = bech32Polymod(values) ^ 1;\n const ret: number[] = [];\n for (let i = 0; i < 6; i++) {\n ret.push((polymod >> (5 * (5 - i))) & 31);\n }\n return ret;\n}\n\nfunction convertBits(\n data: number[],\n fromBits: number,\n toBits: number,\n pad: boolean\n): number[] {\n let acc = 0;\n let bits = 0;\n const ret: number[] = [];\n const maxv = (1 << toBits) - 1;\n for (const value of data) {\n acc = (acc << fromBits) | value;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n ret.push((acc >> bits) & maxv);\n }\n }\n if (pad && bits > 0) {\n ret.push((acc << (toBits - bits)) & maxv);\n }\n return ret;\n}\n\nfunction hexToBytes(hex: string): number[] {\n const bytes: number[] = [];\n for (let i = 0; i < hex.length; i += 2) {\n bytes.push(parseInt(hex.slice(i, i + 2), 16));\n }\n return bytes;\n}\n\n/** Encode a 64-char hex pubkey as an `npub1…` bech32 string. */\nexport function hexToNpub(hexPubkey: string): string {\n const hrp = 'npub';\n const bytes = hexToBytes(hexPubkey);\n const words = convertBits(bytes, 8, 5, true);\n const checksum = bech32CreateChecksum(hrp, words);\n const combined = words.concat(checksum);\n return hrp + '1' + combined.map((d) => BECH32_CHARSET[d]).join('');\n}\n\n/**\n * Decode an `npub1…` bech32 string back to a 64-char hex pubkey.\n * Throws on malformed input (prefix, length, charset, checksum, padding).\n */\nexport function npubToHex(npub: string): string {\n const lower = npub.toLowerCase();\n if (lower !== npub && npub.toUpperCase() !== npub) {\n throw new Error('npub: mixed case');\n }\n if (!lower.startsWith('npub1')) {\n throw new Error('npub: invalid prefix');\n }\n if (lower.length !== 63) {\n throw new Error('npub: invalid length');\n }\n\n const data: number[] = [];\n for (let i = 5; i < lower.length; i++) {\n const idx = BECH32_CHARSET.indexOf(lower[i] as string);\n if (idx === -1) throw new Error('npub: invalid character');\n data.push(idx);\n }\n\n // Verify checksum\n const hrpExpanded = bech32HrpExpand('npub');\n if (bech32Polymod(hrpExpanded.concat(data)) !== 1) {\n throw new Error('npub: invalid checksum');\n }\n\n // Strip 6-word checksum, convert 5-bit words back to 8-bit bytes\n const words = data.slice(0, -6);\n const bytes = convertBits(words, 5, 8, false);\n\n if (bytes.length !== 32) {\n throw new Error('npub: invalid data length');\n }\n\n // Validate trailing bits are zero\n const totalBits = words.length * 5;\n const trailingBits = totalBits - bytes.length * 8;\n if (trailingBits > 0) {\n const lastWord = words[words.length - 1] as number;\n const mask = (1 << trailingBits) - 1;\n if ((lastWord & mask) !== 0) {\n throw new Error('npub: non-zero padding bits');\n }\n }\n\n return bytes.map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nconst HEX64_RE = /^[0-9a-f]{64}$/;\n\n/**\n * Normalize a repo-owner reference to a 64-char hex pubkey: accepts lowercase\n * hex verbatim or an `npub1…` string. Throws a caller-facing error otherwise.\n */\nexport function ownerToHex(owner: string): string {\n if (HEX64_RE.test(owner)) return owner;\n if (owner.startsWith('npub1')) {\n try {\n return npubToHex(owner);\n } catch (err) {\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: expected a 64-char lowercase hex pubkey or an npub1… string`\n );\n}\n","/**\n * Wire shapes of the toon-clientd `/git/*` control routes (epic #222).\n *\n * These are the JSON request/response types the daemon serves (bigints as\n * decimal strings, Maps as plain records) — defined HERE, in the dependency\n * root, so both sides of the route can share them: the `rig` CLI (#229)\n * consumes them as a plain-fetch client, and `@toon-protocol/client-mcp`\n * (which depends on this package for the planner — the reverse import would\n * be circular) can adopt them for its `control-api.ts` declarations. TYPES\n * ONLY plus two pure serializers; no transport code lives here.\n *\n * Keep in byte-for-byte sync with\n * `packages/client-mcp/src/control-api.ts` (`Git*` shapes) and\n * `packages/client-mcp/src/daemon/routes.ts` (error envelopes: 409\n * `non_fast_forward` carries `refs`, 413 `oversize_objects` carries\n * `objects`, 503 `bootstrapping` / 402 `insufficient_gas` are retryable).\n */\n\nimport type { PublishReceipt } from './publisher.js';\nimport type { PlannedObject, PushPlan, PushResult, RefUpdate } from './push.js';\n\n/** One planned ref update (JSON-safe as-is). */\nexport type GitRefUpdate = RefUpdate;\n\n/** One object scheduled for upload (JSON-safe as-is). */\nexport type GitPlannedObject = PlannedObject;\n\n/**\n * `POST /git/estimate` — plan a push (local git plumbing + remote-state read)\n * and price it WITHOUT paying anything. The same body (plus `confirm`) drives\n * `POST /git/push`.\n */\nexport interface GitEstimateRequest {\n /** Path to the local git repository (worktree or .git dir). Must exist. */\n repoPath: string;\n /** Repository identifier (NIP-34 `d` tag). The daemon identity is the owner. */\n repoId: string;\n /**\n * Full refnames to push (e.g. `[\"refs/heads/main\"]`). Default: every local\n * branch and tag.\n */\n refspecs?: string[];\n /** Allow non-fast-forward updates (default false → 409 `non_fast_forward`). */\n force?: boolean;\n /**\n * Relay URLs to read remote state from and publish to. Plural from day one\n * (forward-compat); defaults to the daemon's config-seeded relay.\n */\n relayUrls?: string[];\n /** Repo name/description for the first-push kind:30617 announcement. */\n announcement?: { name?: string; description?: string };\n}\n\n/** Pre-push fee table (all fees in base/micro units, decimal strings). */\nexport interface GitFeeEstimate {\n objectCount: number;\n totalObjectBytes: number;\n /** Σ max(size × uploadFeePerByte, per-upload route-price floor). */\n uploadFee: string;\n /** Events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × per-event fee. */\n eventFees: string;\n /** uploadFee + eventFees. */\n totalFee: string;\n /**\n * Zero-byte objects (the git empty blob) excluded from the upload — the\n * store rejects zero-byte content as malformed, so they are skipped on push\n * and reconstructed on clone/fetch. Optional for wire compatibility with\n * daemons predating the empty-blob handling. Default 0.\n */\n skippedEmptyCount?: number;\n}\n\n/** Serialized `PushPlan` — everything a confirm UI needs. */\nexport interface GitEstimateResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Full new ref state to publish (HEAD target first). */\n newRefs: Record<string, string>;\n headSymref: string | null;\n objects: GitPlannedObject[];\n /** sha→txId hints known WITHOUT uploading (remote tags + resolver finds). */\n knownShaToTxId: Record<string, string>;\n /** True when no kind:30617 exists yet — the push announces first. */\n announceNeeded: boolean;\n announcement: { name: string; description: string };\n estimate: GitFeeEstimate;\n}\n\n/**\n * `POST /git/push` — plan + execute: upload the delta to Arweave and publish\n * the cumulative kind:30618 (+ kind:30617 on first push). PERMANENT + PAID.\n */\nexport interface GitPushRequest extends GitEstimateRequest {\n /** Must be literally `true` — a push spends channel funds irreversibly. */\n confirm: boolean;\n}\n\n/** One object-upload step result. */\nexport interface GitUploadStep {\n sha: string;\n txId: string;\n /** '0' when skipped (already on Arweave — content-addressed resume). */\n feePaid: string;\n skipped: boolean;\n}\n\n/** Receipt for one published event. */\nexport interface GitPublishReceipt {\n eventId: string;\n feePaid: string;\n}\n\n/** Serialized `PushResult` — per-step receipts + total fees actually paid. */\nexport interface GitPushResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Per-object results, in plan order. */\n uploads: GitUploadStep[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: GitPublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: GitPublishReceipt;\n /** Full sha→txId map published in the refs event. */\n arweaveMap: Record<string, string>;\n /** Total fees actually paid (uploads + events), base units, decimal. */\n totalFeePaid: string;\n /** The pre-push estimate the push ran under (compare against totalFeePaid). */\n estimate: GitFeeEstimate;\n}\n\n// ---------------------------------------------------------------------------\n// Single-event git publishes (`/git/issue|comment|patch|status`, #231)\n// ---------------------------------------------------------------------------\n\n/** NIP-34 repository address: the owner+id pair behind `a` tags. */\nexport interface GitRepoAddr {\n /** Repository owner's Nostr pubkey (64-char hex) — author of kind:30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n}\n\n/** `POST /git/issue` — publish a kind:1621 issue against a repo. PAID. */\nexport interface GitIssueRequest {\n repoAddr: GitRepoAddr;\n /** Issue title (`subject` tag). */\n title: string;\n /** Issue body (Markdown content). */\n body: string;\n /** Labels (`t` tags). */\n labels?: string[];\n}\n\n/** `POST /git/comment` — publish a kind:1622 comment on an issue/patch. PAID. */\nexport interface GitCommentRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue or patch being commented on. */\n rootEventId: string;\n /** Comment body (Markdown content). */\n body: string;\n /**\n * Pubkey of the TARGET event's author (NIP-34 `p` threading tag — not the\n * comment author). Defaults to the repo owner.\n */\n parentAuthorPubkey?: string;\n /** `e`-tag marker (default 'root': commenting directly on the issue/patch). */\n marker?: 'root' | 'reply';\n}\n\n/**\n * `POST /git/patch` — publish a kind:1617 patch. Supply EXACTLY ONE of\n * `patchText` (literal `git format-patch` output) or `repoPath`+`range`\n * (the daemon runs `git format-patch --stdout <range>` locally). PAID.\n */\nexport interface GitPatchRequest {\n repoAddr: GitRepoAddr;\n /** Patch/PR title (`subject` tag). */\n title: string;\n /**\n * PR body/cover text (`description` tag). Kept OUT of the event content so\n * `git am` still consumes the patch text verbatim (#280).\n */\n description?: string;\n /** Literal patch text. Mutually exclusive with `repoPath`+`range`. */\n patchText?: string;\n /** Local repository to run format-patch in. Requires `range`. */\n repoPath?: string;\n /** Revision range for format-patch (`<rev>`, `<rev>..<rev>`, `<rev>...<rev>`). */\n range?: string;\n /** Commit/parent pairs for `commit`/`parent-commit` tags. */\n commits?: { sha: string; parentSha: string }[];\n /** Branch name for the `t` tag. */\n branch?: string;\n}\n\nexport type GitStatusValue = 'open' | 'applied' | 'closed' | 'draft';\n\n/** `POST /git/status` — publish a kind:1630-1633 status event. PAID. */\nexport interface GitStatusRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue/patch whose status is being set. */\n targetEventId: string;\n /** open → 1630, applied → 1631, closed → 1632, draft → 1633. */\n status: GitStatusValue;\n /** Pubkey of the target event's author (`p` tag), when known. */\n targetPubkey?: string;\n}\n\n/**\n * Response of the single-event git publishes (issue/comment/patch/status):\n * a publish receipt plus the NIP-34 kind that was published. Daemon\n * responses extend the full `POST /publish` receipt, so the channel fields\n * (`channelId`/`nonce`/…) are present there; they are optional here because\n * the CLI's standalone path publishes through the embedded client and has\n * no channel wire shape to report.\n */\nexport interface GitEventResponse {\n /** Event ID as accepted by the relay. */\n eventId: string;\n /** Fee actually paid for this publish, base units, decimal string. */\n feePaid: string;\n /** The NIP-34 kind that was published. */\n kind: number;\n /** Channel the claim was signed against (daemon responses). */\n channelId?: string;\n /** Channel nonce after this publish (daemon responses). */\n nonce?: number;\n /** FULFILL response data (base64), when the backend returned any. */\n data?: string;\n /** Spendable channel balance after this write, when known. */\n channelBalanceAfter?: string;\n}\n\n/**\n * Uniform error envelope of non-2xx control-route responses. Structured\n * errors put extra fields at the top level: `non_fast_forward` (409) adds\n * `refs`, `oversize_objects` (413) adds `objects`.\n */\nexport interface GitErrorEnvelope {\n error: string;\n detail?: string;\n /** True when the caller should retry (e.g. daemon still bootstrapping). */\n retryable?: boolean;\n [extra: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Serializers (pure) — the exact mapping the daemon routes apply; used by the\n// CLI's standalone mode so both surfaces emit identical wire JSON.\n// ---------------------------------------------------------------------------\n\n/** Serialize a plan's fee estimate onto the wire (bigints → strings). */\nexport function serializeFeeEstimate(plan: PushPlan): GitFeeEstimate {\n return {\n objectCount: plan.estimate.objectCount,\n totalObjectBytes: plan.estimate.totalObjectBytes,\n uploadFee: plan.estimate.uploadFee.toString(),\n eventCount: plan.estimate.eventCount,\n eventFees: plan.estimate.eventFees.toString(),\n totalFee: plan.estimate.totalFee.toString(),\n ...(plan.estimate.skippedEmptyCount > 0\n ? { skippedEmptyCount: plan.estimate.skippedEmptyCount }\n : {}),\n };\n}\n\n/** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */\nexport function serializePushPlan(plan: PushPlan): GitEstimateResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n newRefs: plan.newRefs,\n headSymref: plan.headSymref,\n objects: plan.objects,\n knownShaToTxId: Object.fromEntries(plan.knownShaToTxId),\n announceNeeded: plan.announceNeeded,\n announcement: plan.announcement,\n estimate: serializeFeeEstimate(plan),\n };\n}\n\n/**\n * Serialize a standalone {@link PublishReceipt} into the wire shape the\n * daemon's single-event `/git/*` routes answer with, so `--json` consumers\n * see one `GitEventResponse` shape regardless of publisher mode.\n */\nexport function serializeEventReceipt(\n kind: number,\n receipt: PublishReceipt\n): GitEventResponse {\n return {\n eventId: receipt.eventId,\n feePaid: receipt.feePaid.toString(),\n kind,\n };\n}\n\n/** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */\nexport function serializePushResult(\n plan: PushPlan,\n result: PushResult\n): GitPushResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n uploads: result.uploads.map((u) => ({\n sha: u.sha,\n txId: u.txId,\n feePaid: u.feePaid.toString(),\n skipped: u.skipped,\n })),\n announceReceipt: result.announceReceipt\n ? {\n eventId: result.announceReceipt.eventId,\n feePaid: result.announceReceipt.feePaid.toString(),\n }\n : null,\n refsReceipt: {\n eventId: result.refsReceipt.eventId,\n feePaid: result.refsReceipt.feePaid.toString(),\n },\n arweaveMap: Object.fromEntries(result.arweaveMap),\n totalFeePaid: result.totalFeePaid.toString(),\n estimate: serializeFeeEstimate(plan),\n };\n}\n","/**\n * Push planner/executor — the core of `rig push` (epic #222, ticket #226).\n *\n * `planPush` is network-free (relay/Arweave-wise — it only runs local git\n * plumbing through GitRepoReader plus one injectable async resolver step):\n * it classifies every ref update, computes the object delta against what the\n * remote already stores, hard-errors on oversize objects, and prices the\n * push. The returned {@link PushPlan} carries everything a confirm UI needs.\n *\n * `executePush` spends money: it uploads the planned objects through a\n * {@link Publisher} (ref-tip objects last, so a crashed push never leads to\n * a state where a discoverable tip's history is missing), then publishes ONE\n * cumulative kind:30618 whose `arweave` tags are the MERGE of the remote's\n * existing sha→txId map and the new uploads — kind:30618 is NIP-33\n * replaceable, so dropping prior tags would orphan earlier hints — and whose\n * `r` tags are the full new ref state. On a first push it publishes the\n * kind:30617 announcement before the refs event.\n *\n * Resume safety: uploads are content-addressed (Git-SHA-tagged), so re-running\n * `executePush` after a crash is safe — it consults the merged\n * remote + planned sha→txId map before paying for any upload, and a re-plan\n * with fresh remote state (whose `resolveMissing` finds the already-uploaded\n * objects via GraphQL) skips them entirely.\n */\n\nimport { buildRepoAnnouncement, buildRepoRefs } from './nip34-events.js';\nimport {\n EMPTY_BLOB_SHA,\n MAX_OBJECT_SIZE,\n type GitObjectType,\n} from './objects.js';\nimport {\n flooredUploadFee,\n type FeeRates,\n type PublishReceipt,\n type Publisher,\n} from './publisher.js';\nimport { GitError, type GitRef, type GitRepoReader } from './repo-reader.js';\nimport type { RemoteState } from './remote-state.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** A ref update rejected because it is not a fast-forward. */\nexport interface RejectedRefUpdate {\n refname: string;\n localSha: string;\n remoteSha: string;\n}\n\n/** Thrown by {@link planPush} when a non-fast-forward update lacks `force`. */\nexport class NonFastForwardError extends Error {\n constructor(\n /** The refs that would need `--force` to update. */\n public readonly refs: RejectedRefUpdate[]\n ) {\n super(\n `non-fast-forward update rejected for ${refs\n .map((r) => r.refname)\n .join(', ')} — re-run with force to overwrite the remote ref(s)`\n );\n this.name = 'NonFastForwardError';\n }\n}\n\n/** One object exceeding {@link MAX_OBJECT_SIZE}. */\nexport interface OversizeObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes. */\n size: number;\n /** Path the object was reached by (blobs / non-root trees), if known. */\n path?: string;\n}\n\n/**\n * Thrown by {@link planPush} when any object in the delta exceeds the 95KB\n * upload limit (hard error in v1 — the paid blob path is a follow-up spike).\n */\nexport class OversizeObjectsError extends Error {\n constructor(\n /** The offending objects with paths and sizes. */\n public readonly objects: OversizeObject[]\n ) {\n super(\n `${objects.length} object(s) exceed the ${MAX_OBJECT_SIZE} byte upload limit: ` +\n objects\n .map((o) => `${o.path ?? o.sha} (${o.size} bytes)`)\n .join(', ')\n );\n this.name = 'OversizeObjectsError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plan types\n// ---------------------------------------------------------------------------\n\n/** How a ref moves relative to the remote. */\nexport type RefUpdateKind =\n /** Ref does not exist on the remote yet. */\n | 'new'\n /** Remote tip is an ancestor of the local tip. */\n | 'fast-forward'\n /** Non-fast-forward, allowed because `force` was set. */\n | 'forced'\n /** Local and remote tips already match — nothing to push. */\n | 'up-to-date';\n\n/** One planned ref update (deletions are out of scope in v1). */\nexport interface RefUpdate {\n /** Full refname, e.g. `refs/heads/main`. */\n refname: string;\n /** Local tip SHA (tag object SHA for annotated tags). */\n localSha: string;\n /** Remote tip SHA, or null when the ref is new. */\n remoteSha: string | null;\n kind: RefUpdateKind;\n}\n\n/** One object scheduled for upload. */\nexport interface PlannedObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes (what the upload fee is charged on). */\n size: number;\n /** Path the object was reached by, if any (blobs / non-root trees). */\n path?: string;\n /** True when this SHA is the tip of a planned ref update (uploaded last). */\n isRefTip: boolean;\n}\n\n/** Pre-push fee estimate — render this in the confirm table. */\nexport interface PushFeeEstimate {\n /** Number of objects to upload. */\n objectCount: number;\n /** Total bytes across all planned object bodies. */\n totalObjectBytes: number;\n /** Σ max(size × uploadFeePerByte, minUploadFee) — smallest asset unit. */\n uploadFee: bigint;\n /** Number of events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × eventFee (smallest asset unit). */\n eventFees: bigint;\n /** uploadFee + eventFees. */\n totalFee: bigint;\n /**\n * Objects excluded from the upload because their body is zero bytes — the\n * git empty blob, which the store rejects as malformed (F00). Reconstructed\n * locally on clone/fetch, so nothing is lost; surfaced here so the fee table\n * can report the skip honestly.\n */\n skippedEmptyCount: number;\n}\n\n/** Everything `executePush` (and a confirm UI) needs. */\nexport interface PushPlan {\n repoId: string;\n /** Every considered ref with its classification (incl. up-to-date). */\n refUpdates: RefUpdate[];\n /**\n * Full new ref state to publish as `r` tags: the remote's refs overlaid\n * with the planned updates (refs not being pushed are preserved — v1\n * never deletes). Ordered with the HEAD target first, which is what\n * `buildRepoRefs` derives the HEAD symref tag from.\n */\n newRefs: Record<string, string>;\n /** HEAD symref target for the new state (first key of {@link newRefs}). */\n headSymref: string | null;\n /**\n * Objects to upload, dependency-safe order: ref-tip objects last so a\n * crashed push never uploads a tip whose history is missing.\n */\n objects: PlannedObject[];\n /**\n * Zero-byte objects (the git empty blob) excluded from {@link objects}: the\n * store rejects a zero-byte kind:5094 upload as malformed (F00), so `rig`\n * never uploads it — the commit/tree still references it and clone/fetch\n * synthesizes it locally. Kept for honest receipts, never uploaded.\n */\n skippedEmptyObjects: PlannedObject[];\n /**\n * sha→txId hints known WITHOUT uploading: the remote's `arweave` tags\n * plus anything `resolveMissing` found. Merged into the published\n * kind:30618 so prior hints are never dropped.\n */\n knownShaToTxId: Map<string, string>;\n /** True when no kind:30617 exists yet — executePush announces first. */\n announceNeeded: boolean;\n /** Announcement metadata used when {@link announceNeeded}. */\n announcement: { name: string; description: string };\n estimate: PushFeeEstimate;\n}\n\nexport interface PlanPushOptions {\n repoReader: GitRepoReader;\n remoteState: RemoteState;\n /** Fee rates from `Publisher.getFeeRates()`. */\n feeRates: FeeRates;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /**\n * Full refnames to push (e.g. `['refs/heads/main']`). Defaults to every\n * local branch and tag. Refs that don't exist locally are an error\n * (deletions are out of scope in v1).\n */\n refs?: string[];\n /** Allow non-fast-forward updates (default false → hard error). */\n force?: boolean;\n /** Repo name/description for the first-push announcement. */\n announcement?: { name?: string; description?: string };\n /**\n * Async resolver for SHAs the remote's `arweave` tags don't cover —\n * consulted before deciding to re-upload. Defaults to\n * `remoteState.resolveMissing` (GraphQL fallback); injectable so the\n * planner core stays testable without network.\n */\n resolveMissing?: (shas: string[]) => Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// planPush\n// ---------------------------------------------------------------------------\n\n/**\n * Classify ref updates, compute the object delta, enforce the size limit,\n * and price the push. Throws {@link NonFastForwardError} /\n * {@link OversizeObjectsError} (both carry structured data for UIs).\n */\nexport async function planPush(options: PlanPushOptions): Promise<PushPlan> {\n const { repoReader, remoteState, feeRates, repoId, force = false } = options;\n const resolveMissing =\n options.resolveMissing ?? remoteState.resolveMissing.bind(remoteState);\n\n // 1. Select and classify refs. -------------------------------------------\n const { head, refs: localRefs } = await repoReader.listRefs();\n const localByName = new Map(localRefs.map((r) => [r.refname, r]));\n\n let selected: GitRef[];\n if (options.refs !== undefined) {\n selected = options.refs.map((name) => {\n const ref = localByName.get(name);\n if (!ref) {\n throw new Error(\n `ref ${JSON.stringify(name)} does not exist locally ` +\n '(ref deletion is out of scope in v1)'\n );\n }\n return ref;\n });\n } else {\n selected = localRefs;\n }\n\n const refUpdates: RefUpdate[] = [];\n const rejected: RejectedRefUpdate[] = [];\n for (const ref of selected) {\n const remoteSha = remoteState.refs.get(ref.refname) ?? null;\n if (remoteSha === null) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'new' });\n continue;\n }\n if (remoteSha === ref.sha) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'up-to-date' });\n continue;\n }\n let fastForward = false;\n try {\n fastForward = await repoReader.isAncestor(remoteSha, ref.sha);\n } catch (err) {\n // Remote tip unknown locally (never fetched) or not a commit-ish —\n // we can't prove ancestry, so treat it as non-fast-forward.\n if (!(err instanceof GitError)) throw err;\n fastForward = false;\n }\n if (fastForward) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'fast-forward' });\n } else if (force) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'forced' });\n } else {\n rejected.push({ refname: ref.refname, localSha: ref.sha, remoteSha });\n }\n }\n if (rejected.length > 0) throw new NonFastForwardError(rejected);\n\n const updates = refUpdates.filter((u) => u.kind !== 'up-to-date');\n\n // 2. Object delta: reachable from the new tips, minus what the remote has.\n const wantTips = [...new Set(updates.map((u) => u.localSha))];\n const haveTips = [...new Set(remoteState.refs.values())];\n const delta =\n wantTips.length > 0\n ? await repoReader.objectsBetweenWithPaths(wantTips, haveTips)\n : [];\n\n const knownShaToTxId = new Map(remoteState.shaToTxId);\n let candidates = delta.filter((o) => !knownShaToTxId.has(o.sha));\n if (candidates.length > 0) {\n // The remote's `arweave` tags may lag reality (e.g. a crashed push\n // uploaded objects but never published the refs event) — resolve the\n // gaps before paying to re-upload content-addressed data.\n const resolved = await resolveMissing(candidates.map((o) => o.sha));\n for (const [sha, txId] of resolved) knownShaToTxId.set(sha, txId);\n candidates = candidates.filter((o) => !knownShaToTxId.has(o.sha));\n }\n\n // 3. Sizes + oversize hard error. -----------------------------------------\n const pathBySha = new Map(candidates.map((c) => [c.sha, c.path]));\n const { objects: stats, missing } = await repoReader.statObjects(\n candidates.map((c) => c.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during planning: ${missing.join(', ')}`\n );\n }\n\n const oversize: OversizeObject[] = [];\n for (const stat of stats) {\n if (stat.size > MAX_OBJECT_SIZE) {\n const path = pathBySha.get(stat.sha);\n oversize.push({ ...stat, ...(path ? { path } : {}) });\n }\n }\n if (oversize.length > 0) throw new OversizeObjectsError(oversize);\n\n // 4. Split off the empty blob, then order the rest ref-tips-last. ----------\n // The git empty blob uploads as an empty kind:5094 `i` value, which the\n // store rejects as malformed (F00). Skip it: it is reconstructed locally on\n // clone/fetch. Keyed off the EXACT empty-blob constant SHA — NOT a\n // `size === 0` heuristic, which would also match the (distinct, valid) empty\n // TREE object `4b825dc6…` and silently drop it (it is not synthesized on\n // read). An object whose SHA is EMPTY_BLOB_SHA is provably the empty blob.\n const tipShas = new Set(updates.map((u) => u.localSha));\n const planned: PlannedObject[] = [];\n const skippedEmptyObjects: PlannedObject[] = [];\n for (const stat of stats) {\n const path = pathBySha.get(stat.sha);\n const object: PlannedObject = {\n ...stat,\n ...(path ? { path } : {}),\n isRefTip: tipShas.has(stat.sha),\n };\n if (stat.sha === EMPTY_BLOB_SHA) skippedEmptyObjects.push(object);\n else planned.push(object);\n }\n const objects = [\n ...planned.filter((o) => !o.isRefTip),\n ...planned.filter((o) => o.isRefTip),\n ];\n\n // 5. Full new ref state (remote refs overlaid with updates), HEAD first. --\n const newRefsMap = new Map(remoteState.refs);\n for (const update of updates) newRefsMap.set(update.refname, update.localSha);\n\n const headSymref =\n head && newRefsMap.has(head)\n ? head\n : remoteState.headSymref && newRefsMap.has(remoteState.headSymref)\n ? remoteState.headSymref\n : ([...newRefsMap.keys()][0] ?? null);\n\n const newRefs: Record<string, string> = {};\n const headSha = headSymref ? newRefsMap.get(headSymref) : undefined;\n if (headSymref && headSha) newRefs[headSymref] = headSha;\n for (const [refname, sha] of newRefsMap) {\n if (refname !== headSymref) newRefs[refname] = sha;\n }\n\n // 6. Fee estimate. ---------------------------------------------------------\n // Per-object pricing, each upload floored at the destination route's\n // announced price (`minUploadFee`) — the exact same math the publisher\n // claims per packet, so the confirm table always equals what is paid.\n const announceNeeded = !remoteState.announced;\n const totalObjectBytes = objects.reduce((sum, o) => sum + o.size, 0);\n const uploadFee = objects.reduce(\n (sum, o) =>\n sum +\n flooredUploadFee(o.size, feeRates.uploadFeePerByte, feeRates.minUploadFee),\n 0n\n );\n const eventCount = 1 + (announceNeeded ? 1 : 0);\n const eventFees = BigInt(eventCount) * feeRates.eventFee;\n\n return {\n repoId,\n refUpdates,\n newRefs,\n headSymref,\n objects,\n skippedEmptyObjects,\n knownShaToTxId,\n announceNeeded,\n announcement: {\n name: options.announcement?.name ?? repoId,\n description: options.announcement?.description ?? '',\n },\n estimate: {\n objectCount: objects.length,\n totalObjectBytes,\n uploadFee,\n eventCount,\n eventFees,\n totalFee: uploadFee + eventFees,\n skippedEmptyCount: skippedEmptyObjects.length,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// executePush\n// ---------------------------------------------------------------------------\n\n/** Result of one object-upload step. */\nexport interface UploadStepResult {\n sha: string;\n txId: string;\n /** 0n when skipped (already uploaded — content-addressed resume). */\n feePaid: bigint;\n /** True when the object was already on Arweave and nothing was paid. */\n skipped: boolean;\n}\n\n/** Result of the whole push. */\nexport interface PushResult {\n /** Per-object results, in plan order. */\n uploads: UploadStepResult[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: PublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: PublishReceipt;\n /**\n * The full sha→txId map published in the refs event: remote hints +\n * resolver finds + this push's uploads.\n */\n arweaveMap: Map<string, string>;\n /** Total fees actually paid (uploads + events), smallest asset unit. */\n totalFeePaid: bigint;\n}\n\nexport interface ExecutePushOptions {\n plan: PushPlan;\n publisher: Publisher;\n /**\n * Remote state — pass a FRESH fetch when resuming after a crash so its\n * `shaToTxId` (and `announced`) reflect what the previous attempt already\n * paid for.\n */\n remoteState: RemoteState;\n repoReader: GitRepoReader;\n /** Relay URLs to publish events to (plural from day one; size 1 today). */\n relayUrls: string[];\n}\n\n/** How many object bodies to hold in memory at once between read and upload. */\nconst READ_BATCH_SIZE = 100;\n\n/**\n * Execute a {@link PushPlan}: upload objects (ref tips last), then publish\n * the kind:30617 announcement (first push only) and ONE cumulative\n * kind:30618 whose `arweave` tags merge every known sha→txId hint with the\n * new uploads and whose `r` tags carry the full new ref state.\n *\n * Safe to re-run after a crash: SHAs already present in the merged\n * remote + plan map are skipped without paying.\n */\nexport async function executePush(\n options: ExecutePushOptions\n): Promise<PushResult> {\n const { plan, publisher, remoteState, repoReader, relayUrls } = options;\n\n // Merged sha→txId map: remote hints (fresh on resume) + plan-time finds.\n // Consulted before every upload — this is the resume-safety check.\n const merged = new Map([...remoteState.shaToTxId, ...plan.knownShaToTxId]);\n\n const resultBySha = new Map<string, UploadStepResult>();\n let totalFeePaid = 0n;\n\n const pending: PlannedObject[] = [];\n for (const object of plan.objects) {\n const knownTxId = merged.get(object.sha);\n if (knownTxId !== undefined) {\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: knownTxId,\n feePaid: 0n,\n skipped: true,\n });\n } else {\n pending.push(object);\n }\n }\n\n // Upload in plan order (ref tips are already last), reading bodies in\n // batches so memory stays bounded on large pushes.\n for (let i = 0; i < pending.length; i += READ_BATCH_SIZE) {\n const batch = pending.slice(i, i + READ_BATCH_SIZE);\n const { objects: read, missing } = await repoReader.readObjects(\n batch.map((o) => o.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during push: ${missing.join(', ')}`\n );\n }\n const bodyBySha = new Map(read.map((r) => [r.sha, r.body]));\n for (const object of batch) {\n const body = bodyBySha.get(object.sha);\n if (!body) {\n throw new Error(\n `internal: cat-file returned no body for ${object.sha}`\n );\n }\n const receipt = await publisher.uploadGitObject({\n sha: object.sha,\n type: object.type,\n body,\n repoId: plan.repoId,\n // #368: the path the blob was reached by drives its Content-Type; a\n // non-blob object (no path) uploads as octet-stream.\n ...(object.path ? { path: object.path } : {}),\n });\n merged.set(object.sha, receipt.txId);\n totalFeePaid += receipt.feePaid;\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: receipt.txId,\n feePaid: receipt.feePaid,\n skipped: false,\n });\n }\n }\n\n // Announcement (first push only) goes before the refs event so a repo is\n // never referenced by an `a` tag before its kind:30617 exists. Re-check\n // the (fresh-on-resume) remote state so a crashed push doesn't announce\n // twice.\n let announceReceipt: PublishReceipt | null = null;\n if (plan.announceNeeded && !remoteState.announced) {\n const announceEvent = buildRepoAnnouncement(\n plan.repoId,\n plan.announcement.name,\n plan.announcement.description\n );\n announceReceipt = await publisher.publishEvent(announceEvent, relayUrls);\n totalFeePaid += announceReceipt.feePaid;\n }\n\n // ONE cumulative kind:30618: full ref state + MERGED arweave map (NIP-33\n // replaceable — dropping prior tags would orphan earlier sha→txId hints).\n const refsEvent = buildRepoRefs(\n plan.repoId,\n plan.newRefs,\n Object.fromEntries(merged)\n );\n const refsReceipt = await publisher.publishEvent(refsEvent, relayUrls);\n totalFeePaid += refsReceipt.feePaid;\n\n const uploads = plan.objects.map((o) => {\n const step = resultBySha.get(o.sha);\n if (!step) {\n throw new Error(`internal: no upload result recorded for ${o.sha}`);\n }\n return step;\n });\n\n return {\n uploads,\n announceReceipt,\n refsReceipt,\n arweaveMap: merged,\n totalFeePaid,\n };\n}\n","/**\n * Persists the apex payment-channel id (+ its chain context) per\n * (destination, chain) so a daemon RESTART can resume the EXISTING on-chain\n * channel instead of opening a new one.\n *\n * Why this is needed: `ChannelManager` persists the off-chain nonce/cumulative\n * watermark (keyed by channelId) but NOT the peer→channelId mapping. So after a\n * restart `openChannel()` would open + re-deposit into a fresh channel, which\n * reverts on a chain where the deposit already exists. With the channelId saved\n * here, the runner instead calls `trackChannel(channelId, context)` — which\n * rehydrates the nonce from the channel store — and signs against the live\n * channel with zero on-chain writes.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/** Chain context needed to re-track a channel (matches ChannelManager.trackChannel). */\nexport interface PersistedChannelContext {\n chainType: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n recipient?: string;\n}\n\nexport interface PersistedApexChannel {\n channelId: string;\n context: PersistedChannelContext;\n}\n\ntype Store = Record<string, PersistedApexChannel>;\n\nfunction key(destination: string, chain: string): string {\n return `${destination}|${chain}`;\n}\n\nfunction readStore(path: string): Store {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Store;\n } catch {\n return {};\n }\n}\n\n/** Load the saved apex channel for (destination, chain), or null. */\nexport function loadApexChannel(\n path: string,\n destination: string,\n chain: string\n): PersistedApexChannel | null {\n return readStore(path)[key(destination, chain)] ?? null;\n}\n\n/** Save the apex channel for (destination, chain) with mode 0o600. */\nexport function saveApexChannel(\n path: string,\n destination: string,\n chain: string,\n record: PersistedApexChannel\n): void {\n const store = readStore(path);\n store[key(destination, chain)] = record;\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(store, null, 2), { mode: 0o600 });\n}\n","/**\n * Persists DYNAMIC targets — relays added via `toon_add_relay` and apexes added\n * via `toon_add_apex` — to `~/.toon-client/targets.json` so they survive a\n * daemon restart. The config file's single `relayUrl`/`btpUrl` remain the\n * permanent \"default\" target and are NOT stored here; only runtime additions are.\n *\n * On boot the `ClientRunner` seeds the default from config, then replays this\n * store to re-instantiate every dynamically-added relay/apex. Add/remove tool\n * calls write straight back here (last-write-wins, mode 0o600), mirroring the\n * `apex-channel-store.ts` pattern.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { configDir } from './config.js';\nimport type { ApexNegotiationConfig } from './config.js';\n\n/** A persisted relay read target. */\nexport interface PersistedRelayTarget {\n /** Relay WS URL (the map key). */\n relayUrl: string;\n}\n\n/** A persisted apex write target with its discovered settlement negotiation. */\nexport interface PersistedApexTarget {\n /** BTP WS endpoint of the apex (the map key). */\n btpUrl: string;\n /** Settlement negotiation, discovered from the apex's kind:10032 announcement. */\n negotiation: ApexNegotiationConfig;\n /** Child peers reached via this apex's channel (e.g. `[\"store\",\"swap\"]`). */\n apexChildPeers?: string[];\n /** Per-write fee override (base units). Falls back to the daemon default. */\n feePerEvent?: string;\n /** Relay the negotiation was discovered on (re-discovery / provenance). */\n discoveredFrom?: string;\n}\n\nexport interface TargetsFile {\n relays: PersistedRelayTarget[];\n apexes: PersistedApexTarget[];\n}\n\nconst EMPTY: TargetsFile = { relays: [], apexes: [] };\n\n/** Default targets-store path: `~/.toon-client/targets.json`. */\nexport function defaultTargetsPath(): string {\n return join(configDir(), 'targets.json');\n}\n\n/** Read + parse the targets store, returning empty arrays when absent/invalid. */\nexport function loadTargets(path = defaultTargetsPath()): TargetsFile {\n let parsed: Partial<TargetsFile>;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial<TargetsFile>;\n } catch {\n return { relays: [], apexes: [] };\n }\n return {\n relays: Array.isArray(parsed.relays) ? parsed.relays : [],\n apexes: Array.isArray(parsed.apexes) ? parsed.apexes : [],\n };\n}\n\nfunction write(path: string, data: TargetsFile): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/** Upsert a relay target (idempotent by `relayUrl`). */\nexport function saveRelayTarget(\n relayUrl: string,\n path = defaultTargetsPath()\n): void {\n const store = loadTargets(path);\n if (!store.relays.some((r) => r.relayUrl === relayUrl)) {\n store.relays.push({ relayUrl });\n write(path, store);\n }\n}\n\n/** Remove a relay target. Returns true if it was present. */\nexport function removeRelayTarget(\n relayUrl: string,\n path = defaultTargetsPath()\n): boolean {\n const store = loadTargets(path);\n const next = store.relays.filter((r) => r.relayUrl !== relayUrl);\n if (next.length === store.relays.length) return false;\n store.relays = next;\n write(path, store);\n return true;\n}\n\n/** Upsert an apex target (last-write-wins by `btpUrl`). */\nexport function saveApexTarget(\n target: PersistedApexTarget,\n path = defaultTargetsPath()\n): void {\n const store = loadTargets(path);\n store.apexes = store.apexes.filter((a) => a.btpUrl !== target.btpUrl);\n store.apexes.push(target);\n write(path, store);\n}\n\n/** Remove an apex target. Returns true if it was present. */\nexport function removeApexTarget(\n btpUrl: string,\n path = defaultTargetsPath()\n): boolean {\n const store = loadTargets(path);\n const next = store.apexes.filter((a) => a.btpUrl !== btpUrl);\n if (next.length === store.apexes.length) return false;\n store.apexes = next;\n write(path, store);\n return true;\n}\n\nexport { EMPTY as EMPTY_TARGETS };\n","/**\n * Discover an apex's settlement negotiation by reading its `kind:10032`\n * (`ILP_PEER_INFO_KIND`) announcement off a relay, rather than making the caller\n * hand-supply chain/settlement params. An apex (relay node) publishes its\n * `IlpPeerInfo` — `btpEndpoint`, `supportedChains`, `settlementAddresses`,\n * `preferredTokens`, `tokenNetworks` — to its relay; this module subscribes for\n * it, parses via core's `parseIlpPeerInfo`, and maps it onto the daemon's\n * `ApexNegotiationConfig` so the runner can stand up a `ToonClient` against it.\n *\n * The relay is injected as a `RelaySubscription` (already started) so this is\n * unit-testable with a fake WS and reuses a relay the runner already manages.\n */\n\nimport {\n ILP_PEER_INFO_KIND,\n parseIlpPeerInfo,\n isEventExpired,\n} from '@toon-protocol/core';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { RelaySubscription } from '../relay-subscription.js';\nimport type { ApexNegotiationConfig } from './config.js';\nimport type { SettlementChain } from '../control-api.js';\n\nexport interface DiscoverApexParams {\n /** A started relay subscription to query for the apex's kind:10032. */\n relay: RelaySubscription;\n /** ILP address of the apex to match (e.g. `g.proxy`). */\n ilpAddress: string;\n /** Optional apex Nostr pubkey to narrow the REQ filter (64-char hex). */\n pubkey?: string;\n /** Preferred settlement chain family; defaults to the first supported chain. */\n chain?: SettlementChain;\n /** Child peers reached via this apex's channel (e.g. `[\"store\",\"swap\"]`). */\n childPeers?: string[];\n /** Max time to wait for the announcement, ms. Default 15000. */\n timeoutMs?: number;\n /** Poll interval against the relay buffer, ms. Default 250. */\n pollMs?: number;\n}\n\nexport interface DiscoveredApex {\n btpUrl: string;\n negotiation: ApexNegotiationConfig;\n apexChildPeers?: string[];\n}\n\n/**\n * Thrown when discovery fails. `retryable` is true for a TIMEOUT (the apex may\n * just be slow/offline — the caller can retry once it's reachable) and false\n * for a malformed announcement (retrying won't help until the apex republishes).\n */\nexport class ApexDiscoveryError extends Error {\n constructor(\n message: string,\n readonly retryable = false\n ) {\n super(message);\n this.name = 'ApexDiscoveryError';\n }\n}\n\n/**\n * Subscribe for the apex's `kind:10032`, wait for a match, and map it to an\n * `ApexNegotiationConfig` + `btpUrl`. Rejects with {@link ApexDiscoveryError}\n * on timeout or when the announcement lacks settlement params for any chain.\n */\nexport async function discoverApex(\n params: DiscoverApexParams\n): Promise<DiscoveredApex> {\n const { relay, ilpAddress, pubkey, chain, childPeers } = params;\n const timeoutMs = params.timeoutMs ?? 15_000;\n const pollMs = params.pollMs ?? 250;\n\n const subId = relay.subscribe(\n [\n {\n kinds: [ILP_PEER_INFO_KIND],\n ...(pubkey ? { authors: [pubkey] } : {}),\n },\n ],\n `apex-discovery-${ilpAddress}`\n );\n\n try {\n const deadline = Date.now() + timeoutMs;\n let cursor = 0;\n while (Date.now() < deadline) {\n // Scan the WHOLE relay buffer (no subId filter), not just our discovery\n // subscription. `RelaySubscription` de-dups by event.id globally, so if a\n // pre-existing subscription already buffered the announcement, the relay's\n // replay to our fresh REQ is dropped and would never appear under our\n // subId — buffer-wide reads find it regardless of which sub received it.\n const { events, cursor: next } = relay.getEvents({ cursor });\n cursor = next;\n const match = events.find((e) => matchesApex(e, ilpAddress, pubkey));\n if (match) return mapAnnouncement(match, { chain, childPeers });\n await delay(pollMs);\n }\n throw new ApexDiscoveryError(\n `Timed out after ${timeoutMs}ms waiting for the apex kind:${ILP_PEER_INFO_KIND} ` +\n `announcement for \"${ilpAddress}\" on the relay. Is the relay reachable and the apex online?`,\n true // retryable: the apex may just be slow/offline\n );\n } finally {\n relay.unsubscribe(subId);\n }\n}\n\n/**\n * Whether a kind:10032 event announces the target apex's ILP address. When a\n * `pubkey` is given it must also match the event author — multiple nodes can\n * advertise the same ILP address (e.g. `g.proxy`), so the pubkey is how\n * the caller disambiguates which one to add. (Buffer-wide scanning means we can\n * no longer rely on the REQ's `authors` filter to do this for us.)\n */\nfunction matchesApex(\n event: NostrEvent,\n ilpAddress: string,\n pubkey?: string\n): boolean {\n if (event.kind !== ILP_PEER_INFO_KIND) return false;\n if (pubkey && event.pubkey !== pubkey) return false;\n // A NIP-40-expired announcement means the apex stopped re-publishing — it is\n // offline and its advertised BTP endpoint is unreachable, so don't match it\n // (issue #261). Discovery keeps waiting for a fresh, unexpired announcement.\n if (isEventExpired(event)) return false;\n try {\n const info = parseIlpPeerInfo(event);\n const addrs = info.ilpAddresses ?? [info.ilpAddress];\n return addrs.includes(ilpAddress) || info.ilpAddress === ilpAddress;\n } catch {\n return false;\n }\n}\n\n/** Map a parsed kind:10032 announcement onto the daemon's negotiation config. */\nfunction mapAnnouncement(\n event: NostrEvent,\n opts: { chain?: SettlementChain; childPeers?: string[] }\n): DiscoveredApex {\n const info = parseIlpPeerInfo(event);\n const chains = info.supportedChains ?? [];\n if (chains.length === 0) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no supportedChains — cannot settle.`\n );\n }\n\n // Pick the chainKey: prefer one whose family matches the requested chain,\n // else the first advertised chain.\n const chainKey =\n (opts.chain\n ? chains.find((c) => c.split(':')[0] === opts.chain)\n : undefined) ?? chains[0];\n if (!chainKey) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no usable settlement chain.`\n );\n }\n const family = chainKey.split(':')[0] as SettlementChain;\n const settlementAddress = info.settlementAddresses?.[chainKey];\n if (!settlementAddress) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no settlementAddress for chain \"${chainKey}\".`\n );\n }\n\n const btpUrl = info.btpEndpoint;\n if (!btpUrl) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced an empty btpEndpoint — cannot open a BTP session.`\n );\n }\n\n const negotiation: ApexNegotiationConfig = {\n destination: info.ilpAddress,\n peerId: info.ilpAddress.split('.').at(-1) ?? info.ilpAddress,\n chain: family,\n chainKey,\n // EVM chainKeys are `evm:<network>:<chainId>`; non-EVM carry no numeric id.\n // Tolerate the 2-part `evm:<chainId>` form some connectors advertise.\n chainId:\n family === 'evm'\n ? Number(chainKey.split(':')[2] ?? chainKey.split(':')[1] ?? 0)\n : 0,\n settlementAddress,\n ...(info.preferredTokens?.[chainKey]\n ? { tokenAddress: info.preferredTokens[chainKey] }\n : {}),\n ...(info.tokenNetworks?.[chainKey]\n ? { tokenNetwork: info.tokenNetworks[chainKey] }\n : {}),\n };\n\n return {\n btpUrl,\n negotiation,\n ...(opts.childPeers && opts.childPeers.length > 0\n ? { apexChildPeers: opts.childPeers }\n : {}),\n };\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * Fastify route registration for the `toon-clientd` control API. Each route\n * is a thin adapter: parse/validate the request, call the `ClientRunner`, map\n * errors to the uniform `ErrorResponse` envelope.\n *\n * Bound to loopback only by the daemon entry — there is no auth layer because\n * the surface never leaves `127.0.0.1`.\n */\n\nimport type { FastifyInstance, FastifyReply } from 'fastify';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { ClientRunner } from './client-runner.js';\nimport {\n BalancesUnavailableError,\n InvalidPayloadError,\n NotReadyError,\n PublishRejectedError,\n TargetError,\n} from './client-runner.js';\nimport { ApexDiscoveryError } from './apex-discovery.js';\nimport {\n GitError,\n NonFastForwardError,\n OversizeObjectsError,\n} from '@toon-protocol/rig';\nimport type {\n AddApexRequest,\n AddRelayRequest,\n EventsQuery,\n FundWalletRequest,\n GitCommentRequest,\n GitEstimateRequest,\n GitIssueRequest,\n GitPatchRequest,\n GitPushRequest,\n GitStatusRequest,\n HttpFetchPaidRequest,\n ChannelDepositRequest,\n CloseChannelRequest,\n SettleChannelRequest,\n OpenChannelRequest,\n PublishRequest,\n PublishUnsignedRequest,\n QueryRequest,\n RemoveApexRequest,\n RemoveRelayRequest,\n SettlementChain,\n SettleSwapClaimsRequest,\n SubscribeRequest,\n SwapRequest,\n UploadMediaRequest,\n} from '../control-api.js';\n\nexport function registerRoutes(\n app: FastifyInstance,\n runner: ClientRunner\n): void {\n app.get('/status', async () => runner.getStatus());\n\n app.post<{ Body: PublishRequest }>('/publish', async (req, reply) => {\n const body = req.body;\n if (!body || !isSignedEvent(body.event)) {\n return sendError(reply, 400, 'invalid_event', {\n detail: 'body.event must be a fully-signed Nostr event (id + sig).',\n });\n }\n try {\n return await runner.publish(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: PublishUnsignedRequest }>(\n '/publish-unsigned',\n async (req, reply) => {\n const body = req.body;\n if (!body || !Number.isInteger(body.kind)) {\n return sendError(reply, 400, 'invalid_event', {\n detail: 'body.kind (integer) is required; the daemon signs the event.',\n });\n }\n try {\n return await runner.publishUnsigned(body);\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: UploadMediaRequest }>('/upload-media', async (req, reply) => {\n const body = req.body;\n const hasData = typeof body?.dataBase64 === 'string' && body.dataBase64 !== '';\n const hasPath = typeof body?.filePath === 'string' && body.filePath !== '';\n if (!body || (!hasData && !hasPath)) {\n return sendError(reply, 400, 'invalid_media', {\n detail:\n 'body.dataBase64 (base64-encoded media bytes) or body.filePath (absolute path) is required.',\n });\n }\n try {\n return await runner.uploadMedia(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: QueryRequest }>('/query', async (req, reply) => {\n const body = req.body;\n if (!body || body.filters === undefined) {\n return sendError(reply, 400, 'invalid_filters', {\n detail: 'body.filters is required (a NIP-01 filter or array of filters).',\n });\n }\n try {\n const events = await runner.query(body.filters, body.timeoutMs);\n return { events };\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SubscribeRequest }>('/subscribe', async (req, reply) => {\n const body = req.body;\n if (!body || body.filters === undefined) {\n return sendError(reply, 400, 'invalid_filters', {\n detail:\n 'body.filters is required (a NIP-01 filter or array of filters).',\n });\n }\n try {\n return runner.subscribe(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get<{\n Querystring: {\n subId?: string;\n cursor?: string;\n limit?: string;\n relayUrl?: string;\n };\n }>('/events', async (req) => {\n const q = req.query;\n const query: EventsQuery = {};\n if (q.subId) query.subId = q.subId;\n if (q.cursor !== undefined) query.cursor = Number(q.cursor);\n if (q.limit !== undefined) query.limit = Number(q.limit);\n if (q.relayUrl) query.relayUrl = q.relayUrl;\n return runner.getEvents(query);\n });\n\n app.post<{ Body: OpenChannelRequest }>('/channels', async (req, reply) => {\n try {\n return await runner.openChannel(req.body?.destination);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get('/channels', async () => runner.getChannels());\n\n app.get('/balances', async (_req, reply) => {\n try {\n return await runner.getBalances();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: ChannelDepositRequest }>('/channels/deposit', async (req, reply) => {\n try {\n return await runner.depositToChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: CloseChannelRequest }>('/channels/close', async (req, reply) => {\n try {\n return await runner.closeChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SettleChannelRequest }>('/channels/settle', async (req, reply) => {\n try {\n return await runner.settleChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SwapRequest }>('/swap', async (req, reply) => {\n const body = req.body;\n if (\n !body ||\n !body.destination ||\n body.amount === undefined ||\n !body.swapPubkey ||\n !body.pair ||\n !body.chainRecipient\n ) {\n return sendError(reply, 400, 'invalid_swap', {\n detail:\n 'body.destination, amount, swapPubkey, pair, and chainRecipient are required.',\n });\n }\n try {\n return await runner.swap(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n // Received swap claims: persisted watermarks + settlement drive (#352).\n app.get('/swap/claims', async () => runner.listSwapClaims());\n\n app.post<{ Body: SettleSwapClaimsRequest }>(\n '/swap/settle',\n async (req, reply) => {\n try {\n return await runner.settleSwapClaims(req.body ?? {});\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: HttpFetchPaidRequest }>(\n '/http-fetch-paid',\n async (req, reply) => {\n const body = req.body;\n if (!body || typeof body.url !== 'string' || body.url === '') {\n return sendError(reply, 400, 'invalid_url', {\n detail: 'body.url (absolute resource URL) is required.',\n });\n }\n try {\n return await runner.httpFetchPaid(body);\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: FundWalletRequest }>('/fund-wallet', async (req, reply) => {\n try {\n // Returns immediately with a 'pending' snapshot — the drip runs async in\n // the daemon (the Mina faucet outlasts the host's tool-call timeout).\n return runner.fundWallet(req.body ?? {});\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get<{ Querystring: { chain?: SettlementChain } }>(\n '/fund-wallet/status',\n async (req) => runner.getFundStatus(req.query?.chain)\n );\n\n app.get('/targets', async () => runner.getTargets());\n\n app.post<{ Body: AddRelayRequest }>('/relays', async (req, reply) => {\n const url = req.body?.relayUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_relay', {\n detail: 'body.relayUrl is required.',\n });\n }\n try {\n await runner.addRelay(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.delete<{ Body: RemoveRelayRequest }>('/relays', async (req, reply) => {\n const url = req.body?.relayUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_relay', {\n detail: 'body.relayUrl is required.',\n });\n }\n try {\n runner.removeRelay(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: AddApexRequest }>('/apex', async (req, reply) => {\n const body = req.body;\n if (!body || !body.ilpAddress || !body.relayUrl) {\n return sendError(reply, 400, 'invalid_apex', {\n detail: 'body.ilpAddress and body.relayUrl are required.',\n });\n }\n try {\n return await runner.addApex(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.delete<{ Body: RemoveApexRequest }>('/apex', async (req, reply) => {\n const url = req.body?.btpUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_apex', {\n detail: 'body.btpUrl is required.',\n });\n }\n try {\n await runner.removeApex(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n // ── Git write path (/git/*, epic #222 ticket #227) ─────────────────────────\n\n app.post<{ Body: GitEstimateRequest }>('/git/estimate', async (req, reply) => {\n const body = req.body;\n if (!body || !isNonEmptyString(body.repoPath) || !isNonEmptyString(body.repoId)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoPath (local repository path) and body.repoId are required.',\n });\n }\n try {\n return await runner.gitEstimate(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitPushRequest }>('/git/push', async (req, reply) => {\n const body = req.body;\n if (!body || !isNonEmptyString(body.repoPath) || !isNonEmptyString(body.repoId)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoPath (local repository path) and body.repoId are required.',\n });\n }\n try {\n return await runner.gitPush(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitIssueRequest }>('/git/issue', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.title) || !isNonEmptyString(body.body)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.title, and body.body are required.',\n });\n }\n try {\n return await runner.gitIssue(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitCommentRequest }>('/git/comment', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.rootEventId) || !isNonEmptyString(body.body)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.rootEventId, and body.body are required.',\n });\n }\n try {\n return await runner.gitComment(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitPatchRequest }>('/git/patch', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.title)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }) and body.title are required ' +\n '(plus exactly one of patchText | repoPath+range).',\n });\n }\n try {\n return await runner.gitPatch(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitStatusRequest }>('/git/status', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.targetEventId) || !isNonEmptyString(body.status)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.targetEventId, and ' +\n 'body.status (open | applied | closed | draft) are required.',\n });\n }\n try {\n return await runner.gitStatus(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\n/**\n * Map `/git/*` errors: planPush's structured errors surface as clean JSON\n * with their data attached (a confirm UI / CLI renders the refs or the\n * oversize table directly), a git plumbing failure (not a repo, bad range,\n * vanished objects) is caller-fixable 400, and everything else falls through\n * to the standard mapper (bootstrapping 503, funding 402, ...).\n */\nfunction mapGitError(reply: FastifyReply, err: unknown): FastifyReply {\n if (err instanceof NonFastForwardError) {\n // 409 Conflict: the remote moved — re-run with force or pull first.\n return reply.status(409).send({\n error: 'non_fast_forward',\n detail: err.message,\n refs: err.refs,\n });\n }\n if (err instanceof OversizeObjectsError) {\n // 413 Payload Too Large: objects over the 95KB v1 upload limit.\n return reply.status(413).send({\n error: 'oversize_objects',\n detail: err.message,\n objects: err.objects,\n });\n }\n if (err instanceof GitError) {\n return sendError(reply, 400, 'git_error', { detail: err.message });\n }\n return mapError(reply, err);\n}\n\nfunction isSignedEvent(event: unknown): event is NostrEvent {\n if (typeof event !== 'object' || event === null) return false;\n const e = event as Record<string, unknown>;\n return (\n typeof e['id'] === 'string' &&\n typeof e['sig'] === 'string' &&\n typeof e['pubkey'] === 'string' &&\n typeof e['kind'] === 'number'\n );\n}\n\nfunction mapError(reply: FastifyReply, err: unknown): FastifyReply {\n if (err instanceof NotReadyError) {\n return sendError(reply, 503, 'bootstrapping', {\n detail: err.message,\n retryable: true,\n });\n }\n if (err instanceof InvalidPayloadError) {\n return sendError(reply, 400, 'invalid_payload', { detail: err.message });\n }\n if (err instanceof PublishRejectedError) {\n return sendError(reply, 502, 'rejected', { detail: err.message });\n }\n if (err instanceof BalancesUnavailableError) {\n // The chain RPC/provider behind the balances handler stalled — retryable,\n // and attributed to the balances handler, NOT the relay/apex (#199).\n return sendError(reply, 504, 'balances_unavailable', {\n detail: err.message,\n retryable: true,\n });\n }\n if (err instanceof TargetError) {\n // 404 for \"no such target\", 400 otherwise — both are caller-fixable.\n const status = /no such/i.test(err.message) ? 404 : 400;\n return sendError(reply, status, 'invalid_target', { detail: err.message });\n }\n if (err instanceof ApexDiscoveryError) {\n // A timeout is a retryable 504 (apex may be slow/offline); a malformed\n // announcement is a non-retryable 502 (the apex must republish).\n return err.retryable\n ? sendError(reply, 504, 'discovery_timeout', {\n detail: err.message,\n retryable: true,\n })\n : sendError(reply, 502, 'discovery_failed', { detail: err.message });\n }\n // Settle called before the grace period elapsed — retryable (the UI polls\n // until `now >= settleableAt`). The client throws a tagged error; 425 Too Early.\n if (err instanceof Error && (err as { name?: string }).name === 'SettleTooEarlyError') {\n return sendError(reply, 425, 'settle_too_early', { detail: err.message, retryable: true });\n }\n // First-write channel OPEN reverted because the settlement wallet has no\n // native gas (toon-meta#65). The client throws a tagged `ChannelFundingError`\n // with an actionable \"fund the wallet\" message; on the upload path it is\n // wrapped in a `ToonClientError('Failed to publish event')`, so walk the\n // `cause` chain. 402 Payment Required — caller-fixable + retryable once funded.\n const funding = findChannelFundingError(err);\n if (funding) {\n return sendError(reply, 402, 'insufficient_gas', {\n detail: funding.message,\n retryable: true,\n });\n }\n return sendError(reply, 500, 'internal_error', {\n detail: err instanceof Error ? err.message : String(err),\n });\n}\n\n/**\n * Walk an error's `cause` chain and return the first `ChannelFundingError`\n * (matched by name to avoid a hard import of the client package). The client\n * tags the gas-revert error and `publishEvent` wraps it one level deep on the\n * upload path, so the actionable message can be nested.\n */\nfunction findChannelFundingError(err: unknown): Error | undefined {\n let cur: unknown = err;\n for (let i = 0; i < 10 && cur != null; i++) {\n if (cur instanceof Error && (cur as { name?: string }).name === 'ChannelFundingError') {\n return cur;\n }\n cur = cur instanceof Error ? (cur as { cause?: unknown }).cause : undefined;\n }\n return undefined;\n}\n\nfunction sendError(\n reply: FastifyReply,\n status: number,\n error: string,\n extra: { detail?: string; retryable?: boolean } = {}\n): FastifyReply {\n return reply.status(status).send({\n error,\n ...(extra.detail ? { detail: extra.detail } : {}),\n ...(extra.retryable ? { retryable: true } : {}),\n });\n}\n"],"mappings":"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","names":["delay","join","key","defaultWebSocketFactory","resolve","REPOSITORY_ANNOUNCEMENT_KIND","execFile","spawn","promisify","resolve","execFileAsync","promisify","execFile","stat","mkdirSync","writeFileSync","dirname","mkdirSync","readFileSync","writeFileSync","dirname","join","join","readFileSync","mkdirSync","dirname","writeFileSync","delay","errMsg","join","serializePushPlan","serializePushResult","serializeFeeEstimate","resolve","key"]}
|
|
1
|
+
{"version":3,"sources":["../src/daemon/first-run.ts","../src/relay-subscription.ts","../src/daemon/client-runner.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/constants.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/types.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/ForgejoClient.ts","../../../node_modules/.pnpm/@toon-protocol+core@3.1.2_typescript@5.9.3/node_modules/@toon-protocol/core/src/nip34/NIP34Handler.ts","../../rig/src/objects.ts","../../rig/src/publisher.ts","../../rig/src/nip34-events.ts","../../rig/src/remote-state.ts","../../arweave/src/gateways.ts","../../arweave/src/git-sha.ts","../../rig/src/repo-reader.ts","../../rig/src/object-fetch.ts","../../rig/src/read-pipeline.ts","../../rig/src/materialize.ts","../../rig/src/npub.ts","../../rig/src/routes.ts","../../rig/src/push.ts","../src/daemon/apex-channel-store.ts","../src/daemon/targets-store.ts","../src/daemon/apex-discovery.ts","../src/daemon/routes.ts"],"sourcesContent":["/**\n * First-run onboarding for `toon-clientd`.\n *\n * A brand-new user (`npx`/plugin install) has no identity and no config file.\n * Before the daemon resolves its config it calls {@link scaffoldFirstRun},\n * which makes a fresh install start with zero manual setup:\n *\n * 1. **Identity** — if no mnemonic source is configured (no\n * `TOON_CLIENT_MNEMONIC`, no `keystorePath`, no `mnemonic`), generate a\n * fresh BIP-39 mnemonic, encrypt it to `~/.toon-client/keystore.json`, and\n * record `keystorePath` (+ `keystoreAutoPassword`) in `config.json`. The\n * keystore is encrypted with `TOON_CLIENT_KEYSTORE_PASSWORD` when set, else\n * a default password so the identity survives restarts with no env var.\n * The mnemonic + derived addresses are printed ONCE for backup.\n * 2. **Transport scaffolding** — ensure `config.json` carries the transport\n * knobs (`btpUrl`/`relayUrl`) plus a `_help` block documenting them, so the\n * user can see what to point at.\n *\n * This is all idempotent: on later runs an identity already exists, so nothing\n * is regenerated and the config is left untouched.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { deriveFullIdentity, generateKeystore } from '@toon-protocol/client';\nimport {\n configDir,\n defaultConfigPath,\n readConfigFile,\n DEFAULT_KEYSTORE_PASSWORD,\n type DaemonConfigFile,\n} from './config.js';\n\n/** Default keystore path: `~/.toon-client/keystore.json`. */\nexport function defaultKeystorePath(): string {\n return join(configDir(), 'keystore.json');\n}\n\n/**\n * True when SOME mnemonic source is already configured: the env var, a keystore\n * path, or an inline mnemonic. When false the daemon would otherwise hard-fail\n * with \"No mnemonic configured\".\n */\nexport function hasConfiguredIdentity(file: DaemonConfigFile): boolean {\n return Boolean(\n process.env['TOON_CLIENT_MNEMONIC']?.trim() ||\n file.keystorePath ||\n file.mnemonic\n );\n}\n\n/** The `_help` block written into a scaffolded config to document transport. */\nconst CONFIG_HELP = {\n transport:\n 'Configure ONE uplink for paid writes: either `proxyUrl` (connector ' +\n 'payment-proxy over ILP-over-HTTP, e.g. https://proxy.devnet.toonprotocol.dev) ' +\n 'OR `btpUrl` (BTP WebSocket, e.g. ws://<host>:3000/btp). Reads are always ' +\n 'free over relayUrl.',\n proxyUrl:\n 'Connector-proxy base URL (deployed devnet/testnet edge). When set, paid ' +\n 'writes route through `POST /ilp` and no BTP socket is needed. Set ' +\n '`destination` to the apex ILP address (e.g. g.proxy for devnet).',\n faucetUrl:\n 'Devnet faucet base URL (e.g. https://faucet.devnet.toonprotocol.dev) used ' +\n 'to drip test funds before publishing.',\n btpUrl:\n 'BTP WebSocket URL of the apex/connector for paid writes. Optional when ' +\n '`proxyUrl` is set.',\n relayUrl:\n 'Relay WebSocket URL for FREE reads. Default ws://localhost:7100.',\n destination:\n 'Default ILP publish destination (apex address). Default g.proxy.',\n keystorePath:\n 'Auto-generated encrypted identity. Do not hand-edit; back up your seed phrase.',\n};\n\ninterface ScaffoldOptions {\n /** Config file path (defaults to `TOON_CLIENT_CONFIG` / `~/.toon-client/config.json`). */\n configPath?: string;\n /** Log sink (defaults to stderr, since stdout may carry MCP/JSON). */\n log?: (msg: string) => void;\n}\n\n/**\n * Generate + persist an identity and/or scaffold transport config on first run.\n * Safe to call on every startup — it only acts when something is missing.\n */\nexport async function scaffoldFirstRun(\n opts: ScaffoldOptions = {}\n): Promise<void> {\n const log = opts.log ?? ((m: string): void => console.error(m));\n const configPath =\n opts.configPath ?? process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath();\n\n // Ensure the config directory exists before writing the keystore/config.\n mkdirSync(dirname(configPath), { recursive: true });\n\n const file = readConfigFile(configPath);\n let updated: DaemonConfigFile = { ...file };\n let changed = false;\n\n // --- 1. Identity ----------------------------------------------------------\n if (!hasConfiguredIdentity(file)) {\n const keystorePath = defaultKeystorePath();\n const envPassword = process.env['TOON_CLIENT_KEYSTORE_PASSWORD'];\n const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;\n\n // Reuse an existing keystore file if one is already on disk (e.g. config\n // was deleted but the keystore survived); otherwise mint a new mnemonic.\n let mnemonic: string;\n if (existsSync(keystorePath)) {\n // Leave the existing keystore untouched; just relink it in config.\n mnemonic = '';\n log(\n `[toon-clientd] first run: relinking existing keystore at ${keystorePath}`\n );\n } else {\n const generated = generateKeystore(keystorePath, password);\n mnemonic = generated.mnemonic;\n }\n\n updated = {\n ...updated,\n keystorePath,\n // Only flag auto-password when WE chose it, so a user-imported keystore\n // (custom password) still requires the env var.\n ...(envPassword ? {} : { keystoreAutoPassword: true }),\n };\n changed = true;\n\n if (mnemonic) {\n await printNewIdentity(mnemonic, keystorePath, Boolean(envPassword), log);\n }\n }\n\n // --- 2. Transport scaffolding --------------------------------------------\n if (!existsSync(configPath)) {\n // Fresh install: surface the transport knobs with guidance.\n updated = {\n _help: CONFIG_HELP,\n proxyUrl: '',\n btpUrl: '',\n relayUrl: 'ws://localhost:7100',\n ...updated,\n } as DaemonConfigFile;\n changed = true;\n log(\n `[toon-clientd] wrote starter config at ${configPath} — set \"proxyUrl\" (connector proxy, ILP-over-HTTP) or \"btpUrl\" (BTP) to your apex before publishing.`\n );\n }\n\n if (changed) writeConfigFile(configPath, updated);\n}\n\n/** Derive + print the new identity's addresses and a one-time backup notice. */\nasync function printNewIdentity(\n mnemonic: string,\n keystorePath: string,\n hasEnvPassword: boolean,\n log: (msg: string) => void\n): Promise<void> {\n const id = await deriveFullIdentity(mnemonic);\n const lines = [\n '',\n '════════════════════════════════════════════════════════════════',\n ' TOON client: generated a new identity (first run)',\n '════════════════════════════════════════════════════════════════',\n ` Nostr pubkey : ${id.nostr.pubkey}`,\n ` EVM address : ${id.evm.address}`,\n ...(id.solana.publicKey ? [` Solana : ${id.solana.publicKey}`] : []),\n ...(id.mina.publicKey ? [` Mina : ${id.mina.publicKey}`] : []),\n '',\n ' Seed phrase (BACK THIS UP — shown only once):',\n ` ${mnemonic}`,\n '',\n ` Encrypted keystore: ${keystorePath}`,\n hasEnvPassword\n ? ' Encrypted with TOON_CLIENT_KEYSTORE_PASSWORD.'\n : ' Encrypted with the default password (set TOON_CLIENT_KEYSTORE_PASSWORD\\n' +\n ' + re-import to use your own). Identity reloads automatically on restart.',\n '════════════════════════════════════════════════════════════════',\n '',\n ];\n log(lines.join('\\n'));\n}\n\n/** Write the config file as pretty JSON with mode 0o600. */\nfunction writeConfigFile(path: string, file: DaemonConfigFile): void {\n writeFileSync(path, JSON.stringify(file, null, 2) + '\\n', {\n encoding: 'utf8',\n mode: 0o600,\n });\n}\n","/**\n * Persistent relay Nostr-WS subscription — the read half of the TOON\n * client, which `@toon-protocol/client` does not provide (its bootstrap only\n * issues one-shot WS queries and `DiscoveryTracker` is passive).\n *\n * Reads are FREE: this opens a long-lived NIP-01 connection to the relay,\n * keeps a bounded ring buffer of received events (de-duplicated by `event.id`),\n * and lets callers drain new events via a monotonic cursor. It auto-reconnects\n * with exponential backoff and re-issues every active REQ on reconnect.\n *\n * The WebSocket is injectable (`wsFactory`) so unit tests can drive the wire\n * protocol without a real relay; the default factory uses the `ws` package.\n */\n\nimport { createRequire } from 'node:module';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { NostrFilter } from './control-api.js';\n\n// ESM has no `require`; synchronously load the node-only `ws` dep for the default\n// WebSocket factory without forcing every caller (e.g. the browser-style\n// injected-factory path in tests) to pull it in.\nconst nodeRequire = createRequire(import.meta.url);\n\n/** Minimal WebSocket surface this module depends on (subset of `ws`). */\nexport interface MinimalWebSocket {\n send(data: string): void;\n close(): void;\n on(event: 'open' | 'close', cb: () => void): void;\n on(event: 'message', cb: (data: unknown) => void): void;\n on(event: 'error', cb: (err: unknown) => void): void;\n}\n\nexport type WebSocketFactory = (url: string) => MinimalWebSocket;\n\nexport interface RelaySubscriptionOptions {\n /** Relay WS URL, e.g. `ws://localhost:7100`. */\n relayUrl: string;\n /** Max events retained in the ring buffer (oldest evicted). Default 5000. */\n bufferSize?: number;\n /** Base reconnect delay, ms. Default 1000. */\n reconnectBaseMs?: number;\n /** Max reconnect delay, ms. Default 30000. */\n reconnectMaxMs?: number;\n /** Inject a WebSocket factory (tests / proxy customisation). */\n wsFactory?: WebSocketFactory;\n /**\n * Decode an `EVENT` payload that arrived as a string. The TOON relay sends\n * events TOON-encoded (key/value text) rather than as a JSON object, so the\n * daemon injects a TOON decoder here. When the payload is already a JSON\n * object it is used directly and this is not called.\n */\n decodeEvent?: (raw: string) => NostrEvent;\n /**\n * Invoked once per newly-buffered (de-duplicated) event. The daemon uses this\n * to feed a runner-level MERGED buffer across many relays — so a fan-out read\n * (`toon_read` with no relayUrl) draws from one ordered stream with a single\n * scalar cursor. The relay still keeps its own buffer for `bufferedCount` /\n * single-relay drains.\n */\n onEvent?: (subId: string, event: NostrEvent) => void;\n /** Optional logger. */\n logger?: (msg: string) => void;\n}\n\ninterface BufferedEvent {\n seq: number;\n subId: string;\n event: NostrEvent;\n}\n\n/** Result of {@link RelaySubscription.getEvents}. */\nexport interface DrainResult {\n events: NostrEvent[];\n cursor: number;\n hasMore: boolean;\n}\n\nconst DEFAULT_BUFFER = 5000;\nconst DEFAULT_BASE_MS = 1000;\nconst DEFAULT_MAX_MS = 30_000;\n\n/** Shared no-op used as the default logger. */\nconst noop = (): void => undefined;\n\nexport class RelaySubscription {\n private readonly relayUrl: string;\n private readonly bufferSize: number;\n private readonly reconnectBaseMs: number;\n private readonly reconnectMaxMs: number;\n private readonly log: (msg: string) => void;\n private readonly wsFactory: WebSocketFactory;\n private readonly decodeEvent?: (raw: string) => NostrEvent;\n private readonly onEvent?: (subId: string, event: NostrEvent) => void;\n\n /** Active subscriptions: subId -> filters (re-sent on every (re)connect). */\n private readonly subscriptions = new Map<string, NostrFilter[]>();\n\n /** Ring buffer of received events, ordered by ascending `seq`. */\n private buffer: BufferedEvent[] = [];\n /** De-dup index: event.id -> seq (kept in lockstep with the buffer). */\n private readonly seen = new Set<string>();\n private seqCounter = 0;\n private subIdCounter = 0;\n\n private ws: MinimalWebSocket | null = null;\n private connected = false;\n private closing = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(opts: RelaySubscriptionOptions) {\n this.relayUrl = opts.relayUrl;\n this.bufferSize = opts.bufferSize ?? DEFAULT_BUFFER;\n this.reconnectBaseMs = opts.reconnectBaseMs ?? DEFAULT_BASE_MS;\n this.reconnectMaxMs = opts.reconnectMaxMs ?? DEFAULT_MAX_MS;\n this.log = opts.logger ?? noop;\n this.wsFactory = opts.wsFactory ?? defaultWebSocketFactory();\n this.decodeEvent = opts.decodeEvent;\n this.onEvent = opts.onEvent;\n }\n\n /** Whether the underlying socket is currently open. */\n isConnected(): boolean {\n return this.connected;\n }\n\n /** Number of events currently held in the buffer. */\n bufferedCount(): number {\n return this.buffer.length;\n }\n\n /** Active subscription ids. */\n activeSubscriptions(): string[] {\n return [...this.subscriptions.keys()];\n }\n\n /** Open the connection (idempotent). */\n start(): void {\n this.closing = false;\n if (this.ws) return;\n this.open();\n }\n\n /**\n * Register a persistent subscription and (if connected) send the REQ.\n * Returns the subscription id (caller-supplied or generated).\n */\n subscribe(filters: NostrFilter | NostrFilter[], subId?: string): string {\n const id = subId ?? `sub-${++this.subIdCounter}`;\n const list = Array.isArray(filters) ? filters : [filters];\n this.subscriptions.set(id, list);\n if (this.connected) this.sendReq(id, list);\n return id;\n }\n\n /** Cancel a subscription and send CLOSE if connected. */\n unsubscribe(subId: string): void {\n if (!this.subscriptions.delete(subId)) return;\n if (this.connected) this.sendRaw(['CLOSE', subId]);\n }\n\n /**\n * Drain events newer than `cursor`. The cursor is the highest `seq` returned;\n * pass it back to fetch only events received since. Filtering by `subId`\n * restricts to one subscription.\n */\n getEvents(\n opts: { subId?: string; cursor?: number; limit?: number } = {}\n ): DrainResult {\n const after = opts.cursor ?? 0;\n const limit = opts.limit ?? 200;\n const matches = this.buffer.filter(\n (b) =>\n b.seq > after && (opts.subId === undefined || b.subId === opts.subId)\n );\n const page = matches.slice(0, limit);\n const hasMore = matches.length > page.length;\n const last = page.at(-1);\n const cursor = last ? last.seq : after;\n return { events: page.map((b) => b.event), cursor, hasMore };\n }\n\n /** Close the connection permanently and stop reconnecting. */\n close(): void {\n this.closing = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n /* ignore */\n }\n this.ws = null;\n }\n this.connected = false;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private open(): void {\n let ws: MinimalWebSocket;\n try {\n ws = this.wsFactory(this.relayUrl);\n } catch (err) {\n this.log(`[relay] connect failed: ${errMsg(err)}`);\n this.scheduleReconnect();\n return;\n }\n this.ws = ws;\n\n ws.on('open', () => {\n this.connected = true;\n this.reconnectAttempts = 0;\n this.log(`[relay] connected to ${this.relayUrl}`);\n // Re-issue every active subscription.\n for (const [id, filters] of this.subscriptions) this.sendReq(id, filters);\n });\n\n ws.on('message', (data: unknown) => this.handleMessage(data));\n\n ws.on('error', (err: unknown) => {\n this.log(`[relay] socket error: ${errMsg(err)}`);\n });\n\n ws.on('close', () => {\n this.connected = false;\n this.ws = null;\n if (!this.closing) {\n this.log('[relay] disconnected; scheduling reconnect');\n this.scheduleReconnect();\n }\n });\n }\n\n private scheduleReconnect(): void {\n if (this.closing || this.reconnectTimer) return;\n const delay = Math.min(\n this.reconnectMaxMs,\n this.reconnectBaseMs * 2 ** this.reconnectAttempts\n );\n this.reconnectAttempts += 1;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (!this.closing) this.open();\n }, delay);\n // Don't keep the event loop alive solely for a reconnect timer.\n (this.reconnectTimer as { unref?: () => void }).unref?.();\n }\n\n private sendReq(subId: string, filters: NostrFilter[]): void {\n this.sendRaw(['REQ', subId, ...filters]);\n }\n\n private sendRaw(message: unknown[]): void {\n if (!this.ws || !this.connected) return;\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n this.log(`[relay] send failed: ${errMsg(err)}`);\n }\n }\n\n private handleMessage(data: unknown): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(toText(data));\n } catch {\n return;\n }\n if (!Array.isArray(parsed) || parsed.length === 0) return;\n const type = parsed[0];\n switch (type) {\n case 'EVENT': {\n const subId = typeof parsed[1] === 'string' ? parsed[1] : '';\n const event = this.parseEventPayload(parsed[2]);\n if (event && typeof event.id === 'string')\n this.bufferEvent(subId, event);\n break;\n }\n case 'EOSE':\n // End of stored events — nothing to do; we keep streaming live events.\n break;\n case 'CLOSED':\n this.log(\n `[relay] subscription closed by relay: ${String(parsed[2] ?? '')}`\n );\n break;\n case 'NOTICE':\n this.log(`[relay] NOTICE: ${String(parsed[1] ?? '')}`);\n break;\n default:\n break;\n }\n }\n\n /**\n * Normalise an `EVENT` payload to a NostrEvent. Standard relays send a JSON\n * object; the TOON relay sends a TOON-encoded string, decoded via the injected\n * `decodeEvent`. Returns undefined when it can't be parsed.\n */\n private parseEventPayload(raw: unknown): NostrEvent | undefined {\n if (\n raw &&\n typeof raw === 'object' &&\n typeof (raw as NostrEvent).id === 'string'\n ) {\n return raw as NostrEvent;\n }\n if (typeof raw === 'string' && this.decodeEvent) {\n try {\n return this.decodeEvent(raw);\n } catch (err) {\n this.log(`[relay] event decode failed: ${errMsg(err)}`);\n return undefined;\n }\n }\n return undefined;\n }\n\n private bufferEvent(subId: string, event: NostrEvent): void {\n if (this.seen.has(event.id)) return; // de-dup by event.id\n this.seen.add(event.id);\n this.buffer.push({ seq: ++this.seqCounter, subId, event });\n if (this.buffer.length > this.bufferSize) {\n const evicted = this.buffer.shift();\n if (evicted) this.seen.delete(evicted.event.id);\n }\n // Mirror into the runner-level merged buffer (cross-relay fan-out reads).\n this.onEvent?.(subId, event);\n }\n}\n\nfunction toText(data: unknown): string {\n if (typeof data === 'string') return data;\n if (data instanceof Uint8Array) return Buffer.from(data).toString('utf8');\n if (Buffer.isBuffer(data)) return data.toString('utf8');\n return String(data);\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Default factory backed by the `ws` package.\n */\nfunction defaultWebSocketFactory(): WebSocketFactory {\n return (url: string): MinimalWebSocket => {\n const WebSocketImpl = nodeRequire('ws') as new (\n address: string\n ) => MinimalWebSocket;\n return new WebSocketImpl(url);\n };\n}\n","/**\n * ClientRunner — the daemon's connection owner. The TOON client is 1-to-MANY:\n * it can write through several apexes (each a `ToonClient` + BTP session +\n * payment channel) and read from several relays (each a `RelaySubscription`).\n *\n * • Writes go through BTP, never to a relay directly — `publish`/`swap` select\n * an apex (default: the config-seeded one).\n * • Reads FAN OUT — `subscribe`/`getEvents` apply across every relay and merge\n * into one ordered stream with a single scalar cursor (the runner owns the\n * merged buffer; each `RelaySubscription` mirrors new events into it).\n *\n * Targets are added at runtime (`addRelay`/`addApex`), persisted to\n * `targets.json`, replayed on the next boot, and removable. The config-seeded\n * relay + apex are the permanent DEFAULT targets and cannot be removed.\n *\n * Each apex bootstraps asynchronously and non-blocking: the connection comes up\n * in the background. Until ready, writes against it report `bootstrapping` so\n * tools surface \"retry\".\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, resolve, sep } from 'node:path';\nimport type { NostrEvent, EventTemplate } from 'nostr-tools/pure';\nimport { generateSecretKey } from 'nostr-tools/pure';\nimport { decodeEventFromToon } from '@toon-protocol/core';\nimport {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport { arweaveUrls } from '@toon-protocol/arweave';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport {\n extractArweaveTxId,\n fundWallet as faucetFund,\n mintExecutionCondition,\n ingestAndReveal,\n buildSwapSettlements,\n InMemoryReceivedClaimStore,\n JsonFileReceivedClaimStore,\n InMemoryPreimageRetentionStore,\n type FaucetChain,\n type ReceivedClaimEntry,\n type ReceivedClaimStore,\n type PreimageRetentionStore,\n type RevealFn,\n} from '@toon-protocol/client';\nimport {\n loadMinaSignerClient,\n type SettlementBundle,\n} from '@toon-protocol/sdk';\nimport {\n GitRepoReader,\n buildComment,\n buildIssue,\n buildPatch,\n buildStatus,\n executePush,\n fetchRemoteState,\n planPush,\n type Publisher,\n type PublishReceipt,\n type PushPlan,\n type PushResult,\n type RemoteState,\n type StatusKind,\n type UnsignedEvent,\n type UploadReceipt,\n type GitObjectUpload,\n} from '@toon-protocol/rig';\nimport { streamSwap } from '@toon-protocol/sdk/swap';\nimport {\n AdaptiveDeltaController,\n JsonFileSwapControllerStateStore,\n type PacketProgress,\n} from '@toon-protocol/sdk';\nimport { RelaySubscription } from '../relay-subscription.js';\nimport type {\n AddApexRequest,\n AddApexResponse,\n ApexTargetStatus,\n BalanceInfo,\n BalancesResponse,\n ChannelDepositRequest,\n ChannelDepositResponse,\n CloseChannelRequest,\n CloseChannelResponse,\n SettleChannelRequest,\n SettleChannelResponse,\n ChannelsResponse,\n ChainStatus,\n EventsResponse,\n FundStatusResponse,\n FundWalletRequest,\n FundWalletResponse,\n GitCommentRequest,\n GitEstimateRequest,\n GitEstimateResponse,\n GitEventResponse,\n GitFeeEstimate,\n GitIssueRequest,\n GitPatchRequest,\n GitPushRequest,\n GitPushResponse,\n GitRepoAddr,\n GitStatusRequest,\n HttpFetchPaidRequest,\n HttpFetchPaidResponse,\n NostrFilter,\n PublishResponse,\n PublishUnsignedRequest,\n RelayTargetStatus,\n SettlementChain,\n StatusResponse,\n SubscribeRequest,\n SubscribeResponse,\n SwapControllerParams,\n SwapPacketOutcome,\n SwapResponse,\n SwapClaim,\n ListSwapClaimsResponse,\n ReceivedClaimInfo,\n SettleSwapClaimsRequest,\n SettleSwapClaimsResponse,\n SwapSettlementResult,\n TargetsResponse,\n UploadMediaRequest,\n UploadMediaResponse,\n} from '../control-api.js';\nimport type {\n EventsQuery,\n PublishRequest,\n SwapRequest,\n} from '../control-api.js';\nimport {\n configDir,\n type ApexNegotiationConfig,\n type ResolvedDaemonConfig,\n} from './config.js';\nimport {\n loadApexChannel,\n saveApexChannel,\n type PersistedChannelContext,\n} from './apex-channel-store.js';\nimport {\n loadTargets,\n removeApexTarget,\n removeRelayTarget,\n saveApexTarget,\n saveRelayTarget,\n type PersistedApexTarget,\n} from './targets-store.js';\nimport { discoverApex } from './apex-discovery.js';\n\n/** The subset of `ToonClient` the runner depends on. */\nexport interface ToonClientLike {\n start(): Promise<{ peersDiscovered: number; mode: string }>;\n stop(): Promise<void>;\n getPublicKey(): string;\n getEvmAddress(): string | undefined;\n getSolanaAddress(): string | undefined;\n getMinaAddress(): string | undefined;\n getNetworkStatus(): { evm: string; solana: string; mina: string } | undefined;\n publishEvent(\n event: NostrEvent,\n options?: {\n destination?: string;\n claim?: unknown;\n ilpAmount?: bigint;\n /** HTTP request-target the payment-proxy replays (default '/write';\n * '/store' routes to the Arweave store/DVM backend). */\n proxyPath?: string;\n }\n ): Promise<{\n success: boolean;\n eventId?: string;\n data?: string;\n error?: string;\n }>;\n signBalanceProof(channelId: string, amount: bigint): Promise<unknown>;\n /**\n * Sign an unsigned event template with the daemon-held Nostr key (the key\n * never leaves the daemon). Backs the `publish-unsigned` / `upload-media`\n * paths so a UI/agent supplies only the event shell.\n */\n signEvent(template: EventTemplate): NostrEvent | Promise<NostrEvent>;\n /**\n * Upload bytes to Arweave via the kind:5094 blob-storage DVM (single-packet),\n * returning the Arweave tx id. Reuses the client's claim/channel plumbing.\n */\n uploadBlob(params: {\n blobData: Uint8Array;\n contentType?: string;\n bid?: string;\n destination?: string;\n ilpAmount?: bigint;\n }): Promise<{\n success: boolean;\n txId?: string;\n eventId?: string;\n error?: string;\n }>;\n openChannel(destination?: string): Promise<string>;\n getTrackedChannels(): string[];\n getChannelNonce(channelId: string): number;\n getChannelCumulativeAmount(channelId: string): bigint;\n getChannelDepositTotal(channelId: string): bigint;\n getBalances(): Promise<BalanceInfo[]>;\n depositToChannel(\n channelId: string,\n amount: string\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }>;\n closeChannel(channelId: string): Promise<{\n channelId: string;\n txHash?: string;\n closedAt: string;\n settleableAt: string;\n }>;\n settleChannel(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string }>;\n getChannelCloseState(\n channelId: string\n ): 'open' | 'closing' | 'settleable' | 'settled';\n getSettleableAt(channelId: string): bigint | undefined;\n /**\n * Re-read a resumed channel's on-chain deposit (persisted state omits it).\n * Optional so lightweight fakes need not implement it; the real ToonClient\n * does. Best-effort — callers await + catch.\n */\n rehydrateChannelDeposit?(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined>;\n sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n claim?: unknown;\n /**\n * Sender-chosen 32-byte execution condition (toon-client#350). The\n * transport puts it on the PREPARE and verifies the FULFILL preimage\n * (`sha256(fulfillment) == condition`); absent/all-zero = legacy packet.\n */\n executionCondition?: Uint8Array;\n /** Explicit ILP expiry; defaults to `now + timeout` in the transport. */\n expiresAt?: Date;\n }): Promise<{\n accepted: boolean;\n data?: string;\n code?: string;\n message?: string;\n }>;\n /**\n * Payment-aware HTTP fetch: issue the request and, on a `402 Payment\n * Required`, transparently pay over TOON and retry, returning the settled Web\n * `Response`. Pinned to the `ToonClient.h402Fetch` shape (issue #50).\n */\n h402Fetch(\n url: string,\n opts?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string | Uint8Array;\n timeout?: number;\n destination?: string;\n }\n ): Promise<Response>;\n /**\n * Submit a receive-side swap settlement bundle on-chain (#352). EVM only;\n * env-gated on `chainRpcUrls[bundle.chain]`. Optional so lightweight fakes\n * need not implement it — the runner surfaces a result-shaped\n * `SUBMISSION_UNAVAILABLE` when absent.\n */\n settleSwapBundle?(\n bundle: SettlementBundle\n ): Promise<{ txHash: string; status?: 'success' | 'reverted' }>;\n}\n\n/** A started managed proxy: just the teardown handle the runner needs. */\n/** Builds a `ToonClient` (or a fake) for a given resolved client config. */\nexport type CreateClient = (config: ToonClientConfig) => ToonClientLike;\n\n/** Builds a `RelaySubscription` for a given relay URL. */\nexport type CreateRelay = (opts: {\n relayUrl: string;\n onEvent: (subId: string, event: NostrEvent) => void;\n logger?: (msg: string) => void;\n}) => RelaySubscription;\n\nexport interface ClientRunnerDeps {\n config: ResolvedDaemonConfig;\n /** Factory producing the (real or fake) ToonClient for a client config. */\n createClient: CreateClient;\n /** Factory producing a relay subscription (defaults to the real one). */\n createRelay?: CreateRelay;\n logger?: (msg: string) => void;\n /** Path to the dynamic-targets store (tests override). */\n targetsPath?: string;\n /**\n * Test seams for the `/git/*` pipeline (default: the real\n * @toon-protocol/rig implementations). `fetchRemoteState` opens relay\n * WebSockets, so tests inject a canned reader instead of hitting the network.\n */\n gitDeps?: {\n fetchRemoteState?: typeof fetchRemoteState;\n createRepoReader?: (repoPath: string) => GitRepoReader;\n };\n}\n\n/** One apex write target: a BTP session + its payment channel + settlement. */\ninterface ApexConnection {\n btpUrl: string;\n client: ToonClientLike;\n negotiation?: ApexNegotiationConfig;\n childPeers: string[];\n destination: string;\n chain: SettlementChain;\n /** Per-apex channel-store path (distinct so parallel apexes don't race it). */\n channelStorePath: string;\n feePerEvent: bigint;\n apexChannelId?: string;\n ready: boolean;\n bootstrapping: boolean;\n /** In-flight bootstrap, so concurrent callers await the same work (not re-run). */\n bootstrapPromise?: Promise<void>;\n lastError?: string;\n isDefault: boolean;\n}\n\n/** A runner-level merged read-buffer entry, tagged with its source relay. */\ninterface MergedEvent {\n seq: number;\n relayUrl: string;\n subId: string;\n event: NostrEvent;\n}\n\nconst MERGED_BUFFER = 5000;\n\n/**\n * Per-attempt bound for an on-chain balance read, kept WELL under the control\n * client's `/balances` timeout (12s) so a stalled provider fast-fails inside the\n * daemon instead of letting the whole control request hang to the wire timeout\n * (#199). With {@link BALANCES_READ_ATTEMPTS} the worst case stays under 12s.\n */\nconst BALANCES_READ_TIMEOUT_MS = 5_000;\n/** Bounded retry for a transient provider stall on a balance read (#199). */\nconst BALANCES_READ_ATTEMPTS = 2;\n\n/**\n * In-memory record of one background faucet drip. Structurally identical to the\n * wire {@link FundWalletResponse} snapshot the daemon returns, so a job can be\n * handed back verbatim.\n */\ntype FundJob = FundWalletResponse;\n\nexport class ClientRunner {\n private readonly config: ResolvedDaemonConfig;\n private readonly createClient: CreateClient;\n private readonly createRelay: CreateRelay;\n private readonly log: (msg: string) => void;\n private readonly targetsPath?: string;\n\n /** Remote-state reader for `/git/*` (injectable — opens relay sockets). */\n private readonly fetchGitRemoteState: typeof fetchRemoteState;\n /** Local-repo reader factory for `/git/*` (injectable for tests). */\n private readonly createRepoReader: (repoPath: string) => GitRepoReader;\n\n /**\n * Identity-level chain-read client. Reading your OWN on-chain wallet balance is\n * a pure (wallet keys + chain RPC) operation that has nothing to do with the\n * ILP/payment peer, so it lives at the daemon level rather than inside an apex.\n * Built once from the daemon's own `toonClientConfig` (the same keys + chain\n * RPC config every apex shares) and REUSED as the default apex's client, so a\n * funded apex's `start()` (which derives Solana/Mina keys) also benefits this\n * reader. `getBalances` uses it directly, so balances work even with zero\n * apexes registered (follow-up to #199/#200).\n */\n private readonly identityClient: ToonClientLike;\n\n private readonly startedAt = Date.now();\n\n /** Apex write targets, keyed by btpUrl. */\n private readonly apexes = new Map<string, ApexConnection>();\n /** Relay read targets, keyed by relayUrl. */\n private readonly relays = new Map<string, RelaySubscription>();\n\n /**\n * Durable store for VERIFIED received swap claims (chain-B watermarks,\n * #352) — file-backed when the config names a path (production), in-memory\n * otherwise (manually-built test configs). Claims survive a daemon restart.\n */\n private readonly receivedClaimStore: ReceivedClaimStore;\n\n /**\n * Async faucet drip jobs, keyed by chain. A drip is launched in the background\n * (the Mina faucet legitimately takes ~75s — longer than the MCP host's ~60s\n * tool-call budget) and its terminal state is observed via {@link getFundStatus}\n * / re-reading balances rather than by blocking the caller.\n */\n private readonly fundJobs = new Map<FaucetChain, FundJob>();\n\n /** Runner-level merged read buffer across all relays (de-duped by event.id). */\n private merged: MergedEvent[] = [];\n private readonly mergedSeen = new Set<string>();\n private mergedSeq = 0;\n\n /**\n * Fan-out subscriptions (no relayUrl restriction): replayed onto relays added\n * later so a new relay immediately participates in existing reads.\n */\n private readonly fanoutSubs = new Map<string, NostrFilter[]>();\n private subIdCounter = 0;\n\n private readonly defaultBtpUrl: string;\n private readonly defaultRelayUrl: string;\n\n private stopped = false;\n private started = false;\n\n constructor(deps: ClientRunnerDeps) {\n this.config = deps.config;\n this.createClient = deps.createClient;\n this.log = deps.logger ?? ((): void => undefined);\n if (deps.targetsPath !== undefined) this.targetsPath = deps.targetsPath;\n this.fetchGitRemoteState =\n deps.gitDeps?.fetchRemoteState ?? fetchRemoteState;\n this.createRepoReader =\n deps.gitDeps?.createRepoReader ??\n ((repoPath) => new GitRepoReader(repoPath));\n this.defaultBtpUrl = deps.config.toonClientConfig.btpUrl ?? '';\n this.defaultRelayUrl = deps.config.relayUrl;\n this.receivedClaimStore = deps.config.receivedClaimStorePath\n ? new JsonFileReceivedClaimStore(deps.config.receivedClaimStorePath)\n : new InMemoryReceivedClaimStore();\n\n this.createRelay =\n deps.createRelay ??\n ((opts) =>\n new RelaySubscription({\n relayUrl: opts.relayUrl,\n ...(opts.logger ? { logger: opts.logger } : {}),\n onEvent: opts.onEvent,\n // The TOON relay sends events TOON-encoded (text) on reads, not JSON.\n decodeEvent: (raw) =>\n decodeEventFromToon(new TextEncoder().encode(raw)),\n }));\n\n // Build the permanent config-seeded default relay + apex up front (not yet\n // started/bootstrapped) so `bootstrap()` works standalone (the daemon and\n // tests both rely on constructing then awaiting bootstrap()).\n this.registerRelay(this.defaultRelayUrl);\n // Build the identity-level read client ONCE and reuse it as the default\n // apex's client (same keys + chain RPC config), so on-chain balance reads\n // never depend on an apex existing.\n this.identityClient = this.createClient(this.config.toonClientConfig);\n const defaultApex = this.makeApex({\n btpUrl: this.defaultBtpUrl,\n client: this.identityClient,\n ...(this.config.apex ? { negotiation: this.config.apex } : {}),\n childPeers: this.config.apexChildPeers ?? [],\n destination: this.config.destination,\n chain: this.config.chain,\n channelStorePath:\n this.config.toonClientConfig.channelStorePath ??\n this.apexChannelStorePathFor(this.defaultBtpUrl),\n feePerEvent: this.config.feePerEvent,\n isDefault: true,\n });\n this.apexes.set(defaultApex.btpUrl, defaultApex);\n }\n\n /**\n * Start the live connections: the shared read proxy, every relay socket, the\n * default apex bootstrap (non-blocking), then replay persisted dynamic\n * targets. Returns immediately; apexes become ready asynchronously.\n */\n start(): void {\n if (this.started) return;\n this.started = true;\n for (const relay of this.relays.values()) relay.start();\n void this.bootstrap();\n this.replayPersistedTargets();\n }\n\n /** Await the default apex's bootstrap (kicking it off if not already running). */\n bootstrap(): Promise<void> {\n // Read-only daemon (no proxy/BTP uplink): never bootstrap an apex — there is\n // no write transport and FREE reads run off the relay subscription (#69).\n if (!this.config.hasUplink) return Promise.resolve();\n const apex = this.apexes.get(this.defaultBtpUrl);\n if (!apex) return Promise.resolve();\n return this.bootstrapApex(apex);\n }\n\n // ── Relays (reads) ─────────────────────────────────────────────────────────\n\n /**\n * Build + register a relay (idempotent by URL), wiring its events into the\n * merged buffer and replaying active fan-out subscriptions. Does NOT start the\n * socket — callers start it (so construction stays side-effect-free for tests).\n */\n private registerRelay(relayUrl: string): RelaySubscription {\n const existing = this.relays.get(relayUrl);\n if (existing) return existing;\n const relay = this.createRelay({\n relayUrl,\n logger: this.log,\n onEvent: (subId, event) => this.pushMerged(relayUrl, subId, event),\n });\n this.relays.set(relayUrl, relay);\n // A new relay joins every active fan-out subscription.\n for (const [subId, filters] of this.fanoutSubs)\n relay.subscribe(filters, subId);\n return relay;\n }\n\n /**\n * Add a relay read target at runtime. Persisted unless `persist` is false.\n */\n async addRelay(relayUrl: string, persist = true): Promise<void> {\n if (this.relays.has(relayUrl)) return;\n const relay = this.registerRelay(relayUrl);\n relay.start();\n if (persist) saveRelayTarget(relayUrl, this.targetsPath);\n }\n\n /** Remove a relay read target. The config-seeded default cannot be removed. */\n removeRelay(relayUrl: string): void {\n if (relayUrl === this.defaultRelayUrl) {\n throw new TargetError('Cannot remove the default (config-seeded) relay.');\n }\n const relay = this.relays.get(relayUrl);\n if (!relay) throw new TargetError(`No such relay: ${relayUrl}`);\n relay.close();\n this.relays.delete(relayUrl);\n // Drop its events from the merged buffer (and dedup index).\n this.merged = this.merged.filter((m) => {\n if (m.relayUrl === relayUrl) {\n this.mergedSeen.delete(m.event.id);\n return false;\n }\n return true;\n });\n removeRelayTarget(relayUrl, this.targetsPath);\n }\n\n /** Mirror a newly-buffered relay event into the merged cross-relay buffer. */\n private pushMerged(relayUrl: string, subId: string, event: NostrEvent): void {\n if (this.mergedSeen.has(event.id)) return;\n this.mergedSeen.add(event.id);\n this.merged.push({ seq: ++this.mergedSeq, relayUrl, subId, event });\n if (this.merged.length > MERGED_BUFFER) {\n const evicted = this.merged.shift();\n if (evicted) this.mergedSeen.delete(evicted.event.id);\n }\n }\n\n /**\n * Register a free-read subscription. With no `relayUrl` it FANS OUT across\n * every relay (and onto relays added later); with one it targets that relay.\n */\n subscribe(req: SubscribeRequest): SubscribeResponse {\n const subId = req.subId ?? `sub-${++this.subIdCounter}`;\n const filters = Array.isArray(req.filters) ? req.filters : [req.filters];\n const targets = req.relayUrl ? [req.relayUrl] : [...this.relays.keys()];\n if (req.relayUrl && !this.relays.has(req.relayUrl)) {\n throw new TargetError(`No such relay: ${req.relayUrl}`);\n }\n if (!req.relayUrl) this.fanoutSubs.set(subId, filters);\n for (const url of targets) this.relays.get(url)?.subscribe(filters, subId);\n return { subId, relays: targets };\n }\n\n /**\n * One-shot free read: subscribe the given filter(s) across all relays, wait a\n * bounded window for the relay(s) to deliver, then return every buffered event\n * matching the filter (matched by content, not subId — so events already\n * buffered by other subscriptions are included despite the global dedup).\n *\n * Backs the apps `toon_query` tool the generative-UI runtime calls to resolve\n * a ViewSpec node's data bind.\n */\n async query(\n filters: NostrFilter | NostrFilter[],\n timeoutMs = 1200\n ): Promise<NostrEvent[]> {\n const list = Array.isArray(filters) ? filters : [filters];\n const subId = `q-${++this.subIdCounter}`;\n const targets = [...this.relays.keys()];\n for (const url of targets) this.relays.get(url)?.subscribe(list, subId);\n await delay(timeoutMs);\n for (const url of targets) this.relays.get(url)?.unsubscribe(subId);\n return this.merged\n .map((m) => m.event)\n .filter((event) => list.some((f) => matchesFilter(event, f)));\n }\n\n /** Drain merged events newer than the cursor (free read), optionally scoped. */\n getEvents(query: EventsQuery): EventsResponse {\n const after = query.cursor ?? 0;\n const limit = query.limit ?? 200;\n const matches = this.merged.filter(\n (m) =>\n m.seq > after &&\n (query.subId === undefined || m.subId === query.subId) &&\n (query.relayUrl === undefined || m.relayUrl === query.relayUrl)\n );\n const page = matches.slice(0, limit);\n const hasMore = matches.length > page.length;\n const last = page.at(-1);\n return {\n events: page.map((m) => m.event),\n cursor: last ? last.seq : after,\n hasMore,\n };\n }\n\n // ── Apexes (writes) ──────────────────────────────────────────────────────\n\n private makeApex(init: {\n btpUrl: string;\n client: ToonClientLike;\n negotiation?: ApexNegotiationConfig;\n childPeers: string[];\n destination: string;\n chain: SettlementChain;\n channelStorePath: string;\n feePerEvent: bigint;\n isDefault: boolean;\n }): ApexConnection {\n return {\n ...init,\n ready: false,\n bootstrapping: false,\n };\n }\n\n /**\n * Bootstrap one apex (memoized): start, inject negotiation, open/resume the\n * channel, route child peers. Concurrent callers await the same in-flight\n * work rather than re-running it.\n */\n private bootstrapApex(apex: ApexConnection): Promise<void> {\n if (apex.ready) return Promise.resolve();\n if (!apex.bootstrapPromise) {\n apex.bootstrapPromise = this.doBootstrapApex(apex);\n }\n return apex.bootstrapPromise;\n }\n\n private async doBootstrapApex(apex: ApexConnection): Promise<void> {\n apex.bootstrapping = true;\n try {\n // PROXY mode (no BTP discovery): if no negotiation was supplied via config,\n // discover the apex's settlement params from its kind:10032 on the default\n // relay before opening the channel (#69). Config-supplied negotiation wins.\n // In BTP mode the legacy bootstrap path handles discovery, so skip this.\n if (!apex.negotiation && this.config.proxyUrl) {\n await this.discoverApexNegotiation(apex);\n }\n await apex.client.start();\n this.injectApexNegotiation(apex);\n // PROXY mode: resume a previously-opened channel up front, else DEFER the\n // on-chain open to the first write / `POST /channels` so the wallet can be\n // funded AFTER the daemon starts (the fund→open→publish demo flow, #69).\n // The apex is \"ready\" once negotiation is in place — `openChannel` /\n // `publish` open lazily and idempotently via the ChannelManager.\n // BTP mode keeps the historical eager open at bootstrap.\n const deferOpen = Boolean(this.config.proxyUrl);\n apex.apexChannelId = await this.openOrResumeApexChannel(apex, {\n resumeOnly: deferOpen,\n });\n this.routeChildPeersThroughApexChannel(apex);\n apex.ready = true;\n apex.lastError = undefined;\n this.log(\n `[runner] apex ${apex.btpUrl || apex.destination} ready; channel ${\n apex.apexChannelId ?? '(deferred — open on first write)'\n }`\n );\n } catch (err) {\n apex.lastError = err instanceof Error ? err.message : String(err);\n this.log(\n `[runner] apex ${apex.btpUrl} bootstrap failed: ${apex.lastError}`\n );\n } finally {\n apex.bootstrapping = false;\n }\n }\n\n /**\n * Add an apex write target. Settlement params are discovered by reading the\n * apex's kind:10032 off the given relay (added first if unknown). Persisted.\n */\n async addApex(req: AddApexRequest): Promise<AddApexResponse> {\n await this.addRelay(req.relayUrl); // ensure + persist the discovery relay\n const relay = this.relays.get(req.relayUrl);\n if (!relay) throw new TargetError(`Relay unavailable: ${req.relayUrl}`);\n\n const discovered = await discoverApex({\n relay,\n ilpAddress: req.ilpAddress,\n ...(req.pubkey ? { pubkey: req.pubkey } : {}),\n ...(req.chain ? { chain: req.chain } : {}),\n ...(req.childPeers ? { childPeers: req.childPeers } : {}),\n });\n\n const feePerEvent =\n req.feePerEvent !== undefined\n ? BigInt(req.feePerEvent)\n : this.config.feePerEvent;\n\n await this.instantiateApex(\n {\n btpUrl: discovered.btpUrl,\n negotiation: discovered.negotiation,\n ...(discovered.apexChildPeers\n ? { apexChildPeers: discovered.apexChildPeers }\n : {}),\n feePerEvent: req.feePerEvent ?? feePerEvent.toString(),\n discoveredFrom: req.relayUrl,\n },\n true\n );\n\n const apex = this.apexes.get(discovered.btpUrl);\n if (!apex) {\n throw new TargetError(\n `Apex ${discovered.btpUrl} failed to register after discovery.`\n );\n }\n return {\n btpUrl: apex.btpUrl,\n destination: apex.destination,\n chain: apex.chain,\n ready: apex.ready,\n };\n }\n\n /** Build + register + bootstrap an apex from a (persisted) target record. */\n private async instantiateApex(\n target: PersistedApexTarget,\n persist: boolean\n ): Promise<void> {\n if (this.apexes.has(target.btpUrl)) return;\n const clientConfig = this.deriveApexClientConfig(\n target.btpUrl,\n target.negotiation.destination\n );\n const apex = this.makeApex({\n btpUrl: target.btpUrl,\n client: this.createClient(clientConfig),\n negotiation: target.negotiation,\n childPeers: target.apexChildPeers ?? [],\n destination: target.negotiation.destination,\n chain: target.negotiation.chain,\n channelStorePath: this.apexChannelStorePathFor(target.btpUrl),\n feePerEvent: BigInt(target.feePerEvent ?? this.config.feePerEvent),\n isDefault: false,\n });\n this.apexes.set(apex.btpUrl, apex);\n if (persist) saveApexTarget(target, this.targetsPath);\n await this.bootstrapApex(apex);\n }\n\n /** Remove an apex write target. The config-seeded default cannot be removed. */\n async removeApex(btpUrl: string): Promise<void> {\n if (btpUrl === this.defaultBtpUrl) {\n throw new TargetError('Cannot remove the default (config-seeded) apex.');\n }\n const apex = this.apexes.get(btpUrl);\n if (!apex) throw new TargetError(`No such apex: ${btpUrl}`);\n try {\n await apex.client.stop();\n } catch (err) {\n this.log(\n `[runner] apex ${btpUrl} stop error: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n this.apexes.delete(btpUrl);\n removeApexTarget(btpUrl, this.targetsPath);\n }\n\n /** Derive a per-apex ToonClientConfig from the default (shared identity/transport). */\n private deriveApexClientConfig(\n btpUrl: string,\n destination: string\n ): ToonClientConfig {\n const base = this.config.toonClientConfig;\n // A DISCOVERED apex (e.g. the store DVM at `wss://proxy.store…:443`) lives on\n // a different connector than the default `proxyUrl`. In direct/HTTP transport\n // every paid packet POSTs to `proxyUrl`, so without a per-apex override the\n // discovered apex's packets go to the DEFAULT connector — which has no route\n // to its ILP prefix (F02 \"No route to destination\"). Derive the apex's own\n // HTTP `/ilp` base from its BTP url so its packets reach the right connector.\n const derivedProxyUrl = btpUrl\n .replace(/^wss:\\/\\//, 'https://')\n .replace(/^ws:\\/\\//, 'http://')\n .replace(/:443(\\/|$)/, '$1')\n .replace(/\\/btp\\/?$/, '')\n .replace(/\\/$/, '');\n return {\n ...base,\n ...(derivedProxyUrl ? { proxyUrl: derivedProxyUrl } : {}),\n btpUrl,\n destinationAddress: destination,\n // Distinct nonce-watermark store per apex so parallel ChannelManagers in\n // this process never race a shared channels.json.\n channelStorePath: this.apexChannelStorePathFor(btpUrl),\n ilpInfo: { ...base.ilpInfo, btpEndpoint: btpUrl },\n };\n }\n\n private apexChannelStorePathFor(btpUrl: string): string {\n return `${configDir()}/channels-${sanitize(btpUrl)}.json`;\n }\n\n // ── Persisted-target replay ────────────────────────────────────────────────\n\n private replayPersistedTargets(): void {\n let store;\n try {\n store = loadTargets(this.targetsPath);\n } catch (err) {\n this.log(\n `[runner] failed to load targets store: ${err instanceof Error ? err.message : String(err)}`\n );\n return;\n }\n for (const r of store.relays) {\n if (r.relayUrl === this.defaultRelayUrl) continue;\n void this.addRelay(r.relayUrl, false).catch((err) =>\n this.log(`[runner] replay relay ${r.relayUrl} failed: ${errMsg(err)}`)\n );\n }\n for (const a of store.apexes) {\n if (a.btpUrl === this.defaultBtpUrl) continue;\n void this.instantiateApex(a, false).catch((err) =>\n this.log(`[runner] replay apex ${a.btpUrl} failed: ${errMsg(err)}`)\n );\n }\n }\n\n // ── Channel / negotiation helpers (per-apex) ───────────────────────────────\n\n /**\n * Open the apex channel — or, on a restart, RESUME the existing one.\n *\n * With `resumeOnly`, only a persisted channel is resumed (no on-chain open);\n * returns undefined when none exists so the caller can defer the open to the\n * first write (funded-after-start demo flow, #69).\n */\n private async openOrResumeApexChannel(\n apex: ApexConnection,\n opts: { resumeOnly?: boolean } = {}\n ): Promise<string | undefined> {\n const { destination, chain } = apex;\n const { apexChannelStorePath } = this.config;\n const saved = loadApexChannel(apexChannelStorePath, destination, chain);\n const cm = (\n apex.client as unknown as {\n channelManager?: {\n trackChannel?: (id: string, ctx: PersistedChannelContext) => void;\n };\n }\n ).channelManager;\n\n if (saved && cm && typeof cm.trackChannel === 'function') {\n cm.trackChannel(saved.channelId, saved.context);\n // Persisted channel state omits the on-chain deposit, so re-read it from\n // chain — otherwise the wallet shows 0 spendable on a funded channel.\n if (saved.context.chainType === 'evm') {\n await apex.client\n .rehydrateChannelDeposit?.(saved.channelId, {\n chain: `evm:${saved.context.chainId}`,\n tokenNetworkAddress: saved.context.tokenNetworkAddress,\n })\n .catch((err) =>\n this.log(\n `[runner] deposit re-hydrate for ${saved.channelId} failed: ${errMsg(err)}`\n )\n );\n }\n this.log(\n `[runner] resumed apex channel ${saved.channelId} (deposit re-read)`\n );\n return saved.channelId;\n }\n\n if (opts.resumeOnly) return undefined;\n\n const channelId = await apex.client.openChannel(destination);\n this.persistApexChannel(apex, channelId);\n return channelId;\n }\n\n /**\n * Persist a (lazily- or eagerly-) opened apex channel so a restart RESUMES it\n * (tracked, no re-deposit) rather than opening a second on-chain channel.\n * No-op when the apex carries no negotiation (nothing to key the store on).\n */\n private persistApexChannel(apex: ApexConnection, channelId: string): void {\n const a = apex.negotiation;\n if (!a) return;\n saveApexChannel(\n this.config.apexChannelStorePath,\n apex.destination,\n apex.chain,\n {\n channelId,\n context: {\n chainType: a.chain,\n chainId: a.chainId,\n tokenNetworkAddress: a.tokenNetwork ?? '',\n ...(a.tokenAddress ? { tokenAddress: a.tokenAddress } : {}),\n recipient: a.settlementAddress,\n },\n }\n );\n }\n\n /**\n * Discover the apex's settlement negotiation from its kind:10032 on the\n * default relay and attach it to the apex (proxy-mode fallback when no config\n * negotiation was supplied, #69). Throws ApexDiscoveryError on timeout/missing\n * settlement params so the apex's `lastError` reports exactly what is missing.\n */\n private async discoverApexNegotiation(apex: ApexConnection): Promise<void> {\n const relay = this.relays.get(this.defaultRelayUrl);\n if (!relay) {\n throw new TargetError(\n `Cannot discover apex \"${apex.destination}\": default relay ` +\n `${this.defaultRelayUrl} is not registered.`\n );\n }\n relay.start();\n const discovered = await discoverApex({\n relay,\n ilpAddress: apex.destination,\n chain: apex.chain,\n ...(apex.childPeers.length > 0 ? { childPeers: apex.childPeers } : {}),\n });\n apex.negotiation = discovered.negotiation;\n if (discovered.apexChildPeers) apex.childPeers = discovered.apexChildPeers;\n this.log(\n `[runner] discovered apex negotiation for \"${apex.destination}\" ` +\n `(chain ${discovered.negotiation.chainKey}, settle ` +\n `${discovered.negotiation.settlementAddress})`\n );\n }\n\n /** Inject the apex settlement negotiation directly into its ToonClient. */\n private injectApexNegotiation(apex: ApexConnection): void {\n const a = apex.negotiation;\n if (!a) return;\n const negotiations = (\n apex.client as unknown as { peerNegotiations?: Map<string, unknown> }\n ).peerNegotiations;\n if (!(negotiations instanceof Map)) {\n throw new Error(\n 'ToonClient.peerNegotiations layout changed — cannot inject apex negotiation'\n );\n }\n negotiations.set(a.peerId, {\n chain: a.chainKey,\n chainType: a.chain,\n chainId: a.chainId,\n settlementAddress: a.settlementAddress,\n tokenAddress: a.tokenAddress,\n tokenNetwork: a.tokenNetwork,\n });\n this.log(`[runner] injected apex negotiation for peer \"${a.peerId}\"`);\n }\n\n /** Route apex CHILD peers (store/swap) through the SAME apex payment channel. */\n private routeChildPeersThroughApexChannel(apex: ApexConnection): void {\n const a = apex.negotiation;\n if (!a || !apex.apexChannelId || apex.childPeers.length === 0) return;\n const client = apex.client as unknown as {\n peerNegotiations?: Map<string, unknown>;\n channelManager?: { peerChannels?: Map<string, string> };\n };\n const negotiations = client.peerNegotiations;\n const peerChannels = client.channelManager?.peerChannels;\n if (!(negotiations instanceof Map) || !(peerChannels instanceof Map)) {\n this.log(\n '[runner] cannot route child peers — ToonClient internals layout changed'\n );\n return;\n }\n for (const peer of apex.childPeers) {\n if (peer === a.peerId) continue;\n negotiations.set(peer, {\n chain: a.chainKey,\n chainType: a.chain,\n chainId: a.chainId,\n settlementAddress: a.settlementAddress,\n tokenAddress: a.tokenAddress,\n tokenNetwork: a.tokenNetwork,\n });\n peerChannels.set(peer, apex.apexChannelId);\n this.log(\n `[runner] routed child peer \"${peer}\" through apex channel ${apex.apexChannelId}`\n );\n }\n }\n\n // ── Status ─────────────────────────────────────────────────────────────────\n\n private defaultApex(): ApexConnection | undefined {\n return this.apexes.get(this.defaultBtpUrl);\n }\n\n /** Whether any apex has finished bootstrapping. */\n isReady(): boolean {\n return [...this.apexes.values()].some((a) => a.ready);\n }\n\n isBootstrapping(): boolean {\n return [...this.apexes.values()].some((a) => a.bootstrapping);\n }\n\n getStatus(): StatusResponse {\n const apex = this.defaultApex();\n const client = apex?.client;\n const net = client?.getNetworkStatus();\n const network: ChainStatus[] | undefined = net\n ? (['evm', 'solana', 'mina'] as const).map((c) => ({\n chain: c,\n ready: net[c] === 'configured',\n detail: net[c],\n }))\n : undefined;\n const relay = this.relays.get(this.defaultRelayUrl);\n return {\n uptimeMs: Date.now() - this.startedAt,\n bootstrapping: apex?.bootstrapping ?? false,\n ready: apex?.ready ?? false,\n settlementChain: this.config.chain,\n feePerEvent: (apex?.feePerEvent ?? this.config.feePerEvent).toString(),\n identity: {\n nostrPubkey: safe(() => client?.getPublicKey()) ?? '',\n evmAddress: safe(() => client?.getEvmAddress()),\n solanaAddress: safe(() => client?.getSolanaAddress()),\n minaAddress: safe(() => client?.getMinaAddress()),\n },\n transport: {\n type: 'direct',\n ...(apex ? { btpUrl: apex.btpUrl } : {}),\n },\n relay: {\n url: this.defaultRelayUrl,\n connected: relay?.isConnected() ?? false,\n buffered: relay?.bufferedCount() ?? 0,\n subscriptions: relay?.activeSubscriptions() ?? [],\n },\n ...(network ? { network } : {}),\n ...(apex?.lastError ? { lastError: apex.lastError } : {}),\n // Advertise the optional-route surface this daemon build serves so a\n // version-skewed rig CLI can capability-gate the `/git/*` write path\n // BEFORE delegating (an old daemon lacking these routes 404s otherwise —\n // #306). Static: these routes are always registered by this build.\n capabilities: ['git'],\n };\n }\n\n /**\n * Drip devnet test funds to a wallet from the configured faucet. Defaults the\n * chain to the active settlement chain and the address to this client's own\n * address on that chain, so a no-arg call funds the caller's own wallet\n * (the typical \"fund me before I open a channel\" flow). The daemon holds the\n * faucet URL + the keys, so the MCP caller never needs either.\n */\n fundWallet(req: FundWalletRequest = {}): FundWalletResponse {\n const faucetUrl = this.config.faucetUrl;\n if (!faucetUrl) {\n throw new InvalidPayloadError(\n 'no faucet configured — set faucetUrl in the daemon config (or the ' +\n 'TOON_CLIENT_FAUCET_URL env var) to fund wallets.'\n );\n }\n const chain: FaucetChain = req.chain ?? this.config.chain;\n const client = this.defaultApex()?.client;\n const address =\n req.address ??\n safe(() =>\n chain === 'evm'\n ? client?.getEvmAddress()\n : chain === 'solana'\n ? client?.getSolanaAddress()\n : client?.getMinaAddress()\n );\n if (!address) {\n throw new InvalidPayloadError(\n `no ${chain} address available to fund — pass an explicit address ` +\n `(this client has no ${chain} key configured).`\n );\n }\n\n // Idempotent: a drip already in flight for this chain returns its snapshot\n // rather than launching a second faucet call (a re-click / poll mustn't\n // double-drip).\n const existing = this.fundJobs.get(chain);\n if (existing && existing.status === 'pending') {\n return { ...existing };\n }\n\n // The drip is ASYNC: launch the faucet call in the background and return a\n // 'pending' snapshot immediately. The Mina faucet mints native MINA + USDC\n // on a slow-settling chain and legitimately takes ~75s — longer than the MCP\n // host's ~60s tool-call budget and the control client's wire timeout — so a\n // blocking call surfaces a working drip as a misleading relay/apex timeout\n // (#199-class). The daemon happily waits the full chain-aware faucet budget\n // in the background; the caller observes the result via getFundStatus /\n // re-reading balances.\n const job: FundJob = {\n chain,\n address,\n faucetUrl,\n status: 'pending',\n startedAt: Date.now(),\n };\n this.fundJobs.set(chain, job);\n\n // The drip runs in the BACKGROUND, so there is no caller to protect from a\n // slow faucet — use a GENEROUS timeout. The faucet client default (30s for\n // evm/solana) is tuned for a synchronous call and falsely aborts a drip that\n // succeeds server-side a bit later: e.g. a loaded EVM faucet answers >30s but\n // the tx still lands, so the job would report `error` while the balance\n // actually went up — causing a misleading failure + double-fund risk. Await\n // the real outcome instead (config `faucetTimeoutMs` still overrides).\n const faucetTimeout =\n this.config.faucetTimeoutMs ?? (chain === 'mina' ? 130_000 : 90_000);\n void faucetFund(faucetUrl, address, chain, { timeout: faucetTimeout })\n .then(({ response }) => {\n job.status = 'success';\n job.response = response;\n job.finishedAt = Date.now();\n this.log(`[runner] faucet drip succeeded: ${chain} → ${address}`);\n })\n .catch((err: unknown) => {\n // The background promise must never become an unhandled rejection.\n try {\n const msg = errMsg(err);\n // A timeout is NOT a definitive failure — the on-chain drip may still\n // settle after the client gives up (observed on EVM). Mark it as a\n // distinct, non-terminal-sounding state and advise re-checking\n // balances before re-funding, rather than asserting it failed.\n const timedOut = /timed out|timeout|aborted/i.test(msg);\n job.status = timedOut ? 'timeout' : 'error';\n job.error = timedOut\n ? `${msg} — the on-chain drip may still have settled; re-check balances before re-funding.`\n : msg;\n job.finishedAt = Date.now();\n this.log(\n `[runner] faucet drip ${timedOut ? 'timed out' : 'failed'}: ${chain} → ${address}: ${msg}`\n );\n } catch {\n // Swallow — recording the failure must not itself reject.\n }\n });\n\n return { ...job };\n }\n\n /**\n * Snapshots of tracked faucet drip jobs — all of them, or just the one for\n * `chain`. Lets a caller poll for the terminal state of an async drip without\n * re-dripping.\n */\n getFundStatus(chain?: FaucetChain): FundStatusResponse {\n const jobs = chain\n ? this.fundJobs.has(chain)\n ? [{ ...this.fundJobs.get(chain)! }]\n : []\n : [...this.fundJobs.values()].map((j) => ({ ...j }));\n return { jobs };\n }\n\n /** Full registry of relay + apex targets with per-target status. */\n getTargets(): TargetsResponse {\n const relays: RelayTargetStatus[] = [...this.relays.entries()].map(\n ([relayUrl, r]) => ({\n relayUrl,\n connected: r.isConnected(),\n buffered: r.bufferedCount(),\n subscriptions: r.activeSubscriptions(),\n isDefault: relayUrl === this.defaultRelayUrl,\n })\n );\n const apexes: ApexTargetStatus[] = [...this.apexes.values()].map((a) => ({\n btpUrl: a.btpUrl,\n destination: a.destination,\n chain: a.chain,\n ready: a.ready,\n bootstrapping: a.bootstrapping,\n ...(a.apexChannelId ? { channelId: a.apexChannelId } : {}),\n ...(a.lastError ? { lastError: a.lastError } : {}),\n isDefault: a.isDefault,\n }));\n return { relays, apexes };\n }\n\n // ── Paid operations ──────────────────────────────────────────────────────\n\n /**\n * Lazily open the apex channel on first paid write (deferred at bootstrap so\n * the wallet can be funded after start, #69) and persist it for resume.\n */\n private async ensureApexChannel(\n apex: ApexConnection,\n destination?: string\n ): Promise<string> {\n let channelId = apex.apexChannelId;\n if (!channelId) {\n channelId = await apex.client.openChannel(destination);\n if (!destination || destination === apex.destination) {\n apex.apexChannelId = channelId;\n this.persistApexChannel(apex, channelId);\n }\n }\n return channelId;\n }\n\n /** Pay-to-write a single event through the selected (or default) apex. */\n async publish(req: PublishRequest): Promise<PublishResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n const channelId = await this.ensureApexChannel(apex, req.destination);\n const fee = req.fee !== undefined ? BigInt(req.fee) : apex.feePerEvent;\n const claim = await apex.client.signBalanceProof(channelId, fee);\n // Relay writes default to the configured publish destination (e.g.\n // g.proxy.relay) — NOT the apex anchor, which on the devnet proxy is\n // g.proxy.relay.store and would forward a /write to the store (→ 404). An\n // explicit per-call destination still wins. The claim is pre-signed on the\n // apex channel, so the destination is pure routing (settlement is unaffected).\n const result = await apex.client.publishEvent(req.event, {\n destination: req.destination ?? this.config.publishDestination,\n claim,\n ilpAmount: fee,\n });\n if (!result.success) {\n throw new PublishRejectedError(result.error ?? 'relay rejected event');\n }\n return {\n eventId: result.eventId ?? req.event.id,\n ...(result.data !== undefined ? { data: result.data } : {}),\n channelId,\n nonce: apex.client.getChannelNonce(channelId),\n feePaid: fee.toString(),\n channelBalanceAfter: this.channelAvailable(apex, channelId),\n };\n }\n\n /**\n * Available (spendable) balance for a channel after a write — locked collateral\n * minus cumulative spent, clamped at 0. Same math as {@link getChannels}; used\n * to report a truthful post-write balance in publish/upload receipts. Returns\n * undefined if the channel isn't tracked on this apex (balance unknown).\n */\n private channelAvailable(\n apex: ApexConnection,\n channelId: string\n ): string | undefined {\n if (!apex.client.getTrackedChannels().includes(channelId)) return undefined;\n const cumulative = apex.client.getChannelCumulativeAmount(channelId);\n const depositTotal = apex.client.getChannelDepositTotal(channelId);\n const available =\n depositTotal > cumulative ? depositTotal - cumulative : 0n;\n return available.toString();\n }\n\n /**\n * Build, sign (with the daemon-held key), and pay-to-write an event. The\n * caller supplies only the event shell; the private key never leaves the\n * daemon. Payloads are MODEL-AUTHORED → validated server-side here (the model\n * is not a security boundary). Replaceable kinds (0/3) merge the latest known\n * event's tags before signing.\n */\n async publishUnsigned(req: PublishUnsignedRequest): Promise<PublishResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n const template = this.buildTemplate(apex, req);\n const signed = await apex.client.signEvent(template);\n return this.publish({\n event: signed,\n ...(req.destination ? { destination: req.destination } : {}),\n ...(req.fee ? { fee: req.fee } : {}),\n ...(req.btpUrl ? { btpUrl: req.btpUrl } : {}),\n });\n }\n\n /**\n * Upload media to Arweave (kind:5094 blob DVM, single-packet) then sign+publish\n * a media event referencing the resulting URL. One spendy operation, two steps,\n * entirely server-side.\n */\n async uploadMedia(req: UploadMediaRequest): Promise<UploadMediaResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n // Source the bytes from EXACTLY ONE of inline base64 or an on-disk path.\n // `filePath` lets agent callers skip materializing the whole payload as a\n // tool argument (it never touches the model context); `dataBase64` stays for\n // back-compat. Both-or-neither is a payload error.\n const hasData = typeof req.dataBase64 === 'string' && req.dataBase64 !== '';\n const hasPath = typeof req.filePath === 'string' && req.filePath !== '';\n if (hasData === hasPath) {\n throw new InvalidPayloadError(\n 'exactly one of dataBase64 (base64 media bytes) | filePath (absolute path) is required.'\n );\n }\n const blobData = hasPath\n ? await this.readUploadFile(req.filePath as string)\n : new Uint8Array(Buffer.from(req.dataBase64 as string, 'base64'));\n const fee = req.fee !== undefined ? BigInt(req.fee) : apex.feePerEvent;\n // ── Leg 1: Arweave blob upload ──────────────────────────────────────────\n // Blob storage terminates at the store/DVM backend (POST /store → Arweave),\n // so it routes to the configured store destination (e.g. g.proxy.store,\n // derived from the `….relay.store` anchor by #143). This makes uploads work\n // via the default apex without the caller hand-passing a store `btpUrl`. A\n // failure here is distinct from the kind:1-equivalent publish below; label\n // it so the UI/agent can tell the upload leg apart from the publish leg.\n const upload = await apex.client.uploadBlob({\n blobData,\n destination: this.config.storeDestination,\n ...(req.mime ? { contentType: req.mime } : {}),\n ilpAmount: fee,\n });\n if (!upload.success || !upload.txId) {\n throw new PublishRejectedError(\n `Arweave upload leg failed (store ${this.config.storeDestination}): ${upload.error ?? 'blob upload rejected'}`\n );\n }\n const { url, fallbacks } = arweaveUrls(\n upload.txId,\n this.config.arweaveGateways\n );\n const kind = req.kind ?? 1063;\n const signed = await apex.client.signEvent({\n kind,\n created_at: nowSeconds(),\n tags: this.buildMediaTags(kind, url, fallbacks, req),\n content: req.caption ?? '',\n });\n // ── Leg 2: publish the NIP-94/NIP-68 reference event ────────────────────\n // The reference event is a normal Nostr write, so it must publish through a\n // RELAY apex, not the store/DVM. `this.publish` routes it to the configured\n // publish destination (e.g. g.proxy.relay) — the exact path #143 made kind:1\n // work. Omit `btpUrl` so it uses the default (relay) apex. Label any failure\n // here as the post-upload publish leg (the blob already stored OK).\n let pub: PublishResponse;\n try {\n pub = await this.publish({\n event: signed,\n ...(req.fee ? { fee: req.fee } : {}),\n });\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new PublishRejectedError(\n `kind:${kind} publish leg failed after upload (blob stored at ${url}): ${detail}`\n );\n }\n // An upload pays TWICE — the blob (leg 1) and the reference event (leg 2) —\n // so the truthful total is the sum, not just the publish leg's fee. The\n // post-write balance from the (last) publish leg is already current.\n const feePaid = (fee + BigInt(pub.feePaid)).toString();\n return { ...pub, feePaid, url, txId: upload.txId };\n }\n\n /**\n * Read media bytes off disk for an upload `filePath`. The path is resolved\n * and, when an `uploadAllowedRoot` is configured, must resolve inside it —\n * bounding which filesystem locations the daemon reads on an agent's behalf.\n * A missing/unreadable file (or an out-of-bounds path) surfaces as an\n * `InvalidPayloadError` (HTTP 400), not an unhandled crash.\n */\n private async readUploadFile(filePath: string): Promise<Uint8Array> {\n const resolved = resolve(filePath);\n const root = this.config.uploadAllowedRoot;\n if (root && resolved !== root && !resolved.startsWith(root + sep)) {\n throw new InvalidPayloadError(\n `filePath must resolve inside the configured upload root (${root}).`\n );\n }\n try {\n const buf = await readFile(resolved);\n return new Uint8Array(buf);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new InvalidPayloadError(\n `failed to read filePath ${resolved}: ${detail}`\n );\n }\n }\n\n /** Validate + assemble a signable event template (with replaceable merge). */\n private buildTemplate(\n apex: ApexConnection,\n req: PublishUnsignedRequest\n ): EventTemplate {\n if (!Number.isInteger(req.kind) || req.kind < 0 || req.kind > 65535) {\n throw new InvalidPayloadError('kind must be an integer in [0, 65535].');\n }\n if (req.content !== undefined && typeof req.content !== 'string') {\n throw new InvalidPayloadError('content must be a string.');\n }\n const tags = normalizeTags(req.tags);\n const content = req.content ?? '';\n\n // Replaceable kinds: merge the latest known self-authored event so a single\n // \"follow X\" / profile edit doesn't clobber prior tags. Best-effort from the\n // read buffer (v1 — concurrent edits can still race; see plan risk #6).\n if (req.kind === 0 || req.kind === 3) {\n const prior = this.latestSelfReplaceable(apex, req.kind);\n if (prior) {\n return {\n kind: req.kind,\n created_at: nowSeconds(),\n tags: mergeTags(prior.tags, tags),\n content: content !== '' ? content : prior.content,\n };\n }\n }\n return { kind: req.kind, created_at: nowSeconds(), tags, content };\n }\n\n /** Latest self-authored event of `kind` currently in the merged read buffer. */\n private latestSelfReplaceable(\n apex: ApexConnection,\n kind: number\n ): NostrEvent | undefined {\n const pubkey = safe(() => apex.client.getPublicKey());\n if (!pubkey) return undefined;\n let latest: NostrEvent | undefined;\n for (const m of this.merged) {\n if (m.event.kind !== kind || m.event.pubkey !== pubkey) continue;\n if (!latest || m.event.created_at > latest.created_at) latest = m.event;\n }\n return latest;\n }\n\n /**\n * Tags for a published media event referencing an Arweave URL. `url` is the\n * primary gateway; `fallbacks` are mirror URLs for the same tx id on other\n * gateways, emitted so readers can fail over if the primary is unreachable.\n */\n private buildMediaTags(\n kind: number,\n url: string,\n fallbacks: string[],\n req: UploadMediaRequest\n ): string[][] {\n const mime = req.mime ?? 'application/octet-stream';\n const extra = normalizeTags(req.tags);\n if (kind === 1063) {\n // NIP-94 file metadata: separate url/m tags, mirrors as `fallback` tags.\n return [\n ['url', url],\n ['m', mime],\n ...fallbacks.map((f) => ['fallback', f]),\n ...extra,\n ];\n }\n // NIP-68/71 picture/video + NIP-92 inline note: a single `imeta` tag with\n // the primary `url` first and the remaining gateways as `fallback` mirrors.\n return [\n [\n 'imeta',\n `url ${url}`,\n `m ${mime}`,\n ...fallbacks.map((f) => `fallback ${f}`),\n ],\n ...extra,\n ];\n }\n\n /** Open (or return) a payment channel on the selected (or default) apex. */\n async openChannel(\n destination?: string,\n btpUrl?: string\n ): Promise<{ channelId: string }> {\n const apex = this.selectApex(btpUrl);\n this.assertApexReady(apex);\n const channelId = await apex.client.openChannel(\n destination ?? apex.destination\n );\n if (!destination || destination === apex.destination) {\n const firstOpen = apex.apexChannelId !== channelId;\n apex.apexChannelId = channelId;\n // Persist the (possibly lazily-opened) apex channel for restart-resume.\n if (firstOpen) this.persistApexChannel(apex, channelId);\n }\n return { channelId };\n }\n\n /** List tracked channels across ALL apexes with nonce + cumulative amount. */\n getChannels(): ChannelsResponse {\n const seen = new Set<string>();\n const channels: ChannelsResponse['channels'] = [];\n for (const apex of this.apexes.values()) {\n for (const channelId of apex.client.getTrackedChannels()) {\n if (seen.has(channelId)) continue;\n seen.add(channelId);\n const cumulative = apex.client.getChannelCumulativeAmount(channelId);\n const depositTotal = apex.client.getChannelDepositTotal(channelId);\n // Available (spendable) balance = locked collateral − cumulative spent.\n // Clamp at 0 so an over-spend estimate never surfaces as negative.\n const available =\n depositTotal > cumulative ? depositTotal - cumulative : 0n;\n const settleableAt = apex.client.getSettleableAt(channelId);\n channels.push({\n channelId,\n nonce: apex.client.getChannelNonce(channelId),\n cumulativeAmount: cumulative.toString(),\n depositTotal: depositTotal.toString(),\n availableBalance: available.toString(),\n closeState: apex.client.getChannelCloseState(channelId),\n ...(settleableAt !== undefined\n ? { settleableAt: settleableAt.toString() }\n : {}),\n });\n }\n }\n return { channels };\n }\n\n /**\n * On-chain wallet balances. The wallet is identity-level (same keys across\n * apexes), so this reads from the daemon's {@link identityClient} — NOT an apex\n * — and therefore works even with zero apexes / no payment peer configured\n * (reading your own balance is a pure wallet-keys + chain-RPC operation).\n * Per-chain reads are best-effort inside the client (a failing chain is simply\n * omitted).\n *\n * Each underlying read hits per-chain RPC providers that can stall\n * indefinitely on devnet (a provider being `detail: \"configured\"` in\n * toon_status means it is WIRED, not that its RPC is live). A stall here used\n * to block the whole control request until the client aborted, surfacing as a\n * misleading \"relay/apex unreachable\" timeout (#199). Bound each attempt well\n * under the control API timeout and retry once so a single transient\n * provider stall FAST-FAILS with an honest \"balances handler / provider\n * stalled\" error instead of hanging.\n */\n async getBalances(): Promise<BalancesResponse> {\n let lastErr: unknown;\n for (let attempt = 1; attempt <= BALANCES_READ_ATTEMPTS; attempt++) {\n try {\n const balances = (await withTimeout(\n this.identityClient.getBalances(),\n BALANCES_READ_TIMEOUT_MS,\n `chain balance read timed out after ${BALANCES_READ_TIMEOUT_MS}ms`\n )) as BalanceInfo[];\n return { balances };\n } catch (err) {\n lastErr = err;\n }\n }\n throw new BalancesUnavailableError(\n `the balances control handler's chain RPC/provider read did not return ` +\n `(${BALANCES_READ_ATTEMPTS} attempts, ${BALANCES_READ_TIMEOUT_MS}ms each) — ` +\n `the on-chain provider stalled, not the relay or apex. Retry shortly.`,\n lastErr instanceof Error ? lastErr.message : undefined\n );\n }\n\n /**\n * Deposit additional collateral into an open channel. Routes to the apex whose\n * client tracks the channel (each apex client opens/tracks its own channels);\n * the client signs its own on-chain tx.\n */\n async depositToChannel(\n req: ChannelDepositRequest\n ): Promise<ChannelDepositResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.depositToChannel(req.channelId, req.amount)\n );\n }\n\n /** Close a channel to begin the settlement grace period (withdraw, step 1). */\n async closeChannel(req: CloseChannelRequest): Promise<CloseChannelResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.closeChannel(req.channelId)\n );\n }\n\n /**\n * Settle a closed channel to release collateral (withdraw, step 2). The client\n * enforces the `now >= settleableAt` guard and throws a retryable error if\n * called early; `mapError` maps that to HTTP 425.\n */\n async settleChannel(\n req: SettleChannelRequest\n ): Promise<SettleChannelResponse> {\n return this.withTrackingApex(req.channelId, (client) =>\n client.settleChannel(req.channelId)\n );\n }\n\n /** Run `fn` against the apex client that tracks `channelId`, else throw. */\n private async withTrackingApex<T>(\n channelId: string,\n fn: (client: ApexConnection['client']) => Promise<T>\n ): Promise<T> {\n for (const apex of this.apexes.values()) {\n if (apex.client.getTrackedChannels().includes(channelId)) {\n return fn(apex.client);\n }\n }\n throw new Error(`Channel \"${channelId}\" is not tracked by any apex.`);\n }\n\n /**\n * Swap source→target asset against a swap peer via the selected apex.\n *\n * sdk ≥2.0.0 (the `mill`→`swap` vocabulary rename, toon commit `af4cd24`):\n * `streamSwap` takes `swapPubkey`/`swapIlpAddress` and accumulated claims\n * carry `swapSignerAddress`. The rename has NO wire back-compat — a\n * pre-rename (sdk ≤1.x) swap peer still emits `millSignerAddress` in its\n * FULFILL settlement metadata, which `decodeFulfillMetadata` silently drops\n * as an unknown field. That skew would otherwise surface only much later as\n * `MISSING_SETTLEMENT_METADATA` in `buildSettlementTx`, so we detect it\n * here (accepted claims with no `swapSignerAddress`) and surface a loud\n * `warning` on the response at swap time (#349).\n *\n * With `req.senderConditions` set, every swap packet is sent with a FRESH\n * sender-minted execution condition (`C_i = sha256(P_i)`, one per packet —\n * toon-client#350, rolling-swap spec §3 R1/R2) and the transport verifies\n * each FULFILL's preimage; a mismatch counts the packet failed. This\n * requires a maker/connector implementing the sender-chosen fulfillment\n * contract (connector#309) — the deployed claim-issuing mill cannot satisfy\n * it — so it is opt-in and the default stays the legacy zero condition.\n *\n * Sender-side rolling-swap defenses (#351, sdk ≥2.1.0, spec §5/§6):\n *\n * - **Hard floor** — `req.minExchangeRate`, or derived\n * `pair.rate × (1 − floorBps/10000)` from `req.floorBps` /\n * `swapDefaults.floorBps`. A below-floor packet records `BELOW_FLOOR` and\n * halts the stream (`abortReason: 'below-floor'`); the armed floor is\n * echoed on the response so hosts can show the guaranteed worst case.\n * - **Adaptive controller** — `req.controller` (or\n * `swapDefaults.controller` when the request pins no `packetCount`)\n * replaces the static even split with `AdaptiveDeltaController` δ/W\n * sizing; per-(source chain, maker, pair) state persists in\n * `swap-controller-state.json` beside the daemon's channel stores. The\n * controller is efficiency-only — it can never relax the floor.\n * - **Telemetry** — `onPacket` is always wired: per-packet outcomes,\n * rejections, and a realized-rate summary land on the response, and each\n * accepted packet is logged. Everything else is strictly opt-in: with no\n * new params and no `swapDefaults`, the `streamSwap` call is the legacy\n * request (no floor, no controller, no expiry stamping, no signal).\n * - **Abort** — `req.timeoutMs` arms an `AbortSignal`; on expiry in-flight\n * packets drain and the partial fill is reported exactly (partial\n * `claims`, cumulatives, `state`/`abortReason`).\n */\n async swap(req: SwapRequest): Promise<SwapResponse> {\n const apex = this.selectApex(req.btpUrl);\n this.assertApexReady(apex);\n if (req.controller && req.packetCount !== undefined) {\n throw new InvalidPayloadError(\n '`controller` and `packetCount` are mutually exclusive: the adaptive ' +\n 'controller replaces the static even split with dynamic δ/W sizing.'\n );\n }\n const defaults = this.config.swapDefaults;\n // Floor precedence: explicit rate → per-request bps → daemon-default bps.\n const minExchangeRate =\n req.minExchangeRate ??\n deriveFloorRate(req.pair.rate, req.floorBps ?? defaults?.floorBps);\n const packetExpiryMs = req.packetExpiryMs ?? defaults?.packetExpiryMs;\n // Controller precedence: per-request params → daemon default — but an\n // explicit packetCount on the request always pins the legacy even split.\n const controllerParams =\n req.controller ??\n (req.packetCount === undefined ? defaults?.controller : undefined);\n const controller = controllerParams\n ? await this.createSwapController(req, controllerParams)\n : undefined;\n\n // Per-packet telemetry: collected for the response and logged. The\n // callback must never throw — streamSwap treats a throwing onPacket as a\n // stop signal, and telemetry must not be able to halt the stream.\n const packets: SwapPacketOutcome[] = [];\n let packetsTruncated = false;\n const onPacket = (p: PacketProgress): void => {\n try {\n this.log(\n `[runner] swap packet ${p.index}: ${p.sourceAmount} → ` +\n `${p.targetAmount} (rate ${p.effectiveRate.toFixed(6)}, ` +\n `deviation ${p.rateDeviation.toFixed(6)}` +\n (p.rate ? `, tape ${p.rate}` : '') +\n ')'\n );\n if (packets.length >= SWAP_PACKETS_RESPONSE_LIMIT) {\n packetsTruncated = true;\n return;\n }\n packets.push({\n index: p.index,\n sourceAmount: p.sourceAmount.toString(),\n targetAmount: p.targetAmount.toString(),\n effectiveRate: p.effectiveRate,\n rateDeviation: p.rateDeviation,\n ...(p.rate !== undefined ? { rate: p.rate } : {}),\n ...(p.rateTimestamp !== undefined\n ? { rateTimestamp: p.rateTimestamp }\n : {}),\n });\n } catch {\n // Swallow: telemetry failures must not stop the stream.\n }\n };\n\n const senderSecretKey = generateSecretKey();\n // Session-scoped preimage retention (#360): populated only on the\n // sender-chosen path, consumed by the leg-B reveal in `ingestAndReveal`.\n const preimages = new InMemoryPreimageRetentionStore();\n const swapClient = req.senderConditions\n ? this.withSenderConditions(apex.client, preimages)\n : apex.client;\n const result = await streamSwap({\n client: swapClient as unknown as Parameters<\n typeof streamSwap\n >[0]['client'],\n swapPubkey: req.swapPubkey,\n swapIlpAddress: req.destination,\n pair: req.pair,\n senderSecretKey,\n chainRecipient: req.chainRecipient,\n totalAmount: BigInt(req.amount),\n // EXACTLY ONE of controller / packetCount (sdk contract).\n ...(controller ? { controller } : { packetCount: req.packetCount ?? 1 }),\n onPacket,\n ...(minExchangeRate !== undefined ? { minExchangeRate } : {}),\n ...(packetExpiryMs !== undefined ? { packetExpiryMs } : {}),\n ...(req.timeoutMs !== undefined\n ? { signal: AbortSignal.timeout(req.timeoutMs) }\n : {}),\n });\n const firstReject = result.rejections[0];\n\n // #352/#360: receipt-time verification + durable ingestion composed\n // ATOMICALLY with the leg-B reveal. Every FULFILLed claim with settlement\n // metadata is verified (signature against the maker's advertised/pinned\n // signer, recipient, chain, nonce/cumulative monotonicity vs the persisted\n // watermark) and, only if it passes, persisted as the channel's\n // highest-nonce watermark; the sender then reveals the retained preimage\n // `P_i` to commit leg A. If the reveal is withheld or fails, the watermark\n // advance is ROLLED BACK so it tracks only revealed packets (engine R8: the\n // maker reuses the rolled-back nonce for the next fill, which must not be\n // falsely rejected as non-monotonic). A claim that fails verification is\n // NEVER revealed and NEVER counted. Claims MISSING settlement metadata take\n // the legacy #349 path (warning, not persisted) unchanged.\n //\n // In the deployed single-round-trip model the FULFILL already resolved\n // leg A inline at `sendSwapPacket` time (the transport verified `P_i`), so\n // the reveal here commits every verified claim — this wires the atomic seam\n // and the retained preimages without withholding. The withhold/rollback\n // branch is exercised by a maker-driven leg-B (spec §3.2) and covered by\n // `ingestAndReveal`'s unit tests.\n const expectedChain = req.pair.to.chain;\n const minaSignerClient = expectedChain.startsWith('mina')\n ? await loadMinaSignerClient()\n : undefined;\n const reveal: RevealFn = () => ({ decision: 'revealed' });\n const ingest = await ingestAndReveal({\n claims: result.claims,\n expectedChain,\n chainRecipient: req.chainRecipient,\n ...(req.swapSignerAddress\n ? { expectedSignerAddress: req.swapSignerAddress }\n : {}),\n // v2 EIP-712 receive-side verify (#365): EVM claims are domain-separated\n // over `(chainId, verifyingContract)`, so the receive path needs the same\n // `tokenNetworks` (chain key → RollingSwapChannel address) map the settle\n // path already threads, or an EVM claim is rejected MISSING_CHAIN_CONFIG.\n ...(this.config.toonClientConfig.tokenNetworks\n ? { tokenNetworks: this.config.toonClientConfig.tokenNetworks }\n : {}),\n store: this.receivedClaimStore,\n ...(minaSignerClient ? { minaSignerClient } : {}),\n preimages,\n reveal,\n });\n preimages.clear();\n const verifiedSet = new Set(ingest.revealed.map((v) => v.claim));\n const rejectionByClaim = new Map(\n ingest.rejected.map((r) => [r.claim, r] as const)\n );\n for (const r of ingest.rejected) {\n this.log(\n `[runner] swap: REJECTED received claim (packet ${r.claim.packetIndex}, ` +\n `channel ${r.claim.channelId ?? '?'}): ${r.code} — ${r.message}`\n );\n }\n\n const claims = result.claims.map((c): SwapClaim => {\n const rejection = rejectionByClaim.get(c);\n return {\n sourceAmount: c.sourceAmount.toString(),\n targetAmount: c.targetAmount.toString(),\n claim: Buffer.from(c.claimBytes).toString('base64'),\n ...(c.channelId ? { channelId: c.channelId } : {}),\n ...(c.recipient ? { recipient: c.recipient } : {}),\n ...(c.swapSignerAddress\n ? { swapSignerAddress: c.swapSignerAddress }\n : {}),\n ...(c.claimId ? { claimId: c.claimId } : {}),\n ...(c.nonce ? { nonce: c.nonce } : {}),\n ...(c.cumulativeAmount ? { cumulativeAmount: c.cumulativeAmount } : {}),\n ...(verifiedSet.has(c) ? { verified: true } : {}),\n ...(rejection\n ? {\n verified: false,\n verificationError: {\n code: rejection.code,\n message: rejection.message,\n },\n }\n : {}),\n };\n });\n\n // Wire-rename skew guard (#349): claims were FULFILLed but none carries\n // the swapSignerAddress settlement metadata — the signature of a\n // pre-rename swap peer (emits `millSignerAddress`, silently dropped by\n // sdk ≥2's decodeFulfillMetadata). Settlement of these claims WILL fail\n // with MISSING_SETTLEMENT_METADATA; say so now instead of then.\n const missingSettlementSigner =\n claims.length > 0 && claims.every((c) => !c.swapSignerAddress);\n if (missingSettlementSigner) {\n this.log(\n '[runner] swap: accepted claims are missing swapSignerAddress ' +\n 'settlement metadata — swap peer is likely pre-rename (sdk <2.0.0)'\n );\n }\n const realizedRate = computeRealizedRate(\n result.cumulativeSource,\n result.cumulativeTarget,\n req.pair\n );\n const warnings: string[] = [];\n if (missingSettlementSigner) {\n warnings.push(\n 'Accepted claims are missing `swapSignerAddress` settlement ' +\n 'metadata, so settling them will fail with ' +\n 'MISSING_SETTLEMENT_METADATA. The swap peer is likely running ' +\n 'a pre-rename SDK (<2.0.0, emits `millSignerAddress`, which ' +\n 'sdk ≥2 silently drops). Upgrade the swap peer before settling.'\n );\n }\n const firstRejected = ingest.rejected[0];\n if (firstRejected) {\n warnings.push(\n `${ingest.rejected.length} received claim(s) FAILED verification and ` +\n `were NOT counted as value received (first: ${firstRejected.code} — ` +\n `${firstRejected.message}). See per-claim verificationError.`\n );\n }\n\n const hadIngestibleClaims =\n ingest.revealed.length + ingest.rejected.length > 0;\n return {\n // A swap only counts as accepted when it yielded a VERIFIED+REVEALED\n // claim (or a legacy no-metadata claim, whose path is unchanged).\n // FULFILLed packets whose claims all failed verification are a failed\n // swap, loudly.\n accepted: ingest.revealed.length + ingest.legacy.length > 0,\n packetsAccepted: result.claims.length,\n claims,\n cumulativeSource: result.cumulativeSource.toString(),\n cumulativeTarget: result.cumulativeTarget.toString(),\n state: result.state,\n abortReason: result.abortReason,\n ...(packets.length > 0 ? { packets } : {}),\n ...(packetsTruncated ? { packetsTruncated } : {}),\n ...(result.rejections.length > 0\n ? {\n rejections: result.rejections.map((r) => ({\n packetIndex: r.packetIndex,\n sourceAmount: r.sourceAmount.toString(),\n code: r.code,\n message: r.message,\n })),\n }\n : {}),\n ...(realizedRate !== undefined ? { realizedRate } : {}),\n ...(minExchangeRate !== undefined ? { minExchangeRate } : {}),\n ...(firstReject\n ? { code: firstReject.code, message: firstReject.message }\n : {}),\n ...(warnings.length > 0 ? { warning: warnings.join('\\n') } : {}),\n ...(hadIngestibleClaims\n ? {\n claimsVerified: ingest.revealed.length,\n claimsRejected: ingest.rejected.length,\n valueReceived: ingest.valueRevealed.toString(),\n }\n : {}),\n };\n }\n\n // ── Received swap claims: persistence + settlement surfaces (#352) ─────────\n\n /** List the persisted received-claim watermarks (`GET /swap/claims`). */\n listSwapClaims(): ListSwapClaimsResponse {\n return {\n claims: this.receivedClaimStore.list().map(toReceivedClaimInfo),\n };\n }\n\n /**\n * Build (and, where chain plumbing is configured, submit) on-chain\n * settlements for persisted received claims (`POST /swap/settle`).\n *\n * Per (chain, channelId) the persisted entry IS the highest-nonce\n * watermark, so N received advances redeem as ONE close. Result-shaped\n * throughout: a channel that cannot build or submit reports `error` instead\n * of failing the batch. Submission is the env-gated seam — it requires the\n * identity client's EVM plumbing plus `chainRpcUrls[chain]`; without them\n * the built (re-verified) tx is returned as `unsignedTx` for an external\n * signer. Solana submission and the Mina receive-side co-sign path are\n * explicit follow-ups (spec §9 dependency 2).\n */\n async settleSwapClaims(\n req: SettleSwapClaimsRequest = {}\n ): Promise<SettleSwapClaimsResponse> {\n const submit = req.submit !== false;\n const entries = this.receivedClaimStore\n .list()\n .filter((e) => (req.chain ? e.chain === req.chain : true))\n .filter((e) => (req.channelId ? e.channelId === req.channelId : true));\n\n const results: SwapSettlementResult[] = [];\n const pending: ReceivedClaimEntry[] = [];\n for (const entry of entries) {\n if (\n entry.settledNonce !== undefined &&\n entry.settledNonce >= entry.nonce\n ) {\n results.push({\n chain: entry.chain,\n channelId: entry.channelId,\n built: false,\n submitted: false,\n error: {\n code: 'ALREADY_SETTLED',\n message: `watermark nonce ${entry.nonce} was already settled (tx ${entry.settleTxHash ?? 'unknown'})`,\n },\n });\n continue;\n }\n pending.push(entry);\n }\n\n const minaSignerClient = pending.some((e) => e.chain.startsWith('mina'))\n ? await loadMinaSignerClient()\n : undefined;\n const builds = buildSwapSettlements({\n entries: pending,\n ...(this.config.toonClientConfig.tokenNetworks\n ? { tokenNetworks: this.config.toonClientConfig.tokenNetworks }\n : {}),\n // Re-verify the stored watermark's signature at settle time\n // (defense-in-depth over the store file). The published v2 sdk\n // (`@toon-protocol/sdk@^3`) verifies EVM claims against the SAME v2\n // EIP-712 domain-separated digest the receive-side used (#365), so a\n // valid v2 signature verifies correctly here — `buildSwapSettlements`\n // threads `chainId` + `verifyingContract` (from `tokenNetworks`) into the\n // sdk signer config so the EIP-712 domain is reconstructed.\n verifySignatures: true,\n ...(minaSignerClient ? { minaSignerClient } : {}),\n });\n\n for (const [i, build] of builds.entries()) {\n const entry = pending[i];\n if (!entry) continue; // builds is index-aligned with pending\n if (!build.bundle) {\n results.push({\n chain: build.chain,\n channelId: build.channelId,\n built: false,\n submitted: false,\n ...(build.error ? { error: build.error } : {}),\n });\n continue;\n }\n const bundle = build.bundle;\n const base: SwapSettlementResult = {\n chain: build.chain,\n channelId: build.channelId,\n built: true,\n submitted: false,\n nonce: bundle.nonce,\n cumulativeAmount: bundle.cumulativeAmount,\n unsignedTx: Buffer.from(bundle.unsignedTxBytes).toString('base64'),\n };\n if (!submit) {\n results.push(base);\n continue;\n }\n if (bundle.chainKind === 'solana') {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNSUPPORTED',\n message:\n 'Solana settlement submission is not wired yet (bundle carries a serialized Message; follow-up under toon-meta#145).',\n },\n });\n continue;\n }\n if (bundle.chainKind === 'mina') {\n // Mina receive-side redemption (#357): the client produces the\n // recipient's co-signature and drives the dual-party `claimFromChannel`\n // (o1js proving) via `settleSwapBundle`. Fails closed with a stable\n // MinaSettlementError code (e.g. NO_GRAPHQL_CONFIGURED,\n // MINA_MAKER_COSIGN_REQUIRED) surfaced as SUBMISSION_FAILED.\n if (!this.identityClient.settleSwapBundle) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNAVAILABLE',\n message: 'The active client does not implement settleSwapBundle.',\n },\n });\n continue;\n }\n try {\n const submitted = await this.identityClient.settleSwapBundle(bundle);\n const latest =\n this.receivedClaimStore.load(entry.chain, entry.channelId) ?? entry;\n this.receivedClaimStore.save({\n ...latest,\n settledAt: Date.now(),\n settledNonce: BigInt(bundle.nonce),\n settleTxHash: submitted.txHash,\n });\n this.log(\n `[runner] swap settle: submitted ${bundle.chain}/${bundle.channelId} ` +\n `nonce ${bundle.nonce} cumulative ${bundle.cumulativeAmount} → ${submitted.txHash}`\n );\n results.push({\n ...base,\n submitted: true,\n txHash: submitted.txHash,\n ...(submitted.status ? { txStatus: submitted.status } : {}),\n });\n } catch (err) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n continue;\n }\n const rpcUrl = this.config.toonClientConfig.chainRpcUrls?.[bundle.chain];\n if (!rpcUrl) {\n results.push({\n ...base,\n error: {\n code: 'NO_RPC_CONFIGURED',\n message: `No RPC URL configured for \"${bundle.chain}\" (chainRpcUrls) — returning the built tx unsubmitted.`,\n },\n });\n continue;\n }\n if (!this.identityClient.settleSwapBundle) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_UNAVAILABLE',\n message: 'The active client does not implement settleSwapBundle.',\n },\n });\n continue;\n }\n try {\n const submitted = await this.identityClient.settleSwapBundle(bundle);\n // Mark the watermark settled so a re-run skips it.\n const latest =\n this.receivedClaimStore.load(entry.chain, entry.channelId) ?? entry;\n this.receivedClaimStore.save({\n ...latest,\n settledAt: Date.now(),\n settledNonce: BigInt(bundle.nonce),\n settleTxHash: submitted.txHash,\n });\n this.log(\n `[runner] swap settle: submitted ${bundle.chain}/${bundle.channelId} ` +\n `nonce ${bundle.nonce} cumulative ${bundle.cumulativeAmount} → ${submitted.txHash}`\n );\n results.push({\n ...base,\n submitted: true,\n txHash: submitted.txHash,\n ...(submitted.status ? { txStatus: submitted.status } : {}),\n });\n } catch (err) {\n results.push({\n ...base,\n error: {\n code: 'SUBMISSION_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n }\n return { results };\n }\n\n /**\n * Wrap an apex client so each `sendSwapPacket` call carries a freshly\n * minted sender-chosen execution condition (one preimage per packet, spec\n * R1 — reuse would let an observer of packet *i* fulfill packet *i+1*).\n * `streamSwap` calls `sendSwapPacket` once per packet, in strictly\n * increasing `packetIndex` order, so minting here yields exactly one\n * condition per packet and the local counter tracks `packetIndex`. The\n * transports verify each FULFILL's preimage against the condition and fail\n * the packet on mismatch.\n *\n * Preimage retention (toon-client#360, spec §3.2 leg-B reveal): every minted\n * `P_i` is retained in `preimages`, keyed by `packetIndex` — the identifier\n * shared with `AccumulatedClaim.packetIndex` — so the receive-side reveal\n * step (`ingestAndReveal`) can correlate and consume the preimage for the\n * claim it commits. Retention is session-scoped (one store per `swap()`\n * call); a fresh stream mints fresh preimages.\n */\n private withSenderConditions(\n client: ToonClientLike,\n preimages: PreimageRetentionStore\n ): ToonClientLike {\n const wrapped: ToonClientLike = Object.create(client) as ToonClientLike;\n let packetIndex = 0;\n wrapped.sendSwapPacket = (params) => {\n const { preimage, condition } = mintExecutionCondition();\n preimages.retain({\n packetIndex: packetIndex++,\n preimage,\n condition,\n retainedAt: Date.now(),\n });\n return client.sendSwapPacket({\n ...params,\n executionCondition: condition,\n });\n };\n return wrapped;\n }\n\n /**\n * Build the adaptive δ/W controller for one swap session (#351, spec §6).\n * State is keyed per-(source chain, maker, pair) and persisted in the\n * daemon's data dir via the sdk's atomic JSON-file store, so ramp/trust\n * survives across swaps and daemon restarts.\n */\n private async createSwapController(\n req: SwapRequest,\n params: SwapControllerParams\n ): Promise<AdaptiveDeltaController> {\n if (\n typeof params.advertisedSpread !== 'number' ||\n !(params.advertisedSpread > 0)\n ) {\n throw new InvalidPayloadError(\n 'controller.advertisedSpread must be a positive fraction (e.g. ' +\n '0.004 = 40 bps): ε is denominated off the half-spread and the sdk ' +\n 'deliberately has no default.'\n );\n }\n const store = new JsonFileSwapControllerStateStore(\n this.swapControllerStatePath()\n );\n return AdaptiveDeltaController.create({\n makerPubkey: req.swapPubkey,\n pair: req.pair,\n advertisedSpread: params.advertisedSpread,\n ...(params.maxPacketAmount !== undefined\n ? { maxPacketAmount: BigInt(params.maxPacketAmount) }\n : {}),\n ...(params.minPacketAmount !== undefined\n ? { minPacketAmount: BigInt(params.minPacketAmount) }\n : {}),\n ...(params.maxWindow !== undefined\n ? { maxWindow: params.maxWindow }\n : {}),\n ...(params.cleanStreakLength !== undefined\n ? { cleanStreakLength: params.cleanStreakLength }\n : {}),\n ...(params.coldStartDivisor !== undefined\n ? { coldStartDivisor: params.coldStartDivisor }\n : {}),\n ...(params.ewmaAlpha !== undefined\n ? { ewmaAlpha: params.ewmaAlpha }\n : {}),\n store,\n });\n }\n\n /**\n * Controller-state file path: resolved config value, or the same\n * `<configDir>` the other daemon stores live in (`channels.json`,\n * `apex-channels.json`) for manually-built configs.\n */\n private swapControllerStatePath(): string {\n return (\n this.config.swapControllerStatePath ??\n join(configDir(), 'swap-controller-state.json')\n );\n }\n\n /**\n * Payment-aware HTTP fetch through an apex's client. The client issues the\n * request and, on `402 Payment Required`, pays over TOON and retries; we\n * translate the resulting Web `Response` into the wire envelope.\n */\n async httpFetchPaid(\n req: HttpFetchPaidRequest\n ): Promise<HttpFetchPaidResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const res = await apex.client.h402Fetch(req.url, {\n ...(req.method ? { method: req.method } : {}),\n ...(req.headers ? { headers: req.headers } : {}),\n ...(req.body !== undefined ? { body: req.body } : {}),\n ...(req.timeout !== undefined ? { timeout: req.timeout } : {}),\n });\n return {\n status: res.status,\n headers: Object.fromEntries(res.headers.entries()),\n body: await res.text(),\n };\n }\n\n // ── Git write path (/git/*, epic #222 ticket #227) ────────────────────────\n\n /**\n * The daemon `Publisher` implementation (see @toon-protocol/rig) for one\n * apex. Maps the interface onto the runner's production paid-write\n * machinery:\n *\n * - `getFeeRates`: flat `apex.feePerEvent` per publish + the network\n * per-byte upload rate.\n * - `uploadGitObject`: kind:5094 store write with Git-SHA/Git-Type/Repo\n * tags (the proven seed-pipeline shape), signed with the daemon key,\n * paid via signBalanceProof on the apex channel, routed to the store\n * destination (`POST /store`); the Arweave txId is decoded from the\n * FULFILL HTTP envelope.\n * - `publishEvent`: sign with the daemon key + the standard paid publish\n * path (signBalanceProof → publishEvent → feePaid). The daemon owns its\n * write routing (config-seeded relay via the apex), so the advisory\n * `relayUrls` list is not consulted here — remote-state reads DO use it.\n */\n private gitPublisher(apex: ApexConnection): Publisher {\n return {\n getFeeRates: async () => ({\n uploadFeePerByte: UPLOAD_FEE_PER_BYTE,\n eventFee: apex.feePerEvent,\n }),\n uploadGitObject: (upload) => this.gitUploadObject(apex, upload),\n publishEvent: (event) => this.gitPublishEvent(apex, event),\n };\n }\n\n /** Upload one git object body as a paid kind:5094 store write. */\n private async gitUploadObject(\n apex: ApexConnection,\n upload: GitObjectUpload\n ): Promise<UploadReceipt> {\n const channelId = await this.ensureApexChannel(apex);\n const fee = BigInt(upload.body.length) * UPLOAD_FEE_PER_BYTE;\n const claim = await apex.client.signBalanceProof(channelId, fee);\n const signed = await apex.client.signEvent({\n kind: 5094,\n content: '',\n tags: [\n ['i', upload.body.toString('base64'), 'blob'],\n ['bid', fee.toString(), 'usdc'],\n ['output', 'application/octet-stream'],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n created_at: nowSeconds(),\n });\n const result = await apex.client.publishEvent(signed, {\n destination: this.config.storeDestination,\n claim,\n ilpAmount: fee,\n // The store/DVM backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload failed (store ` +\n `${this.config.storeDestination}): ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.data) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload FULFILL carried no data — expected the Arweave tx ID`\n );\n }\n let txId: string;\n try {\n txId = extractArweaveTxId(result.data);\n } catch (err) {\n throw new PublishRejectedError(\n `git object ${upload.sha} upload: ${errMsg(err)}`\n );\n }\n return { txId, feePaid: fee };\n }\n\n /** Sign (daemon key) + pay-to-publish one NIP-34 event via the apex. */\n private async gitPublishEvent(\n apex: ApexConnection,\n event: UnsignedEvent\n ): Promise<PublishReceipt> {\n const signed = await apex.client.signEvent(event);\n const pub = await this.publish({\n event: signed,\n ...(apex.btpUrl ? { btpUrl: apex.btpUrl } : {}),\n });\n return { eventId: pub.eventId, feePaid: BigInt(pub.feePaid) };\n }\n\n /**\n * Plan a push: read the local repo + the remote NIP-34 state, classify ref\n * updates, compute the object delta, and price it. Shared by\n * estimate (returns the plan) and push (executes it).\n */\n private async planGitPush(\n apex: ApexConnection,\n req: GitEstimateRequest\n ): Promise<{\n plan: PushPlan;\n remoteState: RemoteState;\n repoReader: GitRepoReader;\n relayUrls: string[];\n publisher: Publisher;\n }> {\n await assertRepoPath(req.repoPath);\n if (typeof req.repoId !== 'string' || req.repoId === '') {\n throw new InvalidPayloadError('repoId is required.');\n }\n const relayUrls =\n req.relayUrls && req.relayUrls.length > 0\n ? req.relayUrls\n : [this.defaultRelayUrl];\n // Pushes publish kind:30617/30618 signed by the daemon key, so the daemon\n // identity IS the repo owner whose remote state we read.\n const ownerPubkey = apex.client.getPublicKey();\n const repoReader = this.createRepoReader(req.repoPath);\n const remoteState = await this.fetchGitRemoteState({\n relayUrls,\n ownerPubkey,\n repoId: req.repoId,\n });\n const publisher = this.gitPublisher(apex);\n const feeRates = await publisher.getFeeRates();\n const plan = await planPush({\n repoReader,\n remoteState,\n feeRates,\n repoId: req.repoId,\n ...(req.refspecs !== undefined ? { refs: req.refspecs } : {}),\n ...(req.force !== undefined ? { force: req.force } : {}),\n ...(req.announcement !== undefined\n ? { announcement: req.announcement }\n : {}),\n });\n return { plan, remoteState, repoReader, relayUrls, publisher };\n }\n\n /** Plan + price a push WITHOUT paying (backs `POST /git/estimate`). */\n async gitEstimate(req: GitEstimateRequest): Promise<GitEstimateResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const { plan } = await this.planGitPush(apex, req);\n return serializePushPlan(plan);\n }\n\n /** Plan + EXECUTE a push: paid uploads + paid publishes (`POST /git/push`). */\n async gitPush(req: GitPushRequest): Promise<GitPushResponse> {\n if (req.confirm !== true) {\n throw new InvalidPayloadError(\n 'a push uploads objects to Arweave and publishes events — permanent ' +\n 'and paid. Run /git/estimate first, then set confirm: true to proceed.'\n );\n }\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const { plan, remoteState, repoReader, relayUrls, publisher } =\n await this.planGitPush(apex, req);\n const result = await executePush({\n plan,\n publisher,\n remoteState,\n repoReader,\n relayUrls,\n });\n return serializePushResult(plan, result);\n }\n\n /** Build, sign, and pay-to-publish a kind:1621 issue. */\n async gitIssue(req: GitIssueRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.title, 'title');\n assertNonEmptyString(req.body, 'body');\n const event = buildIssue(\n addr.ownerPubkey,\n addr.repoId,\n req.title,\n req.body,\n req.labels ?? []\n );\n return this.gitPublishSigned(event);\n }\n\n /** Build, sign, and pay-to-publish a kind:1622 comment on an issue/patch. */\n async gitComment(req: GitCommentRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.rootEventId, 'rootEventId');\n assertNonEmptyString(req.body, 'body');\n const event = buildComment(\n addr.ownerPubkey,\n addr.repoId,\n req.rootEventId,\n req.parentAuthorPubkey ?? addr.ownerPubkey,\n req.body,\n req.marker ?? 'root'\n );\n return this.gitPublishSigned(event);\n }\n\n /**\n * Build, sign, and pay-to-publish a kind:1617 patch. Content is either the\n * supplied `patchText` or real `git format-patch --stdout <range>` output\n * from a local repository — exactly one source must be given.\n */\n async gitPatch(req: GitPatchRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.title, 'title');\n const hasText = typeof req.patchText === 'string' && req.patchText !== '';\n const hasRange =\n typeof req.repoPath === 'string' &&\n req.repoPath !== '' &&\n typeof req.range === 'string' &&\n req.range !== '';\n if (hasText === hasRange) {\n throw new InvalidPayloadError(\n 'exactly one of patchText | repoPath+range is required.'\n );\n }\n let content: string;\n if (hasRange) {\n await assertRepoPath(req.repoPath as string);\n content = await this.createRepoReader(req.repoPath as string).formatPatch(\n req.range as string\n );\n if (content === '') {\n throw new InvalidPayloadError(\n `range ${JSON.stringify(req.range)} selects no commits — nothing to publish.`\n );\n }\n } else {\n content = req.patchText as string;\n }\n const event = buildPatch(\n addr.ownerPubkey,\n addr.repoId,\n req.title,\n req.commits ?? [],\n req.branch,\n content,\n // PR body → `description` tag; never the content (git am safety, #280).\n typeof req.description === 'string' && req.description !== ''\n ? req.description\n : undefined\n );\n return this.gitPublishSigned(event);\n }\n\n /** Build, sign, and pay-to-publish a kind:1630-1633 status event. */\n async gitStatus(req: GitStatusRequest): Promise<GitEventResponse> {\n const addr = validateRepoAddr(req.repoAddr);\n assertNonEmptyString(req.targetEventId, 'targetEventId');\n const kind = STATUS_KIND_BY_VALUE[req.status];\n if (kind === undefined) {\n throw new InvalidPayloadError(\n 'status must be one of open | applied | closed | draft.'\n );\n }\n const event = buildStatus(req.targetEventId, kind, req.targetPubkey);\n // NIP-34 status events also carry the repo `a` tag so readers can scope\n // a status stream to the repository without resolving the target first.\n event.tags.push([\n 'a',\n `${REPOSITORY_ANNOUNCEMENT_KIND}:${addr.ownerPubkey}:${addr.repoId}`,\n ]);\n return this.gitPublishSigned(event);\n }\n\n /** Sign a built NIP-34 event with the daemon key and pay-to-publish it. */\n private async gitPublishSigned(\n event: UnsignedEvent\n ): Promise<GitEventResponse> {\n const apex = this.selectApex();\n this.assertApexReady(apex);\n const signed = await apex.client.signEvent(event);\n const pub = await this.publish({ event: signed });\n return { ...pub, kind: event.kind };\n }\n\n /** Graceful teardown: close every relay + stop every apex client. */\n async stop(): Promise<void> {\n if (this.stopped) return;\n this.stopped = true;\n for (const relay of this.relays.values()) relay.close();\n for (const apex of this.apexes.values()) {\n try {\n await apex.client.stop();\n } catch (err) {\n this.log(`[runner] client stop error (${apex.btpUrl}): ${errMsg(err)}`);\n }\n }\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private selectApex(btpUrl?: string): ApexConnection {\n if (btpUrl) {\n const apex = this.apexes.get(btpUrl);\n if (!apex) throw new TargetError(`No such apex: ${btpUrl}`);\n return apex;\n }\n const def = this.defaultApex();\n if (!def) throw new NotReadyError('No apex configured.');\n return def;\n }\n\n private assertApexReady(apex: ApexConnection): void {\n // FREE reads need no uplink; a write does. Reject early with an actionable\n // message rather than letting the apex sit forever un-bootstrapped (#69).\n if (!this.config.hasUplink) {\n throw new TargetError(\n 'No write uplink configured — this daemon is read-only. Set ' +\n 'TOON_CLIENT_PROXY_URL (connector proxy) or TOON_CLIENT_BTP_URL to ' +\n 'enable paid writes.'\n );\n }\n if (!apex.ready) {\n throw new NotReadyError(\n apex.bootstrapping\n ? 'Apex is still bootstrapping (transport/channel coming up) — retry shortly.'\n : (apex.lastError ?? 'Apex is not ready.')\n );\n }\n }\n}\n\n/** Thrown by paid-write operations while the target apex is not yet ready. */\nexport class NotReadyError extends Error {\n readonly retryable = true;\n constructor(message: string) {\n super(message);\n this.name = 'NotReadyError';\n }\n}\n\n/** Thrown when the relay/connector rejects a paid write. */\nexport class PublishRejectedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PublishRejectedError';\n }\n}\n\n/** Thrown for invalid target add/remove/select operations (maps to HTTP 400/404). */\nexport class TargetError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TargetError';\n }\n}\n\n/** Thrown when a model-authored publish/upload payload fails validation (HTTP 400). */\nexport class InvalidPayloadError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidPayloadError';\n }\n}\n\n/**\n * Thrown when the on-chain balance read stalls past its per-call provider\n * timeout (after the bounded retry). Retryable, and explicitly attributed to the\n * balances handler / chain provider — NOT the relay/apex — so the user-facing\n * message names the real failing subsystem (#199). Maps to HTTP 504.\n */\nexport class BalancesUnavailableError extends Error {\n readonly retryable = true;\n /** The underlying provider error message, when one was captured. */\n readonly providerError?: string;\n constructor(message: string, providerError?: string) {\n super(message);\n this.name = 'BalancesUnavailableError';\n if (providerError !== undefined) this.providerError = providerError;\n }\n}\n\n/**\n * Upload price per git-object body byte, micro-USDC. Matches the network\n * default `basePricePerByte` (10) the ToonClient prices writes with and the\n * seed pipeline bids (`bytes × 10`); the bid tag, the signed claim, and the\n * ILP amount all use this same figure so the pre-push estimate is exactly\n * what a push pays.\n */\nconst UPLOAD_FEE_PER_BYTE = 10n;\n\n/** NIP-34 status kinds by wire value (`GitStatusRequest.status`). */\nconst STATUS_KIND_BY_VALUE: Record<string, StatusKind> = {\n open: STATUS_OPEN_KIND,\n applied: STATUS_APPLIED_KIND,\n closed: STATUS_CLOSED_KIND,\n draft: STATUS_DRAFT_KIND,\n};\n\n/** Validate that `repoPath` names an existing directory (a git repo check\n * proper happens on first plumbing call — a non-repo dir surfaces as a\n * GitError the routes map to 400). */\nasync function assertRepoPath(repoPath: unknown): Promise<void> {\n if (typeof repoPath !== 'string' || repoPath === '') {\n throw new InvalidPayloadError('repoPath is required.');\n }\n let stats;\n try {\n stats = await stat(resolve(repoPath));\n } catch {\n throw new InvalidPayloadError(`repoPath does not exist: ${repoPath}`);\n }\n if (!stats.isDirectory()) {\n throw new InvalidPayloadError(`repoPath is not a directory: ${repoPath}`);\n }\n}\n\nfunction assertNonEmptyString(value: unknown, what: string): void {\n if (typeof value !== 'string' || value === '') {\n throw new InvalidPayloadError(`${what} is required.`);\n }\n}\n\n/** Validate a NIP-34 repo address (owner pubkey + repo id). */\nfunction validateRepoAddr(addr: GitRepoAddr | undefined): GitRepoAddr {\n if (\n !addr ||\n typeof addr.ownerPubkey !== 'string' ||\n !/^[0-9a-f]{64}$/.test(addr.ownerPubkey)\n ) {\n throw new InvalidPayloadError(\n 'repoAddr.ownerPubkey must be a 64-char lowercase hex Nostr pubkey.'\n );\n }\n if (typeof addr.repoId !== 'string' || addr.repoId === '') {\n throw new InvalidPayloadError('repoAddr.repoId is required.');\n }\n return addr;\n}\n\n/** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */\nfunction serializePushPlan(plan: PushPlan): GitEstimateResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n newRefs: plan.newRefs,\n headSymref: plan.headSymref,\n objects: plan.objects,\n knownShaToTxId: Object.fromEntries(plan.knownShaToTxId),\n announceNeeded: plan.announceNeeded,\n announcement: plan.announcement,\n estimate: serializeFeeEstimate(plan),\n };\n}\n\nfunction serializeFeeEstimate(plan: PushPlan): GitFeeEstimate {\n return {\n objectCount: plan.estimate.objectCount,\n totalObjectBytes: plan.estimate.totalObjectBytes,\n uploadFee: plan.estimate.uploadFee.toString(),\n eventCount: plan.estimate.eventCount,\n eventFees: plan.estimate.eventFees.toString(),\n totalFee: plan.estimate.totalFee.toString(),\n };\n}\n\n/** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */\nfunction serializePushResult(\n plan: PushPlan,\n result: PushResult\n): GitPushResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n uploads: result.uploads.map((u) => ({\n sha: u.sha,\n txId: u.txId,\n feePaid: u.feePaid.toString(),\n skipped: u.skipped,\n })),\n announceReceipt: result.announceReceipt\n ? {\n eventId: result.announceReceipt.eventId,\n feePaid: result.announceReceipt.feePaid.toString(),\n }\n : null,\n refsReceipt: {\n eventId: result.refsReceipt.eventId,\n feePaid: result.refsReceipt.feePaid.toString(),\n },\n arweaveMap: Object.fromEntries(result.arweaveMap),\n totalFeePaid: result.totalFeePaid.toString(),\n estimate: serializeFeeEstimate(plan),\n };\n}\n\n/** Current time in whole seconds (Nostr `created_at` unit). */\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** Resolve after `ms` (bounded wait for relay delivery in `query`). */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Race a promise against a timeout, rejecting with `message` if it does not\n * settle in `ms`. The underlying work is NOT cancelled (it may complete in the\n * background) — this just bounds how long the caller waits, so a stalled chain\n * RPC fast-fails instead of blocking the control request (#199).\n */\nfunction withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n message: string\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(message)), ms);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n });\n}\n\n/** NIP-01 filter match (kinds/authors/ids/since/until + `#<letter>` tag filters). */\nfunction matchesFilter(event: NostrEvent, filter: NostrFilter): boolean {\n if (filter.ids && !filter.ids.includes(event.id)) return false;\n if (filter.kinds && !filter.kinds.includes(event.kind)) return false;\n if (filter.authors && !filter.authors.includes(event.pubkey)) return false;\n if (filter.since !== undefined && event.created_at < filter.since)\n return false;\n if (filter.until !== undefined && event.created_at > filter.until)\n return false;\n for (const [key, values] of Object.entries(filter)) {\n if (!key.startsWith('#') || !Array.isArray(values)) continue;\n const letter = key.slice(1);\n const hit = event.tags.some(\n (t) => t[0] === letter && t[1] !== undefined && values.includes(t[1])\n );\n if (!hit) return false;\n }\n return true;\n}\n\n/** Validate that `raw` is an array of string arrays, returning it typed. */\nfunction normalizeTags(raw: unknown): string[][] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw))\n throw new InvalidPayloadError('tags must be an array.');\n return raw.map((tag, i) => {\n if (!Array.isArray(tag) || !tag.every((x) => typeof x === 'string')) {\n throw new InvalidPayloadError(`tags[${i}] must be an array of strings.`);\n }\n return tag as string[];\n });\n}\n\n/** Append `additions` to `base`, de-duping whole tags (for replaceable merges). */\nfunction mergeTags(base: string[][], additions: string[][]): string[][] {\n const seen = new Set(base.map((t) => JSON.stringify(t)));\n const out = [...base];\n for (const tag of additions) {\n const key = JSON.stringify(tag);\n if (!seen.has(key)) {\n seen.add(key);\n out.push(tag);\n }\n }\n return out;\n}\n\nfunction safe<T>(fn: () => T): T | undefined {\n try {\n return fn();\n } catch {\n return undefined;\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** Filesystem-safe slug for a per-apex channel-store filename. */\nfunction sanitize(s: string): string {\n return s\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n}\n\n/**\n * Cap on per-packet outcomes echoed on a `SwapResponse`. Adaptive sizing can\n * schedule an unbounded packet count (δ_min defaults to 1 micro-unit); the\n * cumulative totals stay exact, only the per-packet echo is truncated.\n */\nconst SWAP_PACKETS_RESPONSE_LIMIT = 500;\n\n/**\n * Derive the hard floor from the advertised rate (#351, rolling-swap spec §5:\n * `minExchangeRate = R₀ × (1 − tolerance)`): exact decimal-string\n * `rate × (10000 − floorBps) / 10000` — no float round-trip, so the floor is\n * bit-stable for the sdk's BigInt comparison. Returns `undefined` when no\n * tolerance is configured. Exported for tests.\n */\nexport function deriveFloorRate(\n rate: string,\n floorBps: number | undefined\n): string | undefined {\n if (floorBps === undefined) return undefined;\n if (!Number.isInteger(floorBps) || floorBps < 0 || floorBps >= 10_000) {\n throw new InvalidPayloadError(\n `floorBps must be an integer in [0, 10000), got ${String(floorBps)}.`\n );\n }\n const m = /^(\\d+)(?:\\.(\\d+))?$/.exec(rate.trim());\n if (!m) {\n throw new InvalidPayloadError(\n `pair.rate \"${rate}\" is not a plain positive decimal — cannot derive ` +\n 'a floor from floorBps; pass minExchangeRate explicitly.'\n );\n }\n const [, intDigits = '', fracDigits = ''] = m;\n const digits = intDigits + fracDigits;\n const scale = fracDigits.length + 4; // ×(10000−bps) adds 4 decimal places\n const scaled = (BigInt(digits) * BigInt(10_000 - floorBps))\n .toString()\n .padStart(scale + 1, '0');\n const intPart = scaled.slice(0, -scale);\n const fracPart = scaled.slice(-scale).replace(/0+$/, '');\n return fracPart ? `${intPart}.${fracPart}` : intPart;\n}\n\n/**\n * Realized-rate summary: delivered/spent in WHOLE units, adjusted for the\n * pair's asset scales (display-only `number`, same convention as the sdk's\n * `PacketProgress.effectiveRate`). `undefined` when nothing was filled.\n */\nfunction computeRealizedRate(\n cumulativeSource: bigint,\n cumulativeTarget: bigint,\n pair: SwapRequest['pair']\n): number | undefined {\n if (cumulativeSource <= 0n) return undefined;\n return (\n (Number(cumulativeTarget) / Number(cumulativeSource)) *\n 10 ** (pair.from.assetScale - pair.to.assetScale)\n );\n}\n\n/** Map a persisted received-claim entry onto the wire shape (#352). */\nfunction toReceivedClaimInfo(e: ReceivedClaimEntry): ReceivedClaimInfo {\n return {\n chain: e.chain,\n channelId: e.channelId,\n nonce: e.nonce.toString(),\n cumulativeAmount: e.cumulativeAmount.toString(),\n recipient: e.recipient,\n swapSignerAddress: e.swapSignerAddress,\n receivedAt: e.receivedAt,\n updatedAt: e.updatedAt,\n ...(e.settledAt !== undefined ? { settledAt: e.settledAt } : {}),\n ...(e.settledNonce !== undefined\n ? { settledNonce: e.settledNonce.toString() }\n : {}),\n ...(e.settleTxHash !== undefined ? { settleTxHash: e.settleTxHash } : {}),\n };\n}\n","/**\n * NIP-34: Git Stuff\n * https://github.com/nostr-protocol/nips/blob/master/34.md\n *\n * Event kinds for decentralized code collaboration via Nostr.\n */\n\n/**\n * Repository Announcement (kind 30617)\n * Replaceable event announcing a Git repository's existence.\n * Contains clone URLs, maintainers, and metadata.\n */\nexport const REPOSITORY_ANNOUNCEMENT_KIND = 30617;\n\n/**\n * Patch (kind 1617)\n * Direct patch submission for files under 60KB.\n * Contains git format-patch output and commit metadata.\n */\nexport const PATCH_KIND = 1617;\n\n/**\n * Pull Request (kind 1618)\n * Larger submissions or branch-based changes.\n * References remote repository and commit range.\n */\nexport const PULL_REQUEST_KIND = 1618;\n\n/**\n * Pull Request Status Update (kind 1619)\n * Updates to existing pull requests.\n */\nexport const PR_STATUS_UPDATE_KIND = 1619;\n\n/**\n * Issue (kind 1621)\n * Bug reports and feature requests in Markdown format.\n */\nexport const ISSUE_KIND = 1621;\n\n/**\n * Status: Open (kind 1630)\n */\nexport const STATUS_OPEN_KIND = 1630;\n\n/**\n * Status: Applied/Merged (kind 1631)\n */\nexport const STATUS_APPLIED_KIND = 1631;\n\n/**\n * Status: Closed (kind 1632)\n */\nexport const STATUS_CLOSED_KIND = 1632;\n\n/**\n * Status: Draft (kind 1633)\n */\nexport const STATUS_DRAFT_KIND = 1633;\n\n/**\n * All NIP-34 event kinds\n */\nexport const NIP34_EVENT_KINDS = [\n REPOSITORY_ANNOUNCEMENT_KIND,\n PATCH_KIND,\n PULL_REQUEST_KIND,\n PR_STATUS_UPDATE_KIND,\n ISSUE_KIND,\n STATUS_OPEN_KIND,\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n] as const;\n\n/**\n * Check if an event kind is a NIP-34 event\n */\nexport function isNIP34Event(kind: number): boolean {\n return (NIP34_EVENT_KINDS as readonly number[]).includes(kind);\n}\n","import type { Event as NostrEvent } from 'nostr-tools/pure';\n\n/**\n * Helper to get tag value by name\n */\nexport function getTag(event: NostrEvent, tagName: string): string | undefined {\n return event.tags.find((t) => t[0] === tagName)?.[1];\n}\n\n/**\n * Helper to get all tag values by name\n */\nexport function getTags(event: NostrEvent, tagName: string): string[] {\n return event.tags\n .filter((t) => t[0] === tagName)\n .map((t) => t[1])\n .filter((v): v is string => v !== undefined);\n}\n\n/**\n * Repository Announcement Event (Kind 30617)\n */\nexport interface RepositoryAnnouncement extends NostrEvent {\n kind: 30617;\n tags: (\n | ['d', string] // repository identifier\n | ['name', string] // human-readable name\n | ['description', string] // repository description\n | ['web', string] // browsing URL\n | ['clone', string] // clone URL\n | ['relays', ...string[]] // preferred relays\n | ['r', string, 'euc'] // earliest unique commit\n | ['maintainers', ...string[]]\n )[];\n}\n\n/**\n * Patch Event (Kind 1617)\n */\nexport interface PatchEvent extends NostrEvent {\n kind: 1617;\n content: string; // git format-patch output\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['r', string] // earliest unique commit\n | ['p', string] // repository owner pubkey\n | ['commit', string] // current commit SHA\n | ['parent-commit', string] // parent commit SHA\n | ['commit-pgp-sig', string] // optional PGP signature\n | ['committer', string] // optional committer info\n | ['t', 'root' | 'reply']\n )[];\n}\n\n/**\n * Pull Request Event (Kind 1618)\n */\nexport interface PullRequestEvent extends NostrEvent {\n kind: 1618;\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['r', string] // earliest unique commit\n | ['p', string] // repository owner pubkey\n | ['clone', string] // contributor's clone URL\n | ['c', string] // commit tip SHA\n | ['merge-base', string] // merge base commit\n | ['subject', string] // PR title\n | ['t', 'root' | 'reply']\n )[];\n}\n\n/**\n * Issue Event (Kind 1621)\n */\nexport interface IssueEvent extends NostrEvent {\n kind: 1621;\n content: string; // Markdown issue body\n tags: (\n | ['a', string] // repository reference (30617:pubkey:repo-id)\n | ['p', string] // repository owner pubkey\n | ['subject', string] // issue title\n | ['t', string]\n )[];\n}\n\n/**\n * Status Event (Kinds 1630-1633)\n */\nexport interface StatusEvent extends NostrEvent {\n kind: 1630 | 1631 | 1632 | 1633; // open, applied, closed, draft\n tags: (\n | ['e', string] // event being updated (patch, PR, issue)\n | ['p', string]\n )[];\n}\n\n/**\n * Union type of all NIP-34 events\n */\nexport type NIP34Event =\n | RepositoryAnnouncement\n | PatchEvent\n | PullRequestEvent\n | IssueEvent\n | StatusEvent;\n\n/**\n * Parse repository reference from 'a' tag\n * Format: \"30617:pubkey:repo-id\"\n */\nexport interface RepositoryReference {\n kind: 30617;\n pubkey: string;\n repoId: string;\n}\n\nexport function parseRepositoryReference(aTag: string): RepositoryReference {\n const parts = aTag.split(':');\n if (parts.length !== 3 || parts[0] !== '30617' || !parts[1] || !parts[2]) {\n throw new Error(`Invalid repository reference format: ${aTag}`);\n }\n return {\n kind: 30617,\n pubkey: parts[1],\n repoId: parts[2],\n };\n}\n\n/**\n * Extract commit message from git format-patch content\n */\nexport function extractCommitMessage(patchContent: string): string {\n const lines = patchContent.split('\\n');\n const subjectLine = lines.find((line) => line.startsWith('Subject:'));\n if (!subjectLine) {\n return 'Applied patch from Nostr';\n }\n // Remove \"Subject: [PATCH]\" prefix\n return subjectLine\n .replace(/^Subject:\\s*\\[PATCH\\]\\s*/, '')\n .replace(/^Subject:\\s*/, '');\n}\n","/**\n * Forgejo API Client\n *\n * Wrapper around Forgejo REST API for repository and issue management.\n * Uses fetch API for HTTP requests (compatible with gitea-js SDK structure).\n */\n\nexport interface ForgejoConfig {\n /** Base URL of Forgejo instance (e.g., \"http://forgejo:3000\") */\n baseUrl: string;\n /** API token for authentication */\n token: string;\n /** Default owner/organization for repositories */\n defaultOwner?: string;\n}\n\nexport interface CreateRepositoryOptions {\n name: string;\n description?: string;\n private?: boolean;\n auto_init?: boolean;\n default_branch?: string;\n}\n\nexport interface CreatePullRequestOptions {\n owner: string;\n repo: string;\n title: string;\n head: string; // source branch\n base: string; // target branch\n body?: string;\n}\n\nexport interface CreateIssueOptions {\n owner: string;\n repo: string;\n title: string;\n body?: string;\n labels?: number[]; // label IDs\n}\n\nexport interface ForgejoRepository {\n id: number;\n name: string;\n full_name: string;\n description: string;\n html_url: string;\n clone_url: string;\n ssh_url: string;\n}\n\nexport interface ForgejoPullRequest {\n id: number;\n number: number;\n title: string;\n html_url: string;\n state: 'open' | 'closed';\n}\n\nexport interface ForgejoIssue {\n id: number;\n number: number;\n title: string;\n html_url: string;\n state: 'open' | 'closed';\n}\n\nexport interface CreateOrUpdateFileOptions {\n owner: string;\n repo: string;\n filepath: string;\n content: string; // base64 encoded\n message: string;\n branch?: string;\n sha?: string; // required for updates\n}\n\nexport interface ForgejoFileResponse {\n content: {\n name: string;\n path: string;\n sha: string;\n };\n commit: {\n sha: string;\n };\n}\n\n/**\n * Forgejo API Client\n */\nexport class ForgejoClient {\n private baseUrl: string;\n private token: string;\n private defaultOwner?: string;\n\n constructor(config: ForgejoConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n this.token = config.token;\n this.defaultOwner = config.defaultOwner;\n }\n\n /**\n * Make an authenticated API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}/api/v1${path}`;\n const headers: Record<string, string> = {\n Authorization: `token ${this.token}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Forgejo API error (${response.status}): ${errorText}`);\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Create a new repository\n */\n async createRepository(\n options: CreateRepositoryOptions\n ): Promise<ForgejoRepository> {\n const owner = this.defaultOwner;\n if (!owner) {\n throw new Error('No default owner configured for repository creation');\n }\n\n return this.request<ForgejoRepository>('POST', '/user/repos', {\n name: options.name,\n description: options.description || '',\n private: options.private ?? false,\n auto_init: options.auto_init ?? true,\n default_branch: options.default_branch || 'main',\n });\n }\n\n /**\n * Create a pull request\n */\n async createPullRequest(\n options: CreatePullRequestOptions\n ): Promise<ForgejoPullRequest> {\n return this.request<ForgejoPullRequest>(\n 'POST',\n `/repos/${options.owner}/${options.repo}/pulls`,\n {\n title: options.title,\n head: options.head,\n base: options.base,\n body: options.body || '',\n }\n );\n }\n\n /**\n * Create an issue\n */\n async createIssue(options: CreateIssueOptions): Promise<ForgejoIssue> {\n return this.request<ForgejoIssue>(\n 'POST',\n `/repos/${options.owner}/${options.repo}/issues`,\n {\n title: options.title,\n body: options.body || '',\n labels: options.labels,\n }\n );\n }\n\n /**\n * Get repository information\n */\n async getRepository(owner: string, repo: string): Promise<ForgejoRepository> {\n return this.request<ForgejoRepository>('GET', `/repos/${owner}/${repo}`);\n }\n\n /**\n * Check if repository exists\n */\n async repositoryExists(owner: string, repo: string): Promise<boolean> {\n try {\n await this.getRepository(owner, repo);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get clone URL for internal use (within Docker network)\n */\n getInternalCloneUrl(owner: string, repo: string): string {\n // Use internal Docker network URL\n return `${this.baseUrl}/${owner}/${repo}.git`;\n }\n\n /**\n * Get clone URL for external use\n */\n getExternalCloneUrl(repo: ForgejoRepository): string {\n return repo.clone_url;\n }\n\n /**\n * Create or update a file in a repository\n */\n async createOrUpdateFile(\n options: CreateOrUpdateFileOptions\n ): Promise<ForgejoFileResponse> {\n const path = `/repos/${options.owner}/${options.repo}/contents/${options.filepath}`;\n const body: Record<string, unknown> = {\n content: options.content,\n message: options.message,\n };\n\n if (options.branch) {\n body['branch'] = options.branch;\n }\n if (options.sha) {\n body['sha'] = options.sha;\n }\n\n // Use PUT for updates (when SHA provided), POST for creates\n const method = options.sha ? 'PUT' : 'POST';\n return this.request<ForgejoFileResponse>(method, path, body);\n }\n\n /**\n * Create a new branch\n */\n async createBranch(\n owner: string,\n repo: string,\n branchName: string,\n fromBranch = 'main'\n ): Promise<void> {\n // Create the new branch from the source branch\n // Forgejo API: POST /repos/{owner}/{repo}/branches\n await this.request('POST', `/repos/${owner}/${repo}/branches`, {\n new_branch_name: branchName,\n old_branch_name: fromBranch,\n });\n }\n\n /**\n * Get file content from repository\n */\n async getFileContent(\n owner: string,\n repo: string,\n filepath: string,\n branch?: string\n ): Promise<{ content: string; sha: string } | null> {\n try {\n const path = `/repos/${owner}/${repo}/contents/${filepath}${branch ? `?ref=${branch}` : ''}`;\n const response = await this.request<{\n content: string;\n sha: string;\n }>('GET', path);\n return response;\n } catch {\n return null;\n }\n }\n}\n","/**\n * NIP-34 Handler\n *\n * Processes NIP-34 events (Git stuff on Nostr) and executes corresponding\n * Git operations on a Forgejo instance.\n *\n * Flow:\n * 1. TOON receives NIP-34 event via ILP payment\n * 2. BLS validates payment and stores event\n * 3. BLS calls NIP34Handler.handleEvent()\n * 4. Handler maps event to Git operation\n * 5. Operation executes on Forgejo\n */\n\nimport type { Event as NostrEvent } from 'nostr-tools/pure';\nimport {\n ForgejoClient,\n type CreateRepositoryOptions,\n} from './ForgejoClient.js';\nimport {\n isNIP34Event,\n REPOSITORY_ANNOUNCEMENT_KIND,\n PATCH_KIND,\n PULL_REQUEST_KIND,\n ISSUE_KIND,\n} from './constants.js';\nimport {\n getTag,\n parseRepositoryReference,\n extractCommitMessage,\n type NIP34Event,\n} from './types.js';\n\nexport interface NIP34Config {\n /** Forgejo base URL (e.g., \"http://forgejo:3000\") */\n forgejoUrl: string;\n /** Forgejo API token */\n forgejoToken: string;\n /** Default owner/org for repositories */\n defaultOwner: string;\n /** Git commit identity configuration */\n gitConfig?: {\n userName: string;\n userEmail: string;\n };\n /** Enable verbose logging */\n verbose?: boolean;\n}\n\nexport interface HandleEventResult {\n success: boolean;\n operation:\n | 'repository'\n | 'patch'\n | 'pull_request'\n | 'issue'\n | 'status'\n | 'unsupported';\n message: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * NIP-34 Event Handler\n *\n * Maps Nostr events to Git operations on Forgejo.\n */\nexport class NIP34Handler {\n private forgejo: ForgejoClient;\n private verbose: boolean;\n private defaultOwner: string;\n\n constructor(config: NIP34Config) {\n this.forgejo = new ForgejoClient({\n baseUrl: config.forgejoUrl,\n token: config.forgejoToken,\n defaultOwner: config.defaultOwner,\n });\n\n this.defaultOwner = config.defaultOwner;\n this.verbose = config.verbose ?? false;\n }\n\n /**\n * Handle a NIP-34 event\n *\n * This is the main entry point called by the BLS after storing an event.\n */\n async handleEvent(event: NostrEvent): Promise<HandleEventResult> {\n // Check if this is a NIP-34 event\n if (!isNIP34Event(event.kind)) {\n return {\n success: false,\n operation: 'unsupported',\n message: `Not a NIP-34 event (kind ${event.kind})`,\n };\n }\n\n this.log(\n `Handling NIP-34 event: kind=${event.kind} id=${event.id.substring(0, 8)}`\n );\n\n try {\n switch (event.kind) {\n case REPOSITORY_ANNOUNCEMENT_KIND:\n return await this.handleRepositoryAnnouncement(event as NIP34Event);\n\n case PATCH_KIND:\n return await this.handlePatch(event as NIP34Event);\n\n case PULL_REQUEST_KIND:\n return await this.handlePullRequest(event as NIP34Event);\n\n case ISSUE_KIND:\n return await this.handleIssue(event as NIP34Event);\n\n default:\n // Status events (1630-1633) - not yet implemented\n return {\n success: true,\n operation: 'status',\n message: `Status event kind ${event.kind} received (not yet implemented)`,\n };\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.log(`Error handling event: ${message}`);\n return {\n success: false,\n operation: this.getOperationType(event.kind),\n message: `Failed to process event: ${message}`,\n };\n }\n }\n\n /**\n * Handle Repository Announcement (kind 30617)\n *\n * Creates a new repository in Forgejo.\n */\n private async handleRepositoryAnnouncement(\n event: NIP34Event\n ): Promise<HandleEventResult> {\n const repoId = getTag(event, 'd');\n const name = getTag(event, 'name') || repoId;\n const description = getTag(event, 'description');\n\n if (!repoId) {\n return {\n success: false,\n operation: 'repository',\n message: 'Missing required \"d\" tag (repository identifier)',\n };\n }\n\n // Extract just the repository name (remove owner prefix if present)\n // e.g., \"admin/repo-name\" -> \"repo-name\"\n const repoName = repoId.includes('/')\n ? (repoId.split('/').pop() ?? repoId)\n : repoId;\n\n this.log(`Creating repository: ${repoName}`);\n\n const options: CreateRepositoryOptions = {\n name: repoName,\n description: description || name,\n private: false,\n auto_init: true,\n };\n\n const repo = await this.forgejo.createRepository(options);\n\n this.log(`Repository created: ${repo.html_url}`);\n\n return {\n success: true,\n operation: 'repository',\n message: `Repository \"${repoName}\" created`,\n metadata: {\n repoId: repoName,\n htmlUrl: repo.html_url,\n cloneUrl: repo.clone_url,\n },\n };\n }\n\n /**\n * Handle Patch (kind 1617)\n *\n * Applies a patch to a repository via Forgejo API and creates a pull request.\n */\n private async handlePatch(event: NIP34Event): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const patchContent = event.content;\n\n if (!aTag) {\n return {\n success: false,\n operation: 'patch',\n message: 'Missing required \"a\" tag (repository reference)',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n // Check if repository exists\n const exists = await this.forgejo.repositoryExists(owner, repoName);\n if (!exists) {\n return {\n success: false,\n operation: 'patch',\n message: `Repository ${owner}/${repoName} does not exist`,\n };\n }\n\n this.log(`Applying patch to ${owner}/${repoName}`);\n\n // Parse patch to extract file changes\n const patchInfo = this.parsePatch(patchContent);\n if (!patchInfo) {\n return {\n success: false,\n operation: 'patch',\n message: 'Failed to parse patch content',\n };\n }\n\n const patchBranch = `patch-${event.id.substring(0, 8)}`;\n const commitMessage = extractCommitMessage(patchContent);\n\n try {\n // Create a new branch via API\n await this.forgejo.createBranch(owner, repoName, patchBranch, 'main');\n\n // Apply each file change via API\n for (const file of patchInfo.files) {\n const content = Buffer.from(file.content).toString('base64');\n\n // Check if file exists on the branch to get its SHA for updates\n const existingFile = await this.forgejo.getFileContent(\n owner,\n repoName,\n file.path,\n patchBranch\n );\n\n await this.forgejo.createOrUpdateFile({\n owner,\n repo: repoName,\n filepath: file.path,\n content,\n message: commitMessage,\n branch: patchBranch,\n sha: existingFile?.sha, // Include SHA if file exists (for update)\n });\n }\n\n // Create pull request via API\n const pr = await this.forgejo.createPullRequest({\n owner,\n repo: repoName,\n title: commitMessage,\n head: patchBranch,\n base: 'main',\n body: `Patch from Nostr event: ${event.id}\\n\\nAuthor: ${event.pubkey}`,\n });\n\n this.log(`Pull request created: ${pr.html_url}`);\n\n return {\n success: true,\n operation: 'patch',\n message: `Patch applied and PR #${pr.number} created`,\n metadata: {\n branch: patchBranch,\n prNumber: pr.number,\n prUrl: pr.html_url,\n },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n operation: 'patch',\n message: `Failed to apply patch: ${message}`,\n };\n }\n }\n\n /**\n * Handle Pull Request (kind 1618)\n *\n * Creates an issue documenting the pull request (simplified approach without git).\n */\n private async handlePullRequest(\n event: NIP34Event\n ): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const cloneUrl = getTag(event, 'clone');\n const commitTip = getTag(event, 'c');\n const subject = getTag(event, 'subject');\n\n if (!aTag || !cloneUrl || !commitTip) {\n return {\n success: false,\n operation: 'pull_request',\n message: 'Missing required tags: a, clone, c',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n this.log(`Creating PR issue for ${owner}/${repoName} from ${cloneUrl}`);\n\n // Create an issue documenting the pull request\n const issueBody = `\n**Pull Request from Nostr**\n\nA pull request was submitted via NIP-34 event.\n\n**Details:**\n- Source Repository: ${cloneUrl}\n- Commit: ${commitTip}\n- Nostr Event: ${event.id}\n- Author: ${event.pubkey}\n\n**To apply this pull request:**\n1. Clone the source repository: \\`git clone ${cloneUrl}\\`\n2. Fetch the specific commit: \\`git fetch origin ${commitTip}\\`\n3. Create a branch: \\`git checkout -b pr-${event.id.substring(0, 8)} ${commitTip}\\`\n4. Push to this repository and create a PR manually\n\n${event.content}\n`;\n\n const issue = await this.forgejo.createIssue({\n owner,\n repo: repoName,\n title: subject || `Pull Request from ${event.pubkey.substring(0, 8)}`,\n body: issueBody,\n });\n\n this.log(`PR issue created: ${issue.html_url}`);\n\n return {\n success: true,\n operation: 'pull_request',\n message: `PR documented as issue #${issue.number}`,\n metadata: {\n issueNumber: issue.number,\n issueUrl: issue.html_url,\n },\n };\n }\n\n /**\n * Handle Issue (kind 1621)\n *\n * Creates an issue in Forgejo.\n */\n private async handleIssue(event: NIP34Event): Promise<HandleEventResult> {\n const aTag = getTag(event, 'a');\n const subject = getTag(event, 'subject');\n const body = event.content;\n\n if (!aTag || !subject) {\n return {\n success: false,\n operation: 'issue',\n message: 'Missing required tags: a, subject',\n };\n }\n\n const repoRef = parseRepositoryReference(aTag);\n const owner = this.defaultOwner;\n // Extract just the repository name (remove owner prefix if present)\n const repoName = repoRef.repoId.includes('/')\n ? (repoRef.repoId.split('/').pop() ?? repoRef.repoId)\n : repoRef.repoId;\n\n this.log(`Creating issue in ${owner}/${repoName}`);\n\n const issue = await this.forgejo.createIssue({\n owner,\n repo: repoName,\n title: subject,\n body: `${body}\\n\\n---\\nSubmitted via Nostr event: ${event.id}\\nAuthor: ${event.pubkey}`,\n });\n\n this.log(`Issue created: ${issue.html_url}`);\n\n return {\n success: true,\n operation: 'issue',\n message: `Issue #${issue.number} created`,\n metadata: {\n issueNumber: issue.number,\n issueUrl: issue.html_url,\n },\n };\n }\n\n /**\n * Get operation type from event kind\n */\n private getOperationType(\n kind: number\n ):\n | 'repository'\n | 'patch'\n | 'pull_request'\n | 'issue'\n | 'status'\n | 'unsupported' {\n switch (kind) {\n case REPOSITORY_ANNOUNCEMENT_KIND:\n return 'repository';\n case PATCH_KIND:\n return 'patch';\n case PULL_REQUEST_KIND:\n return 'pull_request';\n case ISSUE_KIND:\n return 'issue';\n case 1630:\n case 1631:\n case 1632:\n case 1633:\n return 'status';\n default:\n return 'unsupported';\n }\n }\n\n /**\n * Parse git format-patch output to extract file changes\n */\n private parsePatch(\n patchContent: string\n ): { files: { path: string; content: string }[] } | null {\n try {\n const files: { path: string; content: string }[] = [];\n\n // Simple parser for git format-patch\n // Look for diff --git lines to identify files\n const diffPattern = /diff --git a\\/(.*?) b\\/(.*?)$/gm;\n const matches = [...patchContent.matchAll(diffPattern)];\n\n if (matches.length === 0) {\n return null;\n }\n\n for (const match of matches) {\n const filePath = match[2]; // Use the \"b/\" path (new file)\n if (!filePath) continue; // Skip if no path found\n\n // Extract the patch content for this file\n // For simplicity, we'll extract everything after the diff header\n // In a full implementation, we'd properly parse hunks and apply them\n const fileStart = match.index ?? 0;\n const nextMatch = matches[matches.indexOf(match) + 1];\n const fileEnd = nextMatch?.index ?? patchContent.length;\n const filePatch = patchContent.substring(fileStart, fileEnd);\n\n // For now, extract content lines (lines starting with +)\n // This is a simplified approach - a full parser would handle hunks properly\n const contentLines = filePatch\n .split('\\n')\n .filter((line) => line.startsWith('+') && !line.startsWith('+++'))\n .map((line) => line.substring(1));\n\n if (contentLines.length > 0) {\n files.push({\n path: filePath,\n content: contentLines.join('\\n'),\n });\n }\n }\n\n return files.length > 0 ? { files } : null;\n } catch (error) {\n this.log(\n `Error parsing patch: ${error instanceof Error ? error.message : 'Unknown'}`\n );\n return null;\n }\n }\n\n /**\n * Log message if verbose mode is enabled\n */\n private log(message: string): void {\n if (this.verbose) {\n console.log(`[NIP34] ${message}`);\n }\n }\n}\n","/**\n * Pure git object construction and SHA-1 envelope hashing.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/git-builder.ts` (#223) —\n * the proven seed pipeline builders, now the core of the Git-to-TOON write\n * path. Everything here is pure: no network, no signing, no payments.\n * Upload/publish lives with the Publisher (#226).\n *\n * Git object format: `<type> <size>\\0<content>`. The SHA-1 is computed over\n * the full envelope (header + NUL + content); the `body` (content only) is\n * what gets uploaded to Arweave.\n */\n\nimport { createHash } from 'node:crypto';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** All git object types TOON can carry. */\nexport type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';\n\nexport interface GitObject {\n /** SHA-1 hex digest computed over full git envelope */\n sha: string;\n /** Full git object (header + null + content) */\n buffer: Buffer;\n /** Body only (content after the null byte) — this is what gets uploaded */\n body: Buffer;\n}\n\n/**\n * Maximum uploadable git object body size: 95KB safety margin under the\n * 100KB free tier (R10-005). Larger objects are a hard error in v1.\n */\nexport const MAX_OBJECT_SIZE = 95 * 1024;\n\n/**\n * Git's universal empty-blob SHA-1: the object for a zero-byte file.\n *\n * It is the ONLY zero-byte object git can produce (trees, commits, and tags\n * are never empty) and it always has this exact constant value. The store\n * rejects a zero-byte kind:5094 blob upload as malformed (F00), so `rig` never\n * uploads this object — it is skipped on push and synthesized locally on\n * clone/fetch (both keyed off an ACTUAL zero-length body, never a heuristic).\n */\nexport const EMPTY_BLOB_SHA = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';\n\n// ---------------------------------------------------------------------------\n// Envelope hashing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a raw object body in the git envelope (`<type> <size>\\0`) and compute\n * its SHA-1. This is exactly what `git hash-object -t <type>` does.\n */\nexport function hashGitObject(type: GitObjectType, body: Buffer): GitObject {\n const header = Buffer.from(`${type} ${body.length}\\0`);\n const fullObject = Buffer.concat([header, body]);\n const sha = createHash('sha1').update(fullObject).digest('hex');\n return { sha, buffer: fullObject, body };\n}\n\n// ---------------------------------------------------------------------------\n// Git object construction\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a git blob object and compute its SHA-1.\n *\n * Format: blob <size>\\0<content>\n * SHA is over the full envelope; body is content only (for upload).\n */\nexport function createGitBlob(content: string): GitObject {\n return hashGitObject('blob', Buffer.from(content, 'utf-8'));\n}\n\n/**\n * Construct a git tree object from sorted entries.\n *\n * Format: tree <size>\\0<entries>\n * Each entry: <mode> <name>\\0<20-byte-raw-sha1>\n * Entries MUST be sorted by name (byte-wise).\n */\nexport function createGitTree(\n entries: { mode: string; name: string; sha: string }[]\n): GitObject {\n // Git sorts tree entries by raw byte order (NOT locale-aware)\n const sorted = [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0\n );\n\n const entryBuffers: Buffer[] = [];\n for (const entry of sorted) {\n const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\\0`);\n // Raw 20-byte SHA-1 (NOT hex)\n const rawSha = Buffer.from(entry.sha, 'hex');\n entryBuffers.push(Buffer.concat([modeAndName, rawSha]));\n }\n\n return hashGitObject('tree', Buffer.concat(entryBuffers));\n}\n\n/**\n * Construct a git commit object.\n *\n * Format: commit <size>\\0tree <tree-sha>\\n[parent ...]\\nauthor ...\\ncommitter ...\\n\\n<message>\n * Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.\n */\nexport function createGitCommit(opts: {\n treeSha: string;\n parentSha?: string;\n authorName: string;\n authorPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `tree ${opts.treeSha}`,\n ...(opts.parentSha ? [`parent ${opts.parentSha}`] : []),\n `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('commit', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n\n/**\n * Construct an annotated git tag object.\n *\n * Format: tag <size>\\0object <sha>\\ntype <type>\\ntag <name>\\ntagger ...\\n\\n<message>\n * The tagged object is usually a commit, but git allows tagging any object\n * type (including another tag).\n */\nexport function createGitTag(opts: {\n /** SHA-1 of the object being tagged (hex, 40 chars) */\n objectSha: string;\n /** Type of the tagged object (usually 'commit') */\n objectType: GitObjectType;\n /** Tag name, e.g. 'v1.0.0' */\n tagName: string;\n taggerName: string;\n taggerPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `object ${opts.objectSha}`,\n `type ${opts.objectType}`,\n `tag ${opts.tagName}`,\n `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('tag', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n","/**\n * Publisher — the paid-write seam between push planning (this package) and\n * the two transport implementations that follow it in epic #222:\n *\n * 1. daemon (#227): client-mcp ControlClient → toon-clientd loopback\n * /git/* routes, backed by `ClientRunner.publish`/`uploadBlob` (the\n * production paid-publish path — apex channel, signBalanceProof, flat\n * per-event fee).\n * 2. standalone (#228): an embedded ToonClient constructed from a\n * mnemonic, uploading git objects as kind:5094 store writes with\n * Git-SHA/Git-Type/Repo tags (the proven seed-pipeline shape) and\n * publishing NIP-34 events through the relay.\n *\n * The interface is deliberately minimal so both fit:\n *\n * - `uploadGitObject` takes the raw object body (content only — no git\n * envelope header; the store re-derives the SHA envelope from the\n * Git-Type/Git-SHA tags) and returns the Arweave txId plus the fee paid.\n * - `publishEvent` takes an UNSIGNED event template — signing stays with\n * the implementation (the daemon-held key never leaves the daemon; the\n * standalone impl signs with its own keypair). Relay URLs are plural\n * from day one (forward-compat for parked #84/#92; size 1 today).\n * - `getFeeRates` exposes what `planPush` needs for the pre-push estimate:\n * a per-byte upload rate (seed pipeline bids bytes × rate) and a flat\n * per-event publish fee (the daemon charges `apex.feePerEvent` regardless\n * of event size).\n *\n * All fees are bigint in the smallest asset unit (matches `@toon-protocol/client`\n * channel math); HTTP transports serialize them as strings and convert at\n * the boundary.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { GitObjectType } from './objects.js';\n\n/** A git object queued for upload to Arweave via the paid store path. */\nexport interface GitObjectUpload {\n /** Full 40-hex SHA-1 (over the git envelope — see `hashGitObject`). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no `<type> <size>\\0` header). */\n body: Buffer;\n /** Repository identifier — becomes the `Repo` tag on the store write. */\n repoId: string;\n /**\n * Path the blob was reached by in the tree (#368), if known. Its extension\n * derives the `Content-Type` sent in the store write's `output` tag so a\n * gateway serves the blob as its real media type instead of\n * `application/octet-stream`. Absent (or a non-blob object) → octet-stream.\n */\n path?: string;\n}\n\n/**\n * A NON-git blob queued for upload to Arweave with an explicit `Content-Type`\n * (#368): the ar.io path manifest that turns a pushed repo into a permaweb\n * site. Unlike {@link GitObjectUpload} it carries no `Git-SHA`/`Git-Type`\n * tags, so the store stores the raw bytes verbatim (no git-envelope\n * re-derivation) — exactly what a manifest needs.\n */\nexport interface BlobUpload {\n /** Raw bytes to store. */\n body: Buffer;\n /** MIME type sent in the store write's `output` tag. */\n contentType: string;\n /** Optional repository identifier for provenance (`Repo` tag). */\n repoId?: string;\n}\n\n/** Receipt for one uploaded git object. */\nexport interface UploadReceipt {\n /** Arweave transaction ID the object is retrievable under. */\n txId: string;\n /** Fee paid for this upload, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Receipt for one published Nostr event. */\nexport interface PublishReceipt {\n /** Event ID as accepted by the relay(s). */\n eventId: string;\n /** Fee paid for this publish, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Fee rates used by `planPush` for the pre-push estimate. */\nexport interface FeeRates {\n /** Upload cost per body byte (smallest asset unit). */\n uploadFeePerByte: bigint;\n /**\n * Flat cost per published event (smallest asset unit). Implementations\n * already fold any per-packet route-price floor into this flat value, so\n * estimates using it match the claims actually signed.\n */\n eventFee: bigint;\n /**\n * FLAT minimum per upload claim (smallest asset unit): the store\n * destination's announced route price. The connector gates every paid\n * packet at the destination route's price — a balance-proof claim\n * advancing the channel by less is rejected (F06) — so each per-upload fee\n * is `max(bytes × uploadFeePerByte, minUploadFee)`. Absent: no floor\n * (pre-floor behavior, e.g. when the peer announces no capability prices).\n */\n minUploadFee?: bigint;\n}\n\n/**\n * Per-upload fee: `bytes × ratePerByte`, floored at `minFee` (the\n * destination's announced flat route price — see\n * {@link FeeRates.minUploadFee}). The single shared implementation keeps\n * every estimate site equal to what the publisher actually claims.\n */\nexport function flooredUploadFee(\n bytes: number,\n ratePerByte: bigint,\n minFee?: bigint\n): bigint {\n const fee = BigInt(bytes) * ratePerByte;\n const min = minFee ?? 0n;\n return fee > min ? fee : min;\n}\n\n/**\n * Paid transport for `executePush` — implemented by the daemon route\n * handlers (#227) and the standalone embedded client (#228).\n *\n * Implementations must be safe to call sequentially (uploads are issued one\n * at a time in dependency-safe order) and should throw on any failure —\n * `executePush` does not retry; a crashed push is resumed by re-planning\n * (content-addressed uploads make the retry idempotent).\n */\nexport interface Publisher {\n /** Current fee rates for estimation (may be queried before every plan). */\n getFeeRates(): Promise<FeeRates>;\n /** Upload one git object body to Arweave; paid. */\n uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;\n /**\n * Upload one raw blob (no git envelope) with an explicit `Content-Type`;\n * paid. Used by `rig site` (#368) for the ar.io path manifest. Optional so\n * transports that only move git objects (and pre-#368 test fakes) need not\n * implement it — `rig site` requires it and errors clearly when absent.\n */\n uploadBlob?(upload: BlobUpload): Promise<UploadReceipt>;\n /**\n * Sign (implementation-held key) and pay-to-publish one event to the\n * given relay(s).\n */\n publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt>;\n}\n","/**\n * Pure NIP-34 event builders for the Git-to-TOON write path.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/event-builders.ts` (#223).\n * All builders return UnsignedEvent — the caller signs with their keypair\n * via finalizeEvent() and publishes through a Publisher (#226). Tag\n * structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.\n */\n\nimport {\n ISSUE_KIND,\n PATCH_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport type {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n} from '@toon-protocol/core/nip34';\n\n// Kinds not (yet) exported by @toon-protocol/core/nip34:\n/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */\nexport const REPOSITORY_STATE_KIND = 30618;\n/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */\nexport const COMMENT_KIND = 1622;\n\n// ---------------------------------------------------------------------------\n// UnsignedEvent type (subset of nostr-tools — no id, sig, or pubkey)\n// ---------------------------------------------------------------------------\n\nexport interface UnsignedEvent {\n kind: number;\n content: string;\n tags: string[][];\n created_at: number;\n}\n\n// ---------------------------------------------------------------------------\n// kind:30617 — Repository Announcement (+ maintainer authority, #287)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared maintainers: one multi-valued tag\n * `[\"maintainers\", \"<hex-pubkey>\", \"<hex-pubkey>\", …]` on the kind:30617\n * announcement (mirrors the spec's multi-valued `relays` tag). The repo\n * OWNER — the announcement event's own pubkey — is ALWAYS an implicit\n * maintainer and need not be listed. Consumers derive an issue/PR's status\n * ONLY from kind:1630-1633 events signed by owner ∪ maintainers (#287): the\n * relay is permissionless, so this is the CONSUMER-side authority filter.\n */\nexport const MAINTAINERS_TAG = 'maintainers';\n\nconst HEX64 = /^[0-9a-f]{64}$/;\n\n/**\n * Collect the declared maintainer pubkeys (lowercased hex) from a kind:30617\n * event's tags. Tolerant of repeated `maintainers` tags and non-hex noise —\n * only 64-char hex values survive. Does NOT include the owner (implicit).\n */\nexport function parseMaintainers(tags: string[][]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== MAINTAINERS_TAG) continue;\n for (const value of tag.slice(1)) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n out.push(hex);\n }\n }\n }\n return out;\n}\n\n/**\n * The set of pubkeys whose kind:1630-1633 status events are authoritative for\n * a repo: the owner (always) ∪ the declared maintainers (from the 30617's\n * `maintainers` tag). All values are lowercased hex.\n */\nexport function authorizedStatusAuthors(\n ownerPubkey: string,\n repoAnnouncementTags: string[][]\n): Set<string> {\n return new Set([\n ownerPubkey.toLowerCase(),\n ...parseMaintainers(repoAnnouncementTags),\n ]);\n}\n\n/**\n * Build a kind:30617 repository announcement event.\n *\n * @param repoId - Repository identifier (d tag)\n * @param name - Human-readable repository name\n * @param description - Repository description\n * @param maintainers - Optional declared maintainer pubkeys (hex). Emitted as\n * a single `[\"maintainers\", …]` tag when non-empty; duplicate and non-64-hex\n * values are dropped. The owner (the signer) is an implicit maintainer and\n * need not be listed — if passed it is emitted, which is harmless since the\n * owner is authorized regardless. See {@link MAINTAINERS_TAG}.\n */\nexport function buildRepoAnnouncement(\n repoId: string,\n name: string,\n description: string,\n maintainers: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['d', repoId],\n ['name', name],\n ['description', description],\n ];\n const declared: string[] = [];\n const seen = new Set<string>();\n for (const value of maintainers) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n declared.push(hex);\n }\n }\n if (declared.length > 0) {\n tags.push([MAINTAINERS_TAG, ...declared]);\n }\n return {\n kind: REPOSITORY_ANNOUNCEMENT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:30618 — Repository Refs/State\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:30618 repository refs/state event.\n *\n * @param repoId - Repository identifier (d tag, matches kind:30617)\n * @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })\n * @param arweaveMap - Map of git SHAs to Arweave transaction IDs\n */\nexport function buildRepoRefs(\n repoId: string,\n refs: Record<string, string>,\n arweaveMap: Record<string, string> = {}\n): UnsignedEvent {\n const tags: string[][] = [['d', repoId]];\n\n // Add ref tags\n for (const [refPath, commitSha] of Object.entries(refs)) {\n tags.push(['r', refPath, commitSha]);\n }\n\n // Default HEAD to first ref (typically refs/heads/main)\n const firstRef = Object.keys(refs)[0];\n if (firstRef) {\n tags.push(['HEAD', `ref: ${firstRef}`]);\n }\n\n // Add arweave SHA-to-txId mapping tags\n for (const [sha, txId] of Object.entries(arweaveMap)) {\n tags.push(['arweave', sha, txId]);\n }\n\n return {\n kind: REPOSITORY_STATE_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1621 — Issue\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1621 issue event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Issue title (subject tag)\n * @param body - Issue body (Markdown content)\n * @param labels - Optional labels (t tags)\n */\nexport function buildIssue(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n body: string,\n labels: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ...labels.map((label) => ['t', label]),\n ];\n\n return {\n kind: ISSUE_KIND,\n content: body,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1622 — Comment (on issue or PR)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1622 comment event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param issueOrPrEventId - Event ID of the issue or PR being commented on\n * @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author\n * @param body - Comment body (Markdown content)\n * @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')\n */\nexport function buildComment(\n repoOwnerPubkey: string,\n repoId: string,\n issueOrPrEventId: string,\n authorPubkey: string,\n body: string,\n marker: 'root' | 'reply' = 'reply'\n): UnsignedEvent {\n return {\n kind: COMMENT_KIND,\n content: body,\n tags: [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['e', issueOrPrEventId, '', marker],\n ['p', authorPubkey],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1617 — Patch / PR\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1617 patch event.\n *\n * The PR body/description travels in a dedicated `description` tag, NEVER in\n * `content` (#280): `content` is real `git format-patch` output that readers\n * pipe straight into `git am`, and git's patch-format detection hard-fails on\n * any leading prose (verified: \"Patch format detection failed.\"). The tag\n * route keeps `git am` consumption intact while `rig pr show` and the\n * rig-web/views `parsePR` renderers surface the description.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Patch/PR title (subject tag)\n * @param commits - Array of { sha, parentSha } for commit and parent-commit tags\n * @param branchTag - Branch name for the t tag\n * @param content - Real `git format-patch` text (NIP-34 patch body); defaults\n * to '' for callers that only reference commits by tag\n * @param description - PR body/cover text (`description` tag) — kept out of\n * `content` so `git am` still applies it\n */\nexport function buildPatch(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n commits: { sha: string; parentSha: string }[],\n branchTag?: string,\n content = '',\n description?: string\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ];\n\n if (description !== undefined && description !== '') {\n tags.push(['description', description]);\n }\n\n for (const commit of commits) {\n tags.push(['commit', commit.sha]);\n tags.push(['parent-commit', commit.parentSha]);\n }\n\n if (branchTag) {\n tags.push(['t', branchTag]);\n }\n\n return {\n kind: PATCH_KIND,\n content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1630-1633 — Status\n// ---------------------------------------------------------------------------\n\n/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */\nexport type StatusKind =\n | typeof STATUS_OPEN_KIND\n | typeof STATUS_APPLIED_KIND\n | typeof STATUS_CLOSED_KIND\n | typeof STATUS_DRAFT_KIND;\n\n/**\n * Build a status event (kind 1630-1633).\n *\n * @param targetEventId - Event ID of the patch, PR, or issue being updated\n * @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)\n * @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)\n */\nexport function buildStatus(\n targetEventId: string,\n statusKind: StatusKind,\n targetPubkey?: string\n): UnsignedEvent {\n const tags: string[][] = [['e', targetEventId]];\n if (targetPubkey) {\n tags.push(['p', targetPubkey]);\n }\n return {\n kind: statusKind,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Remote repository state reader — the \"what does the remote have?\" half of\n * `rig push` (epic #222, ticket #225).\n *\n * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:\n * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,\n * `arweave` tags → git SHA → Arweave txId hints.\n * - kind:30617 (repository announcement): presence = the repo exists on\n * TOON (first-push detection) + name/description/relays metadata.\n *\n * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest\n * event per (kind, author, d) survives, but we still pick the newest across\n * relays (highest created_at, ties broken by lowest id per NIP-01).\n *\n * SHAs missing from the `arweave` tag map can be resolved via the shared\n * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module\n * the rig SPA uses) through {@link RemoteState.resolveMissing}.\n *\n * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):\n * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a\n * double-JSON-encoded string (devnet relay quirk: a JSON string containing\n * the event JSON), or as a TOON-encoded string. All three are tolerated.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport {\n resolveGitSha,\n seedShaCache,\n shaCacheKey,\n} from '@toon-protocol/arweave';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\n\nimport { REPOSITORY_STATE_KIND, parseMaintainers } from './nip34-events.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A signed Nostr event as seen on a relay (read side). */\nexport interface NostrEvent {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** NIP-01 subscription filter (only the fields rig's readers send). */\nexport interface NostrFilter {\n ids?: string[];\n kinds?: number[];\n authors?: string[];\n '#d'?: string[];\n /** Repo address tag filter, e.g. `30617:<owner>:<repoId>` (#278 tracker). */\n '#a'?: string[];\n /** Event-reference tag filter (#278 tracker: statuses + comments). */\n '#e'?: string[];\n limit?: number;\n}\n\n/**\n * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket\n * global (Node >= 22 / undici, browsers) and by the `ws` package.\n */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: string, listener: (event: never) => void): void;\n}\n\n/** Factory for WebSocket connections (injectable for tests / `ws` fallback). */\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport interface FetchRemoteStateOptions {\n /**\n * Relay WebSocket URLs to query. Plural from day one (forward-compat with\n * multi-relay routing); size 1 is typical today. Results are merged and the\n * newest replaceable event across all relays wins.\n */\n relayUrls: string[];\n /** Repository owner's pubkey (hex) — the author of 30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */\n timeoutMs?: number;\n /**\n * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.\n * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;\n * injectable for tests.\n */\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n /** WebSocket constructor override (defaults to the global WebSocket). */\n webSocketFactory?: WebSocketFactory;\n}\n\n/** Remote repository state assembled from relay events. */\nexport interface RemoteState {\n /** True when a kind:30617 announcement exists (false ⇒ first push). */\n announced: boolean;\n /** Ref map from the latest kind:30618: refname → commit SHA. */\n refs: Map<string, string>;\n /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */\n headSymref: string | null;\n /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */\n shaToTxId: Map<string, string>;\n /** The latest kind:30618 event, or null if the repo has no state yet. */\n refsEvent: NostrEvent | null;\n /** The latest kind:30617 event, or null if the repo is unannounced. */\n announceEvent: NostrEvent | null;\n /** Repository name from the announcement `name` tag. */\n name: string | null;\n /** Announcement `description` tag (falls back to event content). */\n description: string | null;\n /** Relay URLs advertised in the announcement `relays` tag(s). */\n relays: string[];\n /**\n * Declared maintainer pubkeys (hex) from the announcement `maintainers`\n * tag (#287). Does NOT include the owner (an implicit maintainer). Empty\n * when unannounced or owner-only.\n */\n maintainers: string[];\n /**\n * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when\n * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve\n * nowhere are omitted from the returned map.\n */\n resolveMissing(shas: string[]): Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// Relay query (NIP-01 REQ → EVENT* → EOSE)\n// ---------------------------------------------------------------------------\n\n/** Validate that a relay URL uses a WebSocket scheme (mirrors rig's url-utils). */\nfunction isValidRelayUrl(url: string): boolean {\n return /^wss?:\\/\\//i.test(url);\n}\n\n/** WHATWG WebSocket OPEN ready state. */\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string): WebSocketLike {\n const ctor = (\n globalThis as { WebSocket?: new (url: string) => WebSocketLike }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) — pass webSocketFactory'\n );\n }\n return new ctor(url);\n}\n\n/**\n * Decode a relay EVENT payload, tolerating every encoding seen in the wild:\n * inline object (standard NIP-01), double-JSON-encoded string (devnet relay\n * serves the event as a JSON string containing the event JSON), or a\n * TOON-encoded string (rig's `decodeToonMessage` path).\n */\nfunction decodeEventPayload(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object') {\n return payload as NostrEvent;\n }\n if (typeof payload !== 'string') {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object') {\n return parsed as NostrEvent;\n }\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/**\n * Query one relay: send a REQ, collect EVENTs until EOSE, then CLOSE.\n * Mirrors rig's `queryRelay` (partial results on timeout / early close).\n * Exported for reuse by the network-bootstrap discovery (kind:10032).\n */\nexport function queryRelay(\n relayUrl: string,\n filter: NostrFilter,\n timeoutMs: number,\n webSocketFactory: WebSocketFactory\n): Promise<NostrEvent[]> {\n return new Promise((resolve, reject) => {\n const events: NostrEvent[] = [];\n const subId = `git-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let ws: WebSocketLike;\n // eslint-disable-next-line prefer-const -- assigned after `settle` is defined\n let timeoutHandle: ReturnType<typeof setTimeout>;\n let settled = false;\n\n const settle = (outcome: 'resolve' | 'reject', error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n try {\n if (ws.readyState === WS_OPEN) {\n ws.send(JSON.stringify(['CLOSE', subId]));\n }\n ws.close();\n } catch {\n // Ignore close errors\n }\n if (outcome === 'reject') {\n reject(error);\n } else {\n resolve(events);\n }\n };\n\n if (!isValidRelayUrl(relayUrl)) {\n reject(\n new Error(\n `Invalid relay URL protocol (must be ws:// or wss://): ${relayUrl}`\n )\n );\n return;\n }\n\n try {\n ws = webSocketFactory(relayUrl);\n } catch (err) {\n reject(new Error(`Failed to connect to relay ${relayUrl}: ${String(err)}`));\n return;\n }\n\n timeoutHandle = setTimeout(() => {\n // Resolve with whatever we collected so far (partial results)\n settle('resolve');\n }, timeoutMs);\n\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.addEventListener('message', (msgEvent: { data?: unknown }) => {\n try {\n const msg = JSON.parse(String(msgEvent.data)) as unknown[];\n if (!Array.isArray(msg) || msg.length < 2) return;\n\n const msgType = msg[0];\n if (msgType === 'EVENT' && msg[1] === subId && msg[2] !== undefined) {\n const event = decodeEventPayload(msg[2]);\n if (event) events.push(event);\n } else if (msgType === 'EOSE' && msg[1] === subId) {\n settle('resolve');\n }\n } catch {\n // Ignore parse errors for individual messages\n }\n });\n\n ws.addEventListener('error', (event: { message?: unknown }) => {\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n settle(\n 'reject',\n new Error(`WebSocket error connecting to ${relayUrl}: ${detail}`)\n );\n });\n\n ws.addEventListener('close', () => {\n // If we haven't settled yet, resolve with what we have\n settle('resolve');\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// NIP-33 replaceable selection + tag parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Pick the winning replaceable event: highest created_at, ties broken by\n * lowest id (NIP-01 replaceable-event convention).\n */\nfunction latestReplaceable(events: NostrEvent[]): NostrEvent | null {\n let winner: NostrEvent | null = null;\n for (const event of events) {\n if (\n winner === null ||\n event.created_at > winner.created_at ||\n (event.created_at === winner.created_at && event.id < winner.id)\n ) {\n winner = event;\n }\n }\n return winner;\n}\n\n/** Get the first value for a tag name. */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n const tag = tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/** Maximum number of refs parsed from a single kind:30618 event (mirrors views). */\nconst MAX_REFS_PER_EVENT = 1000;\n\n/** Symref prefix used in `HEAD` tags: `[\"HEAD\", \"ref: refs/heads/main\"]`. */\nconst SYMREF_PREFIX = 'ref: ';\n\ninterface ParsedRefs {\n refs: Map<string, string>;\n headSymref: string | null;\n shaToTxId: Map<string, string>;\n}\n\n/** Parse a kind:30618 event's `r` / `HEAD` / `arweave` tags. */\nfunction parseRefsEvent(event: NostrEvent): ParsedRefs {\n const refs = new Map<string, string>();\n const shaToTxId = new Map<string, string>();\n let headSymref: string | null = null;\n\n for (const tag of event.tags) {\n const [tagName, v1, v2] = tag;\n if (tagName === 'r' && v1 && v2) {\n if (v1 === 'HEAD' && v2.startsWith(SYMREF_PREFIX)) {\n // Alternate symref spelling: [\"r\", \"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v2.slice(SYMREF_PREFIX.length);\n continue;\n }\n if (refs.size >= MAX_REFS_PER_EVENT) continue;\n refs.set(v1, v2);\n } else if (tagName === 'HEAD' && v1?.startsWith(SYMREF_PREFIX)) {\n // NIP-34 symref tag: [\"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v1.slice(SYMREF_PREFIX.length);\n } else if (tagName === 'arweave' && v1 && v2) {\n shaToTxId.set(v1, v2);\n }\n }\n\n return { refs, headSymref, shaToTxId };\n}\n\n// ---------------------------------------------------------------------------\n// fetchRemoteState\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the remote repository state from the relay(s).\n *\n * Sends one REQ per relay for kind:30617 + kind:30618 with\n * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),\n * and reduces to the latest replaceable event per kind.\n *\n * Resolves as long as at least one relay answers; throws only when every\n * relay fails. Events from other authors / repos are ignored (defense\n * against misbehaving relays that over-return).\n */\nexport async function fetchRemoteState(\n options: FetchRemoteStateOptions\n): Promise<RemoteState> {\n const {\n relayUrls,\n ownerPubkey,\n repoId,\n timeoutMs = 10000,\n resolveSha = resolveGitSha,\n webSocketFactory = defaultWebSocketFactory,\n } = options;\n\n if (relayUrls.length === 0) {\n throw new Error('fetchRemoteState: relayUrls must not be empty');\n }\n if (!ownerPubkey) {\n throw new Error('fetchRemoteState: ownerPubkey is required');\n }\n if (!repoId) {\n throw new Error('fetchRemoteState: repoId is required');\n }\n\n const filter: NostrFilter = {\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [ownerPubkey],\n '#d': [repoId],\n };\n\n const results = await Promise.allSettled(\n relayUrls.map((url) => queryRelay(url, filter, timeoutMs, webSocketFactory))\n );\n\n const failures: string[] = [];\n const byId = new Map<string, NostrEvent>();\n for (const result of results) {\n if (result.status === 'rejected') {\n failures.push(String((result.reason as Error)?.message ?? result.reason));\n continue;\n }\n for (const event of result.value) {\n // Only trust events from the repo owner for this repo — relays are\n // untrusted and may over-return.\n if (event.pubkey !== ownerPubkey) continue;\n if (getTagValue(event.tags, 'd') !== repoId) continue;\n if (typeof event.id === 'string' && !byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n }\n\n if (failures.length === relayUrls.length) {\n throw new Error(\n `fetchRemoteState: all ${relayUrls.length} relay(s) failed: ${failures.join('; ')}`\n );\n }\n\n const events = [...byId.values()];\n const refsEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_STATE_KIND)\n );\n const announceEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND)\n );\n\n const { refs, headSymref, shaToTxId } = refsEvent\n ? parseRefsEvent(refsEvent)\n : {\n refs: new Map<string, string>(),\n headSymref: null,\n shaToTxId: new Map<string, string>(),\n };\n\n // Seed the shared resolver cache so later resolveGitSha calls (here or in\n // any other consumer of @toon-protocol/arweave) skip GraphQL for known SHAs.\n if (shaToTxId.size > 0) {\n seedShaCache(\n [...shaToTxId].map(\n ([sha, txId]) => [shaCacheKey(sha, repoId), txId] as [string, string]\n )\n );\n }\n\n // Announcement metadata (mirrors views' parseRepoAnnouncement defaults).\n const name = announceEvent\n ? (getTagValue(announceEvent.tags, 'name') ?? null)\n : null;\n const description = announceEvent\n ? (getTagValue(announceEvent.tags, 'description') ?? announceEvent.content)\n : null;\n // NIP-34 announcement relays tag is multi-valued: [\"relays\", url1, url2, …]\n const relays: string[] = [];\n if (announceEvent) {\n for (const tag of announceEvent.tags) {\n if (tag[0] === 'relays') {\n relays.push(...tag.slice(1).filter((url) => url.length > 0));\n }\n }\n }\n // Declared maintainers (#287): the `maintainers` tag on the 30617. Owner is\n // an implicit maintainer and is NOT listed here.\n const maintainers = announceEvent\n ? parseMaintainers(announceEvent.tags)\n : [];\n\n const resolveMissing = async (\n shas: string[]\n ): Promise<Map<string, string>> => {\n const resolved = new Map<string, string>();\n const missing: string[] = [];\n for (const sha of new Set(shas)) {\n const known = shaToTxId.get(sha);\n if (known !== undefined) {\n resolved.set(sha, known);\n } else {\n missing.push(sha);\n }\n }\n const lookups = await Promise.all(\n missing.map(\n async (sha) => [sha, await resolveSha(sha, repoId)] as const\n )\n );\n for (const [sha, txId] of lookups) {\n if (txId) resolved.set(sha, txId);\n }\n return resolved;\n };\n\n return {\n announced: announceEvent !== null,\n refs,\n headSymref,\n shaToTxId,\n refsEvent,\n announceEvent,\n name,\n description,\n relays,\n maintainers,\n resolveMissing,\n };\n}\n","/**\n * Arweave gateway redundancy — single source of truth.\n *\n * Media bytes are content-addressed by Arweave tx id, so every gateway serves\n * the same bytes. This module owns the ordered gateway preference list and the\n * URL helpers used on BOTH sides of the wire:\n * - upload (client-mcp daemon): stamp a primary `url` + `fallback` mirrors.\n * - render (views/rig browser): re-point imeta URLs + fail over on error.\n *\n * Previously hand-duplicated in `views`, `rig`, and `client-mcp`; those now all\n * import from here. The default list can be overridden per call (e.g. from a\n * daemon env var) — pass `gateways` to the helpers.\n */\n\n/** Ordered Arweave gateways to try (primary first, then fallbacks). */\nexport const ARWEAVE_GATEWAYS = [\n 'https://ar-io.dev',\n 'https://arweave.net',\n 'https://permagate.io',\n] as const;\n\n/** Timeout for individual Arweave fetch requests in milliseconds. */\nexport const ARWEAVE_FETCH_TIMEOUT_MS = 15000;\n\n/** Arweave transaction IDs are 43-character base64url strings. */\nconst TX_ID_RE = /^[a-zA-Z0-9_-]{43}$/;\n\n/** Hosts recognized as Arweave gateways (path-addressed). */\nconst ARWEAVE_HOST_RE =\n /(^|\\.)(arweave\\.net|ar-io\\.dev|permagate\\.io|g8way\\.io|ar\\.io)$/i;\n\n/**\n * Extract an Arweave tx id from a media URL, or null if it is not\n * Arweave-addressable. Handles `ar://<txid>` and path-style\n * `https://<gateway>/<txid>`.\n *\n * Sandbox-subdomain URLs (`https://<txid>.<gateway>`) are deliberately NOT\n * decoded: tx ids are case-sensitive base64url, but `URL` (and DNS) lower-case\n * the hostname, which would corrupt the id. Real gateway sandboxing uses a\n * base32 label, not the raw id — the canonical id always travels in the path.\n */\nexport function arweaveTxId(rawUrl: string): string | null {\n const ar = /^ar:\\/\\/([a-zA-Z0-9_-]{43})(?:[/?#]|$)/.exec(rawUrl);\n if (ar?.[1]) return ar[1];\n\n let u: URL;\n try {\n u = new URL(rawUrl);\n } catch {\n return null;\n }\n // Only re-point hosts we actually recognize as Arweave gateways, so a stray\n // 43-char path segment on some other CDN is never misread as a tx id.\n if (!ARWEAVE_HOST_RE.test(u.hostname)) return null;\n\n // Path style: https://arweave.net/<txid>\n const seg = u.pathname.split('/').find(Boolean);\n if (seg && TX_ID_RE.test(seg)) return seg;\n\n return null;\n}\n\n/**\n * Primary URL + fallback mirror URLs for an Arweave tx id, one per gateway in\n * preference order. Used by the upload path to stamp `imeta` `url` + `fallback`.\n */\nexport function arweaveUrls(\n txId: string,\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): { url: string; fallbacks: string[] } {\n const all = (gateways.length ? gateways : ARWEAVE_GATEWAYS).map(\n (g) => `${g}/${txId}`\n );\n const [url, ...fallbacks] = all;\n return { url: url ?? `${ARWEAVE_GATEWAYS[0]}/${txId}`, fallbacks };\n}\n\n/**\n * Ordered candidate URLs for a media URL. Arweave-addressable URLs expand to the\n * full gateway-preference list (primary first); anything else is returned\n * unchanged. `extraFallbacks` (e.g. publisher-supplied `imeta` mirrors) are\n * appended last, de-duplicated. Used by the render path to fail over on error.\n */\nexport function arweaveGatewayCandidates(\n rawUrl: string,\n extraFallbacks: string[] = [],\n gateways: readonly string[] = ARWEAVE_GATEWAYS\n): string[] {\n const txId = arweaveTxId(rawUrl);\n const candidates = txId\n ? (gateways.length ? gateways : ARWEAVE_GATEWAYS).map((g) => `${g}/${txId}`)\n : [rawUrl];\n const seen = new Set(candidates);\n for (const f of extraFallbacks) {\n if (f && !seen.has(f)) {\n seen.add(f);\n candidates.push(f);\n }\n }\n return candidates;\n}\n","/**\n * Git-SHA → Arweave transaction ID resolution.\n *\n * Git objects uploaded to Arweave are tagged with `Git-SHA` (the object's\n * SHA-1) and `Repo` (the repository identifier, matching the NIP-34 `d` tag).\n * This module resolves a git SHA to its Arweave tx id via the Arweave GraphQL\n * gateway, with a bounded in-memory cache that can be pre-seeded from relay\n * state (kind:30618 `arweave` tags) to skip the GraphQL indexing delay.\n *\n * Extracted verbatim from rig's `web/arweave-client.ts` (#225) so the browser\n * SPA (rig-web) and the Node write path (@toon-protocol/rig) share ONE resolver.\n * Uses only WHATWG fetch + AbortSignal.timeout — browser and Node compatible.\n */\n\nimport { ARWEAVE_FETCH_TIMEOUT_MS } from './gateways.js';\n\n/** Arweave GraphQL endpoint used for Git-SHA tag lookups. */\nconst ARWEAVE_GRAPHQL_URL = 'https://arweave.net/graphql';\n\n/** Maximum number of entries in the SHA-to-txId cache to prevent unbounded memory growth. */\nconst SHA_CACHE_MAX_SIZE = 10000;\n\n/** In-memory cache for SHA-to-txId resolution. Bounded to prevent memory leaks. */\nconst shaToTxIdCache = new Map<string, string>();\n\n/**\n * Validate a git SHA-1 hash format (40-character hex string).\n */\nfunction isValidGitSha(sha: string): boolean {\n return /^[0-9a-f]{40}$/i.test(sha);\n}\n\n/**\n * Sanitize a string for safe inclusion in a GraphQL query.\n * Removes characters that could break out of a GraphQL string literal,\n * including backticks which some GraphQL parsers may interpret.\n */\nfunction sanitizeGraphQLValue(value: string): string {\n // eslint-disable-next-line no-control-regex -- intentional: strip control chars for GraphQL safety\n return value.replace(/[\"\\\\\\n\\r\\u0000-\\u001f`]/g, '');\n}\n\n/**\n * Build the cache key used by {@link resolveGitSha} / {@link seedShaCache}.\n *\n * The cache is keyed on `\"sha:repo\"` so the same SHA in different repos\n * resolves independently (uploads are tagged per-repo).\n */\nexport function shaCacheKey(sha: string, repo: string): string {\n return `${sha}:${repo}`;\n}\n\n/**\n * Clear the SHA-to-txId cache. Used for test isolation.\n */\nexport function clearShaCache(): void {\n shaToTxIdCache.clear();\n}\n\n/**\n * Pre-seed the SHA-to-txId cache with known mappings.\n *\n * Used when txId mappings are available from relay events (e.g., kind:30618\n * `arweave` tags) to avoid the GraphQL indexing delay after Turbo/Irys uploads.\n *\n * @param mappings - Map of \"sha:repo\" cache keys to Arweave transaction IDs\n */\nexport function seedShaCache(\n mappings: Map<string, string> | [string, string][]\n): void {\n const entries = mappings instanceof Map ? mappings.entries() : mappings;\n for (const [key, txId] of entries) {\n if (shaToTxIdCache.size >= SHA_CACHE_MAX_SIZE) {\n const firstKey = shaToTxIdCache.keys().next().value;\n if (firstKey !== undefined) {\n shaToTxIdCache.delete(firstKey);\n }\n }\n shaToTxIdCache.set(key, txId);\n }\n}\n\n/** Arweave transaction IDs are 43-character base64url strings. */\nconst ARWEAVE_TX_ID_RE = /^[a-zA-Z0-9_-]{43}$/;\n\n/**\n * Validate an Arweave transaction ID format.\n * Arweave tx IDs are 43-character base64url-encoded strings.\n */\nexport function isValidArweaveTxId(txId: string): boolean {\n return ARWEAVE_TX_ID_RE.test(txId);\n}\n\n/**\n * Resolve a git SHA to an Arweave transaction ID via GraphQL.\n *\n * Queries the Arweave GraphQL endpoint for transactions tagged with\n * the given Git-SHA and Repo values. Results are cached in-memory.\n *\n * @param sha - Git object SHA-1 hash (hex)\n * @param repo - Repository identifier (matches d tag)\n * @returns Arweave transaction ID, or null if not found\n */\nexport async function resolveGitSha(\n sha: string,\n repo: string\n): Promise<string | null> {\n // Validate SHA format to prevent injection of arbitrary strings into GraphQL\n if (!isValidGitSha(sha)) {\n return null;\n }\n\n const cacheKey = shaCacheKey(sha, repo);\n const cached = shaToTxIdCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const safeSha = sanitizeGraphQLValue(sha);\n const safeRepo = sanitizeGraphQLValue(repo);\n const query = `query {\n transactions(tags: [\n { name: \"Git-SHA\", values: [\"${safeSha}\"] },\n { name: \"Repo\", values: [\"${safeRepo}\"] }\n ]) {\n edges { node { id } }\n }\n}`;\n\n try {\n const response = await fetch(ARWEAVE_GRAPHQL_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query }),\n signal: AbortSignal.timeout(ARWEAVE_FETCH_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n return null;\n }\n\n const json = (await response.json()) as {\n data?: {\n transactions?: {\n edges?: { node?: { id?: string } }[];\n };\n };\n };\n\n const edges = json.data?.transactions?.edges;\n if (!edges || edges.length === 0) {\n return null;\n }\n\n const txId = edges[0]?.node?.id;\n if (!txId || !isValidArweaveTxId(txId)) {\n return null;\n }\n\n // Evict oldest entries if cache exceeds max size\n if (shaToTxIdCache.size >= SHA_CACHE_MAX_SIZE) {\n const firstKey = shaToTxIdCache.keys().next().value;\n if (firstKey !== undefined) {\n shaToTxIdCache.delete(firstKey);\n }\n }\n shaToTxIdCache.set(cacheKey, txId);\n return txId;\n } catch {\n return null;\n }\n}\n","/**\n * GitRepoReader — read a local repository via `execFile` git plumbing.\n *\n * Real git gives perfect fidelity (packfiles, delta chains, exotic history)\n * with zero new deps — no isomorphic-git. Everything here is read-only and\n * injection-safe:\n *\n * - child processes are spawned with `execFile`/`spawn` and argument\n * ARRAYS — never a shell, never string interpolation;\n * - every caller-supplied revision/range is validated against strict\n * regexes that (among other things) reject a leading `-`, so a value\n * like `--upload-pack=…` can never be parsed as an option;\n * - `--` terminators are appended where git supports them so nothing\n * user-supplied can be re-interpreted as a pathspec.\n *\n * Push planning/publishing live in follow-up tickets of epic\n * toon-client#222 — this module only reads.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { GitObjectType } from './objects.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Generous cap for plumbing stdout (rev-list on big repos, format-patch). */\nconst MAX_BUFFER = 256 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single ref from `git for-each-ref` (branches + tags). */\nexport interface GitRef {\n /** Full refname, e.g. `refs/heads/main` or `refs/tags/v1.0.0`. */\n refname: string;\n /**\n * SHA the ref points at. For annotated tags this is the TAG object's SHA\n * (the peeled commit is in {@link peeledSha}); for branches and\n * lightweight tags it is the commit SHA.\n */\n sha: string;\n /** Type of the referenced object: `commit`, or `tag` for annotated tags. */\n type: GitObjectType;\n /** For annotated tags: the peeled (target) object SHA. */\n peeledSha?: string;\n}\n\n/** Result of {@link GitRepoReader.listRefs}. */\nexport interface RepoRefs {\n /**\n * Full refname HEAD points at (e.g. `refs/heads/main`), or `undefined`\n * when HEAD is detached.\n */\n head?: string;\n refs: GitRef[];\n}\n\n/** One object streamed out of `git cat-file --batch`. */\nexport interface ReadGitObject {\n /** Full 40-hex SHA-1. */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** Result of {@link GitRepoReader.readObjects}. */\nexport interface ReadObjectsResult {\n /** Objects found, in input order (minus missing ones). */\n objects: ReadGitObject[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** One object from `rev-list --objects`: SHA plus the path it was reached by. */\nexport interface ObjectWithPath {\n /** Full 40-hex SHA-1. */\n sha: string;\n /**\n * Path the object was first reached by (blobs and non-root trees);\n * `undefined` for commits, root trees, and tag objects.\n */\n path?: string;\n}\n\n/** One object's metadata from `cat-file --batch-check`. */\nexport interface ObjectStat {\n sha: string;\n type: GitObjectType;\n /** Object body size in bytes (content only, no envelope header). */\n size: number;\n}\n\n/** Result of {@link GitRepoReader.statObjects}. */\nexport interface StatObjectsResult {\n /** Stats found, in input order (minus missing ones). */\n objects: ObjectStat[];\n /** Requested SHAs not present in the repository. */\n missing: string[];\n}\n\n/** Error from a git child process, carrying exit code and stderr. */\nexport class GitError extends Error {\n constructor(\n message: string,\n /** Process exit code (undefined when the process failed to spawn). */\n public readonly exitCode: number | undefined,\n /** Captured stderr, trimmed. */\n public readonly stderr: string\n ) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Argument validation (injection defense)\n// ---------------------------------------------------------------------------\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * One revision token: a SHA prefix (4–40 hex) or a refname-ish word with an\n * optional `^`/`~<n>` ancestry suffix. Must start with an alphanumeric, so a\n * leading `-` (option injection) is impossible; `@{…}`, whitespace, and other\n * revspec exotica are deliberately rejected.\n */\nconst REV_TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*(?:[~^][0-9]*)*$/;\n\nfunction isValidRevision(rev: string): boolean {\n if (rev.length === 0 || rev.length > 1024) return false;\n if (!REV_TOKEN_RE.test(rev)) return false;\n // Refname rules git enforces that our charset alone doesn't:\n if (rev.includes('..')) return false; // range separator / invalid in refnames\n if (rev.endsWith('.lock') || rev.endsWith('/') || rev.endsWith('.')) return false;\n return true;\n}\n\nfunction assertRevision(rev: string, what: string): void {\n if (!isValidRevision(rev)) {\n throw new Error(\n `${what} is not a valid git revision (got ${JSON.stringify(rev)}); ` +\n 'expected a SHA or simple refname — options/ranges are rejected'\n );\n }\n}\n\nfunction assertFullSha(sha: string, what: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(\n `${what} is not a full 40-hex SHA-1 (got ${JSON.stringify(sha)})`\n );\n }\n}\n\n/**\n * A revision range for format-patch: `<rev>`, `<rev>..<rev>`, or\n * `<rev>...<rev>` where each side passes {@link isValidRevision}.\n */\nfunction assertRange(range: string, what: string): void {\n const parts = range.split(/\\.{2,3}/);\n const separators = range.match(/\\.{2,3}/g) ?? [];\n const ok =\n parts.length <= 2 &&\n separators.length === parts.length - 1 &&\n parts.every((p) => isValidRevision(p));\n if (!ok) {\n throw new Error(\n `${what} is not a valid revision range (got ${JSON.stringify(range)}); ` +\n 'expected <rev>, <rev>..<rev>, or <rev>...<rev>'\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// cat-file --batch incremental parser\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: ReadonlySet<string> = new Set(['blob', 'tree', 'commit', 'tag']);\n\n/**\n * Incremental parser for `git cat-file --batch` output:\n * `<sha> <type> <size>\\n<body>\\n` per found object, `<name> missing\\n` for\n * absent ones. Bodies are raw bytes (possibly binary) and may be split\n * across arbitrary chunk boundaries, so parsing is strictly size-driven.\n */\nclass BatchParser {\n private buf: Buffer = Buffer.alloc(0);\n private pending: { sha: string; type: GitObjectType; size: number } | null = null;\n\n readonly objects: ReadGitObject[] = [];\n readonly missing: string[] = [];\n\n push(chunk: Buffer): void {\n this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);\n this.drain();\n }\n\n /** True when no partially-parsed record remains. */\n isComplete(): boolean {\n return this.pending === null && this.buf.length === 0;\n }\n\n private drain(): void {\n for (;;) {\n if (this.pending) {\n // Need body + trailing LF before the record is complete.\n const needed = this.pending.size + 1;\n if (this.buf.length < needed) return;\n const body = Buffer.from(this.buf.subarray(0, this.pending.size));\n this.objects.push({ sha: this.pending.sha, type: this.pending.type, body });\n this.buf = this.buf.subarray(needed);\n this.pending = null;\n continue;\n }\n\n const nl = this.buf.indexOf(0x0a);\n if (nl === -1) return;\n const header = this.buf.subarray(0, nl).toString('utf-8');\n this.buf = this.buf.subarray(nl + 1);\n\n const [name, second, third] = header.split(' ');\n if (name && second === 'missing' && third === undefined) {\n this.missing.push(name);\n continue;\n }\n if (name && second && third !== undefined && OBJECT_TYPES.has(second)) {\n const size = Number.parseInt(third, 10);\n if (Number.isSafeInteger(size) && size >= 0) {\n this.pending = { sha: name, type: second as GitObjectType, size };\n continue;\n }\n }\n throw new GitError(\n `unexpected cat-file --batch header: ${JSON.stringify(header)}`,\n undefined,\n ''\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// GitRepoReader\n// ---------------------------------------------------------------------------\n\n/**\n * Read-only view of a local git repository via git plumbing commands.\n *\n * All methods throw {@link GitError} when the underlying git process fails\n * unexpectedly, and plain `Error` when a caller-supplied argument fails\n * validation (before any process is spawned).\n */\nexport class GitRepoReader {\n constructor(\n /** Absolute or relative path to the repository worktree (or .git dir). */\n public readonly repoPath: string\n ) {}\n\n /** Run git with argument-array safety; resolves stdout as UTF-8. */\n private async git(\n args: string[],\n opts: { allowExitCodes?: number[] } = {}\n ): Promise<{ stdout: string; exitCode: number }> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: this.repoPath,\n maxBuffer: MAX_BUFFER,\n encoding: 'utf-8',\n });\n return { stdout, exitCode: 0 };\n } catch (err) {\n const e = err as NodeJS.ErrnoException & {\n code?: number | string;\n stdout?: string;\n stderr?: string;\n };\n const exitCode = typeof e.code === 'number' ? e.code : undefined;\n if (exitCode !== undefined && opts.allowExitCodes?.includes(exitCode)) {\n return { stdout: e.stdout ?? '', exitCode };\n }\n throw new GitError(\n `git ${args[0]} failed${exitCode !== undefined ? ` (exit ${exitCode})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`,\n exitCode,\n (e.stderr ?? '').trim()\n );\n }\n }\n\n /**\n * List all branches and tags plus the symbolic HEAD.\n *\n * Annotated tags report the tag object's SHA/type with the peeled target\n * in `peeledSha`. A detached HEAD is tolerated (`head` is `undefined`).\n */\n async listRefs(): Promise<RepoRefs> {\n const format = '%(refname)%00%(objectname)%00%(objecttype)%00%(*objectname)';\n const [refsRes, headRes] = await Promise.all([\n this.git(['for-each-ref', `--format=${format}`, 'refs/heads', 'refs/tags']),\n // Exit 1 = detached HEAD (or unborn branch pointer oddities) — tolerated.\n this.git(['symbolic-ref', '--quiet', 'HEAD'], { allowExitCodes: [1] }),\n ]);\n\n const refs: GitRef[] = [];\n for (const line of refsRes.stdout.split('\\n')) {\n if (!line) continue;\n const [refname, sha, objecttype, peeled] = line.split('\\0');\n if (!refname || !sha || !objecttype || !OBJECT_TYPES.has(objecttype)) {\n throw new GitError(`unexpected for-each-ref line: ${JSON.stringify(line)}`, undefined, '');\n }\n refs.push({\n refname,\n sha,\n type: objecttype as GitObjectType,\n ...(peeled ? { peeledSha: peeled } : {}),\n });\n }\n\n const head = headRes.exitCode === 0 ? headRes.stdout.trim() || undefined : undefined;\n return { head, refs };\n }\n\n /**\n * SHAs of every object reachable from `want` but not from `have`\n * (`git rev-list --objects <want…> --not <have…>`), i.e. the push delta.\n *\n * Haves that don't exist locally (e.g. remote tips we never fetched) are\n * filtered out first via one `cat-file --batch-check` pass — rev-list\n * would otherwise die on them.\n */\n async objectsBetween(want: string[], have: string[]): Promise<string[]> {\n const objects = await this.objectsBetweenWithPaths(want, have);\n return objects.map((o) => o.sha);\n }\n\n /**\n * Like {@link objectsBetween} but keeps the path each object was reached\n * by (`rev-list --objects` emits `<sha> <path>` for blobs and non-root\n * trees) — used by push planning to report actionable oversize errors.\n */\n async objectsBetweenWithPaths(\n want: string[],\n have: string[]\n ): Promise<ObjectWithPath[]> {\n for (const w of want) assertRevision(w, 'want');\n for (const h of have) assertRevision(h, 'have');\n if (want.length === 0) return [];\n\n const knownHaves = await this.filterExisting(have);\n\n const args = ['rev-list', '--objects', ...want];\n if (knownHaves.length > 0) args.push('--not', ...knownHaves);\n args.push('--'); // nothing user-supplied can become a pathspec\n const { stdout } = await this.git(args);\n\n const objects: ObjectWithPath[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n // `--objects` lines are `<sha>` or `<sha> <path>`.\n const spaceIdx = line.indexOf(' ');\n if (spaceIdx === -1) {\n objects.push({ sha: line });\n } else {\n const path = line.slice(spaceIdx + 1);\n objects.push({\n sha: line.slice(0, spaceIdx),\n ...(path ? { path } : {}),\n });\n }\n }\n return objects;\n }\n\n /**\n * List every blob (file) reachable from a ref's root tree, recursively,\n * with the path it is served at (#368: the ar.io site manifest join key).\n * Uses `git ls-tree -r -z` — NUL-terminated records so binary/spaced paths\n * survive verbatim, and no path quoting to undo. Submodule (`commit`)\n * gitlink entries and directories are excluded; only real file blobs remain.\n */\n async listBlobs(rev: string): Promise<{ path: string; sha: string }[]> {\n assertRevision(rev, 'ref');\n const { stdout } = await this.git(['ls-tree', '-r', '-z', rev, '--']);\n const blobs: { path: string; sha: string }[] = [];\n for (const record of stdout.split('\\0')) {\n if (!record) continue;\n // `<mode> SP <type> SP <sha> TAB <path>`\n const tab = record.indexOf('\\t');\n if (tab === -1) {\n throw new GitError(\n `unexpected ls-tree record: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n const meta = record.slice(0, tab);\n const path = record.slice(tab + 1);\n const [, type, sha] = meta.split(' ');\n if (type !== 'blob') continue; // trees are flattened by -r; skip gitlinks\n if (!sha || !FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `unexpected ls-tree object id: ${JSON.stringify(record)}`,\n undefined,\n ''\n );\n }\n blobs.push({ path, sha });\n }\n return blobs;\n }\n\n /** Run git feeding `input` on stdin; resolves collected stdout bytes. */\n private runWithStdin(args: string[], input: string): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const child = spawn('git', args, {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const out: Buffer[] = [];\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => out.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git ${args[0]}: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new GitError(`git ${args[0]} failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n resolve(Buffer.concat(out));\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(input);\n child.stdin.end();\n });\n }\n\n /** Of the given revisions, keep only those resolvable locally. */\n private async filterExisting(revs: string[]): Promise<string[]> {\n if (revs.length === 0) return [];\n const { missing } = await this.batchCheck(revs);\n const missingSet = new Set(missing);\n return revs.filter((r) => !missingSet.has(r));\n }\n\n /** One `cat-file --batch-check` pass; returns names reported missing. */\n private async batchCheck(names: string[]): Promise<{ missing: string[] }> {\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n names.join('\\n') + '\\n'\n );\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (line.endsWith(' missing')) missing.push(line.slice(0, -' missing'.length));\n }\n return { missing };\n }\n\n /**\n * Object metadata (type + body size) for a batch of SHAs via one\n * `cat-file --batch-check` pass — no bodies are read. Missing objects are\n * reported, not thrown.\n */\n async statObjects(shas: string[]): Promise<StatObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const stdout = await this.runWithStdin(\n ['cat-file', '--batch-check'],\n shas.join('\\n') + '\\n'\n );\n\n const objects: ObjectStat[] = [];\n const missing: string[] = [];\n for (const line of stdout.toString('utf-8').split('\\n')) {\n if (!line) continue;\n if (line.endsWith(' missing')) {\n missing.push(line.slice(0, -' missing'.length));\n continue;\n }\n const [sha, type, sizeStr] = line.split(' ');\n const size = Number.parseInt(sizeStr ?? '', 10);\n if (\n !sha ||\n !type ||\n !OBJECT_TYPES.has(type) ||\n !Number.isSafeInteger(size) ||\n size < 0\n ) {\n throw new GitError(\n `unexpected cat-file --batch-check line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n objects.push({ sha, type: type as GitObjectType, size });\n }\n return { objects, missing };\n }\n\n /**\n * Read raw object bodies via a single streaming `git cat-file --batch`\n * child process. Bodies may be binary and are parsed size-driven across\n * chunk boundaries. Missing objects are reported, not thrown.\n */\n async readObjects(shas: string[]): Promise<ReadObjectsResult> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return { objects: [], missing: [] };\n\n const parser = new BatchParser();\n await new Promise<void>((resolve, reject) => {\n const child = spawn('git', ['cat-file', '--batch'], {\n cwd: this.repoPath,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n let parseError: Error | null = null;\n\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.stdout.on('data', (chunk: Buffer) => {\n if (parseError) return;\n try {\n parser.push(chunk);\n } catch (err) {\n parseError = err as Error;\n child.kill();\n }\n });\n child.on('error', (err) => {\n reject(new GitError(`failed to spawn git cat-file: ${err.message}`, undefined, ''));\n });\n child.on('close', (code) => {\n if (parseError) return reject(parseError);\n if (code !== 0) {\n return reject(\n new GitError(`git cat-file --batch failed (exit ${code}): ${stderr.trim()}`, code ?? undefined, stderr.trim())\n );\n }\n if (!parser.isComplete()) {\n return reject(\n new GitError('git cat-file --batch output ended mid-record', code ?? undefined, stderr.trim())\n );\n }\n resolve();\n });\n\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' will surface the error.\n });\n child.stdin.write(shas.join('\\n') + '\\n');\n child.stdin.end();\n });\n\n return { objects: parser.objects, missing: parser.missing };\n }\n\n /**\n * `git merge-base --is-ancestor <a> <b>` — true when `a` is an ancestor\n * of `b` (fast-forward check / force detection). Exit codes other than\n * 0/1 (e.g. unknown revisions) throw.\n */\n async isAncestor(a: string, b: string): Promise<boolean> {\n assertRevision(a, 'ancestor candidate');\n assertRevision(b, 'descendant candidate');\n const { exitCode } = await this.git(\n ['merge-base', '--is-ancestor', a, b],\n { allowExitCodes: [1] }\n );\n return exitCode === 0;\n }\n\n /**\n * `git format-patch --stdout <range>` — the full mbox-formatted patch\n * series text (empty string when the range selects no commits).\n */\n async formatPatch(range: string): Promise<string> {\n assertRange(range, 'range');\n const { stdout } = await this.git(['format-patch', '--stdout', range, '--']);\n return stdout;\n }\n\n /**\n * Parent SHAs for a batch of commit SHAs via one\n * `git rev-list --no-walk=unsorted --parents` pass. Root commits map to an\n * empty array. Used to derive the kind:1617 `commit`/`parent-commit` tag\n * pairs for exactly the commits a format-patch series carries.\n */\n async commitParents(shas: string[]): Promise<Map<string, string[]>> {\n for (const sha of shas) assertFullSha(sha, 'sha');\n if (shas.length === 0) return new Map();\n const { stdout } = await this.git([\n 'rev-list',\n '--no-walk=unsorted',\n '--parents',\n ...shas,\n '--',\n ]);\n const parents = new Map<string, string[]>();\n for (const line of stdout.split('\\n')) {\n if (!line) continue;\n const [sha, ...rest] = line.split(' ');\n if (!sha || !FULL_SHA_RE.test(sha) || rest.some((p) => !FULL_SHA_RE.test(p))) {\n throw new GitError(\n `unexpected rev-list --parents line: ${JSON.stringify(line)}`,\n undefined,\n ''\n );\n }\n parents.set(sha, rest);\n }\n return parents;\n }\n\n /**\n * Resolve a ref/revision to a full SHA via `git rev-parse --verify`.\n * Throws {@link GitError} when the name doesn't resolve.\n */\n async resolveRef(name: string): Promise<string> {\n assertRevision(name, 'ref name');\n const { stdout } = await this.git(['rev-parse', '--verify', '--quiet', name]);\n const sha = stdout.trim();\n if (!FULL_SHA_RE.test(sha)) {\n throw new GitError(\n `rev-parse --verify returned unexpected output for ${JSON.stringify(name)}: ${JSON.stringify(sha)}`,\n undefined,\n ''\n );\n }\n return sha;\n }\n}\n","/**\n * The Git-from-TOON READ pipeline core (#278): download git object bodies\n * from Arweave gateways, verify them against their SHA-1, and walk the\n * object graph to prove a ref closure is complete.\n *\n * This is the CLI counterpart of rig-web's proven browser read path\n * (`web/arweave-client.ts` + `web/git-objects.ts` + the commit walker) — the\n * logic is mirrored, not imported, because the SPA package is not a library.\n *\n * Everything here is FREE (reads only: Arweave gateway GETs + the GraphQL\n * Git-SHA resolver) and pure of git — materializing objects into a real\n * repository lives in ./materialize.ts.\n *\n * INTEGRITY IS NON-NEGOTIABLE: an Arweave upload stores the object BODY\n * (content after the envelope NUL). Re-wrapping the body as each of the four\n * git object types and comparing the envelope SHA-1 against the expected SHA\n * both AUTHENTICATES the bytes and DISCOVERS the object's type in one step —\n * a body that matches under no type is rejected as corrupt/tampered, never\n * written.\n */\n\nimport {\n ARWEAVE_FETCH_TIMEOUT_MS,\n ARWEAVE_GATEWAYS,\n isValidArweaveTxId,\n} from '@toon-protocol/arweave';\nimport { hashGitObject, type GitObjectType } from './objects.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A downloaded git object: SHA-verified body + the type that verified it. */\nexport interface FetchedObject {\n /** Full 40-hex SHA-1 (verified against the body). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no envelope header). May be binary. */\n body: Buffer;\n}\n\n/** WHATWG-fetch seam (injectable for tests). */\nexport type FetchLike = (\n url: string,\n init?: { signal?: AbortSignal }\n) => Promise<{\n ok: boolean;\n arrayBuffer(): Promise<ArrayBuffer>;\n}>;\n\nexport interface GatewayFetchOptions {\n /** Ordered gateway base URLs (default: the shared preference list). */\n gateways?: readonly string[];\n /** fetch implementation (default: global fetch). */\n fetchFn?: FetchLike;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n}\n\nexport interface DownloadOptions extends GatewayFetchOptions {\n /** Maximum concurrent gateway downloads (default {@link DEFAULT_CONCURRENCY}). */\n concurrency?: number;\n /** Progress callback, called once per finished object. */\n onObject?: (done: number, total: number) => void;\n}\n\n/** Result of {@link downloadGitObjects}. */\nexport interface DownloadResult {\n /** SHA → verified object, for every SHA that could be downloaded. */\n objects: Map<string, FetchedObject>;\n /** SHAs whose txId 404'd/errored on EVERY gateway (propagation lag). */\n unavailable: { sha: string; txId: string }[];\n}\n\n/** Default parallel-download cap. */\nexport const DEFAULT_CONCURRENCY = 8;\n\n/**\n * A downloaded body did not hash to its expected SHA under ANY git object\n * type — corrupt or tampered content. The clone/fetch pipelines treat this\n * as a hard failure: nothing is written.\n */\nexport class ObjectIntegrityError extends Error {\n constructor(\n /** The objects that failed verification. */\n public readonly objects: { sha: string; txId: string }[]\n ) {\n super(\n `${objects.length} downloaded object(s) failed SHA-1 verification — ` +\n 'the gateway content does not match the announced git SHA(s): ' +\n objects.map((o) => `${o.sha} (tx ${o.txId})`).join(', ') +\n '. Refusing to write corrupt/tampered objects.'\n );\n this.name = 'ObjectIntegrityError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Verification (SHA check == type discovery)\n// ---------------------------------------------------------------------------\n\nconst OBJECT_TYPES: readonly GitObjectType[] = [\n 'blob',\n 'tree',\n 'commit',\n 'tag',\n];\n\n/**\n * Verify a downloaded body against its expected SHA-1 by trying the four git\n * envelope types. Returns the verified object, or null when no type matches\n * (corrupt/tampered bytes).\n */\nexport function verifyObjectBody(\n expectedSha: string,\n bytes: Uint8Array\n): FetchedObject | null {\n const body = Buffer.from(bytes);\n for (const type of OBJECT_TYPES) {\n if (hashGitObject(type, body).sha === expectedSha) {\n return { sha: expectedSha, type, body };\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway download (fallback chain, mirrors rig-web's fetchArweaveObject)\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch raw bytes for an Arweave tx id, trying each gateway in preference\n * order. Returns null when every gateway fails (404 / error / timeout).\n */\nexport async function fetchTxBytes(\n txId: string,\n options: GatewayFetchOptions = {}\n): Promise<Uint8Array | null> {\n if (!isValidArweaveTxId(txId)) return null;\n const gateways = options.gateways ?? ARWEAVE_GATEWAYS;\n const fetchFn = options.fetchFn ?? (fetch as FetchLike);\n const timeoutMs = options.timeoutMs ?? ARWEAVE_FETCH_TIMEOUT_MS;\n\n for (const gateway of gateways) {\n try {\n const response = await fetchFn(`${gateway}/${txId}`, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) continue;\n return new Uint8Array(await response.arrayBuffer());\n } catch {\n // Network error, timeout, or other failure — try the next gateway.\n }\n }\n return null;\n}\n\n/**\n * Download + verify a batch of git objects (sha → Arweave txId) with a\n * concurrency cap and per-gateway fallback.\n *\n * Objects that 404 on every gateway are reported in `unavailable` (Arweave\n * propagation lag — the caller decides whether that is fatal). Objects whose\n * bytes fail SHA-1 verification throw {@link ObjectIntegrityError}: corrupt\n * content is NEVER returned.\n */\nexport async function downloadGitObjects(\n entries: Iterable<[sha: string, txId: string]>,\n options: DownloadOptions = {}\n): Promise<DownloadResult> {\n const queue = [...entries];\n const total = queue.length;\n const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY);\n\n const objects = new Map<string, FetchedObject>();\n const unavailable: { sha: string; txId: string }[] = [];\n const corrupt: { sha: string; txId: string }[] = [];\n let done = 0;\n\n const worker = async (): Promise<void> => {\n for (;;) {\n const next = queue.shift();\n if (!next) return;\n const [sha, txId] = next;\n const bytes = await fetchTxBytes(txId, options);\n if (bytes === null) {\n unavailable.push({ sha, txId });\n } else {\n const verified = verifyObjectBody(sha, bytes);\n if (verified === null) {\n corrupt.push({ sha, txId });\n } else {\n objects.set(sha, verified);\n }\n }\n done += 1;\n options.onObject?.(done, total);\n }\n };\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, total) }, () => worker())\n );\n\n if (corrupt.length > 0) throw new ObjectIntegrityError(corrupt);\n return { objects, unavailable };\n}\n\n// ---------------------------------------------------------------------------\n// Object-graph references (mirrors rig-web's git-objects parsing)\n// ---------------------------------------------------------------------------\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/** Submodule (gitlink) tree-entry mode: references a commit in ANOTHER repo. */\nconst GITLINK_MODE = '160000';\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let hex = '';\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0');\n return hex;\n}\n\n/**\n * SHAs an object references inside the SAME repository:\n * - commit → its tree + parents\n * - tree → entry SHAs, EXCEPT gitlinks (mode 160000: submodule commits\n * live in another repository and are never present — git fsck\n * skips them too)\n * - tag → the tagged object\n * - blob → nothing\n */\nexport function referencedShas(object: FetchedObject): string[] {\n switch (object.type) {\n case 'blob':\n return [];\n case 'tree': {\n const refs: string[] = [];\n const data = object.body;\n let offset = 0;\n while (offset < data.length) {\n const spaceIdx = data.indexOf(0x20, offset);\n if (spaceIdx === -1) break;\n const mode = data.subarray(offset, spaceIdx).toString('utf-8');\n const nulIdx = data.indexOf(0x00, spaceIdx + 1);\n if (nulIdx === -1 || nulIdx + 21 > data.length) break;\n const sha = bytesToHex(data.subarray(nulIdx + 1, nulIdx + 21));\n if (mode !== GITLINK_MODE) refs.push(sha);\n offset = nulIdx + 21;\n }\n return refs;\n }\n case 'commit': {\n const refs: string[] = [];\n const text = object.body.toString('utf-8');\n const headerEnd = text.indexOf('\\n\\n');\n const header = headerEnd === -1 ? text : text.slice(0, headerEnd);\n for (const line of header.split('\\n')) {\n if (line.startsWith('tree ')) refs.push(line.slice(5).trim());\n else if (line.startsWith('parent ')) refs.push(line.slice(7).trim());\n }\n return refs.filter((sha) => FULL_SHA_RE.test(sha));\n }\n case 'tag': {\n const text = object.body.toString('utf-8');\n const match = /^object ([0-9a-f]{40})$/m.exec(text);\n return match?.[1] ? [match[1]] : [];\n }\n }\n}\n\n/** Result of {@link walkClosure}. */\nexport interface ClosureResult {\n /** Every SHA reachable from the tips that lives in `objects`. */\n reachable: Set<string>;\n /** Reachable SHAs found NEITHER in `objects` nor in `presentLocally`. */\n missing: string[];\n}\n\n/**\n * Walk the object graph from the ref tips over the downloaded object set and\n * report which reachable SHAs are missing. `presentLocally` marks SHAs that\n * already exist in the destination repository — the walk does not descend\n * into them (a consistent local repo carries its own closure; the same\n * assumption `git fetch` makes).\n */\nexport function walkClosure(\n tips: Iterable<string>,\n objects: ReadonlyMap<string, FetchedObject>,\n presentLocally: ReadonlySet<string> = new Set()\n): ClosureResult {\n const reachable = new Set<string>();\n const missing = new Set<string>();\n const stack = [...new Set(tips)];\n\n while (stack.length > 0) {\n const sha = stack.pop() as string;\n if (reachable.has(sha) || missing.has(sha)) continue;\n if (presentLocally.has(sha)) continue; // local closure assumed complete\n const object = objects.get(sha);\n if (!object) {\n missing.add(sha);\n continue;\n }\n reachable.add(sha);\n for (const ref of referencedShas(object)) {\n if (!reachable.has(ref) && !missing.has(ref)) stack.push(ref);\n }\n }\n\n return { reachable, missing: [...missing] };\n}\n","/**\n * The shared clone/fetch object-collection engine (#278).\n *\n * Given the remote's ref tips and its kind:30618 sha→Arweave-txId map, gather\n * every object the refs need:\n *\n * 1. download the mapped objects the destination repo doesn't already have\n * (parallel, gateway fallback chain, SHA-verified — ./object-fetch.ts);\n * 2. walk the object graph from the tips (./object-fetch.ts walkClosure) —\n * the local repository's objects count as present (git fetch's own\n * assumption: a consistent repo carries its own closure);\n * 3. SHAs the map doesn't cover are resolved through the Arweave GraphQL\n * Git-SHA resolver (RemoteState.resolveMissing) and downloaded, looping\n * until the closure is complete or no progress can be made.\n *\n * The result separates FATAL gaps (reachable objects that could not be\n * obtained — usually Arweave gateway propagation lag, 10–20 min for fresh\n * pushes) from harmless ones (mapped-but-unreachable objects, e.g. history\n * that was force-pushed away). Corrupt objects throw ObjectIntegrityError\n * from the download layer and never surface here.\n */\n\nimport {\n downloadGitObjects,\n walkClosure,\n type DownloadOptions,\n type FetchedObject,\n} from './object-fetch.js';\nimport { EMPTY_BLOB_SHA } from './objects.js';\n\n/** A reachable object that could not be obtained. */\nexport interface MissingObject {\n sha: string;\n /** The txId that failed on every gateway, or null when no txId resolved. */\n txId: string | null;\n}\n\nexport interface CollectRepoObjectsOptions extends DownloadOptions {\n /** Ref tip SHAs (commits or annotated tags) the closure must reach. */\n tips: string[];\n /** kind:30618 `arweave` tag map: git SHA → Arweave txId. */\n shaToTxId: ReadonlyMap<string, string>;\n /** GraphQL fallback for SHAs the map doesn't cover (RemoteState.resolveMissing). */\n resolveMissing: (shas: string[]) => Promise<Map<string, string>>;\n /** SHAs already present in the destination repository (fetch delta). */\n presentLocally?: ReadonlySet<string>;\n}\n\nexport interface CollectRepoObjectsResult {\n /** Verified objects to write, keyed by SHA. */\n objects: Map<string, FetchedObject>;\n /** Reachable SHAs that could not be obtained — FATAL for clone/fetch. */\n missing: MissingObject[];\n /** Mapped-but-unreachable SHAs that failed to download — warn only. */\n skippedUnavailable: { sha: string; txId: string }[];\n}\n\n/** Iteration cap: closure depth of NEW unmapped SHAs per pass; generous. */\nconst MAX_PASSES = 64;\n\n/** Collect (download + verify + close over) the objects the ref tips need. */\nexport async function collectRepoObjects(\n options: CollectRepoObjectsOptions\n): Promise<CollectRepoObjectsResult> {\n const { tips, shaToTxId, resolveMissing } = options;\n const present = options.presentLocally ?? new Set<string>();\n\n /** All txId knowledge: the 30618 map + GraphQL-resolved additions. */\n const txIds = new Map<string, string>(shaToTxId);\n /** SHAs we already asked the GraphQL resolver about (avoid re-queries). */\n const resolverAsked = new Set<string>();\n /** SHAs whose download failed on every gateway. */\n const undownloadable = new Map<string, string>();\n const objects = new Map<string, FetchedObject>();\n\n // Pass 0: bulk-download everything the map covers that isn't local yet.\n const initial: [string, string][] = [];\n for (const [sha, txId] of txIds) {\n if (!present.has(sha)) initial.push([sha, txId]);\n }\n const bulk = await downloadGitObjects(initial, options);\n for (const [sha, object] of bulk.objects) objects.set(sha, object);\n for (const { sha, txId } of bulk.unavailable) undownloadable.set(sha, txId);\n\n // Iterate: close over the tips; resolve + download whatever is still open.\n for (let pass = 0; pass < MAX_PASSES; pass++) {\n const closure = walkClosure(tips, objects, present);\n const open = closure.missing.filter((sha) => !undownloadable.has(sha));\n if (open.length === 0) break;\n\n // Resolve txIds for open SHAs we haven't asked the resolver about.\n const toResolve = open.filter(\n (sha) => !txIds.has(sha) && !resolverAsked.has(sha)\n );\n for (const sha of toResolve) resolverAsked.add(sha);\n if (toResolve.length > 0) {\n const resolved = await resolveMissing(toResolve);\n for (const [sha, txId] of resolved) txIds.set(sha, txId);\n }\n\n // Download open SHAs that now have a txId and no failed attempt yet.\n const batch: [string, string][] = [];\n for (const sha of open) {\n const txId = txIds.get(sha);\n if (txId !== undefined && !objects.has(sha)) batch.push([sha, txId]);\n }\n if (batch.length === 0) break; // no progress possible\n const result = await downloadGitObjects(batch, options);\n for (const [sha, object] of result.objects) objects.set(sha, object);\n for (const { sha, txId } of result.unavailable)\n undownloadable.set(sha, txId);\n if (result.objects.size === 0) break; // every attempt failed — stop\n }\n\n // The git empty blob is never uploaded (the store rejects zero-byte\n // content; `rig push` skips it), so a tree that references it reports it\n // \"missing\" here even though it is a git constant. Synthesize it locally —\n // a zero-byte blob body always hashes to EMPTY_BLOB_SHA — instead of\n // erroring, so an empty file reconstructs bit-identically (git fsck clean).\n // Keyed off the EXACT constant SHA: the honest lag-error still fires for any\n // genuinely-missing non-empty object.\n let finalClosure = walkClosure(tips, objects, present);\n if (\n finalClosure.missing.includes(EMPTY_BLOB_SHA) &&\n !present.has(EMPTY_BLOB_SHA)\n ) {\n objects.set(EMPTY_BLOB_SHA, {\n sha: EMPTY_BLOB_SHA,\n type: 'blob',\n body: Buffer.alloc(0),\n });\n finalClosure = walkClosure(tips, objects, present);\n }\n\n // Final accounting.\n const missing: MissingObject[] = finalClosure.missing.map((sha) => ({\n sha,\n txId: txIds.get(sha) ?? null,\n }));\n const reachable = finalClosure.reachable;\n const skippedUnavailable = [...undownloadable]\n .filter(\n ([sha]) => !reachable.has(sha) && !finalClosure.missing.includes(sha)\n )\n .map(([sha, txId]) => ({ sha, txId }));\n\n return { objects, missing, skippedUnavailable };\n}\n\n/**\n * The honest propagation-lag error text: which SHAs are unobtainable and why\n * retrying later is the expected remedy.\n */\nexport function missingObjectsMessage(\n missing: MissingObject[],\n context: string\n): string {\n const listed = missing\n .slice(0, 20)\n .map(\n (m) =>\n ` ${m.sha}${m.txId ? ` (tx ${m.txId})` : ' (no Arweave tx found)'}`\n )\n .join('\\n');\n const more =\n missing.length > 20 ? `\\n … and ${missing.length - 20} more` : '';\n return (\n `${context}: ${missing.length} required object(s) could not be downloaded:\\n` +\n `${listed}${more}\\n` +\n 'Recently pushed objects can take 10-20 minutes to become fetchable from ' +\n 'Arweave gateways — if this repo was just pushed, retry in a few minutes. ' +\n 'Nothing was written.'\n );\n}\n","/**\n * Materialize downloaded git objects into a REAL repository (#278).\n *\n * Objects are written through git's own plumbing — `git hash-object -w\n * --stdin -t <type>` with the raw body on stdin — so git computes, validates\n * (syntax checks for trees/commits/tags), stores (loose object + zlib), and\n * RETURNS the SHA. The returned SHA is compared against the expected one:\n * a second, independent integrity gate after ./object-fetch.ts's envelope\n * verification. Refs land via `git update-ref`, HEAD via `git symbolic-ref`.\n *\n * Same injection posture as GitRepoReader: child processes use\n * `execFile`/`spawn` with argument ARRAYS (never a shell), and refnames are\n * validated with `git check-ref-format` semantics before use.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FetchedObject } from './object-fetch.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** A written object's SHA disagreed with what `git hash-object` computed. */\nexport class ObjectWriteMismatchError extends Error {\n constructor(\n public readonly expectedSha: string,\n public readonly writtenSha: string\n ) {\n super(\n `git hash-object wrote ${writtenSha} where ${expectedSha} was expected — ` +\n 'object content does not round-trip; aborting'\n );\n this.name = 'ObjectWriteMismatchError';\n }\n}\n\n/** Full 40-hex SHA-1. */\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/;\n\n/**\n * Conservative refname validation (superset-safe subset of\n * `git check-ref-format`): must start `refs/`, no component may start with\n * `-` or `.`, no `..`, no control/space/git-special characters, no trailing\n * `/`, `.`, or `.lock`. Rejecting odd-but-legal names is fine — these\n * refnames come from relay events, and a hostile relay must not be able to\n * smuggle options or path traversal into git invocations.\n */\nexport function isSafeRefname(refname: string): boolean {\n if (!refname.startsWith('refs/') || refname.length > 1024) return false;\n // eslint-disable-next-line no-control-regex -- explicit control-char reject\n if (/[\\u0000-\\u0020~^:?*[\\\\\\u007f]/.test(refname)) return false;\n if (refname.includes('..') || refname.includes('@{')) return false;\n if (refname.endsWith('/') || refname.endsWith('.')) return false;\n for (const part of refname.split('/')) {\n if (part === '' || part.startsWith('.') || part.startsWith('-'))\n return false;\n if (part.endsWith('.lock')) return false;\n }\n return true;\n}\n\nfunction assertSafeRefname(refname: string): void {\n if (!isSafeRefname(refname)) {\n throw new Error(\n `unsafe ref name from remote state: ${JSON.stringify(refname)} — refusing`\n );\n }\n}\n\nfunction assertFullSha(sha: string): void {\n if (!FULL_SHA_RE.test(sha)) {\n throw new Error(`not a full 40-hex SHA-1: ${JSON.stringify(sha)}`);\n }\n}\n\n/** Run git with argument-array safety in `repoPath`. */\nexport async function runGit(\n repoPath: string,\n args: string[]\n): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, {\n cwd: repoPath,\n encoding: 'utf-8',\n maxBuffer: 64 * 1024 * 1024,\n });\n return stdout;\n } catch (err) {\n const e = err as {\n code?: number | string;\n stderr?: string;\n message?: string;\n };\n throw new Error(\n `git ${args[0]} failed${typeof e.code === 'number' ? ` (exit ${e.code})` : ''}: ` +\n `${(e.stderr ?? e.message ?? '').trim()}`\n );\n }\n}\n\n/**\n * Write one object via `git hash-object -w --stdin -t <type>` (binary-safe\n * stdin) and verify the SHA git computed matches the expected one.\n */\nexport async function writeGitObject(\n repoPath: string,\n object: FetchedObject\n): Promise<void> {\n assertFullSha(object.sha);\n const written = await new Promise<string>((resolve, reject) => {\n const child = spawn(\n 'git',\n ['hash-object', '-w', '--stdin', '-t', object.type],\n { cwd: repoPath, stdio: ['pipe', 'pipe', 'pipe'] }\n );\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf-8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf-8');\n });\n child.on('error', (err) => {\n reject(new Error(`failed to spawn git hash-object: ${err.message}`));\n });\n child.on('close', (code) => {\n if (code !== 0) {\n return reject(\n new Error(`git hash-object failed (exit ${code}): ${stderr.trim()}`)\n );\n }\n resolve(stdout.trim());\n });\n child.stdin.on('error', () => {\n // Child died before consuming stdin; 'close' surfaces the failure.\n });\n child.stdin.write(object.body);\n child.stdin.end();\n });\n if (written !== object.sha) {\n throw new ObjectWriteMismatchError(object.sha, written);\n }\n}\n\n/** Write a batch of verified objects into the repository (sequential). */\nexport async function writeGitObjects(\n repoPath: string,\n objects: Iterable<FetchedObject>\n): Promise<number> {\n let count = 0;\n for (const object of objects) {\n await writeGitObject(repoPath, object);\n count += 1;\n }\n return count;\n}\n\n/** `git update-ref <refname> <sha>` with refname/SHA validation. */\nexport async function updateRef(\n repoPath: string,\n refname: string,\n sha: string\n): Promise<void> {\n assertSafeRefname(refname);\n assertFullSha(sha);\n await runGit(repoPath, ['update-ref', refname, sha]);\n}\n\n/** Point HEAD at a branch via `git symbolic-ref HEAD <refname>`. */\nexport async function setHeadSymref(\n repoPath: string,\n refname: string\n): Promise<void> {\n assertSafeRefname(refname);\n await runGit(repoPath, ['symbolic-ref', 'HEAD', refname]);\n}\n","/**\n * Minimal bech32 npub encoding/decoding for Nostr pubkeys (BIP-173 / NIP-19),\n * dependency-free. Mirrors rig-web's proven `web/npub.ts` — the CLI must not\n * import from the SPA package, so the ~100 lines live here too (#278: `rig\n * clone` accepts `<owner-npub-or-hex>/<repo-id>` addresses).\n */\n\nconst BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';\n\nfunction bech32Polymod(values: number[]): number {\n const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];\n let chk = 1;\n for (const v of values) {\n const b = chk >> 25;\n chk = ((chk & 0x1ffffff) << 5) ^ v;\n for (let i = 0; i < 5; i++) {\n chk ^= (b >> i) & 1 ? (GEN[i] as number) : 0;\n }\n }\n return chk;\n}\n\nfunction bech32HrpExpand(hrp: string): number[] {\n const ret: number[] = [];\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) >> 5);\n }\n ret.push(0);\n for (let i = 0; i < hrp.length; i++) {\n ret.push(hrp.charCodeAt(i) & 31);\n }\n return ret;\n}\n\nfunction bech32CreateChecksum(hrp: string, data: number[]): number[] {\n const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);\n const polymod = bech32Polymod(values) ^ 1;\n const ret: number[] = [];\n for (let i = 0; i < 6; i++) {\n ret.push((polymod >> (5 * (5 - i))) & 31);\n }\n return ret;\n}\n\nfunction convertBits(\n data: number[],\n fromBits: number,\n toBits: number,\n pad: boolean\n): number[] {\n let acc = 0;\n let bits = 0;\n const ret: number[] = [];\n const maxv = (1 << toBits) - 1;\n for (const value of data) {\n acc = (acc << fromBits) | value;\n bits += fromBits;\n while (bits >= toBits) {\n bits -= toBits;\n ret.push((acc >> bits) & maxv);\n }\n }\n if (pad && bits > 0) {\n ret.push((acc << (toBits - bits)) & maxv);\n }\n return ret;\n}\n\nfunction hexToBytes(hex: string): number[] {\n const bytes: number[] = [];\n for (let i = 0; i < hex.length; i += 2) {\n bytes.push(parseInt(hex.slice(i, i + 2), 16));\n }\n return bytes;\n}\n\n/** Encode a 64-char hex pubkey as an `npub1…` bech32 string. */\nexport function hexToNpub(hexPubkey: string): string {\n const hrp = 'npub';\n const bytes = hexToBytes(hexPubkey);\n const words = convertBits(bytes, 8, 5, true);\n const checksum = bech32CreateChecksum(hrp, words);\n const combined = words.concat(checksum);\n return hrp + '1' + combined.map((d) => BECH32_CHARSET[d]).join('');\n}\n\n/**\n * Decode an `npub1…` bech32 string back to a 64-char hex pubkey.\n * Throws on malformed input (prefix, length, charset, checksum, padding).\n */\nexport function npubToHex(npub: string): string {\n const lower = npub.toLowerCase();\n if (lower !== npub && npub.toUpperCase() !== npub) {\n throw new Error('npub: mixed case');\n }\n if (!lower.startsWith('npub1')) {\n throw new Error('npub: invalid prefix');\n }\n if (lower.length !== 63) {\n throw new Error('npub: invalid length');\n }\n\n const data: number[] = [];\n for (let i = 5; i < lower.length; i++) {\n const idx = BECH32_CHARSET.indexOf(lower[i] as string);\n if (idx === -1) throw new Error('npub: invalid character');\n data.push(idx);\n }\n\n // Verify checksum\n const hrpExpanded = bech32HrpExpand('npub');\n if (bech32Polymod(hrpExpanded.concat(data)) !== 1) {\n throw new Error('npub: invalid checksum');\n }\n\n // Strip 6-word checksum, convert 5-bit words back to 8-bit bytes\n const words = data.slice(0, -6);\n const bytes = convertBits(words, 5, 8, false);\n\n if (bytes.length !== 32) {\n throw new Error('npub: invalid data length');\n }\n\n // Validate trailing bits are zero\n const totalBits = words.length * 5;\n const trailingBits = totalBits - bytes.length * 8;\n if (trailingBits > 0) {\n const lastWord = words[words.length - 1] as number;\n const mask = (1 << trailingBits) - 1;\n if ((lastWord & mask) !== 0) {\n throw new Error('npub: non-zero padding bits');\n }\n }\n\n return bytes.map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nconst HEX64_RE = /^[0-9a-f]{64}$/;\n\n/**\n * Normalize a repo-owner reference to a 64-char hex pubkey: accepts lowercase\n * hex verbatim or an `npub1…` string. Throws a caller-facing error otherwise.\n */\nexport function ownerToHex(owner: string): string {\n if (HEX64_RE.test(owner)) return owner;\n if (owner.startsWith('npub1')) {\n try {\n return npubToHex(owner);\n } catch (err) {\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n throw new Error(\n `invalid owner ${JSON.stringify(owner)}: expected a 64-char lowercase hex pubkey or an npub1… string`\n );\n}\n","/**\n * Wire shapes of the toon-clientd `/git/*` control routes (epic #222).\n *\n * These are the JSON request/response types the daemon serves (bigints as\n * decimal strings, Maps as plain records) — defined HERE, in the dependency\n * root, so both sides of the route can share them: the `rig` CLI (#229)\n * consumes them as a plain-fetch client, and `@toon-protocol/client-mcp`\n * (which depends on this package for the planner — the reverse import would\n * be circular) can adopt them for its `control-api.ts` declarations. TYPES\n * ONLY plus two pure serializers; no transport code lives here.\n *\n * Keep in byte-for-byte sync with\n * `packages/client-mcp/src/control-api.ts` (`Git*` shapes) and\n * `packages/client-mcp/src/daemon/routes.ts` (error envelopes: 409\n * `non_fast_forward` carries `refs`, 413 `oversize_objects` carries\n * `objects`, 503 `bootstrapping` / 402 `insufficient_gas` are retryable).\n */\n\nimport type { PublishReceipt } from './publisher.js';\nimport type { PlannedObject, PushPlan, PushResult, RefUpdate } from './push.js';\n\n/** One planned ref update (JSON-safe as-is). */\nexport type GitRefUpdate = RefUpdate;\n\n/** One object scheduled for upload (JSON-safe as-is). */\nexport type GitPlannedObject = PlannedObject;\n\n/**\n * `POST /git/estimate` — plan a push (local git plumbing + remote-state read)\n * and price it WITHOUT paying anything. The same body (plus `confirm`) drives\n * `POST /git/push`.\n */\nexport interface GitEstimateRequest {\n /** Path to the local git repository (worktree or .git dir). Must exist. */\n repoPath: string;\n /** Repository identifier (NIP-34 `d` tag). The daemon identity is the owner. */\n repoId: string;\n /**\n * Full refnames to push (e.g. `[\"refs/heads/main\"]`). Default: every local\n * branch and tag.\n */\n refspecs?: string[];\n /** Allow non-fast-forward updates (default false → 409 `non_fast_forward`). */\n force?: boolean;\n /**\n * Relay URLs to read remote state from and publish to. Plural from day one\n * (forward-compat); defaults to the daemon's config-seeded relay.\n */\n relayUrls?: string[];\n /** Repo name/description for the first-push kind:30617 announcement. */\n announcement?: { name?: string; description?: string };\n}\n\n/** Pre-push fee table (all fees in base/micro units, decimal strings). */\nexport interface GitFeeEstimate {\n objectCount: number;\n totalObjectBytes: number;\n /** Σ max(size × uploadFeePerByte, per-upload route-price floor). */\n uploadFee: string;\n /** Events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × per-event fee. */\n eventFees: string;\n /** uploadFee + eventFees. */\n totalFee: string;\n /**\n * Zero-byte objects (the git empty blob) excluded from the upload — the\n * store rejects zero-byte content as malformed, so they are skipped on push\n * and reconstructed on clone/fetch. Optional for wire compatibility with\n * daemons predating the empty-blob handling. Default 0.\n */\n skippedEmptyCount?: number;\n}\n\n/** Serialized `PushPlan` — everything a confirm UI needs. */\nexport interface GitEstimateResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Full new ref state to publish (HEAD target first). */\n newRefs: Record<string, string>;\n headSymref: string | null;\n objects: GitPlannedObject[];\n /** sha→txId hints known WITHOUT uploading (remote tags + resolver finds). */\n knownShaToTxId: Record<string, string>;\n /** True when no kind:30617 exists yet — the push announces first. */\n announceNeeded: boolean;\n announcement: { name: string; description: string };\n estimate: GitFeeEstimate;\n}\n\n/**\n * `POST /git/push` — plan + execute: upload the delta to Arweave and publish\n * the cumulative kind:30618 (+ kind:30617 on first push). PERMANENT + PAID.\n */\nexport interface GitPushRequest extends GitEstimateRequest {\n /** Must be literally `true` — a push spends channel funds irreversibly. */\n confirm: boolean;\n}\n\n/** One object-upload step result. */\nexport interface GitUploadStep {\n sha: string;\n txId: string;\n /** '0' when skipped (already on Arweave — content-addressed resume). */\n feePaid: string;\n skipped: boolean;\n}\n\n/** Receipt for one published event. */\nexport interface GitPublishReceipt {\n eventId: string;\n feePaid: string;\n}\n\n/** Serialized `PushResult` — per-step receipts + total fees actually paid. */\nexport interface GitPushResponse {\n repoId: string;\n refUpdates: GitRefUpdate[];\n /** Per-object results, in plan order. */\n uploads: GitUploadStep[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: GitPublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: GitPublishReceipt;\n /** Full sha→txId map published in the refs event. */\n arweaveMap: Record<string, string>;\n /** Total fees actually paid (uploads + events), base units, decimal. */\n totalFeePaid: string;\n /** The pre-push estimate the push ran under (compare against totalFeePaid). */\n estimate: GitFeeEstimate;\n}\n\n// ---------------------------------------------------------------------------\n// Single-event git publishes (`/git/issue|comment|patch|status`, #231)\n// ---------------------------------------------------------------------------\n\n/** NIP-34 repository address: the owner+id pair behind `a` tags. */\nexport interface GitRepoAddr {\n /** Repository owner's Nostr pubkey (64-char hex) — author of kind:30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n}\n\n/** `POST /git/issue` — publish a kind:1621 issue against a repo. PAID. */\nexport interface GitIssueRequest {\n repoAddr: GitRepoAddr;\n /** Issue title (`subject` tag). */\n title: string;\n /** Issue body (Markdown content). */\n body: string;\n /** Labels (`t` tags). */\n labels?: string[];\n}\n\n/** `POST /git/comment` — publish a kind:1622 comment on an issue/patch. PAID. */\nexport interface GitCommentRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue or patch being commented on. */\n rootEventId: string;\n /** Comment body (Markdown content). */\n body: string;\n /**\n * Pubkey of the TARGET event's author (NIP-34 `p` threading tag — not the\n * comment author). Defaults to the repo owner.\n */\n parentAuthorPubkey?: string;\n /** `e`-tag marker (default 'root': commenting directly on the issue/patch). */\n marker?: 'root' | 'reply';\n}\n\n/**\n * `POST /git/patch` — publish a kind:1617 patch. Supply EXACTLY ONE of\n * `patchText` (literal `git format-patch` output) or `repoPath`+`range`\n * (the daemon runs `git format-patch --stdout <range>` locally). PAID.\n */\nexport interface GitPatchRequest {\n repoAddr: GitRepoAddr;\n /** Patch/PR title (`subject` tag). */\n title: string;\n /**\n * PR body/cover text (`description` tag). Kept OUT of the event content so\n * `git am` still consumes the patch text verbatim (#280).\n */\n description?: string;\n /** Literal patch text. Mutually exclusive with `repoPath`+`range`. */\n patchText?: string;\n /** Local repository to run format-patch in. Requires `range`. */\n repoPath?: string;\n /** Revision range for format-patch (`<rev>`, `<rev>..<rev>`, `<rev>...<rev>`). */\n range?: string;\n /** Commit/parent pairs for `commit`/`parent-commit` tags. */\n commits?: { sha: string; parentSha: string }[];\n /** Branch name for the `t` tag. */\n branch?: string;\n}\n\nexport type GitStatusValue = 'open' | 'applied' | 'closed' | 'draft';\n\n/** `POST /git/status` — publish a kind:1630-1633 status event. PAID. */\nexport interface GitStatusRequest {\n repoAddr: GitRepoAddr;\n /** Event id of the issue/patch whose status is being set. */\n targetEventId: string;\n /** open → 1630, applied → 1631, closed → 1632, draft → 1633. */\n status: GitStatusValue;\n /** Pubkey of the target event's author (`p` tag), when known. */\n targetPubkey?: string;\n}\n\n/**\n * Response of the single-event git publishes (issue/comment/patch/status):\n * a publish receipt plus the NIP-34 kind that was published. Daemon\n * responses extend the full `POST /publish` receipt, so the channel fields\n * (`channelId`/`nonce`/…) are present there; they are optional here because\n * the CLI's standalone path publishes through the embedded client and has\n * no channel wire shape to report.\n */\nexport interface GitEventResponse {\n /** Event ID as accepted by the relay. */\n eventId: string;\n /** Fee actually paid for this publish, base units, decimal string. */\n feePaid: string;\n /** The NIP-34 kind that was published. */\n kind: number;\n /** Channel the claim was signed against (daemon responses). */\n channelId?: string;\n /** Channel nonce after this publish (daemon responses). */\n nonce?: number;\n /** FULFILL response data (base64), when the backend returned any. */\n data?: string;\n /** Spendable channel balance after this write, when known. */\n channelBalanceAfter?: string;\n}\n\n/**\n * Uniform error envelope of non-2xx control-route responses. Structured\n * errors put extra fields at the top level: `non_fast_forward` (409) adds\n * `refs`, `oversize_objects` (413) adds `objects`.\n */\nexport interface GitErrorEnvelope {\n error: string;\n detail?: string;\n /** True when the caller should retry (e.g. daemon still bootstrapping). */\n retryable?: boolean;\n [extra: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Serializers (pure) — the exact mapping the daemon routes apply; used by the\n// CLI's standalone mode so both surfaces emit identical wire JSON.\n// ---------------------------------------------------------------------------\n\n/** Serialize a plan's fee estimate onto the wire (bigints → strings). */\nexport function serializeFeeEstimate(plan: PushPlan): GitFeeEstimate {\n return {\n objectCount: plan.estimate.objectCount,\n totalObjectBytes: plan.estimate.totalObjectBytes,\n uploadFee: plan.estimate.uploadFee.toString(),\n eventCount: plan.estimate.eventCount,\n eventFees: plan.estimate.eventFees.toString(),\n totalFee: plan.estimate.totalFee.toString(),\n ...(plan.estimate.skippedEmptyCount > 0\n ? { skippedEmptyCount: plan.estimate.skippedEmptyCount }\n : {}),\n };\n}\n\n/** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */\nexport function serializePushPlan(plan: PushPlan): GitEstimateResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n newRefs: plan.newRefs,\n headSymref: plan.headSymref,\n objects: plan.objects,\n knownShaToTxId: Object.fromEntries(plan.knownShaToTxId),\n announceNeeded: plan.announceNeeded,\n announcement: plan.announcement,\n estimate: serializeFeeEstimate(plan),\n };\n}\n\n/**\n * Serialize a standalone {@link PublishReceipt} into the wire shape the\n * daemon's single-event `/git/*` routes answer with, so `--json` consumers\n * see one `GitEventResponse` shape regardless of publisher mode.\n */\nexport function serializeEventReceipt(\n kind: number,\n receipt: PublishReceipt\n): GitEventResponse {\n return {\n eventId: receipt.eventId,\n feePaid: receipt.feePaid.toString(),\n kind,\n };\n}\n\n/** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */\nexport function serializePushResult(\n plan: PushPlan,\n result: PushResult\n): GitPushResponse {\n return {\n repoId: plan.repoId,\n refUpdates: plan.refUpdates,\n uploads: result.uploads.map((u) => ({\n sha: u.sha,\n txId: u.txId,\n feePaid: u.feePaid.toString(),\n skipped: u.skipped,\n })),\n announceReceipt: result.announceReceipt\n ? {\n eventId: result.announceReceipt.eventId,\n feePaid: result.announceReceipt.feePaid.toString(),\n }\n : null,\n refsReceipt: {\n eventId: result.refsReceipt.eventId,\n feePaid: result.refsReceipt.feePaid.toString(),\n },\n arweaveMap: Object.fromEntries(result.arweaveMap),\n totalFeePaid: result.totalFeePaid.toString(),\n estimate: serializeFeeEstimate(plan),\n };\n}\n","/**\n * Push planner/executor — the core of `rig push` (epic #222, ticket #226).\n *\n * `planPush` is network-free (relay/Arweave-wise — it only runs local git\n * plumbing through GitRepoReader plus one injectable async resolver step):\n * it classifies every ref update, computes the object delta against what the\n * remote already stores, hard-errors on oversize objects, and prices the\n * push. The returned {@link PushPlan} carries everything a confirm UI needs.\n *\n * `executePush` spends money: it uploads the planned objects through a\n * {@link Publisher} (ref-tip objects last, so a crashed push never leads to\n * a state where a discoverable tip's history is missing), then publishes ONE\n * cumulative kind:30618 whose `arweave` tags are the MERGE of the remote's\n * existing sha→txId map and the new uploads — kind:30618 is NIP-33\n * replaceable, so dropping prior tags would orphan earlier hints — and whose\n * `r` tags are the full new ref state. On a first push it publishes the\n * kind:30617 announcement before the refs event.\n *\n * Resume safety: uploads are content-addressed (Git-SHA-tagged), so re-running\n * `executePush` after a crash is safe — it consults the merged\n * remote + planned sha→txId map before paying for any upload, and a re-plan\n * with fresh remote state (whose `resolveMissing` finds the already-uploaded\n * objects via GraphQL) skips them entirely.\n */\n\nimport { buildRepoAnnouncement, buildRepoRefs } from './nip34-events.js';\nimport {\n EMPTY_BLOB_SHA,\n MAX_OBJECT_SIZE,\n type GitObjectType,\n} from './objects.js';\nimport {\n flooredUploadFee,\n type FeeRates,\n type PublishReceipt,\n type Publisher,\n} from './publisher.js';\nimport { GitError, type GitRef, type GitRepoReader } from './repo-reader.js';\nimport type { RemoteState } from './remote-state.js';\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** A ref update rejected because it is not a fast-forward. */\nexport interface RejectedRefUpdate {\n refname: string;\n localSha: string;\n remoteSha: string;\n}\n\n/** Thrown by {@link planPush} when a non-fast-forward update lacks `force`. */\nexport class NonFastForwardError extends Error {\n constructor(\n /** The refs that would need `--force` to update. */\n public readonly refs: RejectedRefUpdate[]\n ) {\n super(\n `non-fast-forward update rejected for ${refs\n .map((r) => r.refname)\n .join(', ')} — re-run with force to overwrite the remote ref(s)`\n );\n this.name = 'NonFastForwardError';\n }\n}\n\n/** One object exceeding {@link MAX_OBJECT_SIZE}. */\nexport interface OversizeObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes. */\n size: number;\n /** Path the object was reached by (blobs / non-root trees), if known. */\n path?: string;\n}\n\n/**\n * Thrown by {@link planPush} when any object in the delta exceeds the 95KB\n * upload limit (hard error in v1 — the paid blob path is a follow-up spike).\n */\nexport class OversizeObjectsError extends Error {\n constructor(\n /** The offending objects with paths and sizes. */\n public readonly objects: OversizeObject[]\n ) {\n super(\n `${objects.length} object(s) exceed the ${MAX_OBJECT_SIZE} byte upload limit: ` +\n objects\n .map((o) => `${o.path ?? o.sha} (${o.size} bytes)`)\n .join(', ')\n );\n this.name = 'OversizeObjectsError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plan types\n// ---------------------------------------------------------------------------\n\n/** How a ref moves relative to the remote. */\nexport type RefUpdateKind =\n /** Ref does not exist on the remote yet. */\n | 'new'\n /** Remote tip is an ancestor of the local tip. */\n | 'fast-forward'\n /** Non-fast-forward, allowed because `force` was set. */\n | 'forced'\n /** Local and remote tips already match — nothing to push. */\n | 'up-to-date';\n\n/** One planned ref update (deletions are out of scope in v1). */\nexport interface RefUpdate {\n /** Full refname, e.g. `refs/heads/main`. */\n refname: string;\n /** Local tip SHA (tag object SHA for annotated tags). */\n localSha: string;\n /** Remote tip SHA, or null when the ref is new. */\n remoteSha: string | null;\n kind: RefUpdateKind;\n}\n\n/** One object scheduled for upload. */\nexport interface PlannedObject {\n sha: string;\n type: GitObjectType;\n /** Body size in bytes (what the upload fee is charged on). */\n size: number;\n /** Path the object was reached by, if any (blobs / non-root trees). */\n path?: string;\n /** True when this SHA is the tip of a planned ref update (uploaded last). */\n isRefTip: boolean;\n}\n\n/** Pre-push fee estimate — render this in the confirm table. */\nexport interface PushFeeEstimate {\n /** Number of objects to upload. */\n objectCount: number;\n /** Total bytes across all planned object bodies. */\n totalObjectBytes: number;\n /** Σ max(size × uploadFeePerByte, minUploadFee) — smallest asset unit. */\n uploadFee: bigint;\n /** Number of events to publish (refs event + announcement on first push). */\n eventCount: number;\n /** eventCount × eventFee (smallest asset unit). */\n eventFees: bigint;\n /** uploadFee + eventFees. */\n totalFee: bigint;\n /**\n * Objects excluded from the upload because their body is zero bytes — the\n * git empty blob, which the store rejects as malformed (F00). Reconstructed\n * locally on clone/fetch, so nothing is lost; surfaced here so the fee table\n * can report the skip honestly.\n */\n skippedEmptyCount: number;\n}\n\n/** Everything `executePush` (and a confirm UI) needs. */\nexport interface PushPlan {\n repoId: string;\n /** Every considered ref with its classification (incl. up-to-date). */\n refUpdates: RefUpdate[];\n /**\n * Full new ref state to publish as `r` tags: the remote's refs overlaid\n * with the planned updates (refs not being pushed are preserved — v1\n * never deletes). Ordered with the HEAD target first, which is what\n * `buildRepoRefs` derives the HEAD symref tag from.\n */\n newRefs: Record<string, string>;\n /** HEAD symref target for the new state (first key of {@link newRefs}). */\n headSymref: string | null;\n /**\n * Objects to upload, dependency-safe order: ref-tip objects last so a\n * crashed push never uploads a tip whose history is missing.\n */\n objects: PlannedObject[];\n /**\n * Zero-byte objects (the git empty blob) excluded from {@link objects}: the\n * store rejects a zero-byte kind:5094 upload as malformed (F00), so `rig`\n * never uploads it — the commit/tree still references it and clone/fetch\n * synthesizes it locally. Kept for honest receipts, never uploaded.\n */\n skippedEmptyObjects: PlannedObject[];\n /**\n * sha→txId hints known WITHOUT uploading: the remote's `arweave` tags\n * plus anything `resolveMissing` found. Merged into the published\n * kind:30618 so prior hints are never dropped.\n */\n knownShaToTxId: Map<string, string>;\n /** True when no kind:30617 exists yet — executePush announces first. */\n announceNeeded: boolean;\n /** Announcement metadata used when {@link announceNeeded}. */\n announcement: { name: string; description: string };\n estimate: PushFeeEstimate;\n}\n\nexport interface PlanPushOptions {\n repoReader: GitRepoReader;\n remoteState: RemoteState;\n /** Fee rates from `Publisher.getFeeRates()`. */\n feeRates: FeeRates;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /**\n * Full refnames to push (e.g. `['refs/heads/main']`). Defaults to every\n * local branch and tag. Refs that don't exist locally are an error\n * (deletions are out of scope in v1).\n */\n refs?: string[];\n /** Allow non-fast-forward updates (default false → hard error). */\n force?: boolean;\n /** Repo name/description for the first-push announcement. */\n announcement?: { name?: string; description?: string };\n /**\n * Async resolver for SHAs the remote's `arweave` tags don't cover —\n * consulted before deciding to re-upload. Defaults to\n * `remoteState.resolveMissing` (GraphQL fallback); injectable so the\n * planner core stays testable without network.\n */\n resolveMissing?: (shas: string[]) => Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// planPush\n// ---------------------------------------------------------------------------\n\n/**\n * Classify ref updates, compute the object delta, enforce the size limit,\n * and price the push. Throws {@link NonFastForwardError} /\n * {@link OversizeObjectsError} (both carry structured data for UIs).\n */\nexport async function planPush(options: PlanPushOptions): Promise<PushPlan> {\n const { repoReader, remoteState, feeRates, repoId, force = false } = options;\n const resolveMissing =\n options.resolveMissing ?? remoteState.resolveMissing.bind(remoteState);\n\n // 1. Select and classify refs. -------------------------------------------\n const { head, refs: localRefs } = await repoReader.listRefs();\n const localByName = new Map(localRefs.map((r) => [r.refname, r]));\n\n let selected: GitRef[];\n if (options.refs !== undefined) {\n selected = options.refs.map((name) => {\n const ref = localByName.get(name);\n if (!ref) {\n throw new Error(\n `ref ${JSON.stringify(name)} does not exist locally ` +\n '(ref deletion is out of scope in v1)'\n );\n }\n return ref;\n });\n } else {\n selected = localRefs;\n }\n\n const refUpdates: RefUpdate[] = [];\n const rejected: RejectedRefUpdate[] = [];\n for (const ref of selected) {\n const remoteSha = remoteState.refs.get(ref.refname) ?? null;\n if (remoteSha === null) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'new' });\n continue;\n }\n if (remoteSha === ref.sha) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'up-to-date' });\n continue;\n }\n let fastForward = false;\n try {\n fastForward = await repoReader.isAncestor(remoteSha, ref.sha);\n } catch (err) {\n // Remote tip unknown locally (never fetched) or not a commit-ish —\n // we can't prove ancestry, so treat it as non-fast-forward.\n if (!(err instanceof GitError)) throw err;\n fastForward = false;\n }\n if (fastForward) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'fast-forward' });\n } else if (force) {\n refUpdates.push({ refname: ref.refname, localSha: ref.sha, remoteSha, kind: 'forced' });\n } else {\n rejected.push({ refname: ref.refname, localSha: ref.sha, remoteSha });\n }\n }\n if (rejected.length > 0) throw new NonFastForwardError(rejected);\n\n const updates = refUpdates.filter((u) => u.kind !== 'up-to-date');\n\n // 2. Object delta: reachable from the new tips, minus what the remote has.\n const wantTips = [...new Set(updates.map((u) => u.localSha))];\n const haveTips = [...new Set(remoteState.refs.values())];\n const delta =\n wantTips.length > 0\n ? await repoReader.objectsBetweenWithPaths(wantTips, haveTips)\n : [];\n\n const knownShaToTxId = new Map(remoteState.shaToTxId);\n let candidates = delta.filter((o) => !knownShaToTxId.has(o.sha));\n if (candidates.length > 0) {\n // The remote's `arweave` tags may lag reality (e.g. a crashed push\n // uploaded objects but never published the refs event) — resolve the\n // gaps before paying to re-upload content-addressed data.\n const resolved = await resolveMissing(candidates.map((o) => o.sha));\n for (const [sha, txId] of resolved) knownShaToTxId.set(sha, txId);\n candidates = candidates.filter((o) => !knownShaToTxId.has(o.sha));\n }\n\n // 3. Sizes + oversize hard error. -----------------------------------------\n const pathBySha = new Map(candidates.map((c) => [c.sha, c.path]));\n const { objects: stats, missing } = await repoReader.statObjects(\n candidates.map((c) => c.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during planning: ${missing.join(', ')}`\n );\n }\n\n const oversize: OversizeObject[] = [];\n for (const stat of stats) {\n if (stat.size > MAX_OBJECT_SIZE) {\n const path = pathBySha.get(stat.sha);\n oversize.push({ ...stat, ...(path ? { path } : {}) });\n }\n }\n if (oversize.length > 0) throw new OversizeObjectsError(oversize);\n\n // 4. Split off the empty blob, then order the rest ref-tips-last. ----------\n // The git empty blob uploads as an empty kind:5094 `i` value, which the\n // store rejects as malformed (F00). Skip it: it is reconstructed locally on\n // clone/fetch. Keyed off the EXACT empty-blob constant SHA — NOT a\n // `size === 0` heuristic, which would also match the (distinct, valid) empty\n // TREE object `4b825dc6…` and silently drop it (it is not synthesized on\n // read). An object whose SHA is EMPTY_BLOB_SHA is provably the empty blob.\n const tipShas = new Set(updates.map((u) => u.localSha));\n const planned: PlannedObject[] = [];\n const skippedEmptyObjects: PlannedObject[] = [];\n for (const stat of stats) {\n const path = pathBySha.get(stat.sha);\n const object: PlannedObject = {\n ...stat,\n ...(path ? { path } : {}),\n isRefTip: tipShas.has(stat.sha),\n };\n if (stat.sha === EMPTY_BLOB_SHA) skippedEmptyObjects.push(object);\n else planned.push(object);\n }\n const objects = [\n ...planned.filter((o) => !o.isRefTip),\n ...planned.filter((o) => o.isRefTip),\n ];\n\n // 5. Full new ref state (remote refs overlaid with updates), HEAD first. --\n const newRefsMap = new Map(remoteState.refs);\n for (const update of updates) newRefsMap.set(update.refname, update.localSha);\n\n const headSymref =\n head && newRefsMap.has(head)\n ? head\n : remoteState.headSymref && newRefsMap.has(remoteState.headSymref)\n ? remoteState.headSymref\n : ([...newRefsMap.keys()][0] ?? null);\n\n const newRefs: Record<string, string> = {};\n const headSha = headSymref ? newRefsMap.get(headSymref) : undefined;\n if (headSymref && headSha) newRefs[headSymref] = headSha;\n for (const [refname, sha] of newRefsMap) {\n if (refname !== headSymref) newRefs[refname] = sha;\n }\n\n // 6. Fee estimate. ---------------------------------------------------------\n // Per-object pricing, each upload floored at the destination route's\n // announced price (`minUploadFee`) — the exact same math the publisher\n // claims per packet, so the confirm table always equals what is paid.\n const announceNeeded = !remoteState.announced;\n const totalObjectBytes = objects.reduce((sum, o) => sum + o.size, 0);\n const uploadFee = objects.reduce(\n (sum, o) =>\n sum +\n flooredUploadFee(o.size, feeRates.uploadFeePerByte, feeRates.minUploadFee),\n 0n\n );\n const eventCount = 1 + (announceNeeded ? 1 : 0);\n const eventFees = BigInt(eventCount) * feeRates.eventFee;\n\n return {\n repoId,\n refUpdates,\n newRefs,\n headSymref,\n objects,\n skippedEmptyObjects,\n knownShaToTxId,\n announceNeeded,\n announcement: {\n name: options.announcement?.name ?? repoId,\n description: options.announcement?.description ?? '',\n },\n estimate: {\n objectCount: objects.length,\n totalObjectBytes,\n uploadFee,\n eventCount,\n eventFees,\n totalFee: uploadFee + eventFees,\n skippedEmptyCount: skippedEmptyObjects.length,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// executePush\n// ---------------------------------------------------------------------------\n\n/** Result of one object-upload step. */\nexport interface UploadStepResult {\n sha: string;\n txId: string;\n /** 0n when skipped (already uploaded — content-addressed resume). */\n feePaid: bigint;\n /** True when the object was already on Arweave and nothing was paid. */\n skipped: boolean;\n}\n\n/** Result of the whole push. */\nexport interface PushResult {\n /** Per-object results, in plan order. */\n uploads: UploadStepResult[];\n /** kind:30617 receipt, or null when the repo was already announced. */\n announceReceipt: PublishReceipt | null;\n /** kind:30618 (cumulative refs + arweave map) receipt. */\n refsReceipt: PublishReceipt;\n /**\n * The full sha→txId map published in the refs event: remote hints +\n * resolver finds + this push's uploads.\n */\n arweaveMap: Map<string, string>;\n /** Total fees actually paid (uploads + events), smallest asset unit. */\n totalFeePaid: bigint;\n}\n\nexport interface ExecutePushOptions {\n plan: PushPlan;\n publisher: Publisher;\n /**\n * Remote state — pass a FRESH fetch when resuming after a crash so its\n * `shaToTxId` (and `announced`) reflect what the previous attempt already\n * paid for.\n */\n remoteState: RemoteState;\n repoReader: GitRepoReader;\n /** Relay URLs to publish events to (plural from day one; size 1 today). */\n relayUrls: string[];\n}\n\n/** How many object bodies to hold in memory at once between read and upload. */\nconst READ_BATCH_SIZE = 100;\n\n/**\n * Execute a {@link PushPlan}: upload objects (ref tips last), then publish\n * the kind:30617 announcement (first push only) and ONE cumulative\n * kind:30618 whose `arweave` tags merge every known sha→txId hint with the\n * new uploads and whose `r` tags carry the full new ref state.\n *\n * Safe to re-run after a crash: SHAs already present in the merged\n * remote + plan map are skipped without paying.\n */\nexport async function executePush(\n options: ExecutePushOptions\n): Promise<PushResult> {\n const { plan, publisher, remoteState, repoReader, relayUrls } = options;\n\n // Merged sha→txId map: remote hints (fresh on resume) + plan-time finds.\n // Consulted before every upload — this is the resume-safety check.\n const merged = new Map([...remoteState.shaToTxId, ...plan.knownShaToTxId]);\n\n const resultBySha = new Map<string, UploadStepResult>();\n let totalFeePaid = 0n;\n\n const pending: PlannedObject[] = [];\n for (const object of plan.objects) {\n const knownTxId = merged.get(object.sha);\n if (knownTxId !== undefined) {\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: knownTxId,\n feePaid: 0n,\n skipped: true,\n });\n } else {\n pending.push(object);\n }\n }\n\n // Upload in plan order (ref tips are already last), reading bodies in\n // batches so memory stays bounded on large pushes.\n for (let i = 0; i < pending.length; i += READ_BATCH_SIZE) {\n const batch = pending.slice(i, i + READ_BATCH_SIZE);\n const { objects: read, missing } = await repoReader.readObjects(\n batch.map((o) => o.sha)\n );\n if (missing.length > 0) {\n throw new Error(\n `objects vanished from the local repository during push: ${missing.join(', ')}`\n );\n }\n const bodyBySha = new Map(read.map((r) => [r.sha, r.body]));\n for (const object of batch) {\n const body = bodyBySha.get(object.sha);\n if (!body) {\n throw new Error(\n `internal: cat-file returned no body for ${object.sha}`\n );\n }\n const receipt = await publisher.uploadGitObject({\n sha: object.sha,\n type: object.type,\n body,\n repoId: plan.repoId,\n // #368: the path the blob was reached by drives its Content-Type; a\n // non-blob object (no path) uploads as octet-stream.\n ...(object.path ? { path: object.path } : {}),\n });\n merged.set(object.sha, receipt.txId);\n totalFeePaid += receipt.feePaid;\n resultBySha.set(object.sha, {\n sha: object.sha,\n txId: receipt.txId,\n feePaid: receipt.feePaid,\n skipped: false,\n });\n }\n }\n\n // Announcement (first push only) goes before the refs event so a repo is\n // never referenced by an `a` tag before its kind:30617 exists. Re-check\n // the (fresh-on-resume) remote state so a crashed push doesn't announce\n // twice.\n let announceReceipt: PublishReceipt | null = null;\n if (plan.announceNeeded && !remoteState.announced) {\n const announceEvent = buildRepoAnnouncement(\n plan.repoId,\n plan.announcement.name,\n plan.announcement.description\n );\n announceReceipt = await publisher.publishEvent(announceEvent, relayUrls);\n totalFeePaid += announceReceipt.feePaid;\n }\n\n // ONE cumulative kind:30618: full ref state + MERGED arweave map (NIP-33\n // replaceable — dropping prior tags would orphan earlier sha→txId hints).\n const refsEvent = buildRepoRefs(\n plan.repoId,\n plan.newRefs,\n Object.fromEntries(merged)\n );\n const refsReceipt = await publisher.publishEvent(refsEvent, relayUrls);\n totalFeePaid += refsReceipt.feePaid;\n\n const uploads = plan.objects.map((o) => {\n const step = resultBySha.get(o.sha);\n if (!step) {\n throw new Error(`internal: no upload result recorded for ${o.sha}`);\n }\n return step;\n });\n\n return {\n uploads,\n announceReceipt,\n refsReceipt,\n arweaveMap: merged,\n totalFeePaid,\n };\n}\n","/**\n * Persists the apex payment-channel id (+ its chain context) per\n * (destination, chain) so a daemon RESTART can resume the EXISTING on-chain\n * channel instead of opening a new one.\n *\n * Why this is needed: `ChannelManager` persists the off-chain nonce/cumulative\n * watermark (keyed by channelId) but NOT the peer→channelId mapping. So after a\n * restart `openChannel()` would open + re-deposit into a fresh channel, which\n * reverts on a chain where the deposit already exists. With the channelId saved\n * here, the runner instead calls `trackChannel(channelId, context)` — which\n * rehydrates the nonce from the channel store — and signs against the live\n * channel with zero on-chain writes.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/** Chain context needed to re-track a channel (matches ChannelManager.trackChannel). */\nexport interface PersistedChannelContext {\n chainType: string;\n chainId: number;\n tokenNetworkAddress: string;\n tokenAddress?: string;\n recipient?: string;\n}\n\nexport interface PersistedApexChannel {\n channelId: string;\n context: PersistedChannelContext;\n}\n\ntype Store = Record<string, PersistedApexChannel>;\n\nfunction key(destination: string, chain: string): string {\n return `${destination}|${chain}`;\n}\n\nfunction readStore(path: string): Store {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Store;\n } catch {\n return {};\n }\n}\n\n/** Load the saved apex channel for (destination, chain), or null. */\nexport function loadApexChannel(\n path: string,\n destination: string,\n chain: string\n): PersistedApexChannel | null {\n return readStore(path)[key(destination, chain)] ?? null;\n}\n\n/** Save the apex channel for (destination, chain) with mode 0o600. */\nexport function saveApexChannel(\n path: string,\n destination: string,\n chain: string,\n record: PersistedApexChannel\n): void {\n const store = readStore(path);\n store[key(destination, chain)] = record;\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(store, null, 2), { mode: 0o600 });\n}\n","/**\n * Persists DYNAMIC targets — relays added via `toon_add_relay` and apexes added\n * via `toon_add_apex` — to `~/.toon-client/targets.json` so they survive a\n * daemon restart. The config file's single `relayUrl`/`btpUrl` remain the\n * permanent \"default\" target and are NOT stored here; only runtime additions are.\n *\n * On boot the `ClientRunner` seeds the default from config, then replays this\n * store to re-instantiate every dynamically-added relay/apex. Add/remove tool\n * calls write straight back here (last-write-wins, mode 0o600), mirroring the\n * `apex-channel-store.ts` pattern.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { configDir } from './config.js';\nimport type { ApexNegotiationConfig } from './config.js';\n\n/** A persisted relay read target. */\nexport interface PersistedRelayTarget {\n /** Relay WS URL (the map key). */\n relayUrl: string;\n}\n\n/** A persisted apex write target with its discovered settlement negotiation. */\nexport interface PersistedApexTarget {\n /** BTP WS endpoint of the apex (the map key). */\n btpUrl: string;\n /** Settlement negotiation, discovered from the apex's kind:10032 announcement. */\n negotiation: ApexNegotiationConfig;\n /** Child peers reached via this apex's channel (e.g. `[\"store\",\"swap\"]`). */\n apexChildPeers?: string[];\n /** Per-write fee override (base units). Falls back to the daemon default. */\n feePerEvent?: string;\n /** Relay the negotiation was discovered on (re-discovery / provenance). */\n discoveredFrom?: string;\n}\n\nexport interface TargetsFile {\n relays: PersistedRelayTarget[];\n apexes: PersistedApexTarget[];\n}\n\nconst EMPTY: TargetsFile = { relays: [], apexes: [] };\n\n/** Default targets-store path: `~/.toon-client/targets.json`. */\nexport function defaultTargetsPath(): string {\n return join(configDir(), 'targets.json');\n}\n\n/** Read + parse the targets store, returning empty arrays when absent/invalid. */\nexport function loadTargets(path = defaultTargetsPath()): TargetsFile {\n let parsed: Partial<TargetsFile>;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial<TargetsFile>;\n } catch {\n return { relays: [], apexes: [] };\n }\n return {\n relays: Array.isArray(parsed.relays) ? parsed.relays : [],\n apexes: Array.isArray(parsed.apexes) ? parsed.apexes : [],\n };\n}\n\nfunction write(path: string, data: TargetsFile): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\n/** Upsert a relay target (idempotent by `relayUrl`). */\nexport function saveRelayTarget(\n relayUrl: string,\n path = defaultTargetsPath()\n): void {\n const store = loadTargets(path);\n if (!store.relays.some((r) => r.relayUrl === relayUrl)) {\n store.relays.push({ relayUrl });\n write(path, store);\n }\n}\n\n/** Remove a relay target. Returns true if it was present. */\nexport function removeRelayTarget(\n relayUrl: string,\n path = defaultTargetsPath()\n): boolean {\n const store = loadTargets(path);\n const next = store.relays.filter((r) => r.relayUrl !== relayUrl);\n if (next.length === store.relays.length) return false;\n store.relays = next;\n write(path, store);\n return true;\n}\n\n/** Upsert an apex target (last-write-wins by `btpUrl`). */\nexport function saveApexTarget(\n target: PersistedApexTarget,\n path = defaultTargetsPath()\n): void {\n const store = loadTargets(path);\n store.apexes = store.apexes.filter((a) => a.btpUrl !== target.btpUrl);\n store.apexes.push(target);\n write(path, store);\n}\n\n/** Remove an apex target. Returns true if it was present. */\nexport function removeApexTarget(\n btpUrl: string,\n path = defaultTargetsPath()\n): boolean {\n const store = loadTargets(path);\n const next = store.apexes.filter((a) => a.btpUrl !== btpUrl);\n if (next.length === store.apexes.length) return false;\n store.apexes = next;\n write(path, store);\n return true;\n}\n\nexport { EMPTY as EMPTY_TARGETS };\n","/**\n * Discover an apex's settlement negotiation by reading its `kind:10032`\n * (`ILP_PEER_INFO_KIND`) announcement off a relay, rather than making the caller\n * hand-supply chain/settlement params. An apex (relay node) publishes its\n * `IlpPeerInfo` — `btpEndpoint`, `supportedChains`, `settlementAddresses`,\n * `preferredTokens`, `tokenNetworks` — to its relay; this module subscribes for\n * it, parses via core's `parseIlpPeerInfo`, and maps it onto the daemon's\n * `ApexNegotiationConfig` so the runner can stand up a `ToonClient` against it.\n *\n * The relay is injected as a `RelaySubscription` (already started) so this is\n * unit-testable with a fake WS and reuses a relay the runner already manages.\n */\n\nimport {\n ILP_PEER_INFO_KIND,\n parseIlpPeerInfo,\n isEventExpired,\n} from '@toon-protocol/core';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { RelaySubscription } from '../relay-subscription.js';\nimport type { ApexNegotiationConfig } from './config.js';\nimport type { SettlementChain } from '../control-api.js';\n\nexport interface DiscoverApexParams {\n /** A started relay subscription to query for the apex's kind:10032. */\n relay: RelaySubscription;\n /** ILP address of the apex to match (e.g. `g.proxy`). */\n ilpAddress: string;\n /** Optional apex Nostr pubkey to narrow the REQ filter (64-char hex). */\n pubkey?: string;\n /** Preferred settlement chain family; defaults to the first supported chain. */\n chain?: SettlementChain;\n /** Child peers reached via this apex's channel (e.g. `[\"store\",\"swap\"]`). */\n childPeers?: string[];\n /** Max time to wait for the announcement, ms. Default 15000. */\n timeoutMs?: number;\n /** Poll interval against the relay buffer, ms. Default 250. */\n pollMs?: number;\n}\n\nexport interface DiscoveredApex {\n btpUrl: string;\n negotiation: ApexNegotiationConfig;\n apexChildPeers?: string[];\n}\n\n/**\n * Thrown when discovery fails. `retryable` is true for a TIMEOUT (the apex may\n * just be slow/offline — the caller can retry once it's reachable) and false\n * for a malformed announcement (retrying won't help until the apex republishes).\n */\nexport class ApexDiscoveryError extends Error {\n constructor(\n message: string,\n readonly retryable = false\n ) {\n super(message);\n this.name = 'ApexDiscoveryError';\n }\n}\n\n/**\n * Subscribe for the apex's `kind:10032`, wait for a match, and map it to an\n * `ApexNegotiationConfig` + `btpUrl`. Rejects with {@link ApexDiscoveryError}\n * on timeout or when the announcement lacks settlement params for any chain.\n */\nexport async function discoverApex(\n params: DiscoverApexParams\n): Promise<DiscoveredApex> {\n const { relay, ilpAddress, pubkey, chain, childPeers } = params;\n const timeoutMs = params.timeoutMs ?? 15_000;\n const pollMs = params.pollMs ?? 250;\n\n const subId = relay.subscribe(\n [\n {\n kinds: [ILP_PEER_INFO_KIND],\n ...(pubkey ? { authors: [pubkey] } : {}),\n },\n ],\n `apex-discovery-${ilpAddress}`\n );\n\n try {\n const deadline = Date.now() + timeoutMs;\n let cursor = 0;\n while (Date.now() < deadline) {\n // Scan the WHOLE relay buffer (no subId filter), not just our discovery\n // subscription. `RelaySubscription` de-dups by event.id globally, so if a\n // pre-existing subscription already buffered the announcement, the relay's\n // replay to our fresh REQ is dropped and would never appear under our\n // subId — buffer-wide reads find it regardless of which sub received it.\n const { events, cursor: next } = relay.getEvents({ cursor });\n cursor = next;\n const match = events.find((e) => matchesApex(e, ilpAddress, pubkey));\n if (match) return mapAnnouncement(match, { chain, childPeers });\n await delay(pollMs);\n }\n throw new ApexDiscoveryError(\n `Timed out after ${timeoutMs}ms waiting for the apex kind:${ILP_PEER_INFO_KIND} ` +\n `announcement for \"${ilpAddress}\" on the relay. Is the relay reachable and the apex online?`,\n true // retryable: the apex may just be slow/offline\n );\n } finally {\n relay.unsubscribe(subId);\n }\n}\n\n/**\n * Whether a kind:10032 event announces the target apex's ILP address. When a\n * `pubkey` is given it must also match the event author — multiple nodes can\n * advertise the same ILP address (e.g. `g.proxy`), so the pubkey is how\n * the caller disambiguates which one to add. (Buffer-wide scanning means we can\n * no longer rely on the REQ's `authors` filter to do this for us.)\n */\nfunction matchesApex(\n event: NostrEvent,\n ilpAddress: string,\n pubkey?: string\n): boolean {\n if (event.kind !== ILP_PEER_INFO_KIND) return false;\n if (pubkey && event.pubkey !== pubkey) return false;\n // A NIP-40-expired announcement means the apex stopped re-publishing — it is\n // offline and its advertised BTP endpoint is unreachable, so don't match it\n // (issue #261). Discovery keeps waiting for a fresh, unexpired announcement.\n if (isEventExpired(event)) return false;\n try {\n const info = parseIlpPeerInfo(event);\n const addrs = info.ilpAddresses ?? [info.ilpAddress];\n return addrs.includes(ilpAddress) || info.ilpAddress === ilpAddress;\n } catch {\n return false;\n }\n}\n\n/** Map a parsed kind:10032 announcement onto the daemon's negotiation config. */\nfunction mapAnnouncement(\n event: NostrEvent,\n opts: { chain?: SettlementChain; childPeers?: string[] }\n): DiscoveredApex {\n const info = parseIlpPeerInfo(event);\n const chains = info.supportedChains ?? [];\n if (chains.length === 0) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no supportedChains — cannot settle.`\n );\n }\n\n // Pick the chainKey: prefer one whose family matches the requested chain,\n // else the first advertised chain.\n const chainKey =\n (opts.chain\n ? chains.find((c) => c.split(':')[0] === opts.chain)\n : undefined) ?? chains[0];\n if (!chainKey) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no usable settlement chain.`\n );\n }\n const family = chainKey.split(':')[0] as SettlementChain;\n const settlementAddress = info.settlementAddresses?.[chainKey];\n if (!settlementAddress) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced no settlementAddress for chain \"${chainKey}\".`\n );\n }\n\n const btpUrl = info.btpEndpoint;\n if (!btpUrl) {\n throw new ApexDiscoveryError(\n `Apex \"${info.ilpAddress}\" announced an empty btpEndpoint — cannot open a BTP session.`\n );\n }\n\n const negotiation: ApexNegotiationConfig = {\n destination: info.ilpAddress,\n peerId: info.ilpAddress.split('.').at(-1) ?? info.ilpAddress,\n chain: family,\n chainKey,\n // EVM chainKeys are `evm:<network>:<chainId>`; non-EVM carry no numeric id.\n // Tolerate the 2-part `evm:<chainId>` form some connectors advertise.\n chainId:\n family === 'evm'\n ? Number(chainKey.split(':')[2] ?? chainKey.split(':')[1] ?? 0)\n : 0,\n settlementAddress,\n ...(info.preferredTokens?.[chainKey]\n ? { tokenAddress: info.preferredTokens[chainKey] }\n : {}),\n ...(info.tokenNetworks?.[chainKey]\n ? { tokenNetwork: info.tokenNetworks[chainKey] }\n : {}),\n };\n\n return {\n btpUrl,\n negotiation,\n ...(opts.childPeers && opts.childPeers.length > 0\n ? { apexChildPeers: opts.childPeers }\n : {}),\n };\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * Fastify route registration for the `toon-clientd` control API. Each route\n * is a thin adapter: parse/validate the request, call the `ClientRunner`, map\n * errors to the uniform `ErrorResponse` envelope.\n *\n * Bound to loopback only by the daemon entry — there is no auth layer because\n * the surface never leaves `127.0.0.1`.\n */\n\nimport type { FastifyInstance, FastifyReply } from 'fastify';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport type { ClientRunner } from './client-runner.js';\nimport {\n BalancesUnavailableError,\n InvalidPayloadError,\n NotReadyError,\n PublishRejectedError,\n TargetError,\n} from './client-runner.js';\nimport { ApexDiscoveryError } from './apex-discovery.js';\nimport {\n GitError,\n NonFastForwardError,\n OversizeObjectsError,\n} from '@toon-protocol/rig';\nimport type {\n AddApexRequest,\n AddRelayRequest,\n EventsQuery,\n FundWalletRequest,\n GitCommentRequest,\n GitEstimateRequest,\n GitIssueRequest,\n GitPatchRequest,\n GitPushRequest,\n GitStatusRequest,\n HttpFetchPaidRequest,\n ChannelDepositRequest,\n CloseChannelRequest,\n SettleChannelRequest,\n OpenChannelRequest,\n PublishRequest,\n PublishUnsignedRequest,\n QueryRequest,\n RemoveApexRequest,\n RemoveRelayRequest,\n SettlementChain,\n SettleSwapClaimsRequest,\n SubscribeRequest,\n SwapRequest,\n UploadMediaRequest,\n} from '../control-api.js';\n\nexport function registerRoutes(\n app: FastifyInstance,\n runner: ClientRunner\n): void {\n app.get('/status', async () => runner.getStatus());\n\n app.post<{ Body: PublishRequest }>('/publish', async (req, reply) => {\n const body = req.body;\n if (!body || !isSignedEvent(body.event)) {\n return sendError(reply, 400, 'invalid_event', {\n detail: 'body.event must be a fully-signed Nostr event (id + sig).',\n });\n }\n try {\n return await runner.publish(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: PublishUnsignedRequest }>(\n '/publish-unsigned',\n async (req, reply) => {\n const body = req.body;\n if (!body || !Number.isInteger(body.kind)) {\n return sendError(reply, 400, 'invalid_event', {\n detail: 'body.kind (integer) is required; the daemon signs the event.',\n });\n }\n try {\n return await runner.publishUnsigned(body);\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: UploadMediaRequest }>('/upload-media', async (req, reply) => {\n const body = req.body;\n const hasData = typeof body?.dataBase64 === 'string' && body.dataBase64 !== '';\n const hasPath = typeof body?.filePath === 'string' && body.filePath !== '';\n if (!body || (!hasData && !hasPath)) {\n return sendError(reply, 400, 'invalid_media', {\n detail:\n 'body.dataBase64 (base64-encoded media bytes) or body.filePath (absolute path) is required.',\n });\n }\n try {\n return await runner.uploadMedia(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: QueryRequest }>('/query', async (req, reply) => {\n const body = req.body;\n if (!body || body.filters === undefined) {\n return sendError(reply, 400, 'invalid_filters', {\n detail: 'body.filters is required (a NIP-01 filter or array of filters).',\n });\n }\n try {\n const events = await runner.query(body.filters, body.timeoutMs);\n return { events };\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SubscribeRequest }>('/subscribe', async (req, reply) => {\n const body = req.body;\n if (!body || body.filters === undefined) {\n return sendError(reply, 400, 'invalid_filters', {\n detail:\n 'body.filters is required (a NIP-01 filter or array of filters).',\n });\n }\n try {\n return runner.subscribe(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get<{\n Querystring: {\n subId?: string;\n cursor?: string;\n limit?: string;\n relayUrl?: string;\n };\n }>('/events', async (req) => {\n const q = req.query;\n const query: EventsQuery = {};\n if (q.subId) query.subId = q.subId;\n if (q.cursor !== undefined) query.cursor = Number(q.cursor);\n if (q.limit !== undefined) query.limit = Number(q.limit);\n if (q.relayUrl) query.relayUrl = q.relayUrl;\n return runner.getEvents(query);\n });\n\n app.post<{ Body: OpenChannelRequest }>('/channels', async (req, reply) => {\n try {\n return await runner.openChannel(req.body?.destination);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get('/channels', async () => runner.getChannels());\n\n app.get('/balances', async (_req, reply) => {\n try {\n return await runner.getBalances();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: ChannelDepositRequest }>('/channels/deposit', async (req, reply) => {\n try {\n return await runner.depositToChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: CloseChannelRequest }>('/channels/close', async (req, reply) => {\n try {\n return await runner.closeChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SettleChannelRequest }>('/channels/settle', async (req, reply) => {\n try {\n return await runner.settleChannel(req.body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: SwapRequest }>('/swap', async (req, reply) => {\n const body = req.body;\n if (\n !body ||\n !body.destination ||\n body.amount === undefined ||\n !body.swapPubkey ||\n !body.pair ||\n !body.chainRecipient\n ) {\n return sendError(reply, 400, 'invalid_swap', {\n detail:\n 'body.destination, amount, swapPubkey, pair, and chainRecipient are required.',\n });\n }\n try {\n return await runner.swap(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n // Received swap claims: persisted watermarks + settlement drive (#352).\n app.get('/swap/claims', async () => runner.listSwapClaims());\n\n app.post<{ Body: SettleSwapClaimsRequest }>(\n '/swap/settle',\n async (req, reply) => {\n try {\n return await runner.settleSwapClaims(req.body ?? {});\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: HttpFetchPaidRequest }>(\n '/http-fetch-paid',\n async (req, reply) => {\n const body = req.body;\n if (!body || typeof body.url !== 'string' || body.url === '') {\n return sendError(reply, 400, 'invalid_url', {\n detail: 'body.url (absolute resource URL) is required.',\n });\n }\n try {\n return await runner.httpFetchPaid(body);\n } catch (err) {\n return mapError(reply, err);\n }\n }\n );\n\n app.post<{ Body: FundWalletRequest }>('/fund-wallet', async (req, reply) => {\n try {\n // Returns immediately with a 'pending' snapshot — the drip runs async in\n // the daemon (the Mina faucet outlasts the host's tool-call timeout).\n return runner.fundWallet(req.body ?? {});\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.get<{ Querystring: { chain?: SettlementChain } }>(\n '/fund-wallet/status',\n async (req) => runner.getFundStatus(req.query?.chain)\n );\n\n app.get('/targets', async () => runner.getTargets());\n\n app.post<{ Body: AddRelayRequest }>('/relays', async (req, reply) => {\n const url = req.body?.relayUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_relay', {\n detail: 'body.relayUrl is required.',\n });\n }\n try {\n await runner.addRelay(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.delete<{ Body: RemoveRelayRequest }>('/relays', async (req, reply) => {\n const url = req.body?.relayUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_relay', {\n detail: 'body.relayUrl is required.',\n });\n }\n try {\n runner.removeRelay(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.post<{ Body: AddApexRequest }>('/apex', async (req, reply) => {\n const body = req.body;\n if (!body || !body.ilpAddress || !body.relayUrl) {\n return sendError(reply, 400, 'invalid_apex', {\n detail: 'body.ilpAddress and body.relayUrl are required.',\n });\n }\n try {\n return await runner.addApex(body);\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n app.delete<{ Body: RemoveApexRequest }>('/apex', async (req, reply) => {\n const url = req.body?.btpUrl;\n if (!url) {\n return sendError(reply, 400, 'invalid_apex', {\n detail: 'body.btpUrl is required.',\n });\n }\n try {\n await runner.removeApex(url);\n return runner.getTargets();\n } catch (err) {\n return mapError(reply, err);\n }\n });\n\n // ── Git write path (/git/*, epic #222 ticket #227) ─────────────────────────\n\n app.post<{ Body: GitEstimateRequest }>('/git/estimate', async (req, reply) => {\n const body = req.body;\n if (!body || !isNonEmptyString(body.repoPath) || !isNonEmptyString(body.repoId)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoPath (local repository path) and body.repoId are required.',\n });\n }\n try {\n return await runner.gitEstimate(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitPushRequest }>('/git/push', async (req, reply) => {\n const body = req.body;\n if (!body || !isNonEmptyString(body.repoPath) || !isNonEmptyString(body.repoId)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoPath (local repository path) and body.repoId are required.',\n });\n }\n try {\n return await runner.gitPush(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitIssueRequest }>('/git/issue', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.title) || !isNonEmptyString(body.body)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.title, and body.body are required.',\n });\n }\n try {\n return await runner.gitIssue(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitCommentRequest }>('/git/comment', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.rootEventId) || !isNonEmptyString(body.body)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.rootEventId, and body.body are required.',\n });\n }\n try {\n return await runner.gitComment(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitPatchRequest }>('/git/patch', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.title)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }) and body.title are required ' +\n '(plus exactly one of patchText | repoPath+range).',\n });\n }\n try {\n return await runner.gitPatch(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n\n app.post<{ Body: GitStatusRequest }>('/git/status', async (req, reply) => {\n const body = req.body;\n if (!body || !body.repoAddr || !isNonEmptyString(body.targetEventId) || !isNonEmptyString(body.status)) {\n return sendError(reply, 400, 'invalid_git_request', {\n detail:\n 'body.repoAddr ({ ownerPubkey, repoId }), body.targetEventId, and ' +\n 'body.status (open | applied | closed | draft) are required.',\n });\n }\n try {\n return await runner.gitStatus(body);\n } catch (err) {\n return mapGitError(reply, err);\n }\n });\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\n/**\n * Map `/git/*` errors: planPush's structured errors surface as clean JSON\n * with their data attached (a confirm UI / CLI renders the refs or the\n * oversize table directly), a git plumbing failure (not a repo, bad range,\n * vanished objects) is caller-fixable 400, and everything else falls through\n * to the standard mapper (bootstrapping 503, funding 402, ...).\n */\nfunction mapGitError(reply: FastifyReply, err: unknown): FastifyReply {\n if (err instanceof NonFastForwardError) {\n // 409 Conflict: the remote moved — re-run with force or pull first.\n return reply.status(409).send({\n error: 'non_fast_forward',\n detail: err.message,\n refs: err.refs,\n });\n }\n if (err instanceof OversizeObjectsError) {\n // 413 Payload Too Large: objects over the 95KB v1 upload limit.\n return reply.status(413).send({\n error: 'oversize_objects',\n detail: err.message,\n objects: err.objects,\n });\n }\n if (err instanceof GitError) {\n return sendError(reply, 400, 'git_error', { detail: err.message });\n }\n return mapError(reply, err);\n}\n\nfunction isSignedEvent(event: unknown): event is NostrEvent {\n if (typeof event !== 'object' || event === null) return false;\n const e = event as Record<string, unknown>;\n return (\n typeof e['id'] === 'string' &&\n typeof e['sig'] === 'string' &&\n typeof e['pubkey'] === 'string' &&\n typeof e['kind'] === 'number'\n );\n}\n\nfunction mapError(reply: FastifyReply, err: unknown): FastifyReply {\n if (err instanceof NotReadyError) {\n return sendError(reply, 503, 'bootstrapping', {\n detail: err.message,\n retryable: true,\n });\n }\n if (err instanceof InvalidPayloadError) {\n return sendError(reply, 400, 'invalid_payload', { detail: err.message });\n }\n if (err instanceof PublishRejectedError) {\n return sendError(reply, 502, 'rejected', { detail: err.message });\n }\n if (err instanceof BalancesUnavailableError) {\n // The chain RPC/provider behind the balances handler stalled — retryable,\n // and attributed to the balances handler, NOT the relay/apex (#199).\n return sendError(reply, 504, 'balances_unavailable', {\n detail: err.message,\n retryable: true,\n });\n }\n if (err instanceof TargetError) {\n // 404 for \"no such target\", 400 otherwise — both are caller-fixable.\n const status = /no such/i.test(err.message) ? 404 : 400;\n return sendError(reply, status, 'invalid_target', { detail: err.message });\n }\n if (err instanceof ApexDiscoveryError) {\n // A timeout is a retryable 504 (apex may be slow/offline); a malformed\n // announcement is a non-retryable 502 (the apex must republish).\n return err.retryable\n ? sendError(reply, 504, 'discovery_timeout', {\n detail: err.message,\n retryable: true,\n })\n : sendError(reply, 502, 'discovery_failed', { detail: err.message });\n }\n // Settle called before the grace period elapsed — retryable (the UI polls\n // until `now >= settleableAt`). The client throws a tagged error; 425 Too Early.\n if (err instanceof Error && (err as { name?: string }).name === 'SettleTooEarlyError') {\n return sendError(reply, 425, 'settle_too_early', { detail: err.message, retryable: true });\n }\n // First-write channel OPEN reverted because the settlement wallet has no\n // native gas (toon-meta#65). The client throws a tagged `ChannelFundingError`\n // with an actionable \"fund the wallet\" message; on the upload path it is\n // wrapped in a `ToonClientError('Failed to publish event')`, so walk the\n // `cause` chain. 402 Payment Required — caller-fixable + retryable once funded.\n const funding = findChannelFundingError(err);\n if (funding) {\n return sendError(reply, 402, 'insufficient_gas', {\n detail: funding.message,\n retryable: true,\n });\n }\n return sendError(reply, 500, 'internal_error', {\n detail: err instanceof Error ? err.message : String(err),\n });\n}\n\n/**\n * Walk an error's `cause` chain and return the first `ChannelFundingError`\n * (matched by name to avoid a hard import of the client package). The client\n * tags the gas-revert error and `publishEvent` wraps it one level deep on the\n * upload path, so the actionable message can be nested.\n */\nfunction findChannelFundingError(err: unknown): Error | undefined {\n let cur: unknown = err;\n for (let i = 0; i < 10 && cur != null; i++) {\n if (cur instanceof Error && (cur as { name?: string }).name === 'ChannelFundingError') {\n return cur;\n }\n cur = cur instanceof Error ? (cur as { cause?: unknown }).cause : undefined;\n }\n return undefined;\n}\n\nfunction sendError(\n reply: FastifyReply,\n status: number,\n error: string,\n extra: { detail?: string; retryable?: boolean } = {}\n): FastifyReply {\n return reply.status(status).send({\n error,\n ...(extra.detail ? { detail: extra.detail } : {}),\n ...(extra.retryable ? { retryable: true } : {}),\n });\n}\n"],"mappings":"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","names":["delay","join","key","defaultWebSocketFactory","resolve","REPOSITORY_ANNOUNCEMENT_KIND","execFile","spawn","promisify","resolve","execFileAsync","promisify","execFile","stat","mkdirSync","writeFileSync","dirname","mkdirSync","readFileSync","writeFileSync","dirname","join","join","readFileSync","mkdirSync","dirname","writeFileSync","delay","errMsg","join","serializePushPlan","serializePushResult","serializeFeeEstimate","resolve","key"]}
|