@toon-protocol/rig 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,595 @@
1
+ import {
2
+ fetchRemoteState,
3
+ queryRelay
4
+ } from "./chunk-PLKZAUTG.js";
5
+ import {
6
+ resolveIdentity
7
+ } from "./chunk-CW4HJNMU.js";
8
+ import "./chunk-HPSOQP7Q.js";
9
+ import {
10
+ StandalonePublisher
11
+ } from "./chunk-3EKP7PMM.js";
12
+ import {
13
+ ChannelMapStore,
14
+ RIG_CHANNEL_MAP_FILENAME
15
+ } from "./chunk-O6TXHKWG.js";
16
+ import "./chunk-X2CZPPDM.js";
17
+
18
+ // src/cli/standalone-mode.ts
19
+ import { readFileSync } from "fs";
20
+ import { homedir } from "os";
21
+ import { join } from "path";
22
+ import { EvmSigner, deriveNostrKeyFromMnemonic } from "@toon-protocol/client";
23
+ import {
24
+ decodeEventFromToon,
25
+ encodeEventToToon
26
+ } from "@toon-protocol/core";
27
+
28
+ // src/standalone/network-bootstrap.ts
29
+ import {
30
+ CHAIN_PRESETS,
31
+ GenesisPeerLoader,
32
+ isEventExpired,
33
+ parseIlpPeerInfo
34
+ } from "@toon-protocol/core";
35
+ var ILP_PEER_INFO_KIND = 10032;
36
+ var DISCOVERY_TIMEOUT_MS = 5e3;
37
+ function parseRoutes(content) {
38
+ try {
39
+ const parsed = JSON.parse(content);
40
+ const routes = parsed.routes;
41
+ if (typeof routes !== "object" || routes === null) return void 0;
42
+ const { publish, store } = routes;
43
+ const out = {
44
+ ...typeof publish === "string" && publish.length > 0 ? { publish } : {},
45
+ ...typeof store === "string" && store.length > 0 ? { store } : {}
46
+ };
47
+ return out.publish || out.store ? out : void 0;
48
+ } catch {
49
+ return void 0;
50
+ }
51
+ }
52
+ async function discoverAnnouncedPeers(relayUrl, options = {}) {
53
+ const factory = options.webSocketFactory ?? defaultDiscoveryWebSocketFactory();
54
+ const events = await queryRelay(
55
+ relayUrl,
56
+ { kinds: [ILP_PEER_INFO_KIND], limit: 100 },
57
+ options.timeoutMs ?? DISCOVERY_TIMEOUT_MS,
58
+ factory
59
+ );
60
+ const latestByAuthor = /* @__PURE__ */ new Map();
61
+ for (const event of events) {
62
+ if (event.kind !== ILP_PEER_INFO_KIND) continue;
63
+ const prev = latestByAuthor.get(event.pubkey);
64
+ if (!prev || event.created_at > prev.created_at) {
65
+ latestByAuthor.set(event.pubkey, event);
66
+ }
67
+ }
68
+ const peers = [];
69
+ for (const event of latestByAuthor.values()) {
70
+ if (isEventExpired(event)) {
71
+ continue;
72
+ }
73
+ let info;
74
+ try {
75
+ info = parseIlpPeerInfo(event);
76
+ } catch {
77
+ continue;
78
+ }
79
+ const routes = parseRoutes(event.content);
80
+ peers.push({
81
+ pubkey: event.pubkey,
82
+ info,
83
+ ...routes ? { routes } : {},
84
+ createdAt: event.created_at
85
+ });
86
+ }
87
+ return peers;
88
+ }
89
+ function defaultDiscoveryWebSocketFactory() {
90
+ return (url) => {
91
+ const ctor = globalThis.WebSocket;
92
+ if (!ctor) {
93
+ throw new Error(
94
+ "No global WebSocket constructor (Node >= 22 required) for announce discovery"
95
+ );
96
+ }
97
+ return new ctor(url);
98
+ };
99
+ }
100
+ function pickPaymentPeer(peers, seedPubkeys) {
101
+ const seeded = peers.filter((p) => seedPubkeys.includes(p.pubkey));
102
+ if (seeded.length > 0) {
103
+ return seeded.sort((a, b) => b.createdAt - a.createdAt)[0];
104
+ }
105
+ const payable = peers.filter(
106
+ (p) => (p.info.httpEndpoint || p.info.btpEndpoint) && p.info.settlementAddresses && Object.keys(p.info.settlementAddresses).length > 0
107
+ );
108
+ if (payable.length === 0) return void 0;
109
+ const publishEdges = payable.filter(
110
+ (p) => p.routes?.publish !== void 0 && p.routes.publish === p.info.ilpAddress
111
+ );
112
+ const pool = publishEdges.length > 0 ? publishEdges : payable;
113
+ return pool.sort((a, b) => b.createdAt - a.createdAt)[0];
114
+ }
115
+ var DEVNET_ZONE = "devnet.toonprotocol.dev";
116
+ var DEVNET_CHAIN_RPC_URLS = {
117
+ "evm:31337": "https://evm-rpc.devnet.toonprotocol.dev",
118
+ "solana:devnet": "https://solana-rpc.devnet.toonprotocol.dev"
119
+ };
120
+ function hostOf(url) {
121
+ if (!url) return void 0;
122
+ try {
123
+ return new URL(url).hostname;
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ }
128
+ function isDevnetZonePeer(peer) {
129
+ if (!peer) return false;
130
+ return [
131
+ hostOf(peer.info.httpEndpoint),
132
+ hostOf(peer.info.btpEndpoint),
133
+ hostOf(peer.info.relayUrl)
134
+ ].some((h) => h !== void 0 && (h === DEVNET_ZONE || h.endsWith(`.${DEVNET_ZONE}`)));
135
+ }
136
+ function evmChainIdOf(chain) {
137
+ const parts = chain.split(":");
138
+ if (parts[0] !== "evm") return void 0;
139
+ const raw = parts.length >= 3 ? parts[2] : parts[1];
140
+ const id = Number.parseInt(raw ?? "", 10);
141
+ return Number.isNaN(id) ? void 0 : id;
142
+ }
143
+ function evmPresetForChain(chain) {
144
+ const id = evmChainIdOf(chain);
145
+ if (id === void 0) return void 0;
146
+ for (const preset of Object.values(CHAIN_PRESETS)) {
147
+ if (preset.chainId === id) {
148
+ return {
149
+ rpcUrl: preset.rpcUrl,
150
+ usdcAddress: preset.usdcAddress,
151
+ tokenNetworkAddress: preset.tokenNetworkAddress
152
+ };
153
+ }
154
+ }
155
+ return void 0;
156
+ }
157
+ function resolveChainSettlement(chain, explicit, announce) {
158
+ const family = chain.split(":")[0] ?? chain;
159
+ const preset = evmPresetForChain(chain);
160
+ const devnetRpc = isDevnetZonePeer(announce) ? DEVNET_CHAIN_RPC_URLS[chain] : void 0;
161
+ const rpcUrl = explicit.chainRpcUrls?.[chain] ?? devnetRpc ?? preset?.rpcUrl;
162
+ const tokenAddress = explicit.preferredTokens?.[chain] ?? announce?.info.preferredTokens?.[chain] ?? (preset?.usdcAddress || void 0);
163
+ const tokenNetwork = explicit.tokenNetworks?.[chain] ?? announce?.info.tokenNetworks?.[chain] ?? (preset?.tokenNetworkAddress || void 0);
164
+ return {
165
+ chain,
166
+ family,
167
+ ...rpcUrl ? { rpcUrl } : {},
168
+ ...tokenAddress ? { tokenAddress } : {},
169
+ ...tokenNetwork ? { tokenNetwork } : {}
170
+ };
171
+ }
172
+ var TokenNetworkUnderivableError = class extends Error {
173
+ constructor(chain, announce, relayUrl) {
174
+ const announceRef = announce ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}\u2026 on ${relayUrl}` : `no kind:10032 announce was found on ${relayUrl}`;
175
+ super(
176
+ `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)`
177
+ );
178
+ this.name = "TokenNetworkUnderivableError";
179
+ }
180
+ };
181
+ async function selectSettlementChain(options) {
182
+ const { explicitChain, announcedChains } = options;
183
+ if (explicitChain) {
184
+ if (explicitChain.includes(":")) {
185
+ return {
186
+ chain: explicitChain,
187
+ reason: "explicit",
188
+ detail: `chain ${explicitChain} set by config`
189
+ };
190
+ }
191
+ const familyMatch = announcedChains.find(
192
+ (c) => (c.split(":")[0] ?? c) === explicitChain
193
+ );
194
+ if (!familyMatch) {
195
+ throw new Error(
196
+ `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`
197
+ );
198
+ }
199
+ return {
200
+ chain: familyMatch,
201
+ reason: "explicit",
202
+ detail: `chain family "${explicitChain}" set by config \u2192 ${familyMatch}`
203
+ };
204
+ }
205
+ const live = (options.records ?? []).filter((r) => !r.closed && announcedChains.includes(r.chain)).sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
206
+ const persisted = live[0];
207
+ if (persisted) {
208
+ return {
209
+ chain: persisted.chain,
210
+ reason: "persisted-channel",
211
+ detail: `existing payment channel on ${persisted.chain} (rig channel map)`
212
+ };
213
+ }
214
+ const evmChains = announcedChains.filter((c) => c.startsWith("evm:"));
215
+ if (options.evmAddress && options.probeBalance) {
216
+ for (const chain of evmChains) {
217
+ const settlement = options.resolveSettlement(chain);
218
+ if (!settlement.rpcUrl || !settlement.tokenAddress) continue;
219
+ try {
220
+ const balance = await options.probeBalance({
221
+ rpcUrl: settlement.rpcUrl,
222
+ tokenAddress: settlement.tokenAddress,
223
+ owner: options.evmAddress
224
+ });
225
+ if (balance > 0n) {
226
+ return {
227
+ chain,
228
+ reason: "funded",
229
+ detail: `wallet holds ${balance} token base units on ${chain}`
230
+ };
231
+ }
232
+ } catch {
233
+ }
234
+ }
235
+ }
236
+ const fallback = evmChains[0] ?? announcedChains[0];
237
+ if (!fallback) {
238
+ throw new Error(
239
+ "the payment peer announces no settlement chains \u2014 cannot select a chain for paid writes (set supportedChains/chain in the client config)"
240
+ );
241
+ }
242
+ return {
243
+ chain: fallback,
244
+ reason: "default",
245
+ detail: evmChains[0] ? `first EVM chain announced by the payment peer` : `first chain announced by the payment peer (no EVM chain announced)`
246
+ };
247
+ }
248
+ var BALANCE_OF_SELECTOR = "0x70a08231";
249
+ async function evmTokenBalance(args) {
250
+ const fetchImpl = args.fetchImpl ?? fetch;
251
+ const owner = args.owner.replace(/^0x/, "").toLowerCase().padStart(64, "0");
252
+ const controller = new AbortController();
253
+ const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5e3);
254
+ try {
255
+ const res = await fetchImpl(args.rpcUrl, {
256
+ method: "POST",
257
+ headers: { "content-type": "application/json" },
258
+ body: JSON.stringify({
259
+ jsonrpc: "2.0",
260
+ id: 1,
261
+ method: "eth_call",
262
+ params: [
263
+ { to: args.tokenAddress, data: `${BALANCE_OF_SELECTOR}${owner}` },
264
+ "latest"
265
+ ]
266
+ }),
267
+ signal: controller.signal
268
+ });
269
+ if (!res.ok) {
270
+ throw new Error(`eth_call failed: HTTP ${res.status}`);
271
+ }
272
+ const body = await res.json();
273
+ if (typeof body.result !== "string") {
274
+ throw new Error(`eth_call failed: ${body.error?.message ?? "no result"}`);
275
+ }
276
+ return BigInt(body.result === "0x" ? "0x0" : body.result);
277
+ } finally {
278
+ clearTimeout(timer);
279
+ }
280
+ }
281
+ function loadGenesisSeed() {
282
+ return GenesisPeerLoader.loadGenesisPeers()[0];
283
+ }
284
+ function genesisSeedPubkeys() {
285
+ return GenesisPeerLoader.loadGenesisPeers().map((p) => p.pubkey);
286
+ }
287
+
288
+ // src/cli/standalone-mode.ts
289
+ var MissingUplinkError = class extends Error {
290
+ constructor(configPath, relayUrl) {
291
+ const discovered = relayUrl ? `no announce with a btp/http endpoint was found on ${relayUrl} and the genesis seed has none; ` : "";
292
+ super(
293
+ `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL (connector payment proxy) or TOON_CLIENT_BTP_URL, or add proxyUrl/btpUrl to ${configPath}`
294
+ );
295
+ this.name = "MissingUplinkError";
296
+ }
297
+ };
298
+ function configDir(env) {
299
+ return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
300
+ }
301
+ function readClientConfig(path) {
302
+ try {
303
+ return JSON.parse(readFileSync(path, "utf8"));
304
+ } catch (err) {
305
+ if (err.code === "ENOENT") return {};
306
+ throw new Error(
307
+ `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
308
+ );
309
+ }
310
+ }
311
+ function proxyBaseOf(httpEndpoint) {
312
+ return httpEndpoint.replace(/\/+$/, "").replace(/\/ilp$/i, "");
313
+ }
314
+ async function resolveNetworkTopology(inputs) {
315
+ const { env, file, configPath, relayUrl, announce, genesisSeed, warn } = inputs;
316
+ const explicitProxyUrl = env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl;
317
+ const explicitBtpUrl = env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl;
318
+ const explicitDestination = env["TOON_CLIENT_DESTINATION"] ?? file.destination;
319
+ const explicitPublish = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? file.publishDestination;
320
+ const explicitStore = env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
321
+ const explicitChain = env["TOON_CLIENT_CHAIN"] ?? file.chain;
322
+ const explicitMaps = {
323
+ ...file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {},
324
+ ...file.preferredTokens ? { preferredTokens: file.preferredTokens } : {},
325
+ ...file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}
326
+ };
327
+ let proxyUrl = explicitProxyUrl;
328
+ let btpUrl = explicitBtpUrl;
329
+ if (!proxyUrl && !btpUrl) {
330
+ if (announce?.info.httpEndpoint) {
331
+ proxyUrl = proxyBaseOf(announce.info.httpEndpoint);
332
+ } else if (announce?.info.btpEndpoint) {
333
+ btpUrl = announce.info.btpEndpoint;
334
+ } else if (genesisSeed?.btpEndpoint) {
335
+ btpUrl = genesisSeed.btpEndpoint;
336
+ } else if (inputs.requireUplink !== false) {
337
+ throw new MissingUplinkError(configPath, relayUrl);
338
+ }
339
+ }
340
+ const destination = explicitDestination ?? announce?.info.ilpAddress ?? genesisSeed?.ilpAddress ?? "g.proxy";
341
+ const publishDestination = explicitPublish ?? announce?.routes?.publish;
342
+ const storeDestination = explicitStore ?? announce?.routes?.store;
343
+ const knownPeers = announce ? [
344
+ {
345
+ pubkey: announce.pubkey,
346
+ relayUrl,
347
+ btpEndpoint: announce.info.btpEndpoint ?? ""
348
+ }
349
+ ] : genesisSeed ? [
350
+ {
351
+ pubkey: genesisSeed.pubkey,
352
+ relayUrl: genesisSeed.relayUrl,
353
+ btpEndpoint: genesisSeed.btpEndpoint
354
+ }
355
+ ] : [];
356
+ const announcedChains = announce?.info.supportedChains ?? [];
357
+ const resolveSettlement = (chain) => resolveChainSettlement(chain, explicitMaps, announce);
358
+ let selection;
359
+ let supportedChains;
360
+ let preferredTokens;
361
+ let tokenNetworks;
362
+ let chainRpcUrls;
363
+ if (file.supportedChains?.length) {
364
+ supportedChains = file.supportedChains;
365
+ preferredTokens = { ...file.preferredTokens };
366
+ tokenNetworks = { ...file.tokenNetworks };
367
+ chainRpcUrls = { ...file.chainRpcUrls };
368
+ for (const chain of supportedChains) {
369
+ const s = resolveSettlement(chain);
370
+ if (s.tokenAddress && !preferredTokens[chain]) {
371
+ preferredTokens[chain] = s.tokenAddress;
372
+ }
373
+ if (s.tokenNetwork && !tokenNetworks[chain]) {
374
+ tokenNetworks[chain] = s.tokenNetwork;
375
+ }
376
+ if (s.rpcUrl && !chainRpcUrls[chain]) {
377
+ chainRpcUrls[chain] = s.rpcUrl;
378
+ }
379
+ if (s.family === "evm") {
380
+ if (!tokenNetworks[chain]) {
381
+ throw new TokenNetworkUnderivableError(chain, announce, relayUrl);
382
+ }
383
+ if (!chainRpcUrls[chain]) {
384
+ throw new Error(
385
+ `no RPC URL is derivable for settlement chain "${chain}" \u2014 add chainRpcUrls["${chain}"] to ${configPath}`
386
+ );
387
+ }
388
+ }
389
+ }
390
+ selection = {
391
+ chain: supportedChains[0],
392
+ reason: "explicit",
393
+ detail: "supportedChains set by config"
394
+ };
395
+ } else if (explicitChain || announcedChains.length > 0) {
396
+ const { secretKey } = deriveNostrKeyFromMnemonic(
397
+ inputs.identity.mnemonic,
398
+ inputs.identity.accountIndex
399
+ );
400
+ const evmAddress = new EvmSigner(secretKey).address;
401
+ selection = await selectSettlementChain({
402
+ ...explicitChain ? { explicitChain } : {},
403
+ announcedChains,
404
+ records: inputs.channelRecords(),
405
+ evmAddress,
406
+ resolveSettlement,
407
+ probeBalance: inputs.probeBalance ?? evmTokenBalance
408
+ });
409
+ const settlement = resolveSettlement(selection.chain);
410
+ if (settlement.family === "evm") {
411
+ if (!settlement.tokenNetwork) {
412
+ throw new TokenNetworkUnderivableError(
413
+ selection.chain,
414
+ announce,
415
+ relayUrl
416
+ );
417
+ }
418
+ if (!settlement.rpcUrl) {
419
+ throw new Error(
420
+ `no RPC URL is derivable for settlement chain "${selection.chain}" \u2014 add chainRpcUrls["${selection.chain}"] to ${configPath}`
421
+ );
422
+ }
423
+ }
424
+ supportedChains = [selection.chain];
425
+ preferredTokens = settlement.tokenAddress ? { [selection.chain]: settlement.tokenAddress } : void 0;
426
+ tokenNetworks = settlement.tokenNetwork ? { [selection.chain]: settlement.tokenNetwork } : void 0;
427
+ chainRpcUrls = settlement.rpcUrl ? { [selection.chain]: settlement.rpcUrl } : void 0;
428
+ if (selection.reason !== "explicit") {
429
+ warn(
430
+ `rig: settlement chain ${selection.chain} selected \u2014 ${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains in the client config) to override`
431
+ );
432
+ }
433
+ } else {
434
+ warn(
435
+ `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}`
436
+ );
437
+ }
438
+ const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
439
+ if (network && network !== "custom" && !file.supportedChains?.length) {
440
+ warn(
441
+ `rig: ignoring the "${network}" network preset for settlement \u2014 the chain comes from the announce/config (#260); set supportedChains explicitly to use preset chains`
442
+ );
443
+ }
444
+ return {
445
+ ...proxyUrl ? { proxyUrl } : {},
446
+ ...btpUrl ? { btpUrl } : {},
447
+ destination,
448
+ ...publishDestination ? { publishDestination } : {},
449
+ ...storeDestination ? { storeDestination } : {},
450
+ knownPeers,
451
+ ...selection ? { selection } : {},
452
+ ...supportedChains ? { supportedChains } : {},
453
+ ...preferredTokens && Object.keys(preferredTokens).length > 0 ? { preferredTokens } : {},
454
+ ...tokenNetworks && Object.keys(tokenNetworks).length > 0 ? { tokenNetworks } : {},
455
+ ...chainRpcUrls && Object.keys(chainRpcUrls).length > 0 ? { chainRpcUrls } : {}
456
+ };
457
+ }
458
+ function chainRecordsFor(map, identity) {
459
+ return map.list().filter((r) => r.identity === identity).map((r) => {
460
+ const watermark = map.readWatermark(r.channelId);
461
+ return {
462
+ chain: r.chain,
463
+ lastUsedAt: r.lastUsedAt,
464
+ closed: watermark?.closedAt !== void 0 || watermark?.settledAt !== void 0
465
+ };
466
+ });
467
+ }
468
+ async function createStandaloneContext(options) {
469
+ const { env } = options;
470
+ const warn = (line) => options.warn(line);
471
+ const dir = configDir(env);
472
+ const configPath = join(dir, "config.json");
473
+ const file = readClientConfig(configPath);
474
+ const identity = await resolveIdentity(options);
475
+ const genesisSeed = loadGenesisSeed();
476
+ const relayUrl = options.relayUrl ?? env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl ?? genesisSeed?.relayUrl ?? "ws://localhost:7100";
477
+ const fullyExplicit = Boolean(
478
+ (env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl) || (env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl)
479
+ ) && Boolean(env["TOON_CLIENT_DESTINATION"] ?? file.destination) && Boolean(file.supportedChains?.length);
480
+ let announce;
481
+ if (!fullyExplicit) {
482
+ try {
483
+ const peers = await discoverAnnouncedPeers(relayUrl, {
484
+ timeoutMs: DISCOVERY_TIMEOUT_MS
485
+ });
486
+ announce = pickPaymentPeer(peers, genesisSeedPubkeys());
487
+ if (!announce) {
488
+ warn(
489
+ `rig: no payment-peer announce (kind:10032) found on ${relayUrl} \u2014 falling back to the genesis peer seed`
490
+ );
491
+ }
492
+ } catch (err) {
493
+ warn(
494
+ `rig: announce discovery on ${relayUrl} failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to the genesis peer seed`
495
+ );
496
+ }
497
+ }
498
+ const channelStorePath = file.channelStorePath ?? join(dir, "channels.json");
499
+ const channelMap = new ChannelMapStore({
500
+ mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),
501
+ watermarkPath: channelStorePath
502
+ });
503
+ const topology = await resolveNetworkTopology({
504
+ env,
505
+ file,
506
+ configPath,
507
+ relayUrl,
508
+ announce,
509
+ genesisSeed,
510
+ identity: {
511
+ mnemonic: identity.mnemonic,
512
+ accountIndex: identity.accountIndex,
513
+ pubkey: identity.pubkey
514
+ },
515
+ channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),
516
+ ...options.requireUplink !== void 0 ? { requireUplink: options.requireUplink } : {},
517
+ warn
518
+ });
519
+ const eventFee = BigInt(file.feePerEvent ?? "1");
520
+ const clientConfig = {
521
+ // validateConfig requires connectorUrl OR proxyUrl; with BTP-only config a
522
+ // dummy connectorUrl satisfies it (unused at runtime — same convention as
523
+ // the daemon).
524
+ ...topology.proxyUrl ? { proxyUrl: topology.proxyUrl } : { connectorUrl: "http://127.0.0.1:1" },
525
+ mnemonic: identity.mnemonic,
526
+ mnemonicAccountIndex: identity.accountIndex,
527
+ ilpInfo: {
528
+ pubkey: "00".repeat(32),
529
+ ilpAddress: "g.toon.client",
530
+ btpEndpoint: topology.btpUrl ?? "",
531
+ assetCode: "USD",
532
+ assetScale: 6
533
+ },
534
+ toonEncoder: encodeEventToToon,
535
+ toonDecoder: decodeEventFromToon,
536
+ ...topology.btpUrl ? { btpUrl: topology.btpUrl, btpAuthToken: "" } : {},
537
+ destinationAddress: topology.destination,
538
+ // The embedded client bootstraps against the known peer above; its
539
+ // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.
540
+ relayUrl: "",
541
+ knownPeers: topology.knownPeers,
542
+ channelStorePath,
543
+ ...topology.supportedChains ? { supportedChains: topology.supportedChains } : {},
544
+ ...file.settlementAddresses ? { settlementAddresses: file.settlementAddresses } : {},
545
+ ...topology.preferredTokens ? { preferredTokens: topology.preferredTokens } : {},
546
+ ...topology.tokenNetworks ? { tokenNetworks: topology.tokenNetworks } : {},
547
+ ...topology.chainRpcUrls ? { chainRpcUrls: topology.chainRpcUrls } : {},
548
+ ...file.solanaChannel ? { solanaChannel: file.solanaChannel } : {},
549
+ ...file.minaChannel ? { minaChannel: file.minaChannel } : {}
550
+ };
551
+ const publisher = new StandalonePublisher({
552
+ clientConfig,
553
+ eventFee,
554
+ channelMap,
555
+ warn,
556
+ ...topology.publishDestination ? { publishDestination: topology.publishDestination } : {},
557
+ ...topology.storeDestination ? { storeDestination: topology.storeDestination } : {},
558
+ // `rig channel open --peer` (#263): anchor the channel (and its map key)
559
+ // to an explicit peer destination instead of the configured default.
560
+ ...options.channelDestination ? { channelDestination: options.channelDestination } : {},
561
+ // The peer's announce does not carry TokenNetwork/token parameters, so
562
+ // the client's negotiation leaves them empty (#260 root cause 3) — the
563
+ // publisher back-fills them from the derived per-chain maps before the
564
+ // channel opens.
565
+ ...topology.tokenNetworks || topology.preferredTokens ? {
566
+ negotiationFallbacks: {
567
+ ...topology.tokenNetworks ? { tokenNetworks: topology.tokenNetworks } : {},
568
+ ...topology.preferredTokens ? { preferredTokens: topology.preferredTokens } : {}
569
+ }
570
+ } : {}
571
+ });
572
+ return {
573
+ ownerPubkey: publisher.getPublicKey(),
574
+ identitySource: identity.source,
575
+ identitySourceLabel: identity.sourceLabel,
576
+ publisher,
577
+ defaultRelayUrls: [relayUrl],
578
+ fetchRemote: (args) => fetchRemoteState(args),
579
+ // Money lifecycle (#263): same guard/start/channel-map machinery as the
580
+ // paid-write path, surfaced for fund/balance/channel open|close|settle.
581
+ money: {
582
+ openChannel: (opts) => publisher.openChannelExplicit(opts),
583
+ closeChannel: (record) => publisher.closeRecordedChannel(record),
584
+ settleChannel: (record) => publisher.settleRecordedChannel(record),
585
+ walletBalances: () => publisher.readWalletBalances()
586
+ },
587
+ stop: () => publisher.stop()
588
+ };
589
+ }
590
+ export {
591
+ MissingUplinkError,
592
+ createStandaloneContext,
593
+ resolveNetworkTopology
594
+ };
595
+ //# sourceMappingURL=standalone-mode-FCKTQ33Y.js.map