@toon-protocol/rig 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -13
- package/dist/{chunk-G4W4MH6G.js → chunk-JBB7HBQC.js} +120 -6
- package/dist/chunk-JBB7HBQC.js.map +1 -0
- package/dist/chunk-OIJNRACA.js +588 -0
- package/dist/chunk-OIJNRACA.js.map +1 -0
- package/dist/{chunk-LFGDLD6J.js → chunk-PS5QOT62.js} +464 -4
- package/dist/chunk-PS5QOT62.js.map +1 -0
- package/dist/chunk-SW7ZHMGS.js +358 -0
- package/dist/chunk-SW7ZHMGS.js.map +1 -0
- package/dist/cli/rig.js +2664 -410
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +271 -3
- package/dist/index.js +44 -8
- package/dist/{publisher-CClD9OIZ.d.ts → publisher-CrwaNQrK.d.ts} +10 -1
- package/dist/standalone/index.d.ts +410 -3
- package/dist/standalone/index.js +20 -6
- package/dist/standalone-mode-64CKUVU2.js +865 -0
- package/dist/standalone-mode-64CKUVU2.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-G4W4MH6G.js.map +0 -1
- package/dist/chunk-HPSOQP7Q.js +0 -109
- package/dist/chunk-HPSOQP7Q.js.map +0 -1
- package/dist/chunk-LFGDLD6J.js.map +0 -1
- package/dist/chunk-XRRU6YBQ.js +0 -402
- package/dist/chunk-XRRU6YBQ.js.map +0 -1
- package/dist/standalone-mode-Q3FFZUXE.js +0 -111
- package/dist/standalone-mode-Q3FFZUXE.js.map +0 -1
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveIdentity
|
|
3
|
+
} from "./chunk-CW4HJNMU.js";
|
|
4
|
+
import {
|
|
5
|
+
fetchRemoteState,
|
|
6
|
+
queryRelay
|
|
7
|
+
} from "./chunk-JBB7HBQC.js";
|
|
8
|
+
import {
|
|
9
|
+
StandalonePublisher
|
|
10
|
+
} from "./chunk-OIJNRACA.js";
|
|
11
|
+
import {
|
|
12
|
+
ChannelMapStore,
|
|
13
|
+
RIG_CHANNEL_MAP_FILENAME
|
|
14
|
+
} from "./chunk-SW7ZHMGS.js";
|
|
15
|
+
import "./chunk-X2CZPPDM.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/standalone-mode.ts
|
|
18
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
import { EvmSigner, deriveNostrKeyFromMnemonic } from "@toon-protocol/client";
|
|
22
|
+
import {
|
|
23
|
+
decodeEventFromToon,
|
|
24
|
+
encodeEventToToon
|
|
25
|
+
} from "@toon-protocol/core";
|
|
26
|
+
|
|
27
|
+
// src/standalone/topology-cache.ts
|
|
28
|
+
import { createHash } from "crypto";
|
|
29
|
+
import { mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
30
|
+
import { dirname } from "path";
|
|
31
|
+
var TOPOLOGY_CACHE_FILENAME = "rig-topology-cache.json";
|
|
32
|
+
var DEFAULT_TOPOLOGY_TTL_MS = 15 * 60 * 1e3;
|
|
33
|
+
var TOPOLOGY_TTL_ENV = "RIG_TOPOLOGY_TTL_MS";
|
|
34
|
+
function topologyCacheTtlMs(env) {
|
|
35
|
+
const raw = env[TOPOLOGY_TTL_ENV];
|
|
36
|
+
if (raw === void 0 || raw === "") return DEFAULT_TOPOLOGY_TTL_MS;
|
|
37
|
+
const parsed = Number(raw);
|
|
38
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_TOPOLOGY_TTL_MS;
|
|
39
|
+
}
|
|
40
|
+
function topologyCacheKey(args) {
|
|
41
|
+
return createHash("sha256").update(`${args.relayUrl}
|
|
42
|
+
${args.identity}
|
|
43
|
+
${args.fingerprint}`).digest("hex");
|
|
44
|
+
}
|
|
45
|
+
function explicitConfigFingerprint(env, file) {
|
|
46
|
+
const envKeys = [
|
|
47
|
+
"TOON_CLIENT_PROXY_URL",
|
|
48
|
+
"TOON_CLIENT_BTP_URL",
|
|
49
|
+
"TOON_CLIENT_DESTINATION",
|
|
50
|
+
"TOON_CLIENT_PUBLISH_DESTINATION",
|
|
51
|
+
"TOON_CLIENT_STORE_DESTINATION",
|
|
52
|
+
"TOON_CLIENT_CHAIN",
|
|
53
|
+
"TOON_CLIENT_NETWORK"
|
|
54
|
+
];
|
|
55
|
+
const fileKeys = [
|
|
56
|
+
"network",
|
|
57
|
+
"btpUrl",
|
|
58
|
+
"proxyUrl",
|
|
59
|
+
"destination",
|
|
60
|
+
"publishDestination",
|
|
61
|
+
"storeDestination",
|
|
62
|
+
"chain",
|
|
63
|
+
"supportedChains",
|
|
64
|
+
"settlementAddresses",
|
|
65
|
+
"preferredTokens",
|
|
66
|
+
"tokenNetworks",
|
|
67
|
+
"chainRpcUrls"
|
|
68
|
+
];
|
|
69
|
+
const picked = {};
|
|
70
|
+
for (const key of envKeys) {
|
|
71
|
+
if (env[key] !== void 0) picked[`env:${key}`] = env[key];
|
|
72
|
+
}
|
|
73
|
+
for (const key of fileKeys) {
|
|
74
|
+
if (file[key] !== void 0) picked[`file:${key}`] = file[key];
|
|
75
|
+
}
|
|
76
|
+
return JSON.stringify(picked);
|
|
77
|
+
}
|
|
78
|
+
var TopologyCache = class {
|
|
79
|
+
path;
|
|
80
|
+
ttlMs;
|
|
81
|
+
validate;
|
|
82
|
+
now;
|
|
83
|
+
constructor(options) {
|
|
84
|
+
this.path = options.path;
|
|
85
|
+
this.ttlMs = options.ttlMs;
|
|
86
|
+
this.validate = options.validate;
|
|
87
|
+
this.now = options.now ?? Date.now;
|
|
88
|
+
}
|
|
89
|
+
/** True when the cache is disabled (`RIG_TOPOLOGY_TTL_MS=0`). */
|
|
90
|
+
get disabled() {
|
|
91
|
+
return this.ttlMs <= 0;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A fresh, structurally-valid entry for `key` — or undefined (expired,
|
|
95
|
+
* missing, invalid, corrupt file, or disabled). Also reports the entry age
|
|
96
|
+
* for the "topology from cache" stderr line.
|
|
97
|
+
*/
|
|
98
|
+
read(key) {
|
|
99
|
+
if (this.disabled) return void 0;
|
|
100
|
+
const entry = this.readFile().entries[key];
|
|
101
|
+
if (!entry) return void 0;
|
|
102
|
+
const cachedAt = Date.parse(entry.cachedAt);
|
|
103
|
+
if (!Number.isFinite(cachedAt)) return void 0;
|
|
104
|
+
const ageMs = this.now() - cachedAt;
|
|
105
|
+
if (ageMs < 0 || ageMs > this.ttlMs) return void 0;
|
|
106
|
+
if (!this.validate(entry.topology)) return void 0;
|
|
107
|
+
return { topology: entry.topology, ageMs };
|
|
108
|
+
}
|
|
109
|
+
/** Persist `topology` under `key` (prunes expired entries; best-effort). */
|
|
110
|
+
write(key, topology) {
|
|
111
|
+
if (this.disabled) return;
|
|
112
|
+
try {
|
|
113
|
+
const file = this.readFile();
|
|
114
|
+
const nowMs = this.now();
|
|
115
|
+
const fresh = Object.fromEntries(
|
|
116
|
+
Object.entries(file.entries).filter(([, entry]) => {
|
|
117
|
+
const at = Date.parse(entry.cachedAt);
|
|
118
|
+
return Number.isFinite(at) && nowMs - at <= this.ttlMs;
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
fresh[key] = {
|
|
122
|
+
cachedAt: new Date(nowMs).toISOString(),
|
|
123
|
+
topology
|
|
124
|
+
};
|
|
125
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
126
|
+
writeFileSync(
|
|
127
|
+
this.path,
|
|
128
|
+
JSON.stringify(
|
|
129
|
+
{ version: 1, entries: fresh },
|
|
130
|
+
null,
|
|
131
|
+
2
|
|
132
|
+
),
|
|
133
|
+
{ mode: 384 }
|
|
134
|
+
);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Drop `key` (a cached topology failed to bootstrap). Best-effort. */
|
|
139
|
+
invalidate(key) {
|
|
140
|
+
try {
|
|
141
|
+
const file = this.readFile();
|
|
142
|
+
if (!(key in file.entries)) return;
|
|
143
|
+
const remaining = Object.fromEntries(
|
|
144
|
+
Object.entries(file.entries).filter(([k]) => k !== key)
|
|
145
|
+
);
|
|
146
|
+
if (Object.keys(remaining).length === 0) {
|
|
147
|
+
unlinkSync(this.path);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
writeFileSync(
|
|
151
|
+
this.path,
|
|
152
|
+
JSON.stringify(
|
|
153
|
+
{ version: 1, entries: remaining },
|
|
154
|
+
null,
|
|
155
|
+
2
|
|
156
|
+
),
|
|
157
|
+
{ mode: 384 }
|
|
158
|
+
);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
readFile() {
|
|
163
|
+
const empty = { version: 1, entries: {} };
|
|
164
|
+
let raw;
|
|
165
|
+
try {
|
|
166
|
+
raw = readFileSync(this.path, "utf8");
|
|
167
|
+
} catch {
|
|
168
|
+
return empty;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(raw);
|
|
172
|
+
if (typeof parsed !== "object" || parsed === null || parsed.version !== 1 || typeof parsed.entries !== "object" || parsed.entries === null) {
|
|
173
|
+
return empty;
|
|
174
|
+
}
|
|
175
|
+
return parsed;
|
|
176
|
+
} catch {
|
|
177
|
+
return empty;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/standalone/network-bootstrap.ts
|
|
183
|
+
import {
|
|
184
|
+
CHAIN_PRESETS,
|
|
185
|
+
GenesisPeerLoader,
|
|
186
|
+
isEventExpired,
|
|
187
|
+
parseIlpPeerInfo
|
|
188
|
+
} from "@toon-protocol/core";
|
|
189
|
+
var ILP_PEER_INFO_KIND = 10032;
|
|
190
|
+
var DISCOVERY_TIMEOUT_MS = 5e3;
|
|
191
|
+
function parseRoutes(content) {
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(content);
|
|
194
|
+
const routes = parsed.routes;
|
|
195
|
+
if (typeof routes !== "object" || routes === null) return void 0;
|
|
196
|
+
const { publish, store } = routes;
|
|
197
|
+
const out = {
|
|
198
|
+
...typeof publish === "string" && publish.length > 0 ? { publish } : {},
|
|
199
|
+
...typeof store === "string" && store.length > 0 ? { store } : {}
|
|
200
|
+
};
|
|
201
|
+
return out.publish || out.store ? out : void 0;
|
|
202
|
+
} catch {
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function discoverAnnouncedPeers(relayUrl, options = {}) {
|
|
207
|
+
const factory = options.webSocketFactory ?? defaultDiscoveryWebSocketFactory();
|
|
208
|
+
const events = await queryRelay(
|
|
209
|
+
relayUrl,
|
|
210
|
+
{ kinds: [ILP_PEER_INFO_KIND], limit: 100 },
|
|
211
|
+
options.timeoutMs ?? DISCOVERY_TIMEOUT_MS,
|
|
212
|
+
factory
|
|
213
|
+
);
|
|
214
|
+
const latestByAuthor = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const event of events) {
|
|
216
|
+
if (event.kind !== ILP_PEER_INFO_KIND) continue;
|
|
217
|
+
const prev = latestByAuthor.get(event.pubkey);
|
|
218
|
+
if (!prev || event.created_at > prev.created_at) {
|
|
219
|
+
latestByAuthor.set(event.pubkey, event);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const peers = [];
|
|
223
|
+
for (const event of latestByAuthor.values()) {
|
|
224
|
+
if (isEventExpired(event)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
let info;
|
|
228
|
+
try {
|
|
229
|
+
info = parseIlpPeerInfo(event);
|
|
230
|
+
} catch {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const routes = parseRoutes(event.content);
|
|
234
|
+
peers.push({
|
|
235
|
+
pubkey: event.pubkey,
|
|
236
|
+
info,
|
|
237
|
+
...routes ? { routes } : {},
|
|
238
|
+
createdAt: event.created_at
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return peers;
|
|
242
|
+
}
|
|
243
|
+
function defaultDiscoveryWebSocketFactory() {
|
|
244
|
+
return (url) => {
|
|
245
|
+
const ctor = globalThis.WebSocket;
|
|
246
|
+
if (!ctor) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"No global WebSocket constructor (Node >= 22 required) for announce discovery"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return new ctor(url);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function pickPaymentPeer(peers, seedPubkeys) {
|
|
255
|
+
const seeded = peers.filter((p) => seedPubkeys.includes(p.pubkey));
|
|
256
|
+
if (seeded.length > 0) {
|
|
257
|
+
return seeded.sort((a, b) => b.createdAt - a.createdAt)[0];
|
|
258
|
+
}
|
|
259
|
+
const payable = peers.filter(
|
|
260
|
+
(p) => (p.info.httpEndpoint || p.info.btpEndpoint) && p.info.settlementAddresses && Object.keys(p.info.settlementAddresses).length > 0
|
|
261
|
+
);
|
|
262
|
+
if (payable.length === 0) return void 0;
|
|
263
|
+
const publishEdges = payable.filter(
|
|
264
|
+
(p) => p.routes?.publish !== void 0 && p.routes.publish === p.info.ilpAddress
|
|
265
|
+
);
|
|
266
|
+
const pool = publishEdges.length > 0 ? publishEdges : payable;
|
|
267
|
+
return pool.sort((a, b) => b.createdAt - a.createdAt)[0];
|
|
268
|
+
}
|
|
269
|
+
var DEVNET_ZONE = "devnet.toonprotocol.dev";
|
|
270
|
+
var DEVNET_CHAIN_RPC_URLS = {
|
|
271
|
+
"evm:31337": "https://evm-rpc.devnet.toonprotocol.dev",
|
|
272
|
+
"solana:devnet": "https://solana-rpc.devnet.toonprotocol.dev"
|
|
273
|
+
};
|
|
274
|
+
function hostOf(url) {
|
|
275
|
+
if (!url) return void 0;
|
|
276
|
+
try {
|
|
277
|
+
return new URL(url).hostname;
|
|
278
|
+
} catch {
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isDevnetZonePeer(peer) {
|
|
283
|
+
if (!peer) return false;
|
|
284
|
+
return [
|
|
285
|
+
hostOf(peer.info.httpEndpoint),
|
|
286
|
+
hostOf(peer.info.btpEndpoint),
|
|
287
|
+
hostOf(peer.info.relayUrl)
|
|
288
|
+
].some((h) => h !== void 0 && (h === DEVNET_ZONE || h.endsWith(`.${DEVNET_ZONE}`)));
|
|
289
|
+
}
|
|
290
|
+
function evmChainIdOf(chain) {
|
|
291
|
+
const parts = chain.split(":");
|
|
292
|
+
if (parts[0] !== "evm") return void 0;
|
|
293
|
+
const raw = parts.length >= 3 ? parts[2] : parts[1];
|
|
294
|
+
const id = Number.parseInt(raw ?? "", 10);
|
|
295
|
+
return Number.isNaN(id) ? void 0 : id;
|
|
296
|
+
}
|
|
297
|
+
function evmPresetForChain(chain) {
|
|
298
|
+
const id = evmChainIdOf(chain);
|
|
299
|
+
if (id === void 0) return void 0;
|
|
300
|
+
for (const preset of Object.values(CHAIN_PRESETS)) {
|
|
301
|
+
if (preset.chainId === id) {
|
|
302
|
+
return {
|
|
303
|
+
rpcUrl: preset.rpcUrl,
|
|
304
|
+
usdcAddress: preset.usdcAddress,
|
|
305
|
+
tokenNetworkAddress: preset.tokenNetworkAddress
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
function resolveChainSettlement(chain, explicit, announce) {
|
|
312
|
+
const family = chain.split(":")[0] ?? chain;
|
|
313
|
+
const preset = evmPresetForChain(chain);
|
|
314
|
+
const devnetRpc = isDevnetZonePeer(announce) ? DEVNET_CHAIN_RPC_URLS[chain] : void 0;
|
|
315
|
+
const rpcUrl = explicit.chainRpcUrls?.[chain] ?? devnetRpc ?? preset?.rpcUrl;
|
|
316
|
+
const tokenAddress = explicit.preferredTokens?.[chain] ?? announce?.info.preferredTokens?.[chain] ?? (preset?.usdcAddress || void 0);
|
|
317
|
+
const tokenNetwork = explicit.tokenNetworks?.[chain] ?? announce?.info.tokenNetworks?.[chain] ?? (preset?.tokenNetworkAddress || void 0);
|
|
318
|
+
return {
|
|
319
|
+
chain,
|
|
320
|
+
family,
|
|
321
|
+
...rpcUrl ? { rpcUrl } : {},
|
|
322
|
+
...tokenAddress ? { tokenAddress } : {},
|
|
323
|
+
...tokenNetwork ? { tokenNetwork } : {}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
var TokenNetworkUnderivableError = class extends Error {
|
|
327
|
+
constructor(chain, announce, relayUrl) {
|
|
328
|
+
const announceRef = announce ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}\u2026 on ${relayUrl}` : `no kind:10032 announce was found on ${relayUrl}`;
|
|
329
|
+
super(
|
|
330
|
+
`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)`
|
|
331
|
+
);
|
|
332
|
+
this.name = "TokenNetworkUnderivableError";
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
async function selectSettlementChain(options) {
|
|
336
|
+
const { explicitChain, announcedChains } = options;
|
|
337
|
+
if (explicitChain) {
|
|
338
|
+
if (explicitChain.includes(":")) {
|
|
339
|
+
return {
|
|
340
|
+
chain: explicitChain,
|
|
341
|
+
reason: "explicit",
|
|
342
|
+
detail: `chain ${explicitChain} set by config`
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const familyMatch = announcedChains.find(
|
|
346
|
+
(c) => (c.split(":")[0] ?? c) === explicitChain
|
|
347
|
+
);
|
|
348
|
+
if (!familyMatch) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`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`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
chain: familyMatch,
|
|
355
|
+
reason: "explicit",
|
|
356
|
+
detail: `chain family "${explicitChain}" set by config \u2192 ${familyMatch}`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const live = (options.records ?? []).filter((r) => !r.closed && announcedChains.includes(r.chain)).sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
|
|
360
|
+
const persisted = live[0];
|
|
361
|
+
if (persisted) {
|
|
362
|
+
return {
|
|
363
|
+
chain: persisted.chain,
|
|
364
|
+
reason: "persisted-channel",
|
|
365
|
+
detail: `existing payment channel on ${persisted.chain} (rig channel map)`
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const evmChains = announcedChains.filter((c) => c.startsWith("evm:"));
|
|
369
|
+
if (options.evmAddress && options.probeBalance) {
|
|
370
|
+
for (const chain of evmChains) {
|
|
371
|
+
const settlement = options.resolveSettlement(chain);
|
|
372
|
+
if (!settlement.rpcUrl || !settlement.tokenAddress) continue;
|
|
373
|
+
try {
|
|
374
|
+
const balance = await options.probeBalance({
|
|
375
|
+
rpcUrl: settlement.rpcUrl,
|
|
376
|
+
tokenAddress: settlement.tokenAddress,
|
|
377
|
+
owner: options.evmAddress
|
|
378
|
+
});
|
|
379
|
+
if (balance > 0n) {
|
|
380
|
+
return {
|
|
381
|
+
chain,
|
|
382
|
+
reason: "funded",
|
|
383
|
+
detail: `wallet holds ${balance} token base units on ${chain}`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const fallback = evmChains[0] ?? announcedChains[0];
|
|
391
|
+
if (!fallback) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"the payment peer announces no settlement chains \u2014 cannot select a chain for paid writes (set supportedChains/chain in the client config)"
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
chain: fallback,
|
|
398
|
+
reason: "default",
|
|
399
|
+
detail: evmChains[0] ? `first EVM chain announced by the payment peer` : `first chain announced by the payment peer (no EVM chain announced)`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
var BALANCE_OF_SELECTOR = "0x70a08231";
|
|
403
|
+
async function evmTokenBalance(args) {
|
|
404
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
405
|
+
const owner = args.owner.replace(/^0x/, "").toLowerCase().padStart(64, "0");
|
|
406
|
+
const controller = new AbortController();
|
|
407
|
+
const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5e3);
|
|
408
|
+
try {
|
|
409
|
+
const res = await fetchImpl(args.rpcUrl, {
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: { "content-type": "application/json" },
|
|
412
|
+
body: JSON.stringify({
|
|
413
|
+
jsonrpc: "2.0",
|
|
414
|
+
id: 1,
|
|
415
|
+
method: "eth_call",
|
|
416
|
+
params: [
|
|
417
|
+
{ to: args.tokenAddress, data: `${BALANCE_OF_SELECTOR}${owner}` },
|
|
418
|
+
"latest"
|
|
419
|
+
]
|
|
420
|
+
}),
|
|
421
|
+
signal: controller.signal
|
|
422
|
+
});
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
throw new Error(`eth_call failed: HTTP ${res.status}`);
|
|
425
|
+
}
|
|
426
|
+
const body = await res.json();
|
|
427
|
+
if (typeof body.result !== "string") {
|
|
428
|
+
throw new Error(`eth_call failed: ${body.error?.message ?? "no result"}`);
|
|
429
|
+
}
|
|
430
|
+
return BigInt(body.result === "0x" ? "0x0" : body.result);
|
|
431
|
+
} finally {
|
|
432
|
+
clearTimeout(timer);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function loadGenesisSeed() {
|
|
436
|
+
return GenesisPeerLoader.loadGenesisPeers()[0];
|
|
437
|
+
}
|
|
438
|
+
function genesisSeedPubkeys() {
|
|
439
|
+
return GenesisPeerLoader.loadGenesisPeers().map((p) => p.pubkey);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/cli/standalone-mode.ts
|
|
443
|
+
var MissingUplinkError = class extends Error {
|
|
444
|
+
constructor(configPath, relayUrl) {
|
|
445
|
+
const discovered = relayUrl ? `no announce with a btp/http endpoint was found on ${relayUrl} and the genesis seed has none; ` : "";
|
|
446
|
+
super(
|
|
447
|
+
`no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL (connector payment proxy) or TOON_CLIENT_BTP_URL, or add proxyUrl/btpUrl to ${configPath}`
|
|
448
|
+
);
|
|
449
|
+
this.name = "MissingUplinkError";
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
function configDir(env) {
|
|
453
|
+
return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
|
|
454
|
+
}
|
|
455
|
+
function readClientConfig(path) {
|
|
456
|
+
try {
|
|
457
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
458
|
+
} catch (err) {
|
|
459
|
+
if (err.code === "ENOENT") return {};
|
|
460
|
+
throw new Error(
|
|
461
|
+
`failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function proxyBaseOf(httpEndpoint) {
|
|
466
|
+
return httpEndpoint.replace(/\/+$/, "").replace(/\/ilp$/i, "");
|
|
467
|
+
}
|
|
468
|
+
async function resolveNetworkTopology(inputs) {
|
|
469
|
+
const { env, file, configPath, relayUrl, announce, genesisSeed, warn } = inputs;
|
|
470
|
+
const explicitProxyUrl = env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl;
|
|
471
|
+
const explicitBtpUrl = env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl;
|
|
472
|
+
const explicitDestination = env["TOON_CLIENT_DESTINATION"] ?? file.destination;
|
|
473
|
+
const explicitPublish = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? file.publishDestination;
|
|
474
|
+
const explicitStore = env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
|
|
475
|
+
const explicitChain = env["TOON_CLIENT_CHAIN"] ?? file.chain;
|
|
476
|
+
const explicitMaps = {
|
|
477
|
+
...file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {},
|
|
478
|
+
...file.preferredTokens ? { preferredTokens: file.preferredTokens } : {},
|
|
479
|
+
...file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}
|
|
480
|
+
};
|
|
481
|
+
let proxyUrl = explicitProxyUrl;
|
|
482
|
+
let btpUrl = explicitBtpUrl;
|
|
483
|
+
if (!proxyUrl && !btpUrl) {
|
|
484
|
+
if (announce?.info.httpEndpoint) {
|
|
485
|
+
proxyUrl = proxyBaseOf(announce.info.httpEndpoint);
|
|
486
|
+
} else if (announce?.info.btpEndpoint) {
|
|
487
|
+
btpUrl = announce.info.btpEndpoint;
|
|
488
|
+
} else if (genesisSeed?.btpEndpoint) {
|
|
489
|
+
btpUrl = genesisSeed.btpEndpoint;
|
|
490
|
+
} else if (inputs.requireUplink !== false) {
|
|
491
|
+
throw new MissingUplinkError(configPath, relayUrl);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const destination = explicitDestination ?? announce?.info.ilpAddress ?? genesisSeed?.ilpAddress ?? "g.proxy";
|
|
495
|
+
const publishDestination = explicitPublish ?? announce?.routes?.publish;
|
|
496
|
+
const storeDestination = explicitStore ?? announce?.routes?.store;
|
|
497
|
+
const knownPeers = announce ? [
|
|
498
|
+
{
|
|
499
|
+
pubkey: announce.pubkey,
|
|
500
|
+
relayUrl,
|
|
501
|
+
btpEndpoint: announce.info.btpEndpoint ?? ""
|
|
502
|
+
}
|
|
503
|
+
] : genesisSeed ? [
|
|
504
|
+
{
|
|
505
|
+
pubkey: genesisSeed.pubkey,
|
|
506
|
+
relayUrl: genesisSeed.relayUrl,
|
|
507
|
+
btpEndpoint: genesisSeed.btpEndpoint
|
|
508
|
+
}
|
|
509
|
+
] : [];
|
|
510
|
+
const announcedChains = announce?.info.supportedChains ?? [];
|
|
511
|
+
const resolveSettlement = (chain) => resolveChainSettlement(chain, explicitMaps, announce);
|
|
512
|
+
let selection;
|
|
513
|
+
let supportedChains;
|
|
514
|
+
let preferredTokens;
|
|
515
|
+
let tokenNetworks;
|
|
516
|
+
let chainRpcUrls;
|
|
517
|
+
if (file.supportedChains?.length) {
|
|
518
|
+
supportedChains = file.supportedChains;
|
|
519
|
+
preferredTokens = { ...file.preferredTokens };
|
|
520
|
+
tokenNetworks = { ...file.tokenNetworks };
|
|
521
|
+
chainRpcUrls = { ...file.chainRpcUrls };
|
|
522
|
+
for (const chain of supportedChains) {
|
|
523
|
+
const s = resolveSettlement(chain);
|
|
524
|
+
if (s.tokenAddress && !preferredTokens[chain]) {
|
|
525
|
+
preferredTokens[chain] = s.tokenAddress;
|
|
526
|
+
}
|
|
527
|
+
if (s.tokenNetwork && !tokenNetworks[chain]) {
|
|
528
|
+
tokenNetworks[chain] = s.tokenNetwork;
|
|
529
|
+
}
|
|
530
|
+
if (s.rpcUrl && !chainRpcUrls[chain]) {
|
|
531
|
+
chainRpcUrls[chain] = s.rpcUrl;
|
|
532
|
+
}
|
|
533
|
+
if (s.family === "evm") {
|
|
534
|
+
if (!tokenNetworks[chain]) {
|
|
535
|
+
throw new TokenNetworkUnderivableError(chain, announce, relayUrl);
|
|
536
|
+
}
|
|
537
|
+
if (!chainRpcUrls[chain]) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`no RPC URL is derivable for settlement chain "${chain}" \u2014 add chainRpcUrls["${chain}"] to ${configPath}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
selection = {
|
|
545
|
+
chain: supportedChains[0],
|
|
546
|
+
reason: "explicit",
|
|
547
|
+
detail: "supportedChains set by config"
|
|
548
|
+
};
|
|
549
|
+
} else if (explicitChain || announcedChains.length > 0) {
|
|
550
|
+
const { secretKey } = deriveNostrKeyFromMnemonic(
|
|
551
|
+
inputs.identity.mnemonic,
|
|
552
|
+
inputs.identity.accountIndex
|
|
553
|
+
);
|
|
554
|
+
const evmAddress = new EvmSigner(secretKey).address;
|
|
555
|
+
selection = await selectSettlementChain({
|
|
556
|
+
...explicitChain ? { explicitChain } : {},
|
|
557
|
+
announcedChains,
|
|
558
|
+
records: inputs.channelRecords(),
|
|
559
|
+
evmAddress,
|
|
560
|
+
resolveSettlement,
|
|
561
|
+
probeBalance: inputs.probeBalance ?? evmTokenBalance
|
|
562
|
+
});
|
|
563
|
+
const settlement = resolveSettlement(selection.chain);
|
|
564
|
+
if (settlement.family === "evm") {
|
|
565
|
+
if (!settlement.tokenNetwork) {
|
|
566
|
+
throw new TokenNetworkUnderivableError(
|
|
567
|
+
selection.chain,
|
|
568
|
+
announce,
|
|
569
|
+
relayUrl
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
if (!settlement.rpcUrl) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`no RPC URL is derivable for settlement chain "${selection.chain}" \u2014 add chainRpcUrls["${selection.chain}"] to ${configPath}`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
supportedChains = [selection.chain];
|
|
579
|
+
preferredTokens = settlement.tokenAddress ? { [selection.chain]: settlement.tokenAddress } : void 0;
|
|
580
|
+
tokenNetworks = settlement.tokenNetwork ? { [selection.chain]: settlement.tokenNetwork } : void 0;
|
|
581
|
+
chainRpcUrls = settlement.rpcUrl ? { [selection.chain]: settlement.rpcUrl } : void 0;
|
|
582
|
+
if (selection.reason !== "explicit") {
|
|
583
|
+
warn(
|
|
584
|
+
`rig: settlement chain ${selection.chain} selected \u2014 ${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains in the client config) to override`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
warn(
|
|
589
|
+
`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}`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
|
|
593
|
+
if (network && network !== "custom" && !file.supportedChains?.length) {
|
|
594
|
+
warn(
|
|
595
|
+
`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`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
...proxyUrl ? { proxyUrl } : {},
|
|
600
|
+
...btpUrl ? { btpUrl } : {},
|
|
601
|
+
destination,
|
|
602
|
+
...publishDestination ? { publishDestination } : {},
|
|
603
|
+
...storeDestination ? { storeDestination } : {},
|
|
604
|
+
knownPeers,
|
|
605
|
+
...selection ? { selection } : {},
|
|
606
|
+
...supportedChains ? { supportedChains } : {},
|
|
607
|
+
...preferredTokens && Object.keys(preferredTokens).length > 0 ? { preferredTokens } : {},
|
|
608
|
+
...tokenNetworks && Object.keys(tokenNetworks).length > 0 ? { tokenNetworks } : {},
|
|
609
|
+
...chainRpcUrls && Object.keys(chainRpcUrls).length > 0 ? { chainRpcUrls } : {}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function isNetworkTopology(value) {
|
|
613
|
+
if (typeof value !== "object" || value === null) return false;
|
|
614
|
+
const t = value;
|
|
615
|
+
return typeof t["destination"] === "string" && Array.isArray(t["knownPeers"]);
|
|
616
|
+
}
|
|
617
|
+
var NON_RECOVERABLE_START_ERRORS = /* @__PURE__ */ new Set([
|
|
618
|
+
"DaemonIdentityConflictError",
|
|
619
|
+
"StandaloneLockError",
|
|
620
|
+
"ChannelMapCorruptError"
|
|
621
|
+
]);
|
|
622
|
+
var TopologyRecoveringPublisher = class {
|
|
623
|
+
inner;
|
|
624
|
+
rebuild;
|
|
625
|
+
warn;
|
|
626
|
+
constructor(inner, rebuild, warn) {
|
|
627
|
+
this.inner = inner;
|
|
628
|
+
this.rebuild = rebuild;
|
|
629
|
+
this.warn = warn;
|
|
630
|
+
}
|
|
631
|
+
getPublicKey() {
|
|
632
|
+
return this.inner.getPublicKey();
|
|
633
|
+
}
|
|
634
|
+
getFeeRates() {
|
|
635
|
+
return this.inner.getFeeRates();
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Run the bootstrap step, recovering ONCE from a stale cached topology.
|
|
639
|
+
* A rebuild failure surfaces the LIVE resolution's error — that is the
|
|
640
|
+
* network's real state, strictly more actionable than the cached failure.
|
|
641
|
+
*/
|
|
642
|
+
async ensure(start) {
|
|
643
|
+
try {
|
|
644
|
+
await start(this.inner);
|
|
645
|
+
return this.inner;
|
|
646
|
+
} catch (err) {
|
|
647
|
+
const rebuild = this.rebuild;
|
|
648
|
+
this.rebuild = void 0;
|
|
649
|
+
const name = err instanceof Error ? err.name : "";
|
|
650
|
+
if (!rebuild || NON_RECOVERABLE_START_ERRORS.has(name)) throw err;
|
|
651
|
+
this.warn(
|
|
652
|
+
`rig: bootstrap with the cached network topology failed (${err instanceof Error ? err.message : String(err)}) \u2014 invalidating the cache and re-resolving live`
|
|
653
|
+
);
|
|
654
|
+
const fresh = await rebuild();
|
|
655
|
+
try {
|
|
656
|
+
await this.inner.stop();
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
this.inner = fresh;
|
|
660
|
+
await start(this.inner);
|
|
661
|
+
return this.inner;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async publishEvent(event, relayUrls) {
|
|
665
|
+
const p = await this.ensure((x) => x.start());
|
|
666
|
+
return p.publishEvent(event, relayUrls);
|
|
667
|
+
}
|
|
668
|
+
async uploadGitObject(upload) {
|
|
669
|
+
const p = await this.ensure((x) => x.start());
|
|
670
|
+
return p.uploadGitObject(upload);
|
|
671
|
+
}
|
|
672
|
+
// ── money lifecycle passthroughs (#263) — same recovery contract ─────────
|
|
673
|
+
async openChannelExplicit(opts) {
|
|
674
|
+
const p = await this.ensure((x) => x.start());
|
|
675
|
+
return p.openChannelExplicit(opts);
|
|
676
|
+
}
|
|
677
|
+
async closeRecordedChannel(record) {
|
|
678
|
+
const p = await this.ensure((x) => x.startClientOnly());
|
|
679
|
+
return p.closeRecordedChannel(record);
|
|
680
|
+
}
|
|
681
|
+
async settleRecordedChannel(record) {
|
|
682
|
+
const p = await this.ensure((x) => x.startClientOnly());
|
|
683
|
+
return p.settleRecordedChannel(record);
|
|
684
|
+
}
|
|
685
|
+
/** Free read on the unstarted client — no bootstrap, no recovery needed. */
|
|
686
|
+
readWalletBalances() {
|
|
687
|
+
return this.inner.readWalletBalances();
|
|
688
|
+
}
|
|
689
|
+
async stop() {
|
|
690
|
+
await this.inner.stop();
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
function chainRecordsFor(map, identity) {
|
|
694
|
+
return map.list().filter((r) => r.identity === identity).map((r) => {
|
|
695
|
+
const watermark = map.readWatermark(r.channelId);
|
|
696
|
+
return {
|
|
697
|
+
chain: r.chain,
|
|
698
|
+
lastUsedAt: r.lastUsedAt,
|
|
699
|
+
closed: watermark?.closedAt !== void 0 || watermark?.settledAt !== void 0
|
|
700
|
+
};
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
async function createStandaloneContext(options) {
|
|
704
|
+
const { env } = options;
|
|
705
|
+
const warn = (line) => options.warn(line);
|
|
706
|
+
const dir = configDir(env);
|
|
707
|
+
const configPath = join(dir, "config.json");
|
|
708
|
+
const file = readClientConfig(configPath);
|
|
709
|
+
const identity = await resolveIdentity(options);
|
|
710
|
+
const genesisSeed = loadGenesisSeed();
|
|
711
|
+
const relayUrl = options.relayUrl ?? env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl ?? genesisSeed?.relayUrl ?? "ws://localhost:7100";
|
|
712
|
+
const channelStorePath = file.channelStorePath ?? join(dir, "channels.json");
|
|
713
|
+
const channelMap = new ChannelMapStore({
|
|
714
|
+
mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),
|
|
715
|
+
watermarkPath: channelStorePath
|
|
716
|
+
});
|
|
717
|
+
const cache = new TopologyCache({
|
|
718
|
+
path: join(dir, TOPOLOGY_CACHE_FILENAME),
|
|
719
|
+
ttlMs: topologyCacheTtlMs(env),
|
|
720
|
+
validate: isNetworkTopology
|
|
721
|
+
});
|
|
722
|
+
const cacheKey = topologyCacheKey({
|
|
723
|
+
relayUrl,
|
|
724
|
+
identity: identity.pubkey,
|
|
725
|
+
fingerprint: explicitConfigFingerprint(
|
|
726
|
+
env,
|
|
727
|
+
file
|
|
728
|
+
)
|
|
729
|
+
});
|
|
730
|
+
const resolveLiveTopology = async () => {
|
|
731
|
+
const fullyExplicit = Boolean(
|
|
732
|
+
(env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl) || (env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl)
|
|
733
|
+
) && Boolean(env["TOON_CLIENT_DESTINATION"] ?? file.destination) && Boolean(file.supportedChains?.length);
|
|
734
|
+
let announce;
|
|
735
|
+
if (!fullyExplicit) {
|
|
736
|
+
try {
|
|
737
|
+
const peers = await discoverAnnouncedPeers(relayUrl, {
|
|
738
|
+
timeoutMs: DISCOVERY_TIMEOUT_MS
|
|
739
|
+
});
|
|
740
|
+
announce = pickPaymentPeer(peers, genesisSeedPubkeys());
|
|
741
|
+
if (!announce) {
|
|
742
|
+
warn(
|
|
743
|
+
`rig: no payment-peer announce (kind:10032) found on ${relayUrl} \u2014 falling back to the genesis peer seed`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
} catch (err) {
|
|
747
|
+
warn(
|
|
748
|
+
`rig: announce discovery on ${relayUrl} failed (${err instanceof Error ? err.message : String(err)}) \u2014 falling back to the genesis peer seed`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const resolved = await resolveNetworkTopology({
|
|
753
|
+
env,
|
|
754
|
+
file,
|
|
755
|
+
configPath,
|
|
756
|
+
relayUrl,
|
|
757
|
+
announce,
|
|
758
|
+
genesisSeed,
|
|
759
|
+
identity: {
|
|
760
|
+
mnemonic: identity.mnemonic,
|
|
761
|
+
accountIndex: identity.accountIndex,
|
|
762
|
+
pubkey: identity.pubkey
|
|
763
|
+
},
|
|
764
|
+
channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),
|
|
765
|
+
...options.requireUplink !== void 0 ? { requireUplink: options.requireUplink } : {},
|
|
766
|
+
warn
|
|
767
|
+
});
|
|
768
|
+
if (options.requireUplink !== false) cache.write(cacheKey, resolved);
|
|
769
|
+
return resolved;
|
|
770
|
+
};
|
|
771
|
+
const cached = cache.read(cacheKey);
|
|
772
|
+
if (cached) {
|
|
773
|
+
warn(
|
|
774
|
+
`rig: network topology from cache (${Math.round(cached.ageMs / 1e3)}s old; ${TOPOLOGY_TTL_ENV}=0 disables) \u2014 skipping announce discovery`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const topology = cached?.topology ?? await resolveLiveTopology();
|
|
778
|
+
const eventFee = BigInt(file.feePerEvent ?? "1");
|
|
779
|
+
const buildPublisher = (topo) => {
|
|
780
|
+
const clientConfig = {
|
|
781
|
+
// validateConfig requires connectorUrl OR proxyUrl; with BTP-only
|
|
782
|
+
// config a dummy connectorUrl satisfies it (unused at runtime — same
|
|
783
|
+
// convention as the daemon).
|
|
784
|
+
...topo.proxyUrl ? { proxyUrl: topo.proxyUrl } : { connectorUrl: "http://127.0.0.1:1" },
|
|
785
|
+
mnemonic: identity.mnemonic,
|
|
786
|
+
mnemonicAccountIndex: identity.accountIndex,
|
|
787
|
+
ilpInfo: {
|
|
788
|
+
pubkey: "00".repeat(32),
|
|
789
|
+
ilpAddress: "g.toon.client",
|
|
790
|
+
btpEndpoint: topo.btpUrl ?? "",
|
|
791
|
+
assetCode: "USD",
|
|
792
|
+
assetScale: 6
|
|
793
|
+
},
|
|
794
|
+
toonEncoder: encodeEventToToon,
|
|
795
|
+
toonDecoder: decodeEventFromToon,
|
|
796
|
+
...topo.btpUrl ? { btpUrl: topo.btpUrl, btpAuthToken: "" } : {},
|
|
797
|
+
destinationAddress: topo.destination,
|
|
798
|
+
// The embedded client bootstraps against the known peer above; its
|
|
799
|
+
// `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.
|
|
800
|
+
relayUrl: "",
|
|
801
|
+
knownPeers: topo.knownPeers,
|
|
802
|
+
channelStorePath,
|
|
803
|
+
...topo.supportedChains ? { supportedChains: topo.supportedChains } : {},
|
|
804
|
+
...file.settlementAddresses ? { settlementAddresses: file.settlementAddresses } : {},
|
|
805
|
+
...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {},
|
|
806
|
+
...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
|
|
807
|
+
...topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {},
|
|
808
|
+
...file.solanaChannel ? { solanaChannel: file.solanaChannel } : {},
|
|
809
|
+
...file.minaChannel ? { minaChannel: file.minaChannel } : {}
|
|
810
|
+
};
|
|
811
|
+
return new StandalonePublisher({
|
|
812
|
+
clientConfig,
|
|
813
|
+
eventFee,
|
|
814
|
+
channelMap,
|
|
815
|
+
warn,
|
|
816
|
+
...topo.publishDestination ? { publishDestination: topo.publishDestination } : {},
|
|
817
|
+
...topo.storeDestination ? { storeDestination: topo.storeDestination } : {},
|
|
818
|
+
// `rig channel open --peer` (#263): anchor the channel (and its map
|
|
819
|
+
// key) to an explicit peer destination instead of the configured
|
|
820
|
+
// default.
|
|
821
|
+
...options.channelDestination ? { channelDestination: options.channelDestination } : {},
|
|
822
|
+
// The peer's announce does not carry TokenNetwork/token parameters, so
|
|
823
|
+
// the client's negotiation leaves them empty (#260 root cause 3) — the
|
|
824
|
+
// publisher back-fills them from the derived per-chain maps before the
|
|
825
|
+
// channel opens.
|
|
826
|
+
...topo.tokenNetworks || topo.preferredTokens ? {
|
|
827
|
+
negotiationFallbacks: {
|
|
828
|
+
...topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {},
|
|
829
|
+
...topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {}
|
|
830
|
+
}
|
|
831
|
+
} : {}
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
const publisher = new TopologyRecoveringPublisher(
|
|
835
|
+
buildPublisher(topology),
|
|
836
|
+
cached ? async () => {
|
|
837
|
+
cache.invalidate(cacheKey);
|
|
838
|
+
return buildPublisher(await resolveLiveTopology());
|
|
839
|
+
} : void 0,
|
|
840
|
+
warn
|
|
841
|
+
);
|
|
842
|
+
return {
|
|
843
|
+
ownerPubkey: publisher.getPublicKey(),
|
|
844
|
+
identitySource: identity.source,
|
|
845
|
+
identitySourceLabel: identity.sourceLabel,
|
|
846
|
+
publisher,
|
|
847
|
+
defaultRelayUrls: [relayUrl],
|
|
848
|
+
fetchRemote: (args) => fetchRemoteState(args),
|
|
849
|
+
// Money lifecycle (#263): same guard/start/channel-map machinery as the
|
|
850
|
+
// paid-write path, surfaced for fund/balance/channel open|close|settle.
|
|
851
|
+
money: {
|
|
852
|
+
openChannel: (opts) => publisher.openChannelExplicit(opts),
|
|
853
|
+
closeChannel: (record) => publisher.closeRecordedChannel(record),
|
|
854
|
+
settleChannel: (record) => publisher.settleRecordedChannel(record),
|
|
855
|
+
walletBalances: () => publisher.readWalletBalances()
|
|
856
|
+
},
|
|
857
|
+
stop: () => publisher.stop()
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
export {
|
|
861
|
+
MissingUplinkError,
|
|
862
|
+
createStandaloneContext,
|
|
863
|
+
resolveNetworkTopology
|
|
864
|
+
};
|
|
865
|
+
//# sourceMappingURL=standalone-mode-64CKUVU2.js.map
|