@toon-protocol/rig 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Git-to-TOON write path core — build git objects and NIP-34 events for the Rig
7
7
  | `rig identity create` | rig (free) | mint a fresh BIP-39 identity into the encrypted keystore — the phrase is shown ONCE. Removes the cold-start wall |
8
8
  | `rig identity show` | rig (free) | the active identity's source + derived pubkey (never the phrase) |
9
9
  | `rig identity import` | rig (free) | write an existing phrase (read from stdin, never argv) to the keystore |
10
- | `rig init` | rig (free) | one-shot repo setup: identity (offers to generate one) + `toon.*` git config |
10
+ | `rig init` | rig (free) | one-shot repo setup: git repo (offers to `git init`, or `--git-init`) + identity (offers to generate one) + `toon.*` git config + repo-local git commit-author from your nostr identity (so `rig commit` works out of the box) |
11
11
  | `rig remote add/remove/list` | rig (free) | relays as REAL git remotes (`origin` = default publish target) |
12
12
  | `rig clone <relay-url> <owner>/<repo-id> [dir]` | rig (free) | bootstrap a repo from TOON: relay state + SHA-verified Arweave objects → a real, push-capable git repository. Shadows `git clone` |
13
13
  | `rig fetch [remote]` | rig (free) | download the missing object delta + update `refs/remotes/<remote>/*` (no merge — `rig merge origin/main`). Shadows `git fetch` |
