@toon-protocol/rig 3.6.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +112 -152
  2. package/dist/chunk-CDSFVZDV.js +290 -0
  3. package/dist/chunk-CDSFVZDV.js.map +1 -0
  4. package/dist/{chunk-B5ISARMU.js → chunk-GVXBUL3C.js} +15 -2
  5. package/dist/chunk-GVXBUL3C.js.map +1 -0
  6. package/dist/chunk-LTFMPOHU.js +194 -0
  7. package/dist/chunk-LTFMPOHU.js.map +1 -0
  8. package/dist/{chunk-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
  9. package/dist/chunk-YZRR2UO6.js.map +1 -0
  10. package/dist/{chunk-HJDPM37T.js → chunk-ZZBA5VVL.js} +11 -7
  11. package/dist/{chunk-HJDPM37T.js.map → chunk-ZZBA5VVL.js.map} +1 -1
  12. package/dist/cli/rig.js +290 -523
  13. package/dist/cli/rig.js.map +1 -1
  14. package/dist/index.d.ts +3 -115
  15. package/dist/index.js +9 -121
  16. package/dist/index.js.map +1 -1
  17. package/dist/{publisher-BFcnZeJI.d.ts → publisher-CcL96ugq.d.ts} +58 -1
  18. package/dist/standalone/index.d.ts +241 -438
  19. package/dist/standalone/index.js +19 -11
  20. package/dist/standalone-mode-LEAC4A2L.js +390 -0
  21. package/dist/standalone-mode-LEAC4A2L.js.map +1 -0
  22. package/package.json +5 -4
  23. package/dist/chunk-B5ISARMU.js.map +0 -1
  24. package/dist/chunk-IZ6KLYQ6.js +0 -724
  25. package/dist/chunk-IZ6KLYQ6.js.map +0 -1
  26. package/dist/chunk-LCYCKWAT.js.map +0 -1
  27. package/dist/chunk-PWNZWQOH.js +0 -404
  28. package/dist/chunk-PWNZWQOH.js.map +0 -1
  29. package/dist/chunk-UZZ2RN7A.js +0 -469
  30. package/dist/chunk-UZZ2RN7A.js.map +0 -1
  31. package/dist/network-bootstrap-27FYE3N7.js +0 -40
  32. package/dist/network-bootstrap-27FYE3N7.js.map +0 -1
  33. package/dist/standalone-mode-5MNNJC2D.js +0 -928
  34. package/dist/standalone-mode-5MNNJC2D.js.map +0 -1
@@ -1,928 +0,0 @@
1
- import {
2
- MinaZkAppStore,
3
- TOPOLOGY_CACHE_FILENAME,
4
- TOPOLOGY_TTL_ENV,
5
- TopologyCache,
6
- explicitConfigFingerprint,
7
- resolveIdentity,
8
- topologyCacheKey,
9
- topologyCacheTtlMs
10
- } from "./chunk-PWNZWQOH.js";
11
- import {
12
- StandalonePublisher,
13
- deriveRouteDestinations
14
- } from "./chunk-IZ6KLYQ6.js";
15
- import {
16
- ChannelMapStore,
17
- RIG_CHANNEL_MAP_FILENAME,
18
- standaloneForced
19
- } from "./chunk-LCYCKWAT.js";
20
- import "./chunk-B5ISARMU.js";
21
- import {
22
- DISCOVERY_TIMEOUT_MS,
23
- MinaChannelUnderivableError,
24
- SolanaChannelUnderivableError,
25
- TokenNetworkUnderivableError,
26
- discoverAnnouncedPeers,
27
- evmChainIdOf,
28
- evmTokenBalance,
29
- genesisSeedPubkeys,
30
- loadGenesisSeed,
31
- pickPaymentPeer,
32
- resolveChainSettlement,
33
- selectSettlementChain,
34
- solanaTokenBalance
35
- } from "./chunk-UZZ2RN7A.js";
36
- import {
37
- fetchRemoteState
38
- } from "./chunk-2KJJ7LSO.js";
39
-
40
- // src/cli/standalone-mode.ts
41
- import { readFileSync as readFileSync2 } from "fs";
42
- import { homedir } from "os";
43
- import { join } from "path";
44
- import {
45
- EvmSigner,
46
- deriveFullIdentity,
47
- deriveNostrKeyFromMnemonic
48
- } from "@toon-protocol/client";
49
- import {
50
- decodeEventFromToon,
51
- encodeEventToToon,
52
- resolveClientNetwork
53
- } from "@toon-protocol/core";
54
-
55
- // src/standalone/notice.ts
56
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
57
- import { dirname } from "path";
58
- var NOTICE_STORE_FILENAME = "rig-seen-notices.json";
59
- function normalizeNotice(value) {
60
- if (typeof value !== "object" || value === null) return void 0;
61
- const { id, severity, summary, url } = value;
62
- if (typeof id !== "string" || id.length === 0) return void 0;
63
- if (typeof summary !== "string" || summary.length === 0) return void 0;
64
- if (typeof url !== "string" || url.length === 0) return void 0;
65
- return {
66
- id,
67
- summary,
68
- url,
69
- severity: severity === "action-required" ? "action-required" : "info"
70
- };
71
- }
72
- function trustedNoticeFrom(peer, seedPubkeys) {
73
- if (!peer || !seedPubkeys.includes(peer.pubkey)) return void 0;
74
- return normalizeNotice(peer.info.notice);
75
- }
76
- var ACTION_REQUIRED_BORDER = "=".repeat(70);
77
- function formatNoticeLines(notice) {
78
- if (notice.severity === "action-required") {
79
- return [
80
- "",
81
- ACTION_REQUIRED_BORDER,
82
- `rig: ACTION REQUIRED \u2014 ${notice.summary}`,
83
- `rig: ${notice.url}`,
84
- ACTION_REQUIRED_BORDER,
85
- ""
86
- ];
87
- }
88
- return [`rig: notice \u2014 ${notice.summary} (${notice.url})`];
89
- }
90
- var NoticeStore = class {
91
- path;
92
- constructor(options) {
93
- this.path = options.path;
94
- }
95
- hasSeen(id) {
96
- return this.readFile().seenIds.includes(id);
97
- }
98
- /** Best-effort; a failed write just means the notice reprints next run. */
99
- markSeen(id) {
100
- const file = this.readFile();
101
- if (file.seenIds.includes(id)) return;
102
- try {
103
- mkdirSync(dirname(this.path), { recursive: true });
104
- writeFileSync(
105
- this.path,
106
- JSON.stringify(
107
- {
108
- version: 1,
109
- seenIds: [...file.seenIds, id]
110
- },
111
- null,
112
- 2
113
- ),
114
- { mode: 384 }
115
- );
116
- } catch {
117
- }
118
- }
119
- readFile() {
120
- const empty = { version: 1, seenIds: [] };
121
- let raw;
122
- try {
123
- raw = readFileSync(this.path, "utf8");
124
- } catch {
125
- return empty;
126
- }
127
- try {
128
- const parsed = JSON.parse(raw);
129
- if (typeof parsed !== "object" || parsed === null || parsed.version !== 1 || !Array.isArray(parsed.seenIds) || !parsed.seenIds.every((x) => typeof x === "string")) {
130
- return empty;
131
- }
132
- return parsed;
133
- } catch {
134
- return empty;
135
- }
136
- }
137
- };
138
- function showOperatorNoticeOnce(peer, seedPubkeys, store, warn) {
139
- const notice = trustedNoticeFrom(peer, seedPubkeys);
140
- if (!notice || store.hasSeen(notice.id)) return;
141
- for (const line of formatNoticeLines(notice)) warn(line);
142
- store.markSeen(notice.id);
143
- }
144
-
145
- // src/cli/standalone-mode.ts
146
- var OFFICIAL_PROXY_URL = "https://proxy.relay.devnet.toonprotocol.dev/ilp";
147
- var OFFICIAL_PUBLISH_DESTINATION = "g.toon.relay";
148
- var OFFICIAL_BTP_URL = "wss://proxy.relay.devnet.toonprotocol.dev/ilp/btp";
149
- var MissingUplinkError = class extends Error {
150
- constructor(configPath, relayUrl) {
151
- const discovered = relayUrl ? `no announce with a btp/http endpoint was found on ${relayUrl} and the genesis seed has none; ` : "";
152
- super(
153
- `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL (connector payment proxy) or TOON_CLIENT_BTP_URL, or add proxyUrl/btpUrl to ${configPath}`
154
- );
155
- this.name = "MissingUplinkError";
156
- }
157
- };
158
- function configDir(env) {
159
- return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
160
- }
161
- function readClientConfig(path) {
162
- try {
163
- return JSON.parse(readFileSync2(path, "utf8"));
164
- } catch (err) {
165
- if (err.code === "ENOENT") return {};
166
- throw new Error(
167
- `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
168
- );
169
- }
170
- }
171
- function buildMinaAutoDeploy(dir, identityPubkey, chain, warn) {
172
- const store = MinaZkAppStore.forHome(dir);
173
- let existing;
174
- try {
175
- existing = store.lookup(identityPubkey, chain);
176
- } catch (err) {
177
- warn(
178
- `rig: unreadable Mina zkApp store at ${store.filePath} (${err instanceof Error ? err.message : String(err)}) \u2014 a fresh zkApp may be deployed`
179
- );
180
- }
181
- return {
182
- ...existing ? {
183
- deployed: {
184
- zkAppAddress: existing.zkAppAddress,
185
- zkAppPrivateKey: existing.zkAppPrivateKey
186
- }
187
- } : {},
188
- // Persist the zkApp key BEFORE the deploy tx is sent (bug #3): a crash or
189
- // failed cache-invalidation retry between send and confirmation then finds
190
- // this record next run and REDEPLOYS the same address, instead of paying
191
- // the ~1.1-MINA account-creation fee for a brand-new zkApp every attempt.
192
- onDeploying: (record) => {
193
- store.save({
194
- identity: identityPubkey,
195
- chain,
196
- zkAppAddress: record.zkAppAddress,
197
- zkAppPrivateKey: record.zkAppPrivateKey,
198
- feePayer: record.feePayer,
199
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
200
- source: "rig auto-deploy (npm @toon-protocol/mina-zkapp build, pending)"
201
- });
202
- warn(
203
- `rig: recorded pending Mina zkApp ${record.zkAppAddress} before deploy \u2014 key saved to ${store.filePath} (reused on retry)`
204
- );
205
- },
206
- onDeployed: (record) => {
207
- store.save({
208
- identity: identityPubkey,
209
- chain,
210
- zkAppAddress: record.zkAppAddress,
211
- zkAppPrivateKey: record.zkAppPrivateKey,
212
- feePayer: record.feePayer,
213
- ...record.deployTxHash !== void 0 ? { deployTxHash: record.deployTxHash } : {},
214
- ...record.vkHash !== void 0 ? { vkHash: record.vkHash } : {},
215
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
216
- source: "rig auto-deploy (npm @toon-protocol/mina-zkapp build)"
217
- });
218
- warn(
219
- `rig: deployed a dedicated Mina PaymentChannel zkApp ${record.zkAppAddress} \u2014 key saved to ${store.filePath}`
220
- );
221
- },
222
- onProgress: (line) => warn(`rig: mina: ${line}`)
223
- };
224
- }
225
- function deriveMinaChannel(settlement, announce, relayUrl) {
226
- const { rpcUrl, zkAppAddress, minaTokenId, networkId } = settlement;
227
- if (!rpcUrl || !zkAppAddress) {
228
- const missing = [
229
- ...rpcUrl ? [] : ["graphqlUrl"],
230
- ...zkAppAddress ? [] : ["zkAppAddress"]
231
- ];
232
- throw new MinaChannelUnderivableError(
233
- settlement.chain,
234
- missing,
235
- announce,
236
- relayUrl
237
- );
238
- }
239
- return {
240
- graphqlUrl: rpcUrl,
241
- zkAppAddress,
242
- ...minaTokenId ? { tokenId: minaTokenId } : {},
243
- ...networkId ? { networkId } : {}
244
- };
245
- }
246
- function deriveSolanaChannel(settlement, announce, relayUrl) {
247
- const { rpcUrl, programId, tokenAddress } = settlement;
248
- if (!rpcUrl || !programId || !tokenAddress) {
249
- const missing = [
250
- ...rpcUrl ? [] : ["rpcUrl"],
251
- ...programId ? [] : ["programId"],
252
- ...tokenAddress ? [] : ["tokenMint"]
253
- ];
254
- throw new SolanaChannelUnderivableError(
255
- settlement.chain,
256
- missing,
257
- announce,
258
- relayUrl
259
- );
260
- }
261
- return { rpcUrl, programId, tokenMint: tokenAddress };
262
- }
263
- async function resolveNetworkTopology(inputs) {
264
- const { env, file, configPath, relayUrl, announce, genesisSeed, warn } = inputs;
265
- const explicitProxyUrl = env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl;
266
- const explicitBtpUrl = env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl;
267
- const explicitDestination = env["TOON_CLIENT_DESTINATION"] ?? file.destination;
268
- const explicitPublish = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? file.publishDestination;
269
- const explicitStore = env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
270
- const explicitChain = env["TOON_CLIENT_CHAIN"] ?? file.chain;
271
- const explicitMaps = {
272
- ...file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {},
273
- ...file.preferredTokens ? { preferredTokens: file.preferredTokens } : {},
274
- ...file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}
275
- };
276
- let proxyUrl = explicitProxyUrl;
277
- let btpUrl = explicitBtpUrl;
278
- if (!proxyUrl && !btpUrl) {
279
- proxyUrl = OFFICIAL_PROXY_URL;
280
- }
281
- let btpUrlDerived = false;
282
- if (!explicitProxyUrl && !explicitBtpUrl) {
283
- btpUrl = announce?.info.btpEndpoint || OFFICIAL_BTP_URL;
284
- btpUrlDerived = true;
285
- }
286
- const destination = explicitDestination ?? announce?.info.ilpAddress ?? genesisSeed?.ilpAddress ?? "g.proxy";
287
- const publishDestination = explicitPublish ?? announce?.routes?.publish ?? (proxyUrl === OFFICIAL_PROXY_URL ? OFFICIAL_PUBLISH_DESTINATION : void 0);
288
- const storeDestination = explicitStore ?? announce?.routes?.store;
289
- const explicitStoreBtpUrl = env["TOON_CLIENT_STORE_BTP_URL"] ?? file.storeBtpUrl;
290
- let storeBtpUrl;
291
- if (storeDestination && storeDestination !== publishDestination) {
292
- const storePeer = inputs.peers?.find(
293
- (peer) => peer.info.ilpAddress === storeDestination
294
- );
295
- storeBtpUrl = explicitStoreBtpUrl || storePeer?.info.btpEndpoint || void 0;
296
- if (!storeBtpUrl && inputs.peers?.length) {
297
- warn(
298
- `rig: no announce found for the store route "${storeDestination}" \u2014 store writes (git objects, the rig page) will be attempted on the publish uplink and the terminating connector will refuse them if it does not hold that channel`
299
- );
300
- }
301
- }
302
- let routePrices;
303
- {
304
- const derived = deriveRouteDestinations(destination);
305
- const priceOf = (address) => {
306
- const owner = inputs.peers?.find((p) => p.info.ilpAddress === address) ?? announce;
307
- return owner?.routePrices?.[address] ?? owner?.capabilities?.find((c) => c.address === address)?.price;
308
- };
309
- const publishPrice = priceOf(publishDestination ?? derived.publish);
310
- const storePrice = priceOf(storeDestination ?? derived.store);
311
- if (publishPrice !== void 0 || storePrice !== void 0) {
312
- routePrices = {
313
- ...publishPrice !== void 0 ? { publish: publishPrice } : {},
314
- ...storePrice !== void 0 ? { store: storePrice } : {}
315
- };
316
- }
317
- }
318
- const knownPeers = announce ? [
319
- {
320
- pubkey: announce.pubkey,
321
- relayUrl,
322
- btpEndpoint: announce.info.btpEndpoint ?? ""
323
- }
324
- ] : genesisSeed ? [
325
- {
326
- pubkey: genesisSeed.pubkey,
327
- relayUrl: genesisSeed.relayUrl,
328
- btpEndpoint: genesisSeed.btpEndpoint
329
- }
330
- ] : [];
331
- const announcedChains = announce?.info.supportedChains ?? [];
332
- const resolveSettlement = (chain) => resolveChainSettlement(chain, explicitMaps, announce);
333
- let selection;
334
- let supportedChains;
335
- let preferredTokens;
336
- let tokenNetworks;
337
- let chainRpcUrls;
338
- let solanaChannel;
339
- let minaChannel;
340
- if (file.supportedChains?.length) {
341
- const listed = file.supportedChains;
342
- const kept = [];
343
- let firstDropError;
344
- preferredTokens = { ...file.preferredTokens };
345
- tokenNetworks = { ...file.tokenNetworks };
346
- chainRpcUrls = { ...file.chainRpcUrls };
347
- for (const chain of listed) {
348
- const s = resolveSettlement(chain);
349
- let key = chain;
350
- if (s.family === "evm" && !announcedChains.includes(chain)) {
351
- const chainId = evmChainIdOf(chain);
352
- const equivalent = chainId !== void 0 ? announcedChains.find((c) => evmChainIdOf(c) === chainId) : void 0;
353
- if (equivalent) {
354
- warn(
355
- `rig: aligning configured settlement chain "${chain}" to the announced spelling "${equivalent}" (same EVM chain id ${chainId}) \u2014 chain negotiation matches identifiers exactly`
356
- );
357
- key = equivalent;
358
- }
359
- }
360
- if (kept.includes(key)) continue;
361
- if (s.tokenAddress && !preferredTokens[key]) {
362
- preferredTokens[key] = s.tokenAddress;
363
- }
364
- if (s.tokenNetwork && !tokenNetworks[key]) {
365
- tokenNetworks[key] = s.tokenNetwork;
366
- }
367
- if (s.rpcUrl && !chainRpcUrls[key]) {
368
- chainRpcUrls[key] = s.rpcUrl;
369
- }
370
- if (s.family === "evm") {
371
- if (!tokenNetworks[key]) {
372
- throw new TokenNetworkUnderivableError(chain, announce, relayUrl);
373
- }
374
- if (!chainRpcUrls[key]) {
375
- throw new Error(
376
- `no RPC URL is derivable for settlement chain "${chain}" \u2014 add chainRpcUrls["${chain}"] to ${configPath}`
377
- );
378
- }
379
- }
380
- if (s.family === "solana" && !file.solanaChannel) {
381
- if (solanaChannel) {
382
- kept.push(key);
383
- continue;
384
- }
385
- try {
386
- solanaChannel = deriveSolanaChannel(s, announce, relayUrl);
387
- } catch (err) {
388
- firstDropError ??= err;
389
- warn(
390
- `rig: dropping settlement chain ${chain} from the configured supportedChains \u2014 ${err instanceof Error ? err.message : String(err)}`
391
- );
392
- continue;
393
- }
394
- }
395
- if (s.family === "mina" && !file.minaChannel) {
396
- if (minaChannel) {
397
- kept.push(key);
398
- continue;
399
- }
400
- try {
401
- minaChannel = deriveMinaChannel(s, announce, relayUrl);
402
- } catch (err) {
403
- firstDropError ??= err;
404
- warn(
405
- `rig: dropping settlement chain ${chain} from the configured supportedChains \u2014 ${err instanceof Error ? err.message : String(err)}`
406
- );
407
- continue;
408
- }
409
- }
410
- kept.push(key);
411
- }
412
- if (kept.length === 0) {
413
- throw firstDropError;
414
- }
415
- supportedChains = kept;
416
- const advertised = new Set(kept);
417
- const prune = (map) => Object.fromEntries(
418
- Object.entries(map).filter(([chain]) => advertised.has(chain))
419
- );
420
- preferredTokens = prune(preferredTokens);
421
- tokenNetworks = prune(tokenNetworks);
422
- chainRpcUrls = prune(chainRpcUrls);
423
- selection = {
424
- chain: supportedChains[0],
425
- reason: "explicit",
426
- detail: "supportedChains set by config"
427
- };
428
- } else if (explicitChain || announcedChains.length > 0) {
429
- const { secretKey } = deriveNostrKeyFromMnemonic(
430
- inputs.identity.mnemonic,
431
- inputs.identity.accountIndex
432
- );
433
- const evmAddress = new EvmSigner(secretKey).address;
434
- let solanaAddress;
435
- if (!explicitChain && announcedChains.some((c) => c.startsWith("solana:"))) {
436
- const fullIdentity = await deriveFullIdentity(
437
- inputs.identity.mnemonic,
438
- inputs.identity.accountIndex
439
- );
440
- solanaAddress = fullIdentity.solana.publicKey || void 0;
441
- fullIdentity.nostr.secretKey.fill(0);
442
- fullIdentity.evm.privateKey.fill(0);
443
- fullIdentity.solana.secretKey.fill(0);
444
- }
445
- selection = await selectSettlementChain({
446
- ...explicitChain ? { explicitChain } : {},
447
- announcedChains,
448
- records: inputs.channelRecords(),
449
- evmAddress,
450
- ...solanaAddress ? { solanaAddress } : {},
451
- resolveSettlement,
452
- probeBalance: inputs.probeBalance ?? evmTokenBalance,
453
- probeSolanaBalance: inputs.probeSolanaBalance ?? solanaTokenBalance
454
- });
455
- const settlement = resolveSettlement(selection.chain);
456
- if (settlement.family === "evm") {
457
- if (!settlement.tokenNetwork) {
458
- throw new TokenNetworkUnderivableError(
459
- selection.chain,
460
- announce,
461
- relayUrl
462
- );
463
- }
464
- if (!settlement.rpcUrl) {
465
- throw new Error(
466
- `no RPC URL is derivable for settlement chain "${selection.chain}" \u2014 add chainRpcUrls["${selection.chain}"] to ${configPath}`
467
- );
468
- }
469
- }
470
- if (settlement.family === "solana" && !file.solanaChannel) {
471
- solanaChannel = deriveSolanaChannel(settlement, announce, relayUrl);
472
- }
473
- if (settlement.family === "mina" && !file.minaChannel) {
474
- minaChannel = deriveMinaChannel(settlement, announce, relayUrl);
475
- }
476
- supportedChains = [selection.chain];
477
- preferredTokens = settlement.tokenAddress ? { [selection.chain]: settlement.tokenAddress } : void 0;
478
- tokenNetworks = settlement.tokenNetwork ? { [selection.chain]: settlement.tokenNetwork } : void 0;
479
- chainRpcUrls = settlement.rpcUrl ? { [selection.chain]: settlement.rpcUrl } : void 0;
480
- if (selection.reason !== "explicit") {
481
- warn(
482
- `rig: settlement chain ${selection.chain} selected \u2014 ${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains in the client config) to override`
483
- );
484
- } else if (explicitChain && selection.chain !== explicitChain) {
485
- warn(
486
- `rig: aligning configured settlement chain "${explicitChain}" to the announced spelling "${selection.chain}" \u2014 ${selection.detail}`
487
- );
488
- }
489
- } else {
490
- warn(
491
- `rig: no settlement chains are configured or announced \u2014 paid writes will fail until a chain is configured (supportedChains) or the payment peer announces its chains on ${relayUrl}`
492
- );
493
- }
494
- const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
495
- if (network && network !== "custom" && !file.supportedChains?.length) {
496
- warn(
497
- `rig: ignoring the "${network}" network preset for settlement \u2014 the settlement chain comes from the payment peer's announce and your config, because preset chains can point at networks your wallet has no funds on; set supportedChains explicitly to use preset chains`
498
- );
499
- }
500
- return {
501
- ...proxyUrl ? { proxyUrl } : {},
502
- ...btpUrl ? { btpUrl, ...btpUrlDerived ? { btpUrlDerived } : {} } : {},
503
- destination,
504
- ...publishDestination ? { publishDestination } : {},
505
- ...storeDestination ? { storeDestination } : {},
506
- ...storeBtpUrl ? { storeBtpUrl } : {},
507
- ...routePrices ? { routePrices } : {},
508
- knownPeers,
509
- ...selection ? { selection } : {},
510
- ...supportedChains ? { supportedChains } : {},
511
- ...preferredTokens && Object.keys(preferredTokens).length > 0 ? { preferredTokens } : {},
512
- ...tokenNetworks && Object.keys(tokenNetworks).length > 0 ? { tokenNetworks } : {},
513
- ...chainRpcUrls && Object.keys(chainRpcUrls).length > 0 ? { chainRpcUrls } : {},
514
- ...solanaChannel ? { solanaChannel } : {},
515
- ...minaChannel ? { minaChannel } : {}
516
- };
517
- }
518
- function resolveUplinkConfig(topo) {
519
- return {
520
- // validateConfig requires connectorUrl OR proxyUrl; with BTP-only
521
- // config a dummy connectorUrl satisfies it (unused at runtime — same
522
- // convention as the daemon).
523
- ...topo.proxyUrl ? { proxyUrl: topo.proxyUrl } : { connectorUrl: "http://127.0.0.1:1" },
524
- ...topo.btpUrl ? {
525
- btpUrl: topo.btpUrl,
526
- btpAuthToken: "",
527
- preferBtpForPaidWrites: true
528
- } : {}
529
- };
530
- }
531
- function isBtpTransportError(err) {
532
- const name = err instanceof Error ? err.name : "";
533
- return name === "BtpConnectionError" || name === "BtpAuthError";
534
- }
535
- function bootstrapRecoveries(args) {
536
- let active = args.topology;
537
- const detail = (err) => err instanceof Error ? err.message : String(err);
538
- const recoveries = [];
539
- const reresolve = args.reresolve;
540
- if (reresolve) {
541
- recoveries.push({
542
- applies: () => true,
543
- describe: (err) => `rig: bootstrap with the cached network topology failed (${detail(err)}) \u2014 invalidating the cache and re-resolving live`,
544
- rebuild: async () => {
545
- active = await reresolve();
546
- return args.buildPublisher(active);
547
- }
548
- });
549
- }
550
- recoveries.push({
551
- applies: (err) => active.btpUrlDerived === true && isBtpTransportError(err),
552
- describe: (err) => `rig: the discovered BTP uplink ${active.btpUrl} is unreachable (${detail(err)}) \u2014 continuing over the HTTP uplink alone; paid writes an edge accepts only over BTP will still fail, so pin a reachable endpoint with \`rig entry <url>\` if they do`,
553
- rebuild: async () => {
554
- const { btpUrl: _btpUrl, btpUrlDerived: _derived, ...http } = active;
555
- active = http;
556
- return args.buildPublisher(active);
557
- }
558
- });
559
- return recoveries;
560
- }
561
- function isNetworkTopology(value) {
562
- if (typeof value !== "object" || value === null) return false;
563
- const t = value;
564
- return typeof t["destination"] === "string" && Array.isArray(t["knownPeers"]);
565
- }
566
- var NON_RECOVERABLE_START_ERRORS = /* @__PURE__ */ new Set([
567
- "DaemonIdentityConflictError",
568
- "StandaloneLockError",
569
- "ChannelMapCorruptError",
570
- // An unfunded Mina fee payer is a wallet-funding problem, not topology
571
- // staleness — re-resolving the topology cannot fix it, so retrying would
572
- // just re-run the (already fail-fast) preflight for nothing.
573
- "MinaFeePayerUnfundedError"
574
- ]);
575
- var TopologyRecoveringPublisher = class {
576
- inner;
577
- recoveries;
578
- warn;
579
- constructor(inner, recoveries, warn) {
580
- this.inner = inner;
581
- this.recoveries = [...recoveries];
582
- this.warn = warn;
583
- }
584
- getPublicKey() {
585
- return this.inner.getPublicKey();
586
- }
587
- getFeeRates() {
588
- return this.inner.getFeeRates();
589
- }
590
- /**
591
- * Run the bootstrap step, retrying against a repaired publisher for as long
592
- * as an unused recovery applies. The LAST attempt's error is what surfaces
593
- * — after a live re-resolution or an HTTP-only rebuild that is the
594
- * network's real state, strictly more actionable than the failure that
595
- * triggered the retry.
596
- */
597
- async ensure(start) {
598
- for (; ; ) {
599
- try {
600
- await start(this.inner);
601
- return this.inner;
602
- } catch (err) {
603
- const name = err instanceof Error ? err.name : "";
604
- if (NON_RECOVERABLE_START_ERRORS.has(name)) throw err;
605
- const index = this.recoveries.findIndex((r) => r.applies(err));
606
- if (index < 0) throw err;
607
- const [recovery] = this.recoveries.splice(index, 1);
608
- this.warn(recovery.describe(err));
609
- const fresh = await recovery.rebuild();
610
- try {
611
- await this.inner.stop();
612
- } catch {
613
- }
614
- this.inner = fresh;
615
- }
616
- }
617
- }
618
- async publishEvent(event, relayUrls) {
619
- const p = await this.ensure((x) => x.start());
620
- return p.publishEvent(event, relayUrls);
621
- }
622
- async uploadGitObject(upload) {
623
- const p = await this.ensure((x) => x.start());
624
- return p.uploadGitObject(upload);
625
- }
626
- async uploadBlob(upload) {
627
- const p = await this.ensure((x) => x.start());
628
- return p.uploadBlob(upload);
629
- }
630
- // ── money lifecycle passthroughs (#263) — same recovery contract ─────────
631
- async openChannelExplicit(opts) {
632
- const p = await this.ensure((x) => x.start());
633
- return p.openChannelExplicit(opts);
634
- }
635
- async closeRecordedChannel(record) {
636
- const p = await this.ensure((x) => x.startClientOnly());
637
- return p.closeRecordedChannel(record);
638
- }
639
- async settleRecordedChannel(record) {
640
- const p = await this.ensure((x) => x.startClientOnly());
641
- return p.settleRecordedChannel(record);
642
- }
643
- /** Free read on the unstarted client — no bootstrap, no recovery needed. */
644
- readWalletChainBalances(...args) {
645
- return this.inner.readWalletChainBalances(...args);
646
- }
647
- async stop() {
648
- await this.inner.stop();
649
- }
650
- };
651
- function chainRecordsFor(map, identity) {
652
- return map.list().filter((r) => r.identity === identity).map((r) => {
653
- const watermark = map.readWatermark(r.channelId);
654
- return {
655
- chain: r.chain,
656
- lastUsedAt: r.lastUsedAt,
657
- closed: watermark?.closedAt !== void 0 || watermark?.settledAt !== void 0
658
- };
659
- });
660
- }
661
- async function createStandaloneContext(options) {
662
- const { env } = options;
663
- const warn = (line) => options.warn(line);
664
- const dir = configDir(env);
665
- const configPath = join(dir, "config.json");
666
- const file = readClientConfig(configPath);
667
- const identity = await resolveIdentity(options);
668
- const skipDaemonCheck = standaloneForced(env);
669
- if (skipDaemonCheck) {
670
- warn(
671
- "rig: force-standalone (RIG_STANDALONE) \u2014 bypassing any running toon-clientd; this rig process signs its own claims (stop the daemon if it is also writing on this identity)"
672
- );
673
- }
674
- const genesisSeed = loadGenesisSeed();
675
- const relayUrl = options.relayUrl ?? env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl ?? genesisSeed?.relayUrl ?? // Last resort: the genesis seed normally supplies the relay, so this is
676
- // only reached when no seed/config/env relay is present. Point at the
677
- // shared public devnet relay rather than a dead local default.
678
- "wss://relay-ws.devnet.toonprotocol.dev";
679
- const channelStorePath = file.channelStorePath ?? join(dir, "channels.json");
680
- const storeChannelStorePath = channelStorePath.replace(
681
- /(\.json)?$/,
682
- ".store.json"
683
- );
684
- const channelMap = new ChannelMapStore({
685
- mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),
686
- watermarkPath: channelStorePath
687
- });
688
- const cache = new TopologyCache({
689
- path: join(dir, TOPOLOGY_CACHE_FILENAME),
690
- ttlMs: topologyCacheTtlMs(env),
691
- validate: isNetworkTopology
692
- });
693
- const cacheKey = topologyCacheKey({
694
- relayUrl,
695
- identity: identity.pubkey,
696
- fingerprint: explicitConfigFingerprint(
697
- env,
698
- file
699
- )
700
- });
701
- const noticeStore = new NoticeStore({
702
- path: join(dir, NOTICE_STORE_FILENAME)
703
- });
704
- const resolveLiveTopology = async () => {
705
- const solanaExplicitlyComplete = (file.supportedChains ?? []).every(
706
- (chain) => !chain.startsWith("solana:") || Boolean(file.solanaChannel) || Boolean(
707
- file.tokenNetworks?.[chain] && file.preferredTokens?.[chain] && file.chainRpcUrls?.[chain]
708
- )
709
- );
710
- const minaExplicitlyComplete = (file.supportedChains ?? []).every(
711
- (chain) => !chain.startsWith("mina:") || Boolean(file.minaChannel)
712
- );
713
- const explicitStoreDest = env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
714
- const explicitPublishDest = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? file.publishDestination;
715
- const storeUplinkKnown = !explicitStoreDest || explicitStoreDest === explicitPublishDest || Boolean(env["TOON_CLIENT_STORE_BTP_URL"] ?? file.storeBtpUrl);
716
- const fullyExplicit = Boolean(
717
- (env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl) || (env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl)
718
- ) && Boolean(env["TOON_CLIENT_DESTINATION"] ?? file.destination) && Boolean(file.supportedChains?.length) && storeUplinkKnown && solanaExplicitlyComplete && minaExplicitlyComplete;
719
- let announce;
720
- let discoveredPeers;
721
- if (!fullyExplicit) {
722
- try {
723
- const peers = await discoverAnnouncedPeers(relayUrl, {
724
- timeoutMs: DISCOVERY_TIMEOUT_MS
725
- });
726
- discoveredPeers = peers;
727
- const seedPubkeys = genesisSeedPubkeys();
728
- announce = pickPaymentPeer(peers, seedPubkeys);
729
- if (!announce) {
730
- warn(
731
- `rig: no payment-peer announce (kind:10032) found on ${relayUrl} \u2014 falling back to the genesis peer seed`
732
- );
733
- }
734
- showOperatorNoticeOnce(announce, seedPubkeys, noticeStore, warn);
735
- } catch (err) {
736
- warn(
737
- `rig: announce discovery on ${relayUrl} failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to the genesis peer seed`
738
- );
739
- }
740
- }
741
- const resolved = await resolveNetworkTopology({
742
- env,
743
- file,
744
- configPath,
745
- relayUrl,
746
- announce,
747
- ...discoveredPeers ? { peers: discoveredPeers } : {},
748
- genesisSeed,
749
- identity: {
750
- mnemonic: identity.mnemonic,
751
- accountIndex: identity.accountIndex,
752
- pubkey: identity.pubkey
753
- },
754
- channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),
755
- ...options.requireUplink !== void 0 ? { requireUplink: options.requireUplink } : {},
756
- warn
757
- });
758
- if (options.requireUplink !== false) cache.write(cacheKey, resolved);
759
- return resolved;
760
- };
761
- const cached = cache.read(cacheKey);
762
- if (cached) {
763
- warn(
764
- `rig: network topology from cache (${Math.round(cached.ageMs / 1e3)}s old; ${TOPOLOGY_TTL_ENV}=0 disables) \u2014 skipping announce discovery`
765
- );
766
- }
767
- const topology = cached?.topology ?? await resolveLiveTopology();
768
- const eventFee = BigInt(file.feePerEvent ?? "1");
769
- const buildPublisher = (topo) => {
770
- const clientConfig = {
771
- ...resolveUplinkConfig(topo),
772
- mnemonic: identity.mnemonic,
773
- mnemonicAccountIndex: identity.accountIndex,
774
- ilpInfo: {
775
- pubkey: "00".repeat(32),
776
- ilpAddress: "g.toon.client",
777
- btpEndpoint: topo.btpUrl ?? "",
778
- assetCode: "USD",
779
- assetScale: 6
780
- },
781
- toonEncoder: encodeEventToToon,
782
- toonDecoder: decodeEventFromToon,
783
- destinationAddress: topo.destination,
784
- // The embedded client bootstraps against the known peer above; its
785
- // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.
786
- relayUrl: "",
787
- knownPeers: topo.knownPeers,
788
- channelStorePath,
789
- ...topo.supportedChains ? { supportedChains: topo.supportedChains } : {},
790
- ...file.settlementAddresses ? { settlementAddresses: file.settlementAddresses } : {},
791
- ...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {},
792
- ...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
793
- ...topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {},
794
- // Solana channel params: an explicit config object wins verbatim; else
795
- // the topology's derivation (announce/presets — see resolveNetworkTopology).
796
- ...file.solanaChannel ? { solanaChannel: file.solanaChannel } : topo.solanaChannel ? { solanaChannel: topo.solanaChannel } : {},
797
- // Mina channel params: an explicit config object wins verbatim; else the
798
- // topology's derivation (announce zkApp/tokenId + preset graphqlUrl —
799
- // see resolveNetworkTopology / deriveMinaChannel).
800
- // An explicit config object keeps today's pin-a-zkApp semantics
801
- // verbatim. The DERIVED path additionally gets `autoDeploy`: the
802
- // announce/preset zkApp is shared and single-pair, so a fresh identity
803
- // can never open on it — the client checks on-chain ownership and
804
- // deploys a dedicated per-pair zkApp when needed, persisting the key
805
- // through the rig zkApp store (losing it would strand ~1.1 MINA).
806
- ...file.minaChannel ? { minaChannel: file.minaChannel } : topo.minaChannel ? {
807
- minaChannel: {
808
- ...topo.minaChannel,
809
- autoDeploy: buildMinaAutoDeploy(
810
- dir,
811
- identity.pubkey,
812
- `mina:${topo.minaChannel.networkId ?? "devnet"}`,
813
- warn
814
- )
815
- }
816
- } : {}
817
- };
818
- const storeClientConfig = topo.storeBtpUrl ? {
819
- ...clientConfig,
820
- proxyUrl: void 0,
821
- connectorUrl: "http://127.0.0.1:1",
822
- btpUrl: topo.storeBtpUrl,
823
- btpAuthToken: "",
824
- preferBtpForPaidWrites: true,
825
- // Its OWN watermark file. A client's channel store and its
826
- // `.peers.json` binding sidecar are one unit that the client
827
- // rewrites wholesale, so pointing two live clients at one path has
828
- // them clobber each other's watermarks — observed as the publish
829
- // leg losing its channel ("is not being tracked", then F01) the
830
- // moment the store leg opened. Separate files; the legs stay
831
- // correlated through rig's own channel map, which is keyed by
832
- // anchor and records both.
833
- channelStorePath: storeChannelStorePath,
834
- ilpInfo: {
835
- ...clientConfig.ilpInfo,
836
- btpEndpoint: topo.storeBtpUrl
837
- },
838
- destinationAddress: topo.storeDestination ?? topo.destination
839
- } : void 0;
840
- return new StandalonePublisher({
841
- clientConfig,
842
- ...storeClientConfig ? { storeClientConfig } : {},
843
- eventFee,
844
- channelMap,
845
- warn,
846
- ...skipDaemonCheck ? { skipDaemonCheck: true } : {},
847
- ...topo.publishDestination ? { publishDestination: topo.publishDestination } : {},
848
- ...topo.storeDestination ? { storeDestination: topo.storeDestination } : {},
849
- // Route-price floors (F06): every per-packet claim must advance the
850
- // channel by at least the destination route's announced price.
851
- ...topo.routePrices ? {
852
- routePrices: {
853
- ...topo.routePrices.publish !== void 0 ? { publish: BigInt(topo.routePrices.publish) } : {},
854
- ...topo.routePrices.store !== void 0 ? { store: BigInt(topo.routePrices.store) } : {}
855
- }
856
- } : {},
857
- // `rig channel open --peer` (#263): anchor the channel (and its map
858
- // key) to an explicit peer destination instead of the configured
859
- // default.
860
- ...options.channelDestination ? { channelDestination: options.channelDestination } : {},
861
- // The peer's announce does not carry TokenNetwork/token parameters, so
862
- // the client's negotiation leaves them empty (#260 root cause 3) — the
863
- // publisher back-fills them from the derived per-chain maps before the
864
- // channel opens.
865
- ...topo.tokenNetworks || topo.preferredTokens ? {
866
- negotiationFallbacks: {
867
- ...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
868
- ...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {}
869
- }
870
- } : {}
871
- });
872
- };
873
- const publisher = new TopologyRecoveringPublisher(
874
- buildPublisher(topology),
875
- bootstrapRecoveries({
876
- topology,
877
- buildPublisher,
878
- // Only a CACHE-sourced publisher gets the re-resolve recovery;
879
- // live-resolved ones have nothing staler to fall back from.
880
- ...cached ? {
881
- reresolve: async () => {
882
- cache.invalidate(cacheKey);
883
- return resolveLiveTopology();
884
- }
885
- } : {}
886
- }),
887
- warn
888
- );
889
- const walletViewNetwork = env["TOON_CLIENT_NETWORK"] ?? file.network;
890
- let walletViewFallback;
891
- if (walletViewNetwork === "devnet" || walletViewNetwork === "testnet" || walletViewNetwork === "mainnet") {
892
- const presets = resolveClientNetwork(walletViewNetwork);
893
- walletViewFallback = {
894
- ...presets.solanaChannel ? { solanaChannel: presets.solanaChannel } : {},
895
- ...presets.minaChannel ? { minaChannel: presets.minaChannel } : {}
896
- };
897
- }
898
- return {
899
- ownerPubkey: publisher.getPublicKey(),
900
- identitySource: identity.source,
901
- identitySourceLabel: identity.sourceLabel,
902
- publisher,
903
- defaultRelayUrls: [relayUrl],
904
- fetchRemote: (args) => fetchRemoteState(args),
905
- // Money lifecycle (#263): same guard/start/channel-map machinery as the
906
- // paid-write path, surfaced for fund/balance/channel open|close|settle.
907
- money: {
908
- openChannel: (opts) => publisher.openChannelExplicit(opts),
909
- closeChannel: (record) => publisher.closeRecordedChannel(record),
910
- settleChannel: (record) => publisher.settleRecordedChannel(record),
911
- walletChainBalances: () => publisher.readWalletChainBalances(walletViewFallback)
912
- },
913
- stop: () => publisher.stop()
914
- };
915
- }
916
- export {
917
- MissingUplinkError,
918
- OFFICIAL_BTP_URL,
919
- OFFICIAL_PROXY_URL,
920
- OFFICIAL_PUBLISH_DESTINATION,
921
- bootstrapRecoveries,
922
- buildMinaAutoDeploy,
923
- createStandaloneContext,
924
- isBtpTransportError,
925
- resolveNetworkTopology,
926
- resolveUplinkConfig
927
- };
928
- //# sourceMappingURL=standalone-mode-5MNNJC2D.js.map