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