@@ -26,9 +26,13 @@ Git-to-TOON write path core — build git objects and NIP-34 events for the Rig
26
26
  ```sh
27
27
  npm install -g @toon-protocol/rig
28
28
 
29
+ # 0. start anywhere — an empty directory is fine; `rig init` (step 2) will
30
+ # offer to `git init` it for you.
31
+ mkdir my-repo && cd my-repo
32
+
29
33
  # 1. identity — mint one on the spot (no BIP-39 tooling needed). The seed
30
34
  # phrase is shown ONCE (write it down) and stored in an encrypted keystore
31
- # under ~/.toon-client. This is the cold-start step `git init` never needs.
35
+ # under ~/.toon-client.
32
36
  rig identity create
33
37
  rig identity show # active source + derived pubkey (never the phrase)
34
38
  # Already have a phrase? Bring it instead of generating:
@@ -38,8 +42,15 @@ rig identity import # …or import it into the keystore (reads stdin)
38
42
 
39
43
  # 2. one-shot repo setup (free): writes toon.repoid + toon.owner to the
40
44
  # repo's local git config and reports which identity source is active.
41
- # No identity yet? `rig init` offers to generate one (or pass
42
- # --generate-identity for the non-interactive path).
45
+ # Not a git repo yet? `rig init` offers to `git init` here (or pass
46
+ # --git-init). No identity yet? It offers to generate one (or pass
47
+ # --generate-identity). `--git-init --generate-identity` is a fully
48
+ # non-interactive fresh setup: empty dir → rig-ready in one command.
49
+ # It ALSO sets the repo-local git commit-author from your nostr identity
50
+ # (never --global) so `rig commit`/`git commit` work out of the box and
51
+ # every commit is attributed to the signer: user.email = <npub>@nostr,
52
+ # user.name = your kind:0 profile display name when published (read
53
+ # best-effort from a relay) else the npub. Re-run to refresh the name.
43
54
  rig init # default repo id = directory name
44
55
  rig init --repo-id my-repo # or pick one
45
56
 
@@ -49,7 +60,9 @@ rig remote add origin wss://relay.example
49
60
  rig remote list # names + URLs; --json for machines
50
61
  rig remote remove origin
51
62
 
52
- # 4. work exactly like git — unowned commands pass through to system git:
63
+ # 4. work exactly like git — unowned commands pass through to system git.
64
+ # `rig commit`/`git commit` already work: `rig init` set this repo's
65
+ # author to your nostr identity (no `git config user.*` step needed).
53
66
  rig status # IS `git status`
54
67
  rig add -p && rig commit -m "fix"
55
68
  rig log --oneline # pagers, colors, prompts all behave like git
@@ -0,0 +1,281 @@
1
+ import {
2
+ queryRelay
3
+ } from "./chunk-3HRFDH7H.js";
4
+
5
+ // src/standalone/network-bootstrap.ts
6
+ import {
7
+ CHAIN_PRESETS,
8
+ GenesisPeerLoader,
9
+ isEventExpired,
10
+ parseIlpPeerInfo
11
+ } from "@toon-protocol/core";
12
+ var ILP_PEER_INFO_KIND = 10032;
13
+ var DISCOVERY_TIMEOUT_MS = 5e3;
14
+ function parseRoutes(content) {
15
+ try {
16
+ const parsed = JSON.parse(content);
17
+ const routes = parsed.routes;
18
+ if (typeof routes !== "object" || routes === null) return void 0;
19
+ const { publish, store } = routes;
20
+ const out = {
21
+ ...typeof publish === "string" && publish.length > 0 ? { publish } : {},
22
+ ...typeof store === "string" && store.length > 0 ? { store } : {}
23
+ };
24
+ return out.publish || out.store ? out : void 0;
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ async function discoverAnnouncedPeers(relayUrl, options = {}) {
30
+ const factory = options.webSocketFactory ?? defaultDiscoveryWebSocketFactory();
31
+ const events = await queryRelay(
32
+ relayUrl,
33
+ { kinds: [ILP_PEER_INFO_KIND], limit: 100 },
34
+ options.timeoutMs ?? DISCOVERY_TIMEOUT_MS,
35
+ factory
36
+ );
37
+ const latestByAuthor = /* @__PURE__ */ new Map();
38
+ for (const event of events) {
39
+ if (event.kind !== ILP_PEER_INFO_KIND) continue;
40
+ const prev = latestByAuthor.get(event.pubkey);
41
+ if (!prev || event.created_at > prev.created_at) {
42
+ latestByAuthor.set(event.pubkey, event);
43
+ }
44
+ }
45
+ const peers = [];
46
+ for (const event of latestByAuthor.values()) {
47
+ if (isEventExpired(event)) {
48
+ continue;
49
+ }
50
+ let info;
51
+ try {
52
+ info = parseIlpPeerInfo(event);
53
+ } catch {
54
+ continue;
55
+ }
56
+ const routes = parseRoutes(event.content);
57
+ peers.push({
58
+ pubkey: event.pubkey,
59
+ info,
60
+ ...routes ? { routes } : {},
61
+ createdAt: event.created_at
62
+ });
63
+ }
64
+ return peers;
65
+ }
66
+ function defaultDiscoveryWebSocketFactory() {
67
+ return (url) => {
68
+ const ctor = globalThis.WebSocket;
69
+ if (!ctor) {
70
+ throw new Error(
71
+ "No global WebSocket constructor (Node >= 22 required) for announce discovery"
72
+ );
73
+ }
74
+ return new ctor(url);
75
+ };
76
+ }
77
+ function pickPaymentPeer(peers, seedPubkeys) {
78
+ const seeded = peers.filter((p) => seedPubkeys.includes(p.pubkey));
79
+ if (seeded.length > 0) {
80
+ return seeded.sort((a, b) => b.createdAt - a.createdAt)[0];
81
+ }
82
+ const payable = peers.filter(
83
+ (p) => (p.info.httpEndpoint || p.info.btpEndpoint) && p.info.settlementAddresses && Object.keys(p.info.settlementAddresses).length > 0
84
+ );
85
+ if (payable.length === 0) return void 0;
86
+ const publishEdges = payable.filter(
87
+ (p) => p.routes?.publish !== void 0 && p.routes.publish === p.info.ilpAddress
88
+ );
89
+ const pool = publishEdges.length > 0 ? publishEdges : payable;
90
+ return pool.sort((a, b) => b.createdAt - a.createdAt)[0];
91
+ }
92
+ var DEVNET_ZONE = "devnet.toonprotocol.dev";
93
+ var DEVNET_CHAIN_RPC_URLS = {
94
+ "evm:31337": "https://evm-rpc.devnet.toonprotocol.dev",
95
+ "solana:devnet": "https://solana-rpc.devnet.toonprotocol.dev"
96
+ };
97
+ function hostOf(url) {
98
+ if (!url) return void 0;
99
+ try {
100
+ return new URL(url).hostname;
101
+ } catch {
102
+ return void 0;
103
+ }
104
+ }
105
+ function isDevnetZonePeer(peer) {
106
+ if (!peer) return false;
107
+ return [
108
+ hostOf(peer.info.httpEndpoint),
109
+ hostOf(peer.info.btpEndpoint),
110
+ hostOf(peer.info.relayUrl)
111
+ ].some((h) => h !== void 0 && (h === DEVNET_ZONE || h.endsWith(`.${DEVNET_ZONE}`)));
112
+ }
113
+ function evmChainIdOf(chain) {
114
+ const parts = chain.split(":");
115
+ if (parts[0] !== "evm") return void 0;
116
+ const raw = parts.length >= 3 ? parts[2] : parts[1];
117
+ const id = Number.parseInt(raw ?? "", 10);
118
+ return Number.isNaN(id) ? void 0 : id;
119
+ }
120
+ function evmPresetForChain(chain) {
121
+ const id = evmChainIdOf(chain);
122
+ if (id === void 0) return void 0;
123
+ for (const preset of Object.values(CHAIN_PRESETS)) {
124
+ if (preset.chainId === id) {
125
+ return {
126
+ rpcUrl: preset.rpcUrl,
127
+ usdcAddress: preset.usdcAddress,
128
+ tokenNetworkAddress: preset.tokenNetworkAddress
129
+ };
130
+ }
131
+ }
132
+ return void 0;
133
+ }
134
+ function resolveChainSettlement(chain, explicit, announce) {
135
+ const family = chain.split(":")[0] ?? chain;
136
+ const preset = evmPresetForChain(chain);
137
+ const devnetRpc = isDevnetZonePeer(announce) ? DEVNET_CHAIN_RPC_URLS[chain] : void 0;
138
+ const rpcUrl = explicit.chainRpcUrls?.[chain] ?? devnetRpc ?? preset?.rpcUrl;
139
+ const tokenAddress = explicit.preferredTokens?.[chain] ?? announce?.info.preferredTokens?.[chain] ?? (preset?.usdcAddress || void 0);
140
+ const tokenNetwork = explicit.tokenNetworks?.[chain] ?? announce?.info.tokenNetworks?.[chain] ?? (preset?.tokenNetworkAddress || void 0);
141
+ return {
142
+ chain,
143
+ family,
144
+ ...rpcUrl ? { rpcUrl } : {},
145
+ ...tokenAddress ? { tokenAddress } : {},
146
+ ...tokenNetwork ? { tokenNetwork } : {}
147
+ };
148
+ }
149
+ var TokenNetworkUnderivableError = class extends Error {
150
+ constructor(chain, announce, relayUrl) {
151
+ const announceRef = announce ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}\u2026 on ${relayUrl}` : `no kind:10032 announce was found on ${relayUrl}`;
152
+ super(
153
+ `cannot derive the TokenNetwork contract for settlement chain "${chain}": ${announceRef} carries no tokenNetworks["${chain}"], and no built-in chain preset matches its chain id \u2014 add tokenNetworks["${chain}"] to the client config (or pick another chain via TOON_CLIENT_CHAIN / the chain config field)`
154
+ );
155
+ this.name = "TokenNetworkUnderivableError";
156
+ }
157
+ };
158
+ async function selectSettlementChain(options) {
159
+ const { explicitChain, announcedChains } = options;
160
+ if (explicitChain) {
161
+ if (explicitChain.includes(":")) {
162
+ return {
163
+ chain: explicitChain,
164
+ reason: "explicit",
165
+ detail: `chain ${explicitChain} set by config`
166
+ };
167
+ }
168
+ const familyMatch = announcedChains.find(
169
+ (c) => (c.split(":")[0] ?? c) === explicitChain
170
+ );
171
+ if (!familyMatch) {
172
+ throw new Error(
173
+ `configured chain family "${explicitChain}" is not announced by the payment peer (announced: ${announcedChains.join(", ") || "none"}) \u2014 set a full chain id (e.g. "evm:31337") to force it`
174
+ );
175
+ }
176
+ return {
177
+ chain: familyMatch,
178
+ reason: "explicit",
179
+ detail: `chain family "${explicitChain}" set by config \u2192 ${familyMatch}`
180
+ };
181
+ }
182
+ const live = (options.records ?? []).filter((r) => !r.closed && announcedChains.includes(r.chain)).sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
183
+ const persisted = live[0];
184
+ if (persisted) {
185
+ return {
186
+ chain: persisted.chain,
187
+ reason: "persisted-channel",
188
+ detail: `existing payment channel on ${persisted.chain} (rig channel map)`
189
+ };
190
+ }
191
+ const evmChains = announcedChains.filter((c) => c.startsWith("evm:"));
192
+ if (options.evmAddress && options.probeBalance) {
193
+ for (const chain of evmChains) {
194
+ const settlement = options.resolveSettlement(chain);
195
+ if (!settlement.rpcUrl || !settlement.tokenAddress) continue;
196
+ try {
197
+ const balance = await options.probeBalance({
198
+ rpcUrl: settlement.rpcUrl,
199
+ tokenAddress: settlement.tokenAddress,
200
+ owner: options.evmAddress
201
+ });
202
+ if (balance > 0n) {
203
+ return {
204
+ chain,
205
+ reason: "funded",
206
+ detail: `wallet holds ${balance} token base units on ${chain}`
207
+ };
208
+ }
209
+ } catch {
210
+ }
211
+ }
212
+ }
213
+ const fallback = evmChains[0] ?? announcedChains[0];
214
+ if (!fallback) {
215
+ throw new Error(
216
+ "the payment peer announces no settlement chains \u2014 cannot select a chain for paid writes (set supportedChains/chain in the client config)"
217
+ );
218
+ }
219
+ return {
220
+ chain: fallback,
221
+ reason: "default",
222
+ detail: evmChains[0] ? `first EVM chain announced by the payment peer` : `first chain announced by the payment peer (no EVM chain announced)`
223
+ };
224
+ }
225
+ var BALANCE_OF_SELECTOR = "0x70a08231";
226
+ async function evmTokenBalance(args) {
227
+ const fetchImpl = args.fetchImpl ?? fetch;
228
+ const owner = args.owner.replace(/^0x/, "").toLowerCase().padStart(64, "0");
229
+ const controller = new AbortController();
230
+ const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5e3);
231
+ try {
232
+ const res = await fetchImpl(args.rpcUrl, {
233
+ method: "POST",
234
+ headers: { "content-type": "application/json" },
235
+ body: JSON.stringify({
236
+ jsonrpc: "2.0",
237
+ id: 1,
238
+ method: "eth_call",
239
+ params: [
240
+ { to: args.tokenAddress, data: `${BALANCE_OF_SELECTOR}${owner}` },
241
+ "latest"
242
+ ]
243
+ }),
244
+ signal: controller.signal
245
+ });
246
+ if (!res.ok) {
247
+ throw new Error(`eth_call failed: HTTP ${res.status}`);
248
+ }
249
+ const body = await res.json();
250
+ if (typeof body.result !== "string") {
251
+ throw new Error(`eth_call failed: ${body.error?.message ?? "no result"}`);
252
+ }
253
+ return BigInt(body.result === "0x" ? "0x0" : body.result);
254
+ } finally {
255
+ clearTimeout(timer);
256
+ }
257
+ }
258
+ function loadGenesisSeed() {
259
+ return GenesisPeerLoader.loadGenesisPeers()[0];
260
+ }
261
+ function genesisSeedPubkeys() {
262
+ return GenesisPeerLoader.loadGenesisPeers().map((p) => p.pubkey);
263
+ }
264
+
265
+ export {
266
+ ILP_PEER_INFO_KIND,
267
+ DISCOVERY_TIMEOUT_MS,
268
+ discoverAnnouncedPeers,
269
+ pickPaymentPeer,
270
+ DEVNET_ZONE,
271
+ DEVNET_CHAIN_RPC_URLS,
272
+ isDevnetZonePeer,
273
+ evmPresetForChain,
274
+ resolveChainSettlement,
275
+ TokenNetworkUnderivableError,
276
+ selectSettlementChain,
277
+ evmTokenBalance,
278
+ loadGenesisSeed,
279
+ genesisSeedPubkeys
280
+ };
281
+ //# sourceMappingURL=chunk-AFJNFDUQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/network-bootstrap.ts"],"sourcesContent":["/**\n * Network bootstrap for the standalone (embedded-client) rig path (#264):\n * resolve the payment topology — uplink, ILP destinations/routes, settlement\n * chain and its on-chain parameters — from the network itself instead of\n * hand-fed constants.\n *\n * Sources, in strict precedence order (first hit wins, per field):\n *\n * 1. EXPLICIT USER CONFIG — env vars / shared client-config file fields.\n * 2. LIVE kind:10032 ANNOUNCE — the payment peer's `IlpPeerInfo` event\n * discovered on the relay-origin (the relay the paid command resolved\n * via `rig remote`, i.e. what the user pointed rig at). The announce\n * carries `btpEndpoint`/`httpEndpoint` (uplink), `ilpAddress` (channel\n * anchor), `supportedChains` + `settlementAddresses` (settlement), and\n * the out-of-band `routes` map (`{publish, store}` ILP destinations).\n * 3. GENESIS SEED — `@toon-protocol/core`'s committed genesis peer seed\n * (core >= 2.0.1 ships the live devnet apex), the offline fallback when\n * the relay is unreachable or serves no valid announce.\n *\n * Chain-level parameters the announce does NOT carry (EVM TokenNetwork\n * contract, token address, RPC URL) are derived per selected chain:\n * explicit config > announce (`tokenNetworks`/`preferredTokens`, when a peer\n * announces them) > the deployed-devnet endpoint table (canonical\n * `*.devnet.toonprotocol.dev` hosts, keyed off the announce's own hostnames)\n * > core's deterministic chain presets (`CHAIN_PRESETS`, matched by chain\n * id — e.g. `evm:31337` is the Foundry/anvil deploy whose TOON contract\n * addresses are deterministic).\n *\n * Settlement-chain selection (#260 root cause 4) — simple and predictable:\n *\n * 1. EXPLICIT — `TOON_CLIENT_CHAIN` env / `chain` config field (family or\n * full chain id), or the first entry of an explicit `supportedChains`.\n * 2. PERSISTED CHANNEL — the chain of the most recently used live channel\n * recorded for this identity in the #262 channel map (a channel there\n * means collateral is already locked on that chain).\n * 3. FUNDED — the first announced EVM chain where the identity's token\n * balance is > 0 (one `eth_call` per candidate).\n * 4. DEFAULT — the first EVM chain the peer announces (else the first\n * announced chain), with a printed rationale.\n *\n * This module is pure Node + `@toon-protocol/core` (rig's own core 2.x —\n * distinct from the embedded client's internal core; see\n * `../cli/standalone-mode.ts` for the coexistence note).\n */\n\nimport {\n CHAIN_PRESETS,\n GenesisPeerLoader,\n isEventExpired,\n parseIlpPeerInfo,\n type GenesisPeer,\n type IlpPeerInfo,\n} from '@toon-protocol/core';\nimport {\n queryRelay,\n type NostrEvent,\n type WebSocketFactory,\n} from '../remote-state.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** kind:10032 — ILP peer info announcement (mirrors core's constant). */\nexport const ILP_PEER_INFO_KIND = 10032;\n\n/** Publish/store ILP route hints riding out-of-band in announce content. */\nexport interface AnnouncedRoutes {\n publish?: string;\n store?: string;\n}\n\n/** One schema-valid, unexpired kind:10032 announce seen on the relay. */\nexport interface AnnouncedPeer {\n /** Announcing identity (event author, hex). */\n pubkey: string;\n /** Parsed + validated `IlpPeerInfo` (rig's core 2.x parser). */\n info: IlpPeerInfo;\n /** Out-of-band `routes` content field, when present and well-formed. */\n routes?: AnnouncedRoutes;\n /** Announce timestamp (freshness tiebreaker). */\n createdAt: number;\n}\n\n/** Per-chain settlement parameters resolved for the embedded client. */\nexport interface ChainSettlement {\n /** Chain id as announced/configured, e.g. `evm:31337`. */\n chain: string;\n /** `evm` | `solana` | `mina` | … (first chain-id segment). */\n family: string;\n /** JSON-RPC / GraphQL endpoint, when derivable. */\n rpcUrl?: string;\n /** Preferred token (USDC) address, when derivable. */\n tokenAddress?: string;\n /** EVM TokenNetwork contract, when derivable. */\n tokenNetwork?: string;\n}\n\n/** Why a settlement chain was selected (documented rule, in order). */\nexport type ChainSelectionReason =\n | 'explicit'\n | 'persisted-channel'\n | 'funded'\n | 'default';\n\n/** The selected settlement chain plus its rationale. */\nexport interface ChainSelection {\n chain: string;\n reason: ChainSelectionReason;\n /** One human-readable line explaining the pick. */\n detail: string;\n}\n\n// ---------------------------------------------------------------------------\n// Announce discovery\n// ---------------------------------------------------------------------------\n\n/** Default bounded wait for the kind:10032 relay query. */\nexport const DISCOVERY_TIMEOUT_MS = 5000;\n\nfunction parseRoutes(content: string): AnnouncedRoutes | undefined {\n try {\n const parsed = JSON.parse(content) as { routes?: unknown };\n const routes = parsed.routes;\n if (typeof routes !== 'object' || routes === null) return undefined;\n const { publish, store } = routes as Record<string, unknown>;\n const out: AnnouncedRoutes = {\n ...(typeof publish === 'string' && publish.length > 0 ? { publish } : {}),\n ...(typeof store === 'string' && store.length > 0 ? { store } : {}),\n };\n return out.publish || out.store ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Query `relayUrl` for kind:10032 announces and return the latest\n * schema-valid, unexpired announce per author. Invalid/expired events are\n * skipped silently (the relay serves plenty of non-peer 10032 experiments).\n */\nexport async function discoverAnnouncedPeers(\n relayUrl: string,\n options: {\n timeoutMs?: number;\n webSocketFactory?: WebSocketFactory;\n } = {}\n): Promise<AnnouncedPeer[]> {\n const factory =\n options.webSocketFactory ?? defaultDiscoveryWebSocketFactory();\n const events = await queryRelay(\n relayUrl,\n { kinds: [ILP_PEER_INFO_KIND], limit: 100 },\n options.timeoutMs ?? DISCOVERY_TIMEOUT_MS,\n factory\n );\n\n const latestByAuthor = new Map<string, NostrEvent>();\n for (const event of events) {\n if (event.kind !== ILP_PEER_INFO_KIND) continue;\n const prev = latestByAuthor.get(event.pubkey);\n if (!prev || event.created_at > prev.created_at) {\n latestByAuthor.set(event.pubkey, event);\n }\n }\n\n const peers: AnnouncedPeer[] = [];\n for (const event of latestByAuthor.values()) {\n if (isEventExpired(event as Parameters<typeof isEventExpired>[0])) {\n continue;\n }\n let info: IlpPeerInfo;\n try {\n info = parseIlpPeerInfo(event as Parameters<typeof parseIlpPeerInfo>[0]);\n } catch {\n continue; // not a schema-valid IlpPeerInfo announce\n }\n const routes = parseRoutes(event.content);\n peers.push({\n pubkey: event.pubkey,\n info,\n ...(routes ? { routes } : {}),\n createdAt: event.created_at,\n });\n }\n return peers;\n}\n\nfunction defaultDiscoveryWebSocketFactory(): WebSocketFactory {\n return (url) => {\n const ctor = (\n globalThis as {\n WebSocket?: new (url: string) => ReturnType<WebSocketFactory>;\n }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) for announce discovery'\n );\n }\n return new ctor(url);\n };\n}\n\n/**\n * Pick THE payment peer among discovered announces:\n *\n * 1. the announce authored by a genesis-seed pubkey (the committed apex\n * identity — the strongest signal),\n * 2. else an announce that can actually take paid writes: has an uplink\n * endpoint AND `settlementAddresses`, preferring one whose own\n * `ilpAddress` is its `routes.publish` (the publish edge — that is\n * where rig pays first),\n * 3. freshest `created_at` breaks remaining ties.\n */\nexport function pickPaymentPeer(\n peers: AnnouncedPeer[],\n seedPubkeys: readonly string[]\n): AnnouncedPeer | undefined {\n const seeded = peers.filter((p) => seedPubkeys.includes(p.pubkey));\n if (seeded.length > 0) {\n return seeded.sort((a, b) => b.createdAt - a.createdAt)[0];\n }\n\n const payable = peers.filter(\n (p) =>\n (p.info.httpEndpoint || p.info.btpEndpoint) &&\n p.info.settlementAddresses &&\n Object.keys(p.info.settlementAddresses).length > 0\n );\n if (payable.length === 0) return undefined;\n const publishEdges = payable.filter(\n (p) => p.routes?.publish !== undefined && p.routes.publish === p.info.ilpAddress\n );\n const pool = publishEdges.length > 0 ? publishEdges : payable;\n return pool.sort((a, b) => b.createdAt - a.createdAt)[0];\n}\n\n// ---------------------------------------------------------------------------\n// Per-chain settlement derivation\n// ---------------------------------------------------------------------------\n\n/**\n * The one deployed-network endpoint table rig ships: the canonical TOON\n * devnet (`*.devnet.toonprotocol.dev`, see toon-meta docs/deployment.md).\n * Chain RPC endpoints are deployment infrastructure the kind:10032 announce\n * does not carry (yet), so when the discovered peer's own endpoints live\n * under this zone, its self-hosted chains resolve to the canonical hosts.\n * Same status as `DEVNET_FAUCET_URL` in `../cli/fund.ts`. Everything here\n * is overridable by explicit `chainRpcUrls` config.\n */\nexport const DEVNET_ZONE = 'devnet.toonprotocol.dev';\n\n/** Canonical devnet chain RPC endpoints (self-hosted chains only). */\nexport const DEVNET_CHAIN_RPC_URLS: Readonly<Record<string, string>> = {\n 'evm:31337': 'https://evm-rpc.devnet.toonprotocol.dev',\n 'solana:devnet': 'https://solana-rpc.devnet.toonprotocol.dev',\n};\n\n/** Hostname of a ws(s)/http(s) URL, or undefined when unparsable. */\nfunction hostOf(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).hostname;\n } catch {\n return undefined;\n }\n}\n\n/** True when the announce's own endpoints live under the TOON devnet zone. */\nexport function isDevnetZonePeer(\n peer: Pick<AnnouncedPeer, 'info'> | undefined\n): boolean {\n if (!peer) return false;\n return [\n hostOf(peer.info.httpEndpoint),\n hostOf(peer.info.btpEndpoint),\n hostOf(peer.info.relayUrl),\n ].some((h) => h !== undefined && (h === DEVNET_ZONE || h.endsWith(`.${DEVNET_ZONE}`)));\n}\n\n/** Numeric chain id of an `evm:<id>` / `evm:<name>:<id>` chain key. */\nfunction evmChainIdOf(chain: string): number | undefined {\n const parts = chain.split(':');\n if (parts[0] !== 'evm') return undefined;\n const raw = parts.length >= 3 ? parts[2] : parts[1];\n const id = Number.parseInt(raw ?? '', 10);\n return Number.isNaN(id) ? undefined : id;\n}\n\n/**\n * Core chain preset matching an EVM chain key by numeric chain id. Presets\n * carry the DETERMINISTIC TOON contract addresses per chain (e.g. the\n * `anvil` 31337 Foundry deploy), which is what makes `tokenNetwork`\n * derivable without the announce carrying it.\n */\nexport function evmPresetForChain(chain: string):\n | { rpcUrl: string; usdcAddress: string; tokenNetworkAddress: string }\n | undefined {\n const id = evmChainIdOf(chain);\n if (id === undefined) return undefined;\n for (const preset of Object.values(CHAIN_PRESETS)) {\n if (preset.chainId === id) {\n return {\n rpcUrl: preset.rpcUrl,\n usdcAddress: preset.usdcAddress,\n tokenNetworkAddress: preset.tokenNetworkAddress,\n };\n }\n }\n return undefined;\n}\n\n/** Explicit per-chain maps from the shared client-config file. */\nexport interface ExplicitChainConfig {\n chainRpcUrls?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Resolve one chain's settlement parameters: explicit config > announce >\n * devnet endpoint table (RPC only, devnet-zone peers) > core chain preset.\n * Fields stay undefined when no source covers them — callers decide whether\n * that is fatal (see {@link requireTokenNetwork}).\n */\nexport function resolveChainSettlement(\n chain: string,\n explicit: ExplicitChainConfig,\n announce?: AnnouncedPeer\n): ChainSettlement {\n const family = chain.split(':')[0] ?? chain;\n const preset = evmPresetForChain(chain);\n const devnetRpc = isDevnetZonePeer(announce)\n ? DEVNET_CHAIN_RPC_URLS[chain]\n : undefined;\n\n const rpcUrl =\n explicit.chainRpcUrls?.[chain] ?? devnetRpc ?? preset?.rpcUrl;\n const tokenAddress =\n explicit.preferredTokens?.[chain] ??\n announce?.info.preferredTokens?.[chain] ??\n (preset?.usdcAddress || undefined);\n const tokenNetwork =\n explicit.tokenNetworks?.[chain] ??\n announce?.info.tokenNetworks?.[chain] ??\n (preset?.tokenNetworkAddress || undefined);\n\n return {\n chain,\n family,\n ...(rpcUrl ? { rpcUrl } : {}),\n ...(tokenAddress ? { tokenAddress } : {}),\n ...(tokenNetwork ? { tokenNetwork } : {}),\n };\n}\n\n/** An EVM chain was selected but its TokenNetwork cannot be derived. */\nexport class TokenNetworkUnderivableError extends Error {\n constructor(chain: string, announce: AnnouncedPeer | undefined, relayUrl: string) {\n const announceRef = announce\n ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}… on ${relayUrl}`\n : `no kind:10032 announce was found on ${relayUrl}`;\n super(\n `cannot derive the TokenNetwork contract for settlement chain ` +\n `\"${chain}\": ${announceRef} carries no tokenNetworks[\"${chain}\"], ` +\n `and no built-in chain preset matches its chain id — add ` +\n `tokenNetworks[\"${chain}\"] to the client config (or pick another ` +\n `chain via TOON_CLIENT_CHAIN / the chain config field)`\n );\n this.name = 'TokenNetworkUnderivableError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Settlement-chain selection (#260 root cause 4)\n// ---------------------------------------------------------------------------\n\n/** The slice of a #262 channel-map record chain selection consumes. */\nexport interface ChannelRecordLike {\n chain: string;\n lastUsedAt: string;\n /** True when the withdraw flow closed/settled the channel. */\n closed: boolean;\n}\n\n/** Async token-balance probe (injectable; default is a raw `eth_call`). */\nexport type EvmBalanceProbe = (args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n}) => Promise<bigint>;\n\nexport interface SelectChainOptions {\n /**\n * Explicit chain choice: full chain id (`evm:31337`), a family\n * (`evm` | `solana` | `mina`) matched against announced chains, or the\n * first entry of an explicit `supportedChains` config.\n */\n explicitChain?: string;\n /** Chains the payment peer announces (announce order preserved). */\n announcedChains: readonly string[];\n /** #262 channel-map records for this identity (any anchor). */\n records?: readonly ChannelRecordLike[];\n /** Identity's EVM address (funded-chain probe). */\n evmAddress?: string;\n /** Per-chain settlement resolver (for RPC/token of probe candidates). */\n resolveSettlement: (chain: string) => ChainSettlement;\n /** Balance probe; probe errors just skip the candidate. */\n probeBalance?: EvmBalanceProbe;\n}\n\n/**\n * Select the settlement chain per the documented rule: explicit >\n * persisted channel > funded EVM chain > first announced EVM chain.\n */\nexport async function selectSettlementChain(\n options: SelectChainOptions\n): Promise<ChainSelection> {\n const { explicitChain, announcedChains } = options;\n\n // 1 — explicit config always wins (even when the peer does not announce\n // it: the user may know better than a stale announce).\n if (explicitChain) {\n if (explicitChain.includes(':')) {\n return {\n chain: explicitChain,\n reason: 'explicit',\n detail: `chain ${explicitChain} set by config`,\n };\n }\n const familyMatch = announcedChains.find(\n (c) => (c.split(':')[0] ?? c) === explicitChain\n );\n if (!familyMatch) {\n throw new Error(\n `configured chain family \"${explicitChain}\" is not announced by the ` +\n `payment peer (announced: ${announcedChains.join(', ') || 'none'}) — ` +\n 'set a full chain id (e.g. \"evm:31337\") to force it'\n );\n }\n return {\n chain: familyMatch,\n reason: 'explicit',\n detail: `chain family \"${explicitChain}\" set by config → ${familyMatch}`,\n };\n }\n\n // 2 — a live persisted channel means collateral is already locked there.\n const live = (options.records ?? [])\n .filter((r) => !r.closed && announcedChains.includes(r.chain))\n .sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));\n const persisted = live[0];\n if (persisted) {\n return {\n chain: persisted.chain,\n reason: 'persisted-channel',\n detail: `existing payment channel on ${persisted.chain} (rig channel map)`,\n };\n }\n\n // 3 — first announced EVM chain where the identity holds tokens.\n const evmChains = announcedChains.filter((c) => c.startsWith('evm:'));\n if (options.evmAddress && options.probeBalance) {\n for (const chain of evmChains) {\n const settlement = options.resolveSettlement(chain);\n if (!settlement.rpcUrl || !settlement.tokenAddress) continue;\n try {\n const balance = await options.probeBalance({\n rpcUrl: settlement.rpcUrl,\n tokenAddress: settlement.tokenAddress,\n owner: options.evmAddress,\n });\n if (balance > 0n) {\n return {\n chain,\n reason: 'funded',\n detail: `wallet holds ${balance} token base units on ${chain}`,\n };\n }\n } catch {\n // Unreachable RPC / bad token — not a candidate; fall through.\n }\n }\n }\n\n // 4 — predictable default: the first EVM chain the peer announces.\n const fallback = evmChains[0] ?? announcedChains[0];\n if (!fallback) {\n throw new Error(\n 'the payment peer announces no settlement chains — cannot select a ' +\n 'chain for paid writes (set supportedChains/chain in the client config)'\n );\n }\n return {\n chain: fallback,\n reason: 'default',\n detail: evmChains[0]\n ? `first EVM chain announced by the payment peer`\n : `first chain announced by the payment peer (no EVM chain announced)`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Default EVM balance probe (raw JSON-RPC eth_call, no extra deps)\n// ---------------------------------------------------------------------------\n\n/** ERC-20 `balanceOf(address)` selector. */\nconst BALANCE_OF_SELECTOR = '0x70a08231';\n\n/**\n * Read an ERC-20 balance with one raw `eth_call` (keeps the probe free of\n * viem — the embedded client is not started yet when selection runs).\n */\nexport async function evmTokenBalance(args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}): Promise<bigint> {\n const fetchImpl = args.fetchImpl ?? fetch;\n const owner = args.owner.replace(/^0x/, '').toLowerCase().padStart(64, '0');\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5000);\n try {\n const res = await fetchImpl(args.rpcUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'eth_call',\n params: [\n { to: args.tokenAddress, data: `${BALANCE_OF_SELECTOR}${owner}` },\n 'latest',\n ],\n }),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`eth_call failed: HTTP ${res.status}`);\n }\n const body = (await res.json()) as { result?: unknown; error?: { message?: string } };\n if (typeof body.result !== 'string') {\n throw new Error(`eth_call failed: ${body.error?.message ?? 'no result'}`);\n }\n return BigInt(body.result === '0x' ? '0x0' : body.result);\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Genesis seed access (rig's own core 2.x — live devnet apex since 2.0.1)\n// ---------------------------------------------------------------------------\n\n/** The committed genesis peer seed (first entry), if any. */\nexport function loadGenesisSeed(): GenesisPeer | undefined {\n return GenesisPeerLoader.loadGenesisPeers()[0];\n}\n\n/** All committed genesis-seed pubkeys (announce-selection preference). */\nexport function genesisSeedPubkeys(): string[] {\n return GenesisPeerLoader.loadGenesisPeers().map((p) => p.pubkey);\n}\n"],"mappings":"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","names":[]}
@@ -464,16 +464,16 @@ var StandalonePublisher = class {
464
464
  return result;
465
465
  }
