@toon-protocol/rig 2.4.0 → 2.5.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 +18 -5
- package/dist/chunk-AFJNFDUQ.js +281 -0
- package/dist/chunk-AFJNFDUQ.js.map +1 -0
- package/dist/{chunk-OIJNRACA.js → chunk-EBXZUWV3.js} +10 -10
- package/dist/chunk-EBXZUWV3.js.map +1 -0
- package/dist/{chunk-W2SDL2PE.js → chunk-PTXKCR5R.js} +5 -5
- package/dist/cli/rig.js +257 -44
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.js +9 -9
- package/dist/network-bootstrap-WNSHRC5P.js +34 -0
- package/dist/network-bootstrap-WNSHRC5P.js.map +1 -0
- package/dist/standalone/index.d.ts +48 -23
- package/dist/standalone/index.js +1 -1
- package/dist/{standalone-mode-VH2XPOPK.js → standalone-mode-BIFFNDH3.js} +19 -269
- package/dist/standalone-mode-BIFFNDH3.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-OIJNRACA.js.map +0 -1
- package/dist/standalone-mode-VH2XPOPK.js.map +0 -1
- /package/dist/{chunk-W2SDL2PE.js.map → chunk-PTXKCR5R.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -27,7 +27,15 @@ import {
|
|
|
27
27
|
walkClosure,
|
|
28
28
|
writeGitObject,
|
|
29
29
|
writeGitObjects
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-PTXKCR5R.js";
|
|
31
|
+
import {
|
|
32
|
+
MAX_OBJECT_SIZE,
|
|
33
|
+
createGitBlob,
|
|
34
|
+
createGitCommit,
|
|
35
|
+
createGitTag,
|
|
36
|
+
createGitTree,
|
|
37
|
+
hashGitObject
|
|
38
|
+
} from "./chunk-X2CZPPDM.js";
|
|
31
39
|
import {
|
|
32
40
|
COMMENT_KIND,
|
|
33
41
|
MAINTAINERS_TAG,
|
|
@@ -43,14 +51,6 @@ import {
|
|
|
43
51
|
parseMaintainers,
|
|
44
52
|
queryRelay
|
|
45
53
|
} from "./chunk-3HRFDH7H.js";
|
|
46
|
-
import {
|
|
47
|
-
MAX_OBJECT_SIZE,
|
|
48
|
-
createGitBlob,
|
|
49
|
-
createGitCommit,
|
|
50
|
-
createGitTag,
|
|
51
|
-
createGitTree,
|
|
52
|
-
hashGitObject
|
|
53
|
-
} from "./chunk-X2CZPPDM.js";
|
|
54
54
|
export {
|
|
55
55
|
COMMENT_KIND,
|
|
56
56
|
DEFAULT_CONCURRENCY,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEVNET_CHAIN_RPC_URLS,
|
|
3
|
+
DEVNET_ZONE,
|
|
4
|
+
DISCOVERY_TIMEOUT_MS,
|
|
5
|
+
ILP_PEER_INFO_KIND,
|
|
6
|
+
TokenNetworkUnderivableError,
|
|
7
|
+
discoverAnnouncedPeers,
|
|
8
|
+
evmPresetForChain,
|
|
9
|
+
evmTokenBalance,
|
|
10
|
+
genesisSeedPubkeys,
|
|
11
|
+
isDevnetZonePeer,
|
|
12
|
+
loadGenesisSeed,
|
|
13
|
+
pickPaymentPeer,
|
|
14
|
+
resolveChainSettlement,
|
|
15
|
+
selectSettlementChain
|
|
16
|
+
} from "./chunk-AFJNFDUQ.js";
|
|
17
|
+
import "./chunk-3HRFDH7H.js";
|
|
18
|
+
export {
|
|
19
|
+
DEVNET_CHAIN_RPC_URLS,
|
|
20
|
+
DEVNET_ZONE,
|
|
21
|
+
DISCOVERY_TIMEOUT_MS,
|
|
22
|
+
ILP_PEER_INFO_KIND,
|
|
23
|
+
TokenNetworkUnderivableError,
|
|
24
|
+
discoverAnnouncedPeers,
|
|
25
|
+
evmPresetForChain,
|
|
26
|
+
evmTokenBalance,
|
|
27
|
+
genesisSeedPubkeys,
|
|
28
|
+
isDevnetZonePeer,
|
|
29
|
+
loadGenesisSeed,
|
|
30
|
+
pickPaymentPeer,
|
|
31
|
+
resolveChainSettlement,
|
|
32
|
+
selectSettlementChain
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=network-bootstrap-WNSHRC5P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -189,19 +189,38 @@ declare function channelStatus(entry: WatermarkEntry | undefined, nowSec?: numbe
|
|
|
189
189
|
*/
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
* One
|
|
193
|
-
* `@toon-protocol/client`'s `
|
|
194
|
-
* not exported from the package root); keep in sync.
|
|
192
|
+
* One asset amount within a chain's wallet view — structural twin of
|
|
193
|
+
* `@toon-protocol/client`'s `WalletTokenAmount`; keep in sync.
|
|
195
194
|
*/
|
|
196
|
-
interface
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
interface WalletTokenAmountInfo {
|
|
196
|
+
/** Asset symbol (e.g. `'ETH'`, `'SOL'`, `'MINA'`, `'USDC'`), when known. */
|
|
197
|
+
symbol?: string;
|
|
199
198
|
/** Base-unit integer, decimal string. */
|
|
200
199
|
amount: string;
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
/** Token
|
|
204
|
-
|
|
200
|
+
/** Decimals for formatting (ETH 18, SOL 9, MINA 9, USDC 6). */
|
|
201
|
+
decimals?: number;
|
|
202
|
+
/** Token contract / SPL mint address. Absent for the native coin. */
|
|
203
|
+
address?: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The full wallet view for ONE chain — native coin + configured tokens —
|
|
207
|
+
* structural twin of `@toon-protocol/client`'s `WalletChainBalances`
|
|
208
|
+
* (`balance/WalletBalanceReader.ts`, exported from the package root); keep in
|
|
209
|
+
* sync.
|
|
210
|
+
*/
|
|
211
|
+
interface WalletChainBalanceInfo {
|
|
212
|
+
chain: 'evm' | 'solana' | 'mina';
|
|
213
|
+
/** Full chain key, e.g. `'evm:31337'`, `'solana'`, `'mina'`. */
|
|
214
|
+
chainKey: string;
|
|
215
|
+
address: string;
|
|
216
|
+
/** Native-coin balance, when readable. */
|
|
217
|
+
native?: WalletTokenAmountInfo;
|
|
218
|
+
/** Configured token balances (e.g. USDC). */
|
|
219
|
+
tokens: WalletTokenAmountInfo[];
|
|
220
|
+
/** True when nothing on this chain could be read (RPC unreachable). */
|
|
221
|
+
unreadable?: boolean;
|
|
222
|
+
/** First read error, when any read failed. */
|
|
223
|
+
error?: string;
|
|
205
224
|
}
|
|
206
225
|
/** Receipt of an explicit `rig channel open` (fresh open OR resume). */
|
|
207
226
|
interface ChannelOpenOutcome {
|
|
@@ -255,10 +274,12 @@ interface StandaloneMoneyOps {
|
|
|
255
274
|
/** Settle a closed channel after its challenge window — releases funds. */
|
|
256
275
|
settleChannel(record: ChannelMapRecord): Promise<ChannelSettleOutcome>;
|
|
257
276
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
277
|
+
* The full multi-chain wallet view (#299) for the identity's configured
|
|
278
|
+
* chains — native coin + configured tokens (USDC) grouped per chain — a FREE
|
|
279
|
+
* read (no client start, no nonce guard, no uplink). Best-effort per chain:
|
|
280
|
+
* an unreachable RPC yields an `unreadable` chain rather than failing others.
|
|
260
281
|
*/
|
|
261
|
-
|
|
282
|
+
walletChainBalances(): Promise<WalletChainBalanceInfo[]>;
|
|
262
283
|
}
|
|
263
284
|
|
|
264
285
|
/**
|
|
@@ -344,8 +365,12 @@ interface ToonClientLike {
|
|
|
344
365
|
channelId: string;
|
|
345
366
|
txHash?: string;
|
|
346
367
|
}>;
|
|
347
|
-
/**
|
|
348
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Free FULL multi-chain wallet view (#299) — native coin + configured tokens
|
|
370
|
+
* per chain — works on an UNSTARTED client (Solana/Mina addresses are derived
|
|
371
|
+
* from the mnemonic on demand).
|
|
372
|
+
*/
|
|
373
|
+
getWalletBalances?(): Promise<WalletChainBalanceInfo[]>;
|
|
349
374
|
}
|
|
350
375
|
interface StandalonePublisherOptions {
|
|
351
376
|
/**
|
|
@@ -532,14 +557,14 @@ declare class StandalonePublisher implements Publisher {
|
|
|
532
557
|
*/
|
|
533
558
|
settleRecordedChannel(record: ChannelMapRecord): Promise<ChannelSettleOutcome>;
|
|
534
559
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
560
|
+
* The full multi-chain wallet view (#299) for the embedded identity — native
|
|
561
|
+
* coin + configured tokens (USDC) per chain — a FREE read on the UNSTARTED
|
|
562
|
+
* client (no nonce guard, no uplink, no channel). The client derives the
|
|
563
|
+
* Solana/Mina addresses from the mnemonic on demand, so ALL configured chains
|
|
564
|
+
* appear even before a start. Best-effort per chain (an unreachable RPC yields
|
|
565
|
+
* an `unreadable` chain, not a failure).
|
|
541
566
|
*/
|
|
542
|
-
|
|
567
|
+
readWalletChainBalances(): Promise<WalletChainBalanceInfo[]>;
|
|
543
568
|
/**
|
|
544
569
|
* Fee rates for `planPush` estimation: the flat per-event fee and the
|
|
545
570
|
* per-byte upload rate this publisher pays (daemon `feePerEvent` and seed
|
|
@@ -676,4 +701,4 @@ declare class NonceLock {
|
|
|
676
701
|
release(): void;
|
|
677
702
|
}
|
|
678
703
|
|
|
679
|
-
export { type AcquireLockOptions, type ChannelCloseOutcome, ChannelMapCorruptError, type ChannelMapKey, type ChannelMapRecord, ChannelMapStore, type ChannelMapStoreOptions, type ChannelOpenOutcome, type ChannelSettleOutcome, type CheckDaemonOptions, DEFAULT_DAEMON_PORT, DaemonIdentityConflictError, NonceLock, type PersistedChannelContext, RIG_CHANNEL_MAP_FILENAME, type SignedNostrEvent, StandaloneLockError, type StandaloneMoneyOps, StandalonePublishError, StandalonePublisher, type StandalonePublisherOptions, type ToonClientLike, type
|
|
704
|
+
export { type AcquireLockOptions, type ChannelCloseOutcome, ChannelMapCorruptError, type ChannelMapKey, type ChannelMapRecord, ChannelMapStore, type ChannelMapStoreOptions, type ChannelOpenOutcome, type ChannelSettleOutcome, type CheckDaemonOptions, DEFAULT_DAEMON_PORT, DaemonIdentityConflictError, NonceLock, type PersistedChannelContext, RIG_CHANNEL_MAP_FILENAME, type SignedNostrEvent, StandaloneLockError, type StandaloneMoneyOps, StandalonePublishError, StandalonePublisher, type StandalonePublisherOptions, type ToonClientLike, type WalletChainBalanceInfo, type WalletTokenAmountInfo, type WatermarkEntry, channelStatus, checkDaemonIdentity, defaultDaemonPort, defaultLockDir, deriveRouteDestinations, extractArweaveTxId, recordKey, resolveChannelPaths };
|
package/dist/standalone/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveIdentity
|
|
3
3
|
} from "./chunk-XGFBDUQX.js";
|
|
4
|
-
import {
|
|
5
|
-
fetchRemoteState,
|
|
6
|
-
queryRelay
|
|
7
|
-
} from "./chunk-3HRFDH7H.js";
|
|
8
4
|
import {
|
|
9
5
|
StandalonePublisher
|
|
10
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EBXZUWV3.js";
|
|
11
7
|
import {
|
|
12
8
|
ChannelMapStore,
|
|
13
9
|
RIG_CHANNEL_MAP_FILENAME
|
|
14
10
|
} from "./chunk-SW7ZHMGS.js";
|
|
15
11
|
import "./chunk-X2CZPPDM.js";
|
|
12
|
+
import {
|
|
13
|
+
DISCOVERY_TIMEOUT_MS,
|
|
14
|
+
TokenNetworkUnderivableError,
|
|
15
|
+
discoverAnnouncedPeers,
|
|
16
|
+
evmTokenBalance,
|
|
17
|
+
genesisSeedPubkeys,
|
|
18
|
+
loadGenesisSeed,
|
|
19
|
+
pickPaymentPeer,
|
|
20
|
+
resolveChainSettlement,
|
|
21
|
+
selectSettlementChain
|
|
22
|
+
} from "./chunk-AFJNFDUQ.js";
|
|
23
|
+
import {
|
|
24
|
+
fetchRemoteState
|
|
25
|
+
} from "./chunk-3HRFDH7H.js";
|
|
16
26
|
|
|
17
27
|
// src/cli/standalone-mode.ts
|
|
18
28
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -179,266 +189,6 @@ var TopologyCache = class {
|
|
|
179
189
|
}
|
|
180
190
|
};
|
|
181
191
|
|
|
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
192
|
// src/cli/standalone-mode.ts
|
|
443
193
|
var MissingUplinkError = class extends Error {
|
|
444
194
|
constructor(configPath, relayUrl) {
|
|
@@ -683,8 +433,8 @@ var TopologyRecoveringPublisher = class {
|
|
|
683
433
|
return p.settleRecordedChannel(record);
|
|
684
434
|
}
|
|
685
435
|
/** Free read on the unstarted client — no bootstrap, no recovery needed. */
|
|
686
|
-
|
|
687
|
-
return this.inner.
|
|
436
|
+
readWalletChainBalances() {
|
|
437
|
+
return this.inner.readWalletChainBalances();
|
|
688
438
|
}
|
|
689
439
|
async stop() {
|
|
690
440
|
await this.inner.stop();
|
|
@@ -852,7 +602,7 @@ async function createStandaloneContext(options) {
|
|
|
852
602
|
openChannel: (opts) => publisher.openChannelExplicit(opts),
|
|
853
603
|
closeChannel: (record) => publisher.closeRecordedChannel(record),
|
|
854
604
|
settleChannel: (record) => publisher.settleRecordedChannel(record),
|
|
855
|
-
|
|
605
|
+
walletChainBalances: () => publisher.readWalletChainBalances()
|
|
856
606
|
},
|
|
857
607
|
stop: () => publisher.stop()
|
|
858
608
|
};
|
|
@@ -862,4 +612,4 @@ export {
|
|
|
862
612
|
createStandaloneContext,
|
|
863
613
|
resolveNetworkTopology
|
|
864
614
|
};
|
|
865
|
-
//# sourceMappingURL=standalone-mode-
|
|
615
|
+
//# sourceMappingURL=standalone-mode-BIFFNDH3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/standalone-mode.ts","../src/standalone/topology-cache.ts"],"sourcesContent":["/**\n * The embedded-client (standalone) publisher backing every paid `rig`\n * command: build a nonce-guarded {@link StandalonePublisher} from the\n * caller's own identity and config.\n *\n * Identity comes from the #248 precedence chain (`./identity.ts`:\n * RIG_MNEMONIC env → TOON_CLIENT_MNEMONIC env alias → project `.env` →\n * `~/.toon-client` keystore/config). The remaining config resolution\n * DUPLICATES the toon-clientd conventions\n * (`packages/client-mcp/src/daemon/config.ts`) the same way\n * `../standalone/nonce-guard.ts` does — this package must not import\n * `@toon-protocol/client-mcp` (circular; see that module's doc). Keep in sync:\n *\n * - state dir: `TOON_CLIENT_HOME`, else `~/.toon-client`; config `config.json`\n * - env overrides: `TOON_CLIENT_PROXY_URL`, `TOON_CLIENT_BTP_URL`,\n * `TOON_CLIENT_RELAY_URL`, `TOON_CLIENT_DESTINATION`,\n * `TOON_CLIENT_PUBLISH_DESTINATION`, `TOON_CLIENT_STORE_DESTINATION`,\n * `TOON_CLIENT_CHAIN`\n *\n * NETWORK BOOTSTRAP (#264): what explicit config does not pin is resolved\n * from the network itself — the payment peer's live kind:10032 announce on\n * the relay-origin, falling back to `@toon-protocol/core`'s committed\n * genesis peer seed. Uplink, channel anchor, publish/store routes, the\n * settlement chain and its TokenNetwork/token/RPC parameters all follow the\n * `explicit config > live announce > genesis seed` order documented in\n * `../standalone/network-bootstrap.ts`. The pure resolution lives in\n * {@link resolveNetworkTopology} (unit-testable without any network).\n *\n * TOPOLOGY CACHE (#279): the resolved topology is persisted under\n * `TOON_CLIENT_HOME` (`../standalone/topology-cache.ts`) keyed by\n * relay-origin + identity + an explicit-config fingerprint, with a 15-min\n * TTL (`RIG_TOPOLOGY_TTL_MS` overrides; `0` disables). A cache hit skips\n * announce discovery and the funded-chain probes entirely; a cached\n * topology that then fails to BOOTSTRAP is invalidated and re-resolved\n * live in-process ({@link TopologyRecoveringPublisher}), so staleness costs\n * one failed attempt, never a broken run. Only paid-path resolutions\n * (`requireUplink !== false`) are written — a free-read topology may lack\n * an uplink and must not shadow the paid path's MissingUplinkError.\n *\n * CORE COEXISTENCE NOTE: rig performs discovery with ITS OWN\n * `@toon-protocol/core` (^2.0.x — live genesis seed), while the embedded\n * `@toon-protocol/client` keeps its internal core (^1.6.x) for its own\n * bootstrap/negotiation. The two never exchange class instances — rig feeds\n * the client plain config (`knownPeers`, settlement maps), so the version\n * split is safe by construction.\n *\n * This module statically imports `@toon-protocol/client` (heavy: viem,\n * noble, nostr-tools), so it must only ever be reached through the dynamic\n * import in `push.ts` (see `./standalone-context.ts`).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ToonClientConfig } from '@toon-protocol/client';\nimport { EvmSigner, deriveNostrKeyFromMnemonic } from '@toon-protocol/client';\nimport {\n decodeEventFromToon,\n encodeEventToToon,\n} from '@toon-protocol/core';\nimport type {\n FeeRates,\n GitObjectUpload,\n Publisher,\n PublishReceipt,\n UploadReceipt,\n} from '../publisher.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n ChannelMapStore,\n RIG_CHANNEL_MAP_FILENAME,\n type ChannelMapRecord,\n} from '../standalone/channel-map.js';\nimport {\n TOPOLOGY_CACHE_FILENAME,\n TOPOLOGY_TTL_ENV,\n TopologyCache,\n explicitConfigFingerprint,\n topologyCacheKey,\n topologyCacheTtlMs,\n} from '../standalone/topology-cache.js';\nimport {\n DISCOVERY_TIMEOUT_MS,\n TokenNetworkUnderivableError,\n discoverAnnouncedPeers,\n evmTokenBalance,\n genesisSeedPubkeys,\n loadGenesisSeed,\n pickPaymentPeer,\n resolveChainSettlement,\n selectSettlementChain,\n type AnnouncedPeer,\n type ChainSelection,\n type ChannelRecordLike,\n type EvmBalanceProbe,\n type ExplicitChainConfig,\n} from '../standalone/network-bootstrap.js';\nimport { StandalonePublisher } from '../standalone/standalone-publisher.js';\nimport { fetchRemoteState } from '../remote-state.js';\nimport { resolveIdentity } from './identity.js';\nimport type {\n StandaloneContext,\n StandaloneLoadOptions,\n} from './standalone-context.js';\n\n/** The subset of the shared client config file standalone mode consumes. */\nexport interface ClientConfigFile {\n network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';\n mnemonicAccountIndex?: number;\n btpUrl?: string;\n proxyUrl?: string;\n relayUrl?: string;\n destination?: string;\n publishDestination?: string;\n storeDestination?: string;\n feePerEvent?: string;\n channelStorePath?: string;\n /** Settlement chain: family (`evm`) or full id (`evm:31337`). */\n chain?: string;\n supportedChains?: string[];\n settlementAddresses?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n}\n\n/** An identity was resolved, but there is no way to send paid writes. */\nexport class MissingUplinkError extends Error {\n constructor(configPath: string, relayUrl: string | undefined) {\n const discovered = relayUrl\n ? `no announce with a btp/http endpoint was found on ${relayUrl} and ` +\n 'the genesis seed has none; '\n : '';\n super(\n `no write uplink configured: ${discovered}set TOON_CLIENT_PROXY_URL ` +\n '(connector payment proxy) or TOON_CLIENT_BTP_URL, or add ' +\n `proxyUrl/btpUrl to ${configPath}`\n );\n this.name = 'MissingUplinkError';\n }\n}\n\nfunction configDir(env: NodeJS.ProcessEnv): string {\n return env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\nfunction readClientConfig(path: string): ClientConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as ClientConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n/** `https://host/ilp` → `https://host` (the client re-derives `/ilp`). */\nfunction proxyBaseOf(httpEndpoint: string): string {\n return httpEndpoint.replace(/\\/+$/, '').replace(/\\/ilp$/i, '');\n}\n\n// ---------------------------------------------------------------------------\n// Pure topology resolution (#264) — exported for tests\n// ---------------------------------------------------------------------------\n\n/** A genesis-seed peer as this module consumes it (rig's core 2.x shape). */\nexport interface GenesisSeedLike {\n pubkey: string;\n relayUrl: string;\n ilpAddress: string;\n btpEndpoint: string;\n}\n\n/** Inputs to {@link resolveNetworkTopology} (side-effect free). */\nexport interface NetworkTopologyInputs {\n env: NodeJS.ProcessEnv;\n file: ClientConfigFile;\n /** For error messages (\"add X to <configPath>\"). */\n configPath: string;\n /** Resolved relay-origin (already precedence-resolved). */\n relayUrl: string;\n /** The discovered payment-peer announce, if any. */\n announce: AnnouncedPeer | undefined;\n /** The committed genesis seed entry, if any. */\n genesisSeed: GenesisSeedLike | undefined;\n identity: { mnemonic: string; accountIndex: number; pubkey: string };\n /**\n * #262 channel-map records for this identity (chain selection input).\n * A thunk so the (possibly corrupt-throwing) map read only happens when\n * chain selection actually needs it.\n */\n channelRecords: () => ChannelRecordLike[];\n /** Balance probe override (tests); default: raw `eth_call`. */\n probeBalance?: EvmBalanceProbe;\n /**\n * When false (#263 free reads, e.g. `rig balance`), a missing uplink is\n * tolerated instead of throwing {@link MissingUplinkError}.\n */\n requireUplink?: boolean;\n warn: (line: string) => void;\n}\n\n/** The resolved payment topology feeding the embedded client config. */\nexport interface NetworkTopology {\n proxyUrl?: string;\n btpUrl?: string;\n /** Channel anchor / default ILP destination. */\n destination: string;\n publishDestination?: string;\n storeDestination?: string;\n /** The peer the embedded client bootstraps + negotiates with. */\n knownPeers: { pubkey: string; relayUrl: string; btpEndpoint: string }[];\n /** The selected settlement chain + rationale (absent: nothing known). */\n selection?: ChainSelection;\n supportedChains?: string[];\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n chainRpcUrls?: Record<string, string>;\n}\n\n/**\n * Resolve the payment topology per the #264 precedence order —\n * `explicit config > live announce > genesis seed` for every field, plus the\n * documented settlement-chain selection rule (see\n * `../standalone/network-bootstrap.ts`).\n *\n * @throws {MissingUplinkError} when no source yields an uplink.\n * @throws {TokenNetworkUnderivableError} when the selected EVM chain's\n * TokenNetwork cannot be derived from config, announce, or chain preset.\n */\nexport async function resolveNetworkTopology(\n inputs: NetworkTopologyInputs\n): Promise<NetworkTopology> {\n const { env, file, configPath, relayUrl, announce, genesisSeed, warn } =\n inputs;\n\n // ── Explicit config (always wins, per field) ─────────────────────────────\n const explicitProxyUrl = env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl;\n const explicitBtpUrl = env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl;\n const explicitDestination =\n env['TOON_CLIENT_DESTINATION'] ?? file.destination;\n const explicitPublish =\n env['TOON_CLIENT_PUBLISH_DESTINATION'] ?? file.publishDestination;\n const explicitStore =\n env['TOON_CLIENT_STORE_DESTINATION'] ?? file.storeDestination;\n const explicitChain = env['TOON_CLIENT_CHAIN'] ?? file.chain;\n const explicitMaps: ExplicitChainConfig = {\n ...(file.chainRpcUrls ? { chainRpcUrls: file.chainRpcUrls } : {}),\n ...(file.preferredTokens ? { preferredTokens: file.preferredTokens } : {}),\n ...(file.tokenNetworks ? { tokenNetworks: file.tokenNetworks } : {}),\n };\n\n // ── Uplink: explicit > announce (http > btp) > genesis seed ──────────────\n let proxyUrl = explicitProxyUrl;\n let btpUrl = explicitBtpUrl;\n if (!proxyUrl && !btpUrl) {\n if (announce?.info.httpEndpoint) {\n proxyUrl = proxyBaseOf(announce.info.httpEndpoint);\n } else if (announce?.info.btpEndpoint) {\n btpUrl = announce.info.btpEndpoint;\n } else if (genesisSeed?.btpEndpoint) {\n btpUrl = genesisSeed.btpEndpoint;\n } else if (inputs.requireUplink !== false) {\n // Free reads (`rig balance`, #263) tolerate a missing uplink; paid\n // commands fail here, after every source has been tried.\n throw new MissingUplinkError(configPath, relayUrl);\n }\n }\n\n // ── Destination anchor + publish/store routes ────────────────────────────\n // The channel anchors at the peer's announced ilpAddress; publish/store\n // routes come from the announce's `routes` map. Explicit values always\n // win; with neither, the publisher's `<base>.relay.store` anchor-derivation\n // convention remains as the last-resort fallback (explicit anchors only).\n const destination =\n explicitDestination ??\n announce?.info.ilpAddress ??\n genesisSeed?.ilpAddress ??\n 'g.proxy';\n const publishDestination = explicitPublish ?? announce?.routes?.publish;\n const storeDestination = explicitStore ?? announce?.routes?.store;\n\n // ── Known peer for the embedded client's own bootstrap ───────────────────\n // The client re-queries the peer's announce itself (its internal core) and\n // negotiates the settlement chain; rig just tells it WHO to bootstrap with.\n const knownPeers = announce\n ? [\n {\n pubkey: announce.pubkey,\n relayUrl,\n btpEndpoint: announce.info.btpEndpoint ?? '',\n },\n ]\n : genesisSeed\n ? [\n {\n pubkey: genesisSeed.pubkey,\n relayUrl: genesisSeed.relayUrl,\n btpEndpoint: genesisSeed.btpEndpoint,\n },\n ]\n : [];\n\n // ── Settlement chain + per-chain parameters ──────────────────────────────\n // NOTE: the `network` preset field is deliberately NOT forwarded to the\n // embedded client. `applyNetworkPresets` puts preset chains FIRST in\n // `supportedChains`, which is what steered devnet negotiation to the\n // unfunded public Solana preset (#260 root cause 4). The announce + the\n // rule below define the chain; presets only serve as per-chain parameter\n // fallbacks inside `resolveChainSettlement`.\n const announcedChains = announce?.info.supportedChains ?? [];\n const resolveSettlement = (chain: string) =>\n resolveChainSettlement(chain, explicitMaps, announce);\n\n let selection: ChainSelection | undefined;\n let supportedChains: string[] | undefined;\n let preferredTokens: Record<string, string> | undefined;\n let tokenNetworks: Record<string, string> | undefined;\n let chainRpcUrls: Record<string, string> | undefined;\n\n if (file.supportedChains?.length) {\n // Explicit chain list: pass through as-is (its order IS the negotiation\n // preference), filling per-chain parameter gaps from announce/presets.\n supportedChains = file.supportedChains;\n preferredTokens = { ...file.preferredTokens };\n tokenNetworks = { ...file.tokenNetworks };\n chainRpcUrls = { ...file.chainRpcUrls };\n for (const chain of supportedChains) {\n const s = resolveSettlement(chain);\n if (s.tokenAddress && !preferredTokens[chain]) {\n preferredTokens[chain] = s.tokenAddress;\n }\n if (s.tokenNetwork && !tokenNetworks[chain]) {\n tokenNetworks[chain] = s.tokenNetwork;\n }\n if (s.rpcUrl && !chainRpcUrls[chain]) {\n chainRpcUrls[chain] = s.rpcUrl;\n }\n // Same fail-fast guarantee as the announce/selection path below: an\n // explicitly configured EVM chain whose TokenNetwork/RPC cannot be\n // derived must fail HERE with an actionable error, not later as the\n // embedded client's generic \"tokenNetwork address is required\".\n if (s.family === 'evm') {\n if (!tokenNetworks[chain]) {\n throw new TokenNetworkUnderivableError(chain, announce, relayUrl);\n }\n if (!chainRpcUrls[chain]) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${chain}\"` +\n ` — add chainRpcUrls[\"${chain}\"] to ${configPath}`\n );\n }\n }\n }\n selection = {\n chain: supportedChains[0] as string,\n reason: 'explicit',\n detail: 'supportedChains set by config',\n };\n } else if (explicitChain || announcedChains.length > 0) {\n // Selection rule: explicit > persisted channel > funded > first EVM.\n const { secretKey } = deriveNostrKeyFromMnemonic(\n inputs.identity.mnemonic,\n inputs.identity.accountIndex\n );\n const evmAddress = new EvmSigner(secretKey).address;\n selection = await selectSettlementChain({\n ...(explicitChain ? { explicitChain } : {}),\n announcedChains,\n records: inputs.channelRecords(),\n evmAddress,\n resolveSettlement,\n probeBalance: inputs.probeBalance ?? evmTokenBalance,\n });\n const settlement = resolveSettlement(selection.chain);\n if (settlement.family === 'evm') {\n if (!settlement.tokenNetwork) {\n throw new TokenNetworkUnderivableError(\n selection.chain,\n announce,\n relayUrl\n );\n }\n if (!settlement.rpcUrl) {\n throw new Error(\n `no RPC URL is derivable for settlement chain \"${selection.chain}\"` +\n ` — add chainRpcUrls[\"${selection.chain}\"] to ${configPath}`\n );\n }\n }\n supportedChains = [selection.chain];\n preferredTokens = settlement.tokenAddress\n ? { [selection.chain]: settlement.tokenAddress }\n : undefined;\n tokenNetworks = settlement.tokenNetwork\n ? { [selection.chain]: settlement.tokenNetwork }\n : undefined;\n chainRpcUrls = settlement.rpcUrl\n ? { [selection.chain]: settlement.rpcUrl }\n : undefined;\n if (selection.reason !== 'explicit') {\n warn(\n `rig: settlement chain ${selection.chain} selected — ` +\n `${selection.detail}; set TOON_CLIENT_CHAIN (or supportedChains ` +\n 'in the client config) to override'\n );\n }\n } else {\n warn(\n 'rig: no settlement chains are configured or announced — paid writes ' +\n 'will fail until a chain is configured (supportedChains) or the ' +\n `payment peer announces its chains on ${relayUrl}`\n );\n }\n\n const network = env['TOON_CLIENT_NETWORK'] ?? file.network;\n if (network && network !== 'custom' && !file.supportedChains?.length) {\n warn(\n `rig: ignoring the \"${network}\" network preset for settlement — the ` +\n \"settlement chain comes from the payment peer's announce and your \" +\n 'config, because preset chains can point at networks your wallet ' +\n 'has no funds on; set supportedChains explicitly to use preset chains'\n );\n }\n\n return {\n ...(proxyUrl ? { proxyUrl } : {}),\n ...(btpUrl ? { btpUrl } : {}),\n destination,\n ...(publishDestination ? { publishDestination } : {}),\n ...(storeDestination ? { storeDestination } : {}),\n knownPeers,\n ...(selection ? { selection } : {}),\n ...(supportedChains ? { supportedChains } : {}),\n ...(preferredTokens && Object.keys(preferredTokens).length > 0\n ? { preferredTokens }\n : {}),\n ...(tokenNetworks && Object.keys(tokenNetworks).length > 0\n ? { tokenNetworks }\n : {}),\n ...(chainRpcUrls && Object.keys(chainRpcUrls).length > 0\n ? { chainRpcUrls }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cached-topology recovery (#279)\n// ---------------------------------------------------------------------------\n\n/** Structural check for a cached {@link NetworkTopology} document. */\nfunction isNetworkTopology(value: unknown): value is NetworkTopology {\n if (typeof value !== 'object' || value === null) return false;\n const t = value as Record<string, unknown>;\n return typeof t['destination'] === 'string' && Array.isArray(t['knownPeers']);\n}\n\n/**\n * Start failures that must NEVER trigger a cache-invalidation retry: they\n * are concurrency/state guards, not topology staleness — retrying would\n * bypass exactly what they protect.\n */\nconst NON_RECOVERABLE_START_ERRORS: ReadonlySet<string> = new Set([\n 'DaemonIdentityConflictError',\n 'StandaloneLockError',\n 'ChannelMapCorruptError',\n]);\n\n/**\n * Publisher wrapper implementing the #279 cache-invalidation contract: when\n * the inner publisher was built from a CACHED topology and its bootstrap\n * (`start`/`startClientOnly`) fails, the cache entry is invalidated, the\n * topology is re-resolved LIVE, a fresh publisher replaces the inner one,\n * and the operation proceeds — one retry, never more. Bootstrap failures\n * are pre-payment by construction (start() completes before any claim is\n * signed), so the retry can never double-pay. Publishers built from a live\n * resolution get no recovery hook and fail through unchanged.\n */\nclass TopologyRecoveringPublisher implements Publisher {\n private inner: StandalonePublisher;\n private rebuild: (() => Promise<StandalonePublisher>) | undefined;\n private readonly warn: (line: string) => void;\n\n constructor(\n inner: StandalonePublisher,\n rebuild: (() => Promise<StandalonePublisher>) | undefined,\n warn: (line: string) => void\n ) {\n this.inner = inner;\n this.rebuild = rebuild;\n this.warn = warn;\n }\n\n getPublicKey(): string {\n return this.inner.getPublicKey();\n }\n\n getFeeRates(): Promise<FeeRates> {\n return this.inner.getFeeRates();\n }\n\n /**\n * Run the bootstrap step, recovering ONCE from a stale cached topology.\n * A rebuild failure surfaces the LIVE resolution's error — that is the\n * network's real state, strictly more actionable than the cached failure.\n */\n private async ensure(\n start: (p: StandalonePublisher) => Promise<void>\n ): Promise<StandalonePublisher> {\n try {\n await start(this.inner);\n return this.inner;\n } catch (err) {\n const rebuild = this.rebuild;\n this.rebuild = undefined;\n const name = err instanceof Error ? err.name : '';\n if (!rebuild || NON_RECOVERABLE_START_ERRORS.has(name)) throw err;\n this.warn(\n 'rig: bootstrap with the cached network topology failed ' +\n `(${err instanceof Error ? err.message : String(err)}) — ` +\n 'invalidating the cache and re-resolving live'\n );\n const fresh = await rebuild();\n try {\n // Failed starts release their own lock/state; stop() is idempotent.\n await this.inner.stop();\n } catch {\n // best-effort teardown of the abandoned publisher\n }\n this.inner = fresh;\n await start(this.inner);\n return this.inner;\n }\n }\n\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.publishEvent(event, relayUrls);\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n const p = await this.ensure((x) => x.start());\n return p.uploadGitObject(upload);\n }\n\n // ── money lifecycle passthroughs (#263) — same recovery contract ─────────\n\n async openChannelExplicit(\n opts?: Parameters<StandalonePublisher['openChannelExplicit']>[0]\n ): ReturnType<StandalonePublisher['openChannelExplicit']> {\n const p = await this.ensure((x) => x.start());\n return p.openChannelExplicit(opts);\n }\n\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['closeRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.closeRecordedChannel(record);\n }\n\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): ReturnType<StandalonePublisher['settleRecordedChannel']> {\n const p = await this.ensure((x) => x.startClientOnly());\n return p.settleRecordedChannel(record);\n }\n\n /** Free read on the unstarted client — no bootstrap, no recovery needed. */\n readWalletChainBalances(): ReturnType<\n StandalonePublisher['readWalletChainBalances']\n > {\n return this.inner.readWalletChainBalances();\n }\n\n async stop(): Promise<void> {\n await this.inner.stop();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context factory\n// ---------------------------------------------------------------------------\n\n/**\n * #262 channel-map records for this identity, reduced to the slice chain\n * selection consumes (`closed` folds in the claim-watermark timers).\n */\nfunction chainRecordsFor(\n map: ChannelMapStore,\n identity: string\n): ChannelRecordLike[] {\n return map\n .list()\n .filter((r) => r.identity === identity)\n .map((r) => {\n const watermark = map.readWatermark(r.channelId);\n return {\n chain: r.chain,\n lastUsedAt: r.lastUsedAt,\n closed:\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined,\n };\n });\n}\n\n/**\n * Assemble an embedded-client standalone context: resolved identity + config\n * → network bootstrap (announce discovery / genesis seed) → ToonClientConfig\n * → nonce-guarded StandalonePublisher (guard + client start + channel open\n * happen lazily on the first paid call, or eagerly via the publisher's own\n * `start`).\n */\nexport async function createStandaloneContext(\n options: StandaloneLoadOptions\n): Promise<StandaloneContext> {\n const { env } = options;\n const warn = (line: string) => options.warn(line);\n const dir = configDir(env);\n const configPath = join(dir, 'config.json');\n const file = readClientConfig(configPath);\n const identity = await resolveIdentity(options);\n\n const genesisSeed = loadGenesisSeed();\n\n // ── Relay-origin ──────────────────────────────────────────────────────────\n // The relay the paid command resolved via `rig remote` (passed by the\n // command) is the user's clearest network statement; env/file follow, and\n // the genesis seed's relay is the out-of-the-box fallback.\n const relayUrl =\n options.relayUrl ??\n env['TOON_CLIENT_RELAY_URL'] ??\n file.relayUrl ??\n genesisSeed?.relayUrl ??\n 'ws://localhost:7100';\n\n // ── Peer→channel persistence (#262) ──────────────────────────────────────\n const channelStorePath = file.channelStorePath ?? join(dir, 'channels.json');\n const channelMap = new ChannelMapStore({\n mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),\n watermarkPath: channelStorePath,\n });\n\n // ── Topology cache (#279): keyed by relay-origin + identity + explicit\n // config; a hit skips discovery AND the pure-but-probing resolution below.\n const cache = new TopologyCache<NetworkTopology>({\n path: join(dir, TOPOLOGY_CACHE_FILENAME),\n ttlMs: topologyCacheTtlMs(env),\n validate: isNetworkTopology,\n });\n const cacheKey = topologyCacheKey({\n relayUrl,\n identity: identity.pubkey,\n fingerprint: explicitConfigFingerprint(\n env,\n file as Record<string, unknown>\n ),\n });\n\n const resolveLiveTopology = async (): Promise<NetworkTopology> => {\n // ── Live announce discovery ────────────────────────────────────────────\n // Skipped when explicit config already pins the whole payment topology\n // (fully-configured setups keep their zero-roundtrip start and their\n // exact pre-#264 behavior). Discovery failure is non-fatal: warn +\n // genesis seed.\n const fullyExplicit =\n Boolean(\n (env['TOON_CLIENT_PROXY_URL'] ?? file.proxyUrl) ||\n (env['TOON_CLIENT_BTP_URL'] ?? file.btpUrl)\n ) &&\n Boolean(env['TOON_CLIENT_DESTINATION'] ?? file.destination) &&\n Boolean(file.supportedChains?.length);\n let announce: AnnouncedPeer | undefined;\n if (!fullyExplicit) {\n try {\n const peers = await discoverAnnouncedPeers(relayUrl, {\n timeoutMs: DISCOVERY_TIMEOUT_MS,\n });\n announce = pickPaymentPeer(peers, genesisSeedPubkeys());\n if (!announce) {\n warn(\n `rig: no payment-peer announce (kind:10032) found on ${relayUrl} — ` +\n 'falling back to the genesis peer seed'\n );\n }\n } catch (err) {\n warn(\n `rig: announce discovery on ${relayUrl} failed ` +\n `(${err instanceof Error ? err.message : String(err)}) — falling ` +\n 'back to the genesis peer seed'\n );\n }\n }\n\n // ── Topology resolution (pure; explicit > announce > genesis) ──────────\n const resolved = await resolveNetworkTopology({\n env,\n file,\n configPath,\n relayUrl,\n announce,\n genesisSeed,\n identity: {\n mnemonic: identity.mnemonic,\n accountIndex: identity.accountIndex,\n pubkey: identity.pubkey,\n },\n channelRecords: () => chainRecordsFor(channelMap, identity.pubkey),\n ...(options.requireUplink !== undefined\n ? { requireUplink: options.requireUplink }\n : {}),\n warn,\n });\n // Cache only paid-path resolutions: a `requireUplink: false` free read\n // may resolve WITHOUT an uplink, and caching that would let a later paid\n // command skip past MissingUplinkError with a broken topology.\n if (options.requireUplink !== false) cache.write(cacheKey, resolved);\n return resolved;\n };\n\n const cached = cache.read(cacheKey);\n if (cached) {\n warn(\n `rig: network topology from cache (${Math.round(cached.ageMs / 1000)}s ` +\n `old; ${TOPOLOGY_TTL_ENV}=0 disables) — skipping announce discovery`\n );\n }\n const topology = cached?.topology ?? (await resolveLiveTopology());\n\n const eventFee = BigInt(file.feePerEvent ?? '1');\n\n const buildPublisher = (topo: NetworkTopology): StandalonePublisher => {\n const clientConfig: ToonClientConfig = {\n // validateConfig requires connectorUrl OR proxyUrl; with BTP-only\n // config a dummy connectorUrl satisfies it (unused at runtime — same\n // convention as the daemon).\n ...(topo.proxyUrl\n ? { proxyUrl: topo.proxyUrl }\n : { connectorUrl: 'http://127.0.0.1:1' }),\n mnemonic: identity.mnemonic,\n mnemonicAccountIndex: identity.accountIndex,\n ilpInfo: {\n pubkey: '00'.repeat(32),\n ilpAddress: 'g.toon.client',\n btpEndpoint: topo.btpUrl ?? '',\n assetCode: 'USD',\n assetScale: 6,\n },\n toonEncoder: encodeEventToToon,\n toonDecoder: decodeEventFromToon,\n ...(topo.btpUrl ? { btpUrl: topo.btpUrl, btpAuthToken: '' } : {}),\n destinationAddress: topo.destination,\n // The embedded client bootstraps against the known peer above; its\n // `relayUrl` config only seeds ArDrive-merged peers, so it stays unset.\n relayUrl: '',\n knownPeers: topo.knownPeers,\n channelStorePath,\n ...(topo.supportedChains\n ? { supportedChains: topo.supportedChains }\n : {}),\n ...(file.settlementAddresses\n ? { settlementAddresses: file.settlementAddresses }\n : {}),\n ...(topo.preferredTokens ? { preferredTokens: topo.preferredTokens } : {}),\n ...(topo.tokenNetworks ? { tokenNetworks: topo.tokenNetworks } : {}),\n ...(topo.chainRpcUrls ? { chainRpcUrls: topo.chainRpcUrls } : {}),\n ...(file.solanaChannel ? { solanaChannel: file.solanaChannel } : {}),\n ...(file.minaChannel ? { minaChannel: file.minaChannel } : {}),\n };\n\n return new StandalonePublisher({\n clientConfig,\n eventFee,\n channelMap,\n warn,\n ...(topo.publishDestination\n ? { publishDestination: topo.publishDestination }\n : {}),\n ...(topo.storeDestination\n ? { storeDestination: topo.storeDestination }\n : {}),\n // `rig channel open --peer` (#263): anchor the channel (and its map\n // key) to an explicit peer destination instead of the configured\n // default.\n ...(options.channelDestination\n ? { channelDestination: options.channelDestination }\n : {}),\n // The peer's announce does not carry TokenNetwork/token parameters, so\n // the client's negotiation leaves them empty (#260 root cause 3) — the\n // publisher back-fills them from the derived per-chain maps before the\n // channel opens.\n ...(topo.tokenNetworks || topo.preferredTokens\n ? {\n negotiationFallbacks: {\n ...(topo.tokenNetworks\n ? { tokenNetworks: topo.tokenNetworks }\n : {}),\n ...(topo.preferredTokens\n ? { preferredTokens: topo.preferredTokens }\n : {}),\n },\n }\n : {}),\n });\n };\n\n // Cache-sourced publishers get the #279 recovery hook: a failed bootstrap\n // invalidates the entry, re-resolves live (which re-writes the cache), and\n // retries once. Live-resolved publishers fail through unchanged.\n const publisher = new TopologyRecoveringPublisher(\n buildPublisher(topology),\n cached\n ? async () => {\n cache.invalidate(cacheKey);\n return buildPublisher(await resolveLiveTopology());\n }\n : undefined,\n warn\n );\n\n return {\n ownerPubkey: publisher.getPublicKey(),\n identitySource: identity.source,\n identitySourceLabel: identity.sourceLabel,\n publisher,\n defaultRelayUrls: [relayUrl],\n fetchRemote: (args) => fetchRemoteState(args),\n // Money lifecycle (#263): same guard/start/channel-map machinery as the\n // paid-write path, surfaced for fund/balance/channel open|close|settle.\n money: {\n openChannel: (opts) => publisher.openChannelExplicit(opts),\n closeChannel: (record) => publisher.closeRecordedChannel(record),\n settleChannel: (record) => publisher.settleRecordedChannel(record),\n walletChainBalances: () => publisher.readWalletChainBalances(),\n },\n stop: () => publisher.stop(),\n };\n}\n","/**\n * Standalone network-topology cache (#279): persist the #264 bootstrap\n * result — the announce-derived uplink, ILP routes, settlement chain and its\n * TokenNetwork/token/RPC parameters (or the genesis-seed fallback) — so\n * back-to-back CLI invocations skip the kind:10032 relay query, the\n * payment-peer pick, and the funded-chain `eth_call` probes.\n *\n * SAFETY MODEL — the cache holds only DISCOVERY results, never money state:\n *\n * - The claim watermark (nonce/cumulative) lives in the client's\n * `channels.json` and the #262 channel map, untouched by this cache;\n * every paid write still resumes from the persisted cumulative.\n * - Entries are keyed by relay-origin + identity pubkey + a fingerprint of\n * every explicit env/config field that feeds topology resolution, so a\n * changed relay, identity, or override can never hit a stale entry.\n * - Entries expire after a TTL (default 15 min;\n * `RIG_TOPOLOGY_TTL_MS` overrides, `0` disables the cache entirely).\n * - A cached topology that fails to bootstrap is EXPLICITLY invalidated\n * and re-resolved live (see `cli/standalone-mode.ts`), so a rotated peer\n * endpoint costs one failed attempt, not a broken 15 minutes.\n * - A corrupt/unreadable cache file is a MISS (discovery re-runs), never\n * an error: unlike the #262 channel map, nothing here guards money.\n *\n * Dependency-light on purpose (node:crypto + node:fs): the cached value is a\n * generic JSON document; `cli/standalone-mode.ts` instantiates the class\n * with its `NetworkTopology` shape and a structural validator.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/** Cache filename under the shared client state dir (`TOON_CLIENT_HOME`). */\nexport const TOPOLOGY_CACHE_FILENAME = 'rig-topology-cache.json';\n\n/** Default entry TTL: 15 minutes. */\nexport const DEFAULT_TOPOLOGY_TTL_MS = 15 * 60 * 1000;\n\n/** TTL env override (milliseconds; `0` disables caching entirely). */\nexport const TOPOLOGY_TTL_ENV = 'RIG_TOPOLOGY_TTL_MS';\n\n/** Resolve the entry TTL: `RIG_TOPOLOGY_TTL_MS` env (>= 0), else 15 min. */\nexport function topologyCacheTtlMs(env: NodeJS.ProcessEnv): number {\n const raw = env[TOPOLOGY_TTL_ENV];\n if (raw === undefined || raw === '') return DEFAULT_TOPOLOGY_TTL_MS;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0\n ? parsed\n : DEFAULT_TOPOLOGY_TTL_MS;\n}\n\n/**\n * Stable cache key: relay-origin + identity pubkey + the explicit-config\n * fingerprint. Hashed so the key is filename/JSON-safe and the mnemonic-side\n * inputs never appear in the cache file verbatim.\n */\nexport function topologyCacheKey(args: {\n relayUrl: string;\n identity: string;\n fingerprint: string;\n}): string {\n return createHash('sha256')\n .update(`${args.relayUrl}\\n${args.identity}\\n${args.fingerprint}`)\n .digest('hex');\n}\n\n/**\n * Canonical fingerprint of the explicit inputs that steer topology\n * resolution (see `resolveNetworkTopology`'s precedence order): the\n * TOON_CLIENT_* env overrides plus the shared-config fields. Any change\n * produces a different cache key — explicit config can never be shadowed by\n * a cached resolution made under different settings.\n */\nexport function explicitConfigFingerprint(\n env: NodeJS.ProcessEnv,\n file: Record<string, unknown>\n): string {\n const envKeys = [\n 'TOON_CLIENT_PROXY_URL',\n 'TOON_CLIENT_BTP_URL',\n 'TOON_CLIENT_DESTINATION',\n 'TOON_CLIENT_PUBLISH_DESTINATION',\n 'TOON_CLIENT_STORE_DESTINATION',\n 'TOON_CLIENT_CHAIN',\n 'TOON_CLIENT_NETWORK',\n ] as const;\n const fileKeys = [\n 'network',\n 'btpUrl',\n 'proxyUrl',\n 'destination',\n 'publishDestination',\n 'storeDestination',\n 'chain',\n 'supportedChains',\n 'settlementAddresses',\n 'preferredTokens',\n 'tokenNetworks',\n 'chainRpcUrls',\n ] as const;\n const picked: Record<string, unknown> = {};\n for (const key of envKeys) {\n if (env[key] !== undefined) picked[`env:${key}`] = env[key];\n }\n for (const key of fileKeys) {\n if (file[key] !== undefined) picked[`file:${key}`] = file[key];\n }\n // Keys are inserted in fixed order above → deterministic serialization.\n return JSON.stringify(picked);\n}\n\ninterface CacheEntry {\n /** ISO timestamp the entry was written. */\n cachedAt: string;\n /** The cached (JSON-safe) topology document. */\n topology: unknown;\n}\n\ninterface CacheFile {\n version: 1;\n entries: Record<string, CacheEntry>;\n}\n\nexport interface TopologyCacheOptions<T> {\n /** Cache file path (under `TOON_CLIENT_HOME`). */\n path: string;\n /** Entry TTL in ms; `0` (or negative) disables reads AND writes. */\n ttlMs: number;\n /** Structural validator for cached values (a failed check is a miss). */\n validate: (value: unknown) => value is T;\n /** Clock override (tests). */\n now?: () => number;\n}\n\n/**\n * File-backed, TTL'd topology cache. All operations are best-effort and\n * synchronous; failures degrade to cache misses / no-ops (discovery is the\n * always-correct fallback).\n */\nexport class TopologyCache<T> {\n readonly path: string;\n readonly ttlMs: number;\n private readonly validate: (value: unknown) => value is T;\n private readonly now: () => number;\n\n constructor(options: TopologyCacheOptions<T>) {\n this.path = options.path;\n this.ttlMs = options.ttlMs;\n this.validate = options.validate;\n this.now = options.now ?? Date.now;\n }\n\n /** True when the cache is disabled (`RIG_TOPOLOGY_TTL_MS=0`). */\n get disabled(): boolean {\n return this.ttlMs <= 0;\n }\n\n /**\n * A fresh, structurally-valid entry for `key` — or undefined (expired,\n * missing, invalid, corrupt file, or disabled). Also reports the entry age\n * for the \"topology from cache\" stderr line.\n */\n read(key: string): { topology: T; ageMs: number } | undefined {\n if (this.disabled) return undefined;\n const entry = this.readFile().entries[key];\n if (!entry) return undefined;\n const cachedAt = Date.parse(entry.cachedAt);\n if (!Number.isFinite(cachedAt)) return undefined;\n const ageMs = this.now() - cachedAt;\n if (ageMs < 0 || ageMs > this.ttlMs) return undefined;\n if (!this.validate(entry.topology)) return undefined;\n return { topology: entry.topology, ageMs };\n }\n\n /** Persist `topology` under `key` (prunes expired entries; best-effort). */\n write(key: string, topology: T): void {\n if (this.disabled) return;\n try {\n const file = this.readFile();\n const nowMs = this.now();\n const fresh = Object.fromEntries(\n Object.entries(file.entries).filter(([, entry]) => {\n const at = Date.parse(entry.cachedAt);\n return Number.isFinite(at) && nowMs - at <= this.ttlMs;\n })\n );\n fresh[key] = {\n cachedAt: new Date(nowMs).toISOString(),\n topology,\n };\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(\n this.path,\n JSON.stringify(\n { version: 1, entries: fresh } satisfies CacheFile,\n null,\n 2\n ),\n { mode: 0o600 }\n );\n } catch {\n // Best-effort — a failed write just means the next run discovers live.\n }\n }\n\n /** Drop `key` (a cached topology failed to bootstrap). Best-effort. */\n invalidate(key: string): void {\n try {\n const file = this.readFile();\n if (!(key in file.entries)) return;\n const remaining = Object.fromEntries(\n Object.entries(file.entries).filter(([k]) => k !== key)\n );\n if (Object.keys(remaining).length === 0) {\n unlinkSync(this.path);\n return;\n }\n writeFileSync(\n this.path,\n JSON.stringify(\n { version: 1, entries: remaining } satisfies CacheFile,\n null,\n 2\n ),\n { mode: 0o600 }\n );\n } catch {\n // Best-effort — worst case the entry expires by TTL.\n }\n }\n\n private readFile(): CacheFile {\n const empty: CacheFile = { version: 1, entries: {} };\n let raw: string;\n try {\n raw = readFileSync(this.path, 'utf8');\n } catch {\n return empty;\n }\n try {\n const parsed = JSON.parse(raw) as CacheFile;\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n parsed.version !== 1 ||\n typeof parsed.entries !== 'object' ||\n parsed.entries === null\n ) {\n return empty;\n }\n return parsed;\n } catch {\n return empty; // corrupt cache = miss, never an error (see module doc)\n }\n }\n}\n"],"mappings":"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","names":["readFileSync","readFileSync"]}
|