@toon-protocol/rig 3.5.3 → 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 +298 -529
  13. package/dist/cli/rig.js.map +1 -1
  14. package/dist/index.d.ts +3 -109
  15. package/dist/index.js +9 -108
  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 -378
  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-4CAOEXGQ.js +0 -631
  24. package/dist/chunk-4CAOEXGQ.js.map +0 -1
  25. package/dist/chunk-B5ISARMU.js.map +0 -1
  26. package/dist/chunk-FFQXFV4W.js +0 -400
  27. package/dist/chunk-FFQXFV4W.js.map +0 -1
  28. package/dist/chunk-LCYCKWAT.js.map +0 -1
  29. package/dist/chunk-TTJCBGKW.js +0 -447
  30. package/dist/chunk-TTJCBGKW.js.map +0 -1
  31. package/dist/network-bootstrap-CTYIZMP6.js +0 -40
  32. package/dist/network-bootstrap-CTYIZMP6.js.map +0 -1
  33. package/dist/standalone-mode-LBWGU7NV.js +0 -724
  34. package/dist/standalone-mode-LBWGU7NV.js.map +0 -1
@@ -1,724 +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-FFQXFV4W.js";
11
- import {
12
- StandalonePublisher,
13
- deriveRouteDestinations
14
- } from "./chunk-4CAOEXGQ.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-TTJCBGKW.js";
36
- import {
37
- fetchRemoteState
38
- } from "./chunk-2KJJ7LSO.js";
39
-
40
- // src/cli/standalone-mode.ts
41
- import { readFileSync } 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
- var OFFICIAL_PROXY_URL = "https://proxy.devnet.toonprotocol.dev/rust/ilp";
55
- var OFFICIAL_PUBLISH_DESTINATION = "g.toon.relay";
56
- var MissingUplinkError = class extends Error {
57
- constructor(configPath, relayUrl) {
58
- const discovered = relayUrl ? `no announce with a btp/http endpoint was found on ${relayUrl} and the genesis seed has none; ` : "";
59
- super(
60
- `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL (connector payment proxy) or TOON_CLIENT_BTP_URL, or add proxyUrl/btpUrl to ${configPath}`
61
- );
62
- this.name = "MissingUplinkError";
63
- }
64
- };
65
- function configDir(env) {
66
- return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
67
- }
68
- function readClientConfig(path) {
69
- try {
70
- return JSON.parse(readFileSync(path, "utf8"));
71
- } catch (err) {
72
- if (err.code === "ENOENT") return {};
73
- throw new Error(
74
- `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
75
- );
76
- }
77
- }
78
- function buildMinaAutoDeploy(dir, identityPubkey, chain, warn) {
79
- const store = MinaZkAppStore.forHome(dir);
80
- let existing;
81
- try {
82
- existing = store.lookup(identityPubkey, chain);
83
- } catch (err) {
84
- warn(
85
- `rig: unreadable Mina zkApp store at ${store.filePath} (${err instanceof Error ? err.message : String(err)}) \u2014 a fresh zkApp may be deployed`
86
- );
87
- }
88
- return {
89
- ...existing ? {
90
- deployed: {
91
- zkAppAddress: existing.zkAppAddress,
92
- zkAppPrivateKey: existing.zkAppPrivateKey
93
- }
94
- } : {},
95
- // Persist the zkApp key BEFORE the deploy tx is sent (bug #3): a crash or
96
- // failed cache-invalidation retry between send and confirmation then finds
97
- // this record next run and REDEPLOYS the same address, instead of paying
98
- // the ~1.1-MINA account-creation fee for a brand-new zkApp every attempt.
99
- onDeploying: (record) => {
100
- store.save({
101
- identity: identityPubkey,
102
- chain,
103
- zkAppAddress: record.zkAppAddress,
104
- zkAppPrivateKey: record.zkAppPrivateKey,
105
- feePayer: record.feePayer,
106
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
107
- source: "rig auto-deploy (npm @toon-protocol/mina-zkapp build, pending)"
108
- });
109
- warn(
110
- `rig: recorded pending Mina zkApp ${record.zkAppAddress} before deploy \u2014 key saved to ${store.filePath} (reused on retry)`
111
- );
112
- },
113
- onDeployed: (record) => {
114
- store.save({
115
- identity: identityPubkey,
116
- chain,
117
- zkAppAddress: record.zkAppAddress,
118
- zkAppPrivateKey: record.zkAppPrivateKey,
119
- feePayer: record.feePayer,
120
- ...record.deployTxHash !== void 0 ? { deployTxHash: record.deployTxHash } : {},
121
- ...record.vkHash !== void 0 ? { vkHash: record.vkHash } : {},
122
- deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
123
- source: "rig auto-deploy (npm @toon-protocol/mina-zkapp build)"
124
- });
125
- warn(
126
- `rig: deployed a dedicated Mina PaymentChannel zkApp ${record.zkAppAddress} \u2014 key saved to ${store.filePath}`
127
- );
128
- },
129
- onProgress: (line) => warn(`rig: mina: ${line}`)
130
- };
131
- }
132
- function deriveMinaChannel(settlement, announce, relayUrl) {
133
- const { rpcUrl, zkAppAddress, minaTokenId, networkId } = settlement;
134
- if (!rpcUrl || !zkAppAddress) {
135
- const missing = [
136
- ...rpcUrl ? [] : ["graphqlUrl"],
137
- ...zkAppAddress ? [] : ["zkAppAddress"]
138
- ];
139
- throw new MinaChannelUnderivableError(
140
- settlement.chain,
141
- missing,
142
- announce,
143
- relayUrl
144
- );
145
- }
146
- return {
147
- graphqlUrl: rpcUrl,
148
- zkAppAddress,
149
- ...minaTokenId ? { tokenId: minaTokenId } : {},
150
- ...networkId ? { networkId } : {}
151
- };
152
- }
153
- function deriveSolanaChannel(settlement, announce, relayUrl) {
154
- const { rpcUrl, programId, tokenAddress } = settlement;
155
- if (!rpcUrl || !programId || !tokenAddress) {
156
- const missing = [
157
- ...rpcUrl ? [] : ["rpcUrl"],
158
- ...programId ? [] : ["programId"],
159
- ...tokenAddress ? [] : ["tokenMint"]
160
- ];
161
- throw new SolanaChannelUnderivableError(
162
- settlement.chain,
163
- missing,
164
- announce,
165
- relayUrl
166
- );
167
- }
168
- return { rpcUrl, programId, tokenMint: tokenAddress };
169
- }
170
- async function resolveNetworkTopology(inputs) {
171
- const { env, file, configPath, relayUrl, announce, genesisSeed, warn } = inputs;
172
- const explicitProxyUrl = env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl;
173
- const explicitBtpUrl = env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl;
174
- const explicitDestination = env["TOON_CLIENT_DESTINATION"] ?? file.destination;
175
- const explicitPublish = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? file.publishDestination;
176
- const explicitStore = env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
177
- const explicitChain = env["TOON_CLIENT_CHAIN"] ?? file.chain;
178
- const explicitMaps = {
179
- ...file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {},
180
- ...file.preferredTokens ? { preferredTokens: file.preferredTokens } : {},
181
- ...file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}
182
- };
183
- let proxyUrl = explicitProxyUrl;
184
- const btpUrl = explicitBtpUrl;
185
- if (!proxyUrl && !btpUrl) {
186
- proxyUrl = OFFICIAL_PROXY_URL;
187
- }
188
- const destination = explicitDestination ?? announce?.info.ilpAddress ?? genesisSeed?.ilpAddress ?? "g.proxy";
189
- const publishDestination = explicitPublish ?? announce?.routes?.publish ?? (proxyUrl === OFFICIAL_PROXY_URL ? OFFICIAL_PUBLISH_DESTINATION : void 0);
190
- const storeDestination = explicitStore ?? announce?.routes?.store;
191
- let routePrices;
192
- const capabilities = announce?.capabilities;
193
- if (capabilities && capabilities.length > 0) {
194
- const derived = deriveRouteDestinations(destination);
195
- const priceOf = (address) => capabilities.find((c) => c.address === address)?.price;
196
- const publishPrice = priceOf(publishDestination ?? derived.publish);
197
- const storePrice = priceOf(storeDestination ?? derived.store);
198
- if (publishPrice !== void 0 || storePrice !== void 0) {
199
- routePrices = {
200
- ...publishPrice !== void 0 ? { publish: publishPrice } : {},
201
- ...storePrice !== void 0 ? { store: storePrice } : {}
202
- };
203
- }
204
- }
205
- const knownPeers = announce ? [
206
- {
207
- pubkey: announce.pubkey,
208
- relayUrl,
209
- btpEndpoint: announce.info.btpEndpoint ?? ""
210
- }
211
- ] : genesisSeed ? [
212
- {
213
- pubkey: genesisSeed.pubkey,
214
- relayUrl: genesisSeed.relayUrl,
215
- btpEndpoint: genesisSeed.btpEndpoint
216
- }
217
- ] : [];
218
- const announcedChains = announce?.info.supportedChains ?? [];
219
- const resolveSettlement = (chain) => resolveChainSettlement(chain, explicitMaps, announce);
220
- let selection;
221
- let supportedChains;
222
- let preferredTokens;
223
- let tokenNetworks;
224
- let chainRpcUrls;
225
- let solanaChannel;
226
- let minaChannel;
227
- if (file.supportedChains?.length) {
228
- const listed = file.supportedChains;
229
- const kept = [];
230
- let firstDropError;
231
- preferredTokens = { ...file.preferredTokens };
232
- tokenNetworks = { ...file.tokenNetworks };
233
- chainRpcUrls = { ...file.chainRpcUrls };
234
- for (const chain of listed) {
235
- const s = resolveSettlement(chain);
236
- let key = chain;
237
- if (s.family === "evm" && !announcedChains.includes(chain)) {
238
- const chainId = evmChainIdOf(chain);
239
- const equivalent = chainId !== void 0 ? announcedChains.find((c) => evmChainIdOf(c) === chainId) : void 0;
240
- if (equivalent) {
241
- warn(
242
- `rig: aligning configured settlement chain "${chain}" to the announced spelling "${equivalent}" (same EVM chain id ${chainId}) \u2014 chain negotiation matches identifiers exactly`
243
- );
244
- key = equivalent;
245
- }
246
- }
247
- if (kept.includes(key)) continue;
248
- if (s.tokenAddress && !preferredTokens[key]) {
249
- preferredTokens[key] = s.tokenAddress;
250
- }
251
- if (s.tokenNetwork && !tokenNetworks[key]) {
252
- tokenNetworks[key] = s.tokenNetwork;
253
- }
254
- if (s.rpcUrl && !chainRpcUrls[key]) {
255
- chainRpcUrls[key] = s.rpcUrl;
256
- }
257
- if (s.family === "evm") {
258
- if (!tokenNetworks[key]) {
259
- throw new TokenNetworkUnderivableError(chain, announce, relayUrl);
260
- }
261
- if (!chainRpcUrls[key]) {
262
- throw new Error(
263
- `no RPC URL is derivable for settlement chain "${chain}" \u2014 add chainRpcUrls["${chain}"] to ${configPath}`
264
- );
265
- }
266
- }
267
- if (s.family === "solana" && !file.solanaChannel) {
268
- if (solanaChannel) {
269
- kept.push(key);
270
- continue;
271
- }
272
- try {
273
- solanaChannel = deriveSolanaChannel(s, announce, relayUrl);
274
- } catch (err) {
275
- firstDropError ??= err;
276
- warn(
277
- `rig: dropping settlement chain ${chain} from the configured supportedChains \u2014 ${err instanceof Error ? err.message : String(err)}`
278
- );
279
- continue;
280
- }
281
- }
282
- if (s.family === "mina" && !file.minaChannel) {
283
- if (minaChannel) {
284
- kept.push(key);
285
- continue;
286
- }
287
- try {
288
- minaChannel = deriveMinaChannel(s, announce, relayUrl);
289
- } catch (err) {
290
- firstDropError ??= err;
291
- warn(
292
- `rig: dropping settlement chain ${chain} from the configured supportedChains \u2014 ${err instanceof Error ? err.message : String(err)}`
293
- );
294
- continue;
295
- }
296
- }
297
- kept.push(key);
298
- }
299
- if (kept.length === 0) {
300
- throw firstDropError;
301
- }
302
- supportedChains = kept;
303
- const advertised = new Set(kept);
304
- const prune = (map) => Object.fromEntries(
305
- Object.entries(map).filter(([chain]) => advertised.has(chain))
306
- );
307
- preferredTokens = prune(preferredTokens);
308
- tokenNetworks = prune(tokenNetworks);
309
- chainRpcUrls = prune(chainRpcUrls);
310
- selection = {
311
- chain: supportedChains[0],
312
- reason: "explicit",
313
- detail: "supportedChains set by config"
314
- };
315
- } else if (explicitChain || announcedChains.length > 0) {
316
- const { secretKey } = deriveNostrKeyFromMnemonic(
317
- inputs.identity.mnemonic,
318
- inputs.identity.accountIndex
319
- );
320
- const evmAddress = new EvmSigner(secretKey).address;
321
- let solanaAddress;
322
- if (!explicitChain && announcedChains.some((c) => c.startsWith("solana:"))) {
323
- const fullIdentity = await deriveFullIdentity(
324
- inputs.identity.mnemonic,
325
- inputs.identity.accountIndex
326
- );
327
- solanaAddress = fullIdentity.solana.publicKey || void 0;
328
- fullIdentity.nostr.secretKey.fill(0);
329
- fullIdentity.evm.privateKey.fill(0);
330
- fullIdentity.solana.secretKey.fill(0);
331
- }
332
- selection = await selectSettlementChain({
333
- ...explicitChain ? { explicitChain } : {},
334
- announcedChains,
335
- records: inputs.channelRecords(),
336
- evmAddress,
337
- ...solanaAddress ? { solanaAddress } : {},
338
- resolveSettlement,
339
- probeBalance: inputs.probeBalance ?? evmTokenBalance,
340
- probeSolanaBalance: inputs.probeSolanaBalance ?? solanaTokenBalance
341
- });
342
- const settlement = resolveSettlement(selection.chain);
343
- if (settlement.family === "evm") {
344
- if (!settlement.tokenNetwork) {
345
- throw new TokenNetworkUnderivableError(
346
- selection.chain,
347
- announce,
348
- relayUrl
349
- );
350
- }
351
- if (!settlement.rpcUrl) {
352
- throw new Error(
353
- `no RPC URL is derivable for settlement chain "${selection.chain}" \u2014 add chainRpcUrls["${selection.chain}"] to ${configPath}`
354
- );
355
- }
356
- }
357
- if (settlement.family === "solana" && !file.solanaChannel) {
358
- solanaChannel = deriveSolanaChannel(settlement, announce, relayUrl);
359
- }
360
- if (settlement.family === "mina" && !file.minaChannel) {
361
- minaChannel = deriveMinaChannel(settlement, announce, relayUrl);
362
- }
363
- supportedChains = [selection.chain];
364
- preferredTokens = settlement.tokenAddress ? { [selection.chain]: settlement.tokenAddress } : void 0;
365
- tokenNetworks = settlement.tokenNetwork ? { [selection.chain]: settlement.tokenNetwork } : void 0;
366
- chainRpcUrls = settlement.rpcUrl ? { [selection.chain]: settlement.rpcUrl } : void 0;
367
- if (selection.reason !== "explicit") {
368
- warn(
369
- `rig: settlement chain ${selection.chain} selected \u2014 ${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains in the client config) to override`
370
- );
371
- } else if (explicitChain && selection.chain !== explicitChain) {
372
- warn(
373
- `rig: aligning configured settlement chain "${explicitChain}" to the announced spelling "${selection.chain}" \u2014 ${selection.detail}`
374
- );
375
- }
376
- } else {
377
- warn(
378
- `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}`
379
- );
380
- }
381
- const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
382
- if (network && network !== "custom" && !file.supportedChains?.length) {
383
- warn(
384
- `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`
385
- );
386
- }
387
- return {
388
- ...proxyUrl ? { proxyUrl } : {},
389
- ...btpUrl ? { btpUrl } : {},
390
- destination,
391
- ...publishDestination ? { publishDestination } : {},
392
- ...storeDestination ? { storeDestination } : {},
393
- ...routePrices ? { routePrices } : {},
394
- knownPeers,
395
- ...selection ? { selection } : {},
396
- ...supportedChains ? { supportedChains } : {},
397
- ...preferredTokens && Object.keys(preferredTokens).length > 0 ? { preferredTokens } : {},
398
- ...tokenNetworks && Object.keys(tokenNetworks).length > 0 ? { tokenNetworks } : {},
399
- ...chainRpcUrls && Object.keys(chainRpcUrls).length > 0 ? { chainRpcUrls } : {},
400
- ...solanaChannel ? { solanaChannel } : {},
401
- ...minaChannel ? { minaChannel } : {}
402
- };
403
- }
404
- function isNetworkTopology(value) {
405
- if (typeof value !== "object" || value === null) return false;
406
- const t = value;
407
- return typeof t["destination"] === "string" && Array.isArray(t["knownPeers"]);
408
- }
409
- var NON_RECOVERABLE_START_ERRORS = /* @__PURE__ */ new Set([
410
- "DaemonIdentityConflictError",
411
- "StandaloneLockError",
412
- "ChannelMapCorruptError",
413
- // An unfunded Mina fee payer is a wallet-funding problem, not topology
414
- // staleness — re-resolving the topology cannot fix it, so retrying would
415
- // just re-run the (already fail-fast) preflight for nothing.
416
- "MinaFeePayerUnfundedError"
417
- ]);
418
- var TopologyRecoveringPublisher = class {
419
- inner;
420
- rebuild;
421
- warn;
422
- constructor(inner, rebuild, warn) {
423
- this.inner = inner;
424
- this.rebuild = rebuild;
425
- this.warn = warn;
426
- }
427
- getPublicKey() {
428
- return this.inner.getPublicKey();
429
- }
430
- getFeeRates() {
431
- return this.inner.getFeeRates();
432
- }
433
- /**
434
- * Run the bootstrap step, recovering ONCE from a stale cached topology.
435
- * A rebuild failure surfaces the LIVE resolution's error — that is the
436
- * network's real state, strictly more actionable than the cached failure.
437
- */
438
- async ensure(start) {
439
- try {
440
- await start(this.inner);
441
- return this.inner;
442
- } catch (err) {
443
- const rebuild = this.rebuild;
444
- this.rebuild = void 0;
445
- const name = err instanceof Error ? err.name : "";
446
- if (!rebuild || NON_RECOVERABLE_START_ERRORS.has(name)) throw err;
447
- this.warn(
448
- `rig: bootstrap with the cached network topology failed (${err instanceof Error ? err.message : String(err)}) \u2014 invalidating the cache and re-resolving live`
449
- );
450
- const fresh = await rebuild();
451
- try {
452
- await this.inner.stop();
453
- } catch {
454
- }
455
- this.inner = fresh;
456
- await start(this.inner);
457
- return this.inner;
458
- }
459
- }
460
- async publishEvent(event, relayUrls) {
461
- const p = await this.ensure((x) => x.start());
462
- return p.publishEvent(event, relayUrls);
463
- }
464
- async uploadGitObject(upload) {
465
- const p = await this.ensure((x) => x.start());
466
- return p.uploadGitObject(upload);
467
- }
468
- async uploadBlob(upload) {
469
- const p = await this.ensure((x) => x.start());
470
- return p.uploadBlob(upload);
471
- }
472
- // ── money lifecycle passthroughs (#263) — same recovery contract ─────────
473
- async openChannelExplicit(opts) {
474
- const p = await this.ensure((x) => x.start());
475
- return p.openChannelExplicit(opts);
476
- }
477
- async closeRecordedChannel(record) {
478
- const p = await this.ensure((x) => x.startClientOnly());
479
- return p.closeRecordedChannel(record);
480
- }
481
- async settleRecordedChannel(record) {
482
- const p = await this.ensure((x) => x.startClientOnly());
483
- return p.settleRecordedChannel(record);
484
- }
485
- /** Free read on the unstarted client — no bootstrap, no recovery needed. */
486
- readWalletChainBalances(...args) {
487
- return this.inner.readWalletChainBalances(...args);
488
- }
489
- async stop() {
490
- await this.inner.stop();
491
- }
492
- };
493
- function chainRecordsFor(map, identity) {
494
- return map.list().filter((r) => r.identity === identity).map((r) => {
495
- const watermark = map.readWatermark(r.channelId);
496
- return {
497
- chain: r.chain,
498
- lastUsedAt: r.lastUsedAt,
499
- closed: watermark?.closedAt !== void 0 || watermark?.settledAt !== void 0
500
- };
501
- });
502
- }
503
- async function createStandaloneContext(options) {
504
- const { env } = options;
505
- const warn = (line) => options.warn(line);
506
- const dir = configDir(env);
507
- const configPath = join(dir, "config.json");
508
- const file = readClientConfig(configPath);
509
- const identity = await resolveIdentity(options);
510
- const skipDaemonCheck = standaloneForced(env);
511
- if (skipDaemonCheck) {
512
- warn(
513
- "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)"
514
- );
515
- }
516
- const genesisSeed = loadGenesisSeed();
517
- 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
518
- // only reached when no seed/config/env relay is present. Point at the
519
- // shared public devnet relay rather than a dead local default.
520
- "wss://relay-ws.devnet.toonprotocol.dev";
521
- const channelStorePath = file.channelStorePath ?? join(dir, "channels.json");
522
- const channelMap = new ChannelMapStore({
523
- mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),
524
- watermarkPath: channelStorePath
525
- });
526
- const cache = new TopologyCache({
527
- path: join(dir, TOPOLOGY_CACHE_FILENAME),
528
- ttlMs: topologyCacheTtlMs(env),
529
- validate: isNetworkTopology
530
- });
531
- const cacheKey = topologyCacheKey({
532
- relayUrl,
533
- identity: identity.pubkey,
534
- fingerprint: explicitConfigFingerprint(
535
- env,
536
- file
537
- )
538
- });
539
- const resolveLiveTopology = async () => {
540
- const solanaExplicitlyComplete = (file.supportedChains ?? []).every(
541
- (chain) => !chain.startsWith("solana:") || Boolean(file.solanaChannel) || Boolean(
542
- file.tokenNetworks?.[chain] && file.preferredTokens?.[chain] && file.chainRpcUrls?.[chain]
543
- )
544
- );
545
- const minaExplicitlyComplete = (file.supportedChains ?? []).every(
546
- (chain) => !chain.startsWith("mina:") || Boolean(file.minaChannel)
547
- );
548
- const fullyExplicit = Boolean(
549
- (env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl) || (env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl)
550
- ) && Boolean(env["TOON_CLIENT_DESTINATION"] ?? file.destination) && Boolean(file.supportedChains?.length) && solanaExplicitlyComplete && minaExplicitlyComplete;
551
- let announce;
552
- if (!fullyExplicit) {
553
- try {
554
- const peers = await discoverAnnouncedPeers(relayUrl, {
555
- timeoutMs: DISCOVERY_TIMEOUT_MS
556
- });
557
- announce = pickPaymentPeer(peers, genesisSeedPubkeys());
558
- if (!announce) {
559
- warn(
560
- `rig: no payment-peer announce (kind:10032) found on ${relayUrl} \u2014 falling back to the genesis peer seed`
561
- );
562
- }
563
- } catch (err) {
564
- warn(
565
- `rig: announce discovery on ${relayUrl} failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to the genesis peer seed`
566
- );
567
- }
568
- }
569
- const resolved = await resolveNetworkTopology({
570
- env,
571
- file,
572
- configPath,
573
- relayUrl,
574
- announce,
575
- genesisSeed,
576
- identity: {
577
- mnemonic: identity.mnemonic,
578
- accountIndex: identity.accountIndex,
579
- pubkey: identity.pubkey
580
- },
581
- channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),
582
- ...options.requireUplink !== void 0 ? { requireUplink: options.requireUplink } : {},
583
- warn
584
- });
585
- if (options.requireUplink !== false) cache.write(cacheKey, resolved);
586
- return resolved;
587
- };
588
- const cached = cache.read(cacheKey);
589
- if (cached) {
590
- warn(
591
- `rig: network topology from cache (${Math.round(cached.ageMs / 1e3)}s old; ${TOPOLOGY_TTL_ENV}=0 disables) \u2014 skipping announce discovery`
592
- );
593
- }
594
- const topology = cached?.topology ?? await resolveLiveTopology();
595
- const eventFee = BigInt(file.feePerEvent ?? "1");
596
- const buildPublisher = (topo) => {
597
- const clientConfig = {
598
- // validateConfig requires connectorUrl OR proxyUrl; with BTP-only
599
- // config a dummy connectorUrl satisfies it (unused at runtime — same
600
- // convention as the daemon).
601
- ...topo.proxyUrl ? { proxyUrl: topo.proxyUrl } : { connectorUrl: "http://127.0.0.1:1" },
602
- mnemonic: identity.mnemonic,
603
- mnemonicAccountIndex: identity.accountIndex,
604
- ilpInfo: {
605
- pubkey: "00".repeat(32),
606
- ilpAddress: "g.toon.client",
607
- btpEndpoint: topo.btpUrl ?? "",
608
- assetCode: "USD",
609
- assetScale: 6
610
- },
611
- toonEncoder: encodeEventToToon,
612
- toonDecoder: decodeEventFromToon,
613
- ...topo.btpUrl ? { btpUrl: topo.btpUrl, btpAuthToken: "" } : {},
614
- destinationAddress: topo.destination,
615
- // The embedded client bootstraps against the known peer above; its
616
- // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.
617
- relayUrl: "",
618
- knownPeers: topo.knownPeers,
619
- channelStorePath,
620
- ...topo.supportedChains ? { supportedChains: topo.supportedChains } : {},
621
- ...file.settlementAddresses ? { settlementAddresses: file.settlementAddresses } : {},
622
- ...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {},
623
- ...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
624
- ...topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {},
625
- // Solana channel params: an explicit config object wins verbatim; else
626
- // the topology's derivation (announce/presets — see resolveNetworkTopology).
627
- ...file.solanaChannel ? { solanaChannel: file.solanaChannel } : topo.solanaChannel ? { solanaChannel: topo.solanaChannel } : {},
628
- // Mina channel params: an explicit config object wins verbatim; else the
629
- // topology's derivation (announce zkApp/tokenId + preset graphqlUrl —
630
- // see resolveNetworkTopology / deriveMinaChannel).
631
- // An explicit config object keeps today's pin-a-zkApp semantics
632
- // verbatim. The DERIVED path additionally gets `autoDeploy`: the
633
- // announce/preset zkApp is shared and single-pair, so a fresh identity
634
- // can never open on it — the client checks on-chain ownership and
635
- // deploys a dedicated per-pair zkApp when needed, persisting the key
636
- // through the rig zkApp store (losing it would strand ~1.1 MINA).
637
- ...file.minaChannel ? { minaChannel: file.minaChannel } : topo.minaChannel ? {
638
- minaChannel: {
639
- ...topo.minaChannel,
640
- autoDeploy: buildMinaAutoDeploy(
641
- dir,
642
- identity.pubkey,
643
- `mina:${topo.minaChannel.networkId ?? "devnet"}`,
644
- warn
645
- )
646
- }
647
- } : {}
648
- };
649
- return new StandalonePublisher({
650
- clientConfig,
651
- eventFee,
652
- channelMap,
653
- warn,
654
- ...skipDaemonCheck ? { skipDaemonCheck: true } : {},
655
- ...topo.publishDestination ? { publishDestination: topo.publishDestination } : {},
656
- ...topo.storeDestination ? { storeDestination: topo.storeDestination } : {},
657
- // Route-price floors (F06): every per-packet claim must advance the
658
- // channel by at least the destination route's announced price.
659
- ...topo.routePrices ? {
660
- routePrices: {
661
- ...topo.routePrices.publish !== void 0 ? { publish: BigInt(topo.routePrices.publish) } : {},
662
- ...topo.routePrices.store !== void 0 ? { store: BigInt(topo.routePrices.store) } : {}
663
- }
664
- } : {},
665
- // `rig channel open --peer` (#263): anchor the channel (and its map
666
- // key) to an explicit peer destination instead of the configured
667
- // default.
668
- ...options.channelDestination ? { channelDestination: options.channelDestination } : {},
669
- // The peer's announce does not carry TokenNetwork/token parameters, so
670
- // the client's negotiation leaves them empty (#260 root cause 3) — the
671
- // publisher back-fills them from the derived per-chain maps before the
672
- // channel opens.
673
- ...topo.tokenNetworks || topo.preferredTokens ? {
674
- negotiationFallbacks: {
675
- ...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
676
- ...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {}
677
- }
678
- } : {}
679
- });
680
- };
681
- const publisher = new TopologyRecoveringPublisher(
682
- buildPublisher(topology),
683
- cached ? async () => {
684
- cache.invalidate(cacheKey);
685
- return buildPublisher(await resolveLiveTopology());
686
- } : void 0,
687
- warn
688
- );
689
- const walletViewNetwork = env["TOON_CLIENT_NETWORK"] ?? file.network;
690
- let walletViewFallback;
691
- if (walletViewNetwork === "devnet" || walletViewNetwork === "testnet" || walletViewNetwork === "mainnet") {
692
- const presets = resolveClientNetwork(walletViewNetwork);
693
- walletViewFallback = {
694
- ...presets.solanaChannel ? { solanaChannel: presets.solanaChannel } : {},
695
- ...presets.minaChannel ? { minaChannel: presets.minaChannel } : {}
696
- };
697
- }
698
- return {
699
- ownerPubkey: publisher.getPublicKey(),
700
- identitySource: identity.source,
701
- identitySourceLabel: identity.sourceLabel,
702
- publisher,
703
- defaultRelayUrls: [relayUrl],
704
- fetchRemote: (args) => fetchRemoteState(args),
705
- // Money lifecycle (#263): same guard/start/channel-map machinery as the
706
- // paid-write path, surfaced for fund/balance/channel open|close|settle.
707
- money: {
708
- openChannel: (opts) => publisher.openChannelExplicit(opts),
709
- closeChannel: (record) => publisher.closeRecordedChannel(record),
710
- settleChannel: (record) => publisher.settleRecordedChannel(record),
711
- walletChainBalances: () => publisher.readWalletChainBalances(walletViewFallback)
712
- },
713
- stop: () => publisher.stop()
714
- };
715
- }
716
- export {
717
- MissingUplinkError,
718
- OFFICIAL_PROXY_URL,
719
- OFFICIAL_PUBLISH_DESTINATION,
720
- buildMinaAutoDeploy,
721
- createStandaloneContext,
722
- resolveNetworkTopology
723
- };
724
- //# sourceMappingURL=standalone-mode-LBWGU7NV.js.map