466
466
  /**
467
- * On-chain wallet balances for the embedded identity — a FREE read on the
468
- * UNSTARTED client (no nonce guard, no uplink, no channel): the client
469
- * reads the settlement chain its channels actually use (its EVM key is
470
- * derived at construction; Solana/Mina keys only exist after a start, so
471
- * those chains appear once a start-requiring command ran same
472
- * best-effort contract as the client's own getBalances).
467
+ * The full multi-chain wallet view (#299) for the embedded identity — native
468
+ * coin + configured tokens (USDC) per chain a FREE read on the UNSTARTED
469
+ * client (no nonce guard, no uplink, no channel). The client derives the
470
+ * Solana/Mina addresses from the mnemonic on demand, so ALL configured chains
471
+ * appear even before a start. Best-effort per chain (an unreachable RPC yields
472
+ * an `unreadable` chain, not a failure).
473
473
  */
474
- async readWalletBalances() {
475
- if (!this.client.getBalances) return [];
476
- return await this.client.getBalances();
474
+ async readWalletChainBalances() {
475
+ if (!this.client.getWalletBalances) return [];
476
+ return await this.client.getWalletBalances();
477
477
  }
478
478
  // ── Publisher ─────────────────────────────────────────────────────────────
479
479
  /**
@@ -585,4 +585,4 @@ export {
585
585
  extractArweaveTxId,
586
586
  StandalonePublisher
587
587
  };
588
- //# sourceMappingURL=chunk-OIJNRACA.js.map
588
+ //# sourceMappingURL=chunk-EBXZUWV3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/standalone-publisher.ts"],"sourcesContent":["/**\n * StandalonePublisher — impl 2 of the {@link Publisher} seam (#228): an\n * EMBEDDED ToonClient constructed from the caller's config (mnemonic +\n * account index, the exact `packages/client/src/config.ts` shape) instead of\n * routing through a running toon-clientd. Made for CI jobs, servers, and\n * one-shot CLI runs where no daemon exists.\n *\n * Paid-write mechanics mirror the production daemon path\n * (`client-mcp/src/daemon/client-runner.ts`) and the proven seed pipeline\n * (`rig/tests/e2e/seed/lib/{publish,git-builder}.ts`):\n *\n * - one signed balance-proof CLAIM per write (`signBalanceProof(channelId,\n * fee)` — the ChannelManager accumulates the cumulative watermark),\n * - publishes route to the relay write destination with a flat per-event\n * fee (the daemon's `feePerEvent` convention),\n * - git objects upload as kind:5094 store writes tagged\n * Git-SHA/Git-Type/Repo, priced at bytes × per-byte rate (the seed\n * pipeline's bid), routed to the store destination via `proxyPath:\n * '/store'`, with the Arweave txId decoded from the FULFILL data.\n *\n * Because the embedded client signs claims on the SAME channels a daemon on\n * the same identity would, every paid operation is preceded by the nonce\n * guard (./nonce-guard.ts): refuse if a toon-clientd holds this identity,\n * and hold an exclusive per-pubkey lockfile against other standalone\n * processes for the lifetime of this publisher.\n *\n * Channel REUSE (#262): with a `channelMap` configured, start() resumes the\n * channel recorded for (identity, channel anchor) — `trackChannel` rehydrates\n * the cumulative-claim watermark from the client's channels.json — and\n * records any fresh lazy open, so sequential CLI invocations share ONE\n * on-chain channel instead of stranding a deposit per run (./channel-map.ts).\n */\n\nimport type {\n PublishEventResult,\n SignedBalanceProof,\n ToonClientConfig,\n} from '@toon-protocol/client';\nimport { ToonClient, parseFulfillHttp } from '@toon-protocol/client';\nimport { MAX_OBJECT_SIZE } from '../objects.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport type {\n FeeRates,\n GitObjectUpload,\n PublishReceipt,\n Publisher,\n UploadReceipt,\n} from '../publisher.js';\nimport {\n recordKey,\n type ChannelMapRecord,\n type ChannelMapStore,\n type PersistedChannelContext,\n} from './channel-map.js';\nimport type {\n ChannelCloseOutcome,\n ChannelOpenOutcome,\n ChannelSettleOutcome,\n WalletChainBalanceInfo,\n} from './money.js';\nimport { checkDaemonIdentity, NonceLock } from './nonce-guard.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A fully-signed Nostr event (structural subset of nostr-tools' NostrEvent). */\nexport interface SignedNostrEvent extends UnsignedEvent {\n id: string;\n pubkey: string;\n sig: string;\n}\n\n/**\n * The slice of `ToonClient` the publisher drives. Kept structural so tests\n * inject a mock and alternative client builds stay compatible.\n */\nexport interface ToonClientLike {\n start(): Promise<unknown>;\n stop(): Promise<void>;\n isStarted?(): boolean;\n getPublicKey(): string;\n signEvent(template: UnsignedEvent): SignedNostrEvent;\n openChannel(destination?: string): Promise<string>;\n signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof>;\n publishEvent(\n event: SignedNostrEvent,\n options?: {\n destination?: string;\n claim?: SignedBalanceProof;\n ilpAmount?: bigint;\n proxyPath?: string;\n }\n ): Promise<PublishEventResult>;\n /** On-chain deposit total for a tracked channel (channel-map bookkeeping). */\n getChannelDepositTotal?(channelId: string): bigint;\n /** Re-read a RESUMED channel's on-chain deposit (persisted state omits it). */\n rehydrateChannelDeposit?(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined>;\n // ── money lifecycle (#263) — optional, matches ToonClient's surface ──────\n /** Deposit extra collateral (base-unit delta) into an open channel. */\n depositToChannel?(\n channelId: string,\n amount: string | bigint\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }>;\n /** Close a channel — starts the settlement challenge window (on-chain). */\n closeChannel?(channelId: string): Promise<{\n channelId: string;\n txHash?: string;\n /** Unix SECONDS, string-encoded. */\n closedAt: string;\n settleableAt: string;\n }>;\n /** Settle a closed channel after its window — releases funds (on-chain). */\n settleChannel?(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string }>;\n /**\n * Free FULL multi-chain wallet view (#299) — native coin + configured tokens\n * per chain — works on an UNSTARTED client (Solana/Mina addresses are derived\n * from the mnemonic on demand).\n */\n getWalletBalances?(): Promise<WalletChainBalanceInfo[]>;\n}\n\nexport interface StandalonePublisherOptions {\n /**\n * ToonClient config (mnemonic + mnemonicAccountIndex + proxy/BTP uplink +\n * settlement fields — see `packages/client/src/config.ts`). Exactly one of\n * `clientConfig` | `client` is required.\n */\n clientConfig?: ToonClientConfig;\n /** Pre-built client (tests / advanced callers). */\n client?: ToonClientLike;\n /**\n * ILP route for event publishes (relay `/write`). Default: derived from the\n * config's `destinationAddress` anchor (`<base>.relay.store` → `<base>.relay`,\n * matching the daemon's route derivation), else the anchor itself.\n */\n publishDestination?: string;\n /**\n * ILP route for git-object uploads (store `/store` → Arweave). Default:\n * derived like `publishDestination` (`<base>.relay.store` → `<base>.store`).\n */\n storeDestination?: string;\n /**\n * ILP destination the payment channel anchors to. Default: the client\n * config's `destinationAddress`.\n */\n channelDestination?: string;\n /** Flat fee per published event (daemon `feePerEvent` convention). Default 1n. */\n eventFee?: bigint;\n /** Upload fee per object body byte (seed pipeline's bid rate). Default 10n. */\n uploadFeePerByte?: bigint;\n /** Daemon control API port probed by the nonce guard. */\n daemonPort?: number;\n /** Directory for the per-identity advisory lockfile. */\n lockDir?: string;\n /** Fetch impl for the daemon probe (tests). */\n fetchImpl?: typeof fetch;\n /**\n * Peer→channel map store (#262): start() resumes the channel recorded for\n * (identity, channel anchor) instead of opening a fresh one, and records\n * any fresh lazy open for the next invocation. Absent (embedded callers\n * managing their own channel lifecycle): the historical behaviour — open\n * lazily every run, record nothing.\n */\n channelMap?: ChannelMapStore;\n /**\n * Per-chain settlement parameters to BACK-FILL into the client's peer\n * negotiations after start (#264/#260): peers whose kind:10032 announce\n * carries no `tokenNetworks`/`preferredTokens` negotiate with those fields\n * empty, and the on-chain channel open then fails (\"tokenNetwork address\n * is required\"). Values here never override what a peer DID announce —\n * they only fill gaps, keyed by the negotiated chain id.\n */\n negotiationFallbacks?: {\n tokenNetworks?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n };\n /** Sink for non-fatal channel-persistence warnings (default: stderr). */\n warn?: (line: string) => void;\n}\n\n/** A relay/store rejected a paid write (fee NOT spent iff the claim failed too). */\nexport class StandalonePublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StandalonePublishError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route derivation (duplicated from client-mcp daemon/config.ts — that\n// package depends on this one for #227, so importing it back would be\n// circular; keep in sync)\n// ---------------------------------------------------------------------------\n\n/**\n * Derive publish/store routes from the channel anchor. Behind the devnet\n * proxy the anchor is `<base>.relay.store` (e.g. `g.proxy.relay.store`):\n * publishes terminate at `<base>.relay`, uploads at `<base>.store`. Anchors\n * not matching the convention pass through unchanged.\n */\nexport function deriveRouteDestinations(anchor: string): {\n publish: string;\n store: string;\n} {\n const segs = anchor.split('.');\n if (segs.at(-1) === 'store' && segs.at(-2) === 'relay') {\n const base = segs.slice(0, -2).join('.');\n return { publish: `${base}.relay`, store: `${base}.store` };\n }\n return { publish: anchor, store: anchor };\n}\n\n// ---------------------------------------------------------------------------\n// FULFILL → Arweave txId (mirrors @toon-protocol/client blob-storage.ts,\n// whose extractor is not exported; uses the exported parseFulfillHttp)\n// ---------------------------------------------------------------------------\n\n/** Arweave tx IDs are base64url-encoded 32-byte values (43 chars). */\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\n/**\n * Decode the Arweave txId from a store-write FULFILL. The deployed payment\n * proxy returns the store's verbatim HTTP/1.1 response\n * (`{\"accept\":true,\"txId\":…}` body); legacy non-proxy providers return bare\n * `base64(utf8(txId))`.\n *\n * @throws {StandalonePublishError} when no valid txId can be extracted.\n */\nexport function extractArweaveTxId(base64Data: string): string {\n const http = parseFulfillHttp(base64Data);\n\n if (!http.isHttp) {\n const legacy = Buffer.from(base64Data, 'base64').toString('utf8');\n if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {\n throw new StandalonePublishError(\n `FULFILL data is not a valid Arweave tx ID: \"${legacy}\"`\n );\n }\n return legacy;\n }\n\n if (http.status < 200 || http.status >= 300) {\n throw new StandalonePublishError(\n `git-object upload failed: store returned HTTP ${http.status}` +\n (http.body ? ` - ${http.body}` : '')\n );\n }\n\n let parsed: { accept?: boolean; txId?: unknown; data?: unknown; error?: unknown };\n try {\n parsed = JSON.parse(http.body) as typeof parsed;\n } catch {\n throw new StandalonePublishError(\n `git-object upload response body was not valid JSON: \"${http.body}\"`\n );\n }\n\n if (parsed.accept === false) {\n const reason = typeof parsed.error === 'string' ? `: ${parsed.error}` : '';\n throw new StandalonePublishError(\n `git-object upload rejected by store (accept:false)${reason}`\n );\n }\n\n if (typeof parsed.txId === 'string' && ARWEAVE_TX_ID_REGEX.test(parsed.txId)) {\n return parsed.txId;\n }\n if (typeof parsed.data === 'string' && parsed.data.length > 0) {\n const decoded = Buffer.from(parsed.data, 'base64').toString('utf8');\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;\n }\n\n throw new StandalonePublishError(\n `git-object upload response did not contain a valid Arweave tx ID: \"${http.body}\"`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Channel-resume introspection (#262)\n//\n// The peer negotiation table and the ChannelManager's peer→channel map are\n// PRIVATE on ToonClient, so resuming a channel reaches into them through a\n// structural runtime cast — the exact pattern the daemon uses\n// (client-mcp/src/daemon/client-runner.ts, openOrResumeApexChannel /\n// routeChildPeersThroughApexChannel). Keep the shapes in sync with\n// @toon-protocol/client's ToonClient/ChannelManager.\n// ---------------------------------------------------------------------------\n\n/** The slice of a `PeerNegotiation` the channel map records. */\ninterface NegotiationLike {\n chain: string;\n chainType: string;\n chainId: number | string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n}\n\ninterface ChannelInternals {\n peerNegotiations?: Map<string, NegotiationLike>;\n channelManager?: {\n trackChannel?: (\n channelId: string,\n context: PersistedChannelContext\n ) => void;\n peerChannels?: Map<string, string>;\n };\n /**\n * The client's on-chain channel client caches per-channel chain context at\n * OPEN time only; close/settle/deposit on a channel resumed from the map\n * would throw \"No on-chain context\" without re-seeding it (#263). Same\n * structural-cast contract as the rest of this block — keep in sync with\n * `@toon-protocol/client`'s OnChainChannelClient.\n */\n onChainChannelClient?: {\n channelContext?: Map<\n string,\n { chain: string; tokenNetworkAddress: string; tokenAddress?: string }\n >;\n };\n}\n\n/** Best-effort access to the client's private negotiation/channel state. */\nfunction channelInternals(client: ToonClientLike): ChannelInternals {\n const c = client as unknown as ChannelInternals;\n const negotiations =\n c.peerNegotiations instanceof Map ? c.peerNegotiations : undefined;\n const cm =\n c.channelManager &&\n typeof c.channelManager.trackChannel === 'function' &&\n c.channelManager.peerChannels instanceof Map\n ? c.channelManager\n : undefined;\n const onChain =\n c.onChainChannelClient &&\n c.onChainChannelClient.channelContext instanceof Map\n ? c.onChainChannelClient\n : undefined;\n return {\n ...(negotiations ? { peerNegotiations: negotiations } : {}),\n ...(cm ? { channelManager: cm } : {}),\n ...(onChain ? { onChainChannelClient: onChain } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// StandalonePublisher\n// ---------------------------------------------------------------------------\n\nexport class StandalonePublisher implements Publisher {\n private readonly client: ToonClientLike;\n private readonly ownsClient: boolean;\n private readonly publishDestination: string | undefined;\n private readonly storeDestination: string | undefined;\n private readonly channelDestination: string | undefined;\n private readonly eventFee: bigint;\n private readonly uploadFeePerByte: bigint;\n private readonly daemonPort: number | undefined;\n private readonly lockDir: string | undefined;\n private readonly fetchImpl: typeof fetch | undefined;\n private readonly channelMap: ChannelMapStore | undefined;\n /** ILP anchor the channel is keyed by in the map (peer/apex destination). */\n private readonly channelAnchor: string | undefined;\n private readonly negotiationFallbacks:\n | StandalonePublisherOptions['negotiationFallbacks']\n | undefined;\n private readonly warn: (line: string) => void;\n\n private lock: NonceLock | undefined;\n private channelId: string | undefined;\n private readyPromise: Promise<void> | undefined;\n /** Guard + client start only (no channel) — see {@link startClientOnly}. */\n private clientReadyPromise: Promise<void> | undefined;\n /** True when the last start() RESUMED the recorded channel (#263). */\n private lastOpenResumed = false;\n\n constructor(options: StandalonePublisherOptions) {\n if (options.client && options.clientConfig) {\n throw new Error(\n 'StandalonePublisher: provide either `clientConfig` or `client`, not both'\n );\n }\n if (options.client) {\n this.client = options.client;\n this.ownsClient = false;\n } else if (options.clientConfig) {\n this.client = new ToonClient(options.clientConfig);\n this.ownsClient = true;\n } else {\n throw new Error(\n 'StandalonePublisher: one of `clientConfig` (mnemonic-based ToonClient config) or `client` is required'\n );\n }\n\n // Routes: explicit option → derived from the channel anchor (same\n // `<base>.relay.store` convention the daemon resolves).\n const anchor =\n options.channelDestination ?? options.clientConfig?.destinationAddress;\n const routes = anchor ? deriveRouteDestinations(anchor) : undefined;\n this.publishDestination = options.publishDestination ?? routes?.publish;\n this.storeDestination = options.storeDestination ?? routes?.store;\n this.channelDestination = options.channelDestination;\n\n this.eventFee = options.eventFee ?? 1n;\n this.uploadFeePerByte = options.uploadFeePerByte ?? 10n;\n this.daemonPort = options.daemonPort;\n this.lockDir = options.lockDir;\n this.fetchImpl = options.fetchImpl;\n this.channelMap = options.channelMap;\n this.channelAnchor = anchor;\n this.negotiationFallbacks = options.negotiationFallbacks;\n this.warn =\n options.warn ?? ((line) => process.stderr.write(`${line}\\n`));\n }\n\n /** Hex Nostr pubkey of the embedded identity (available before start). */\n getPublicKey(): string {\n return this.client.getPublicKey();\n }\n\n /**\n * Run the nonce guard, start the embedded client, and open (or resume) the\n * payment channel. Called lazily by the first paid operation; safe to call\n * eagerly to fail fast. Idempotent.\n */\n start(): Promise<void> {\n this.readyPromise ??= (async () => {\n await this.startClientOnly();\n try {\n this.channelId = await this.openOrResumeChannel();\n } catch (err) {\n // Release the identity lock on a failed channel open (pre-#263\n // behaviour): nothing holds claims yet, so another process may go.\n this.lock?.release();\n this.lock = undefined;\n this.clientReadyPromise = undefined;\n throw err;\n }\n })().catch((err: unknown) => {\n // Let a later call retry (e.g. after the conflicting daemon stops).\n this.readyPromise = undefined;\n throw err;\n });\n return this.readyPromise;\n }\n\n /**\n * Run the nonce guard and start the embedded client WITHOUT touching the\n * payment channel (#263): `channel close`/`settle` operate on a RECORDED\n * channel and must never open a fresh one as a side effect of starting.\n * Idempotent; `start()` layers the channel open/resume on top of this.\n */\n startClientOnly(): Promise<void> {\n this.clientReadyPromise ??= this.doStartClient().catch((err: unknown) => {\n this.clientReadyPromise = undefined;\n throw err;\n });\n return this.clientReadyPromise;\n }\n\n private async doStartClient(): Promise<void> {\n const pubkey = this.client.getPublicKey();\n\n // Guard 1: refuse while a toon-clientd holds this identity.\n await checkDaemonIdentity(pubkey, {\n ...(this.daemonPort !== undefined ? { port: this.daemonPort } : {}),\n ...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),\n });\n\n // Guard 2: exclusive advisory lock against other standalone processes.\n this.lock = await NonceLock.acquire(pubkey, {\n ...(this.lockDir !== undefined ? { dir: this.lockDir } : {}),\n });\n\n try {\n if (this.client.isStarted?.() !== true) {\n await this.client.start();\n }\n // #264: back-fill negotiation gaps as soon as the client's bootstrap\n // negotiation exists — before any channel open (start()) or recorded-\n // channel operation reads them.\n this.applyNegotiationFallbacks();\n } catch (err) {\n this.lock.release();\n this.lock = undefined;\n throw err;\n }\n }\n\n /**\n * Back-fill negotiated peer metadata the announce did not carry\n * (#264/#260 root cause 3): after the client's bootstrap negotiation, any\n * peer negotiation missing `tokenNetwork`/`tokenAddress` gets the derived\n * per-chain fallback for its negotiated chain, BEFORE the lazy channel\n * open reads them. Announced values are never overridden.\n */\n private applyNegotiationFallbacks(): void {\n const fallbacks = this.negotiationFallbacks;\n if (!fallbacks) return;\n const negotiations = channelInternals(this.client).peerNegotiations;\n if (!negotiations) {\n this.warn(\n 'rig: settlement fallbacks configured but the client does not ' +\n 'expose negotiation internals — on-chain channel opening may fail ' +\n 'if the peer announced no TokenNetwork'\n );\n return;\n }\n for (const negotiation of negotiations.values()) {\n if (!negotiation.tokenNetwork) {\n const tokenNetwork = fallbacks.tokenNetworks?.[negotiation.chain];\n if (tokenNetwork) negotiation.tokenNetwork = tokenNetwork;\n }\n if (!negotiation.tokenAddress) {\n const tokenAddress = fallbacks.preferredTokens?.[negotiation.chain];\n if (tokenAddress) negotiation.tokenAddress = tokenAddress;\n }\n }\n }\n\n /**\n * Open the payment channel — or, when the channel map has a record for\n * (identity, anchor), RESUME the recorded on-chain channel (#262).\n *\n * Resume seeds the client's ChannelManager (`trackChannel` rehydrates the\n * cumulative-claim watermark from channels.json; `peerChannels` makes the\n * subsequent `openChannel` return the same id instead of opening on-chain).\n * A fresh open is RECORDED so the next invocation resumes it. A corrupt map\n * file throws BEFORE anything is opened — never a silent duplicate open.\n */\n private async openOrResumeChannel(): Promise<string> {\n const map = this.channelMap;\n if (!map) {\n // No persistence configured: historical lazy open, nothing recorded.\n return this.client.openChannel(this.channelDestination);\n }\n if (!this.channelAnchor) {\n this.warn(\n 'rig: no channel anchor destination configured — the peer→channel ' +\n 'mapping cannot be persisted, so this run may open a fresh channel'\n );\n return this.client.openChannel(this.channelDestination);\n }\n\n const anchor = this.channelAnchor;\n const identity = this.client.getPublicKey();\n // Corruption check happens HERE, before any on-chain open (throws).\n const candidates = map.listFor(identity, anchor);\n const internals = channelInternals(this.client);\n const resumed = await this.resumeRecordedChannel(map, candidates, internals);\n\n // Idempotent — returns the (resumed or existing) channel for the peer if\n // one is tracked, else opens lazily on-chain.\n const channelId = await this.client.openChannel(this.channelDestination);\n\n if (resumed && channelId === resumed.channelId) {\n this.lastOpenResumed = true;\n map.touch(recordKey(resumed));\n } else {\n this.lastOpenResumed = false;\n this.recordOpenedChannel(map, internals, identity, anchor, channelId);\n }\n return channelId;\n }\n\n /**\n * Try to resume one recorded channel: the first candidate whose peer is\n * still negotiated on the SAME chain + tokenNetwork and whose watermark\n * does not show it closed/settled. Returns the resumed record, if any.\n */\n private async resumeRecordedChannel(\n map: ChannelMapStore,\n candidates: ChannelMapRecord[],\n internals: ChannelInternals\n ): Promise<ChannelMapRecord | undefined> {\n if (candidates.length === 0) return undefined;\n const cm = internals.channelManager;\n if (!cm?.trackChannel || !cm.peerChannels) {\n this.warn(\n 'rig: a recorded channel exists but the client does not expose ' +\n 'channel internals to resume it — a fresh channel may be opened'\n );\n return undefined;\n }\n\n for (const record of candidates) {\n // The peer must still be negotiated on the recorded chain/tokenNetwork;\n // a rotated peer identity or re-negotiated settlement gets a fresh\n // channel (recorded under its own key) instead of stale claims.\n const negotiation = internals.peerNegotiations?.get(record.peerId);\n if (\n !negotiation ||\n negotiation.chain !== record.chain ||\n (negotiation.tokenNetwork ?? '') !== record.tokenNetwork\n ) {\n continue;\n }\n\n // Never resume a channel the withdraw flow already closed/settled.\n const watermark = map.readWatermark(record.channelId);\n if (\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined\n ) {\n continue;\n }\n if (!watermark) {\n // Fresh channels are seeded at record time, so a missing entry means\n // the watermark store was lost. Resuming from nonce 0 fails SAFE: a\n // regressed cumulative claim is rejected by the peer (no double\n // spend), but warn so the failure is diagnosable.\n this.warn(\n `rig: resuming channel ${record.channelId} with no local claim ` +\n `watermark (${map.watermarkPath}) — if this channel was claimed ` +\n 'against before, the peer will reject the stale claims; remove ' +\n `its entry from ${map.mapPath} to open a fresh channel instead`\n );\n }\n\n // trackChannel rehydrates nonce/cumulative from the watermark store;\n // seeding peerChannels makes ensureChannel/openChannel reuse the id.\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n\n // Persisted channel state omits the on-chain deposit — re-read it so\n // fee/balance accounting is right (EVM only; mirrors the daemon).\n // #279 latency trim: skipped when the record already carries a\n // depositTotal — deposits only change through rig's own flows (open\n // --deposit / a later resume), which update the record; this is\n // display/accounting state, not the claim watermark, so a stale value\n // can never double-spend (the peer enforces the cumulative claim).\n if (\n record.context.chainType === 'evm' &&\n record.depositTotal === undefined &&\n this.client.rehydrateChannelDeposit\n ) {\n try {\n const deposit = await this.client.rehydrateChannelDeposit(\n record.channelId,\n {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n }\n );\n if (deposit !== undefined) {\n map.touch(recordKey(record), {\n depositTotal: deposit.toString(),\n });\n }\n } catch (err) {\n this.warn(\n `rig: deposit re-read for resumed channel ${record.channelId} ` +\n `failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n return record;\n }\n return undefined;\n }\n\n /** Record a freshly opened channel (+ seed its claim watermark at 0/0). */\n private recordOpenedChannel(\n map: ChannelMapStore,\n internals: ChannelInternals,\n identity: string,\n destination: string,\n channelId: string\n ): void {\n let peerId: string | undefined;\n for (const [peer, channel] of internals.channelManager?.peerChannels ??\n []) {\n if (channel === channelId) {\n peerId = peer;\n break;\n }\n }\n const negotiation =\n peerId !== undefined\n ? internals.peerNegotiations?.get(peerId)\n : undefined;\n if (peerId === undefined || !negotiation) {\n this.warn(\n `rig: opened channel ${channelId} but could not record the ` +\n 'peer→channel mapping (client does not expose negotiation ' +\n 'internals) — the NEXT invocation may open another channel'\n );\n return;\n }\n\n let depositTotal: bigint | undefined;\n try {\n depositTotal = this.client.getChannelDepositTotal?.(channelId);\n } catch {\n // deposit unknown — recorded without it; a later resume re-reads it\n }\n\n map.record({\n channelId,\n peerId,\n identity,\n destination,\n chain: negotiation.chain,\n tokenNetwork: negotiation.tokenNetwork ?? '',\n // Context mirrors the daemon's persistApexChannel shape — exactly what\n // trackChannel needs on resume.\n context: {\n chainType: negotiation.chainType,\n chainId:\n typeof negotiation.chainId === 'number' ? negotiation.chainId : 0,\n tokenNetworkAddress: negotiation.tokenNetwork ?? '',\n ...(negotiation.tokenAddress\n ? { tokenAddress: negotiation.tokenAddress }\n : {}),\n recipient: negotiation.settlementAddress,\n },\n ...(depositTotal !== undefined && depositTotal > 0n\n ? { depositTotal: depositTotal.toString() }\n : {}),\n });\n // Seed nonce 0 / cumulative 0 so a later resume can tell \"never claimed\n // against\" apart from \"watermark lost\" (which only fails claim-rejected).\n map.seedWatermark(channelId);\n }\n\n /** Release the identity lock and stop the embedded client (if we own it). */\n async stop(): Promise<void> {\n this.lock?.release();\n this.lock = undefined;\n this.readyPromise = undefined;\n this.clientReadyPromise = undefined;\n this.channelId = undefined;\n // Never stop a client that was never started (`ToonClient.stop()` throws\n // INVALID_STATE) — e.g. after a free `rig balance` read (#263).\n if (this.ownsClient && this.client.isStarted?.() !== false) {\n await this.client.stop();\n }\n }\n\n // ── Money lifecycle (#263) ──────────────────────────────────────────────────\n\n /**\n * Explicit `rig channel open`: the SAME resume-or-open path the lazy paid\n * writes use (guard → start → resume the recorded channel or open + record\n * a fresh one), surfaced with a receipt — plus an optional extra collateral\n * deposit on top of the open/resume.\n */\n async openChannelExplicit(opts?: {\n /** Extra collateral to deposit AFTER the open/resume (base units). */\n deposit?: bigint;\n }): Promise<ChannelOpenOutcome> {\n await this.start();\n const channelId = this.requireChannel();\n const identity = this.client.getPublicKey();\n const destination = this.channelAnchor ?? '';\n const record =\n this.channelMap && this.channelAnchor\n ? this.channelMap\n .listFor(identity, this.channelAnchor)\n .find((r) => r.channelId === channelId)\n : undefined;\n\n const outcome: ChannelOpenOutcome = {\n channelId,\n resumed: this.lastOpenResumed,\n destination,\n ...(record ? { chain: record.chain, peerId: record.peerId } : {}),\n ...(record?.depositTotal !== undefined\n ? { depositTotal: record.depositTotal }\n : {}),\n };\n\n if (opts?.deposit !== undefined && opts.deposit > 0n) {\n if (!this.client.depositToChannel) {\n throw new StandalonePublishError(\n 'this client build does not support channel deposits ' +\n '(depositToChannel is unavailable)'\n );\n }\n const deposited = await this.client.depositToChannel(\n channelId,\n opts.deposit\n );\n outcome.depositAdded = opts.deposit.toString();\n outcome.depositTotal = deposited.depositTotal;\n if (deposited.txHash) outcome.depositTxHash = deposited.txHash;\n if (record) {\n this.channelMap?.touch(recordKey(record), {\n depositTotal: deposited.depositTotal,\n });\n }\n }\n return outcome;\n }\n\n /**\n * Adopt a RECORDED channel into the running client so on-chain close/\n * settle/deposit can act on it: `trackChannel` rehydrates the claim\n * watermark + withdraw timers from channels.json, `peerChannels` binds the\n * peer, and the on-chain client's context cache is re-seeded (it only\n * learns context at open time — a resumed channel would otherwise throw\n * \"No on-chain context\").\n */\n private adoptRecordedChannel(record: ChannelMapRecord): void {\n const internals = channelInternals(this.client);\n const cm = internals.channelManager;\n if (cm?.trackChannel && cm.peerChannels) {\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n }\n const contextCache = internals.onChainChannelClient?.channelContext;\n if (contextCache && !contextCache.has(record.channelId)) {\n contextCache.set(record.channelId, {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n ...(record.context.tokenAddress\n ? { tokenAddress: record.context.tokenAddress }\n : {}),\n });\n }\n }\n\n /**\n * Close a recorded channel: starts the on-chain settlement challenge\n * window. The client persists `closedAt`/`settleableAt` into the claim\n * watermark store, which is exactly where `rig channel list`/`balance`\n * derive the closing/settleable/settled status from. Guard + client start,\n * but NEVER a channel open ({@link startClientOnly}).\n */\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelCloseOutcome> {\n await this.startClientOnly();\n if (!this.client.closeChannel) {\n throw new StandalonePublishError(\n 'this client build does not support closing channels ' +\n '(closeChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.closeChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * Settle a recorded channel after its challenge window — releases the\n * remaining collateral. The client enforces the `now >= settleableAt` time\n * guard BEFORE spending gas (a too-early call throws its retryable\n * SettleTooEarlyError) and persists `settledAt` into the watermark store.\n */\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelSettleOutcome> {\n await this.startClientOnly();\n if (!this.client.settleChannel) {\n throw new StandalonePublishError(\n 'this client build does not support settling channels ' +\n '(settleChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.settleChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * The full multi-chain wallet view (#299) for the embedded identity — native\n * coin + configured tokens (USDC) per chain — a FREE read on the UNSTARTED\n * client (no nonce guard, no uplink, no channel). The client derives the\n * Solana/Mina addresses from the mnemonic on demand, so ALL configured chains\n * appear even before a start. Best-effort per chain (an unreachable RPC yields\n * an `unreadable` chain, not a failure).\n */\n async readWalletChainBalances(): Promise<WalletChainBalanceInfo[]> {\n if (!this.client.getWalletBalances) return [];\n return await this.client.getWalletBalances();\n }\n\n // ── Publisher ─────────────────────────────────────────────────────────────\n\n /**\n * Fee rates for `planPush` estimation: the flat per-event fee and the\n * per-byte upload rate this publisher pays (daemon `feePerEvent` and seed\n * bid-rate conventions; override via options).\n */\n getFeeRates(): Promise<FeeRates> {\n return Promise.resolve({\n uploadFeePerByte: this.uploadFeePerByte,\n eventFee: this.eventFee,\n });\n }\n\n /**\n * Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo\n * tagged — the proven seed-pipeline shape), signing one balance-proof claim\n * for `body.length × uploadFeePerByte`.\n */\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n if (upload.body.length > MAX_OBJECT_SIZE) {\n throw new StandalonePublishError(\n `git object ${upload.sha} exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const fee = BigInt(upload.body.length) * this.uploadFeePerByte;\n const event = this.client.signEvent({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\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 });\n\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(event, {\n ...(this.storeDestination ? { destination: this.storeDestination } : {}),\n claim,\n ilpAmount: fee,\n // The store backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `git-object upload rejected (${upload.sha}): ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.data) {\n throw new StandalonePublishError(\n `git-object upload FULFILL carried no data (${upload.sha}); expected the Arweave tx ID`\n );\n }\n return { txId: extractArweaveTxId(result.data), feePaid: fee };\n }\n\n /**\n * Sign the event with the embedded identity and pay-to-publish it through\n * the relay write route, one claim for the flat per-event fee.\n *\n * `relayUrls` is the interface's plural forward-compat surface (parked\n * #84): the standalone impl routes over ILP to its single configured\n * publish destination, so more than one relay is refused rather than\n * silently half-published.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new StandalonePublishError(\n `multi-relay publish is not supported yet (got ${relayUrls.length} relays) — the standalone publisher routes to a single relay destination (#84 parked)`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const signed = this.client.signEvent(event);\n const fee = this.eventFee;\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(signed, {\n ...(this.publishDestination\n ? { destination: this.publishDestination }\n : {}),\n claim,\n ilpAmount: fee,\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `publish rejected (kind ${event.kind}): ${result.error ?? 'relay rejected the event'}`\n );\n }\n return { eventId: result.eventId ?? signed.id, feePaid: fee };\n }\n\n private requireChannel(): string {\n if (!this.channelId) {\n throw new StandalonePublishError(\n 'no payment channel open — start() did not complete'\n );\n }\n return this.channelId;\n }\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n"],"mappings":"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","names":[]}
@@ -1,3 +1,7 @@
1
+ import {
2
+ MAX_OBJECT_SIZE,
3
+ hashGitObject
4
+ } from "./chunk-X2CZPPDM.js";
1
5
  import {
2
6
  ARWEAVE_FETCH_TIMEOUT_MS,
3
7
  ARWEAVE_GATEWAYS,
@@ -5,10 +9,6 @@ import {
5
9
  buildRepoRefs,
6
10
  isValidArweaveTxId
7
11
  } from "./chunk-3HRFDH7H.js";
8
- import {
9
- MAX_OBJECT_SIZE,
10
- hashGitObject
11
- } from "./chunk-X2CZPPDM.js";
12
12
 
13
13
  // src/repo-reader.ts
14
14
  import { execFile, spawn } from "child_process";
@@ -1164,4 +1164,4 @@ export {
1164
1164
  planPush,
1165
1165
  executePush
1166
1166
  };
1167
- //# sourceMappingURL=chunk-W2SDL2PE.js.map
1167
+ //# sourceMappingURL=chunk-PTXKCR5R.js.map