@toon-protocol/rig 3.6.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -152
- 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-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
- package/dist/chunk-YZRR2UO6.js.map +1 -0
- package/dist/{chunk-HJDPM37T.js → chunk-ZZBA5VVL.js} +11 -7
- package/dist/{chunk-HJDPM37T.js.map → chunk-ZZBA5VVL.js.map} +1 -1
- package/dist/cli/rig.js +290 -523
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +3 -115
- package/dist/index.js +9 -121
- package/dist/index.js.map +1 -1
- package/dist/{publisher-BFcnZeJI.d.ts → publisher-CcL96ugq.d.ts} +58 -1
- package/dist/standalone/index.d.ts +241 -438
- package/dist/standalone/index.js +19 -11
- package/dist/standalone-mode-LEAC4A2L.js +390 -0
- package/dist/standalone-mode-LEAC4A2L.js.map +1 -0
- package/package.json +5 -4
- 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
package/dist/chunk-PWNZWQOH.js
DELETED
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
// src/cli/identity.ts
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { dirname, join } from "path";
|
|
5
|
-
var MissingIdentityError = class extends Error {
|
|
6
|
-
constructor(configPath) {
|
|
7
|
-
super(
|
|
8
|
-
`no identity found \u2014 rig needs a BIP-39 seed phrase to sign and pay.
|
|
9
|
-
Generate one now (no BIP-39 tooling needed):
|
|
10
|
-
\u2022 \`rig identity create\` \u2014 mint a fresh identity into the encrypted keystore (shows the phrase once to back up)
|
|
11
|
-
Or point rig at an existing phrase (highest precedence first):
|
|
12
|
-
\u2022 RIG_MNEMONIC environment variable
|
|
13
|
-
\u2022 RIG_MNEMONIC=<phrase> in a project-local .env file (gitignore it!)
|
|
14
|
-
\u2022 the shared client config at ${configPath} (mnemonic or keystorePath + TOON_CLIENT_KEYSTORE_PASSWORD)`
|
|
15
|
-
);
|
|
16
|
-
this.name = "MissingIdentityError";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
var DEFAULT_KEYSTORE_PASSWORD = "toon-client-default";
|
|
20
|
-
function clientStateDir(env) {
|
|
21
|
-
return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
|
|
22
|
-
}
|
|
23
|
-
function clientConfigPath(env) {
|
|
24
|
-
return join(clientStateDir(env), "config.json");
|
|
25
|
-
}
|
|
26
|
-
function clientKeystorePath(env) {
|
|
27
|
-
return join(clientStateDir(env), "keystore.json");
|
|
28
|
-
}
|
|
29
|
-
function readClientConfigFile(path) {
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
32
|
-
} catch (err) {
|
|
33
|
-
if (err.code === "ENOENT") return {};
|
|
34
|
-
throw new Error(
|
|
35
|
-
`failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function linkKeystoreInClientConfig(env, keystorePath, autoPassword) {
|
|
40
|
-
const configPath = clientConfigPath(env);
|
|
41
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
42
|
-
const existing = readClientConfigFile(configPath);
|
|
43
|
-
const merged = {
|
|
44
|
-
...existing,
|
|
45
|
-
keystorePath,
|
|
46
|
-
...autoPassword ? { keystoreAutoPassword: true } : (
|
|
47
|
-
// A user-set password: drop any stale auto flag so decryption
|
|
48
|
-
// requires TOON_CLIENT_KEYSTORE_PASSWORD.
|
|
49
|
-
{ keystoreAutoPassword: false }
|
|
50
|
-
)
|
|
51
|
-
};
|
|
52
|
-
writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", {
|
|
53
|
-
encoding: "utf8",
|
|
54
|
-
mode: 384
|
|
55
|
-
});
|
|
56
|
-
return configPath;
|
|
57
|
-
}
|
|
58
|
-
var DOTENV_LINE_RE = /^\s*(?:export\s+)?RIG_MNEMONIC\s*=\s*(.*)\s*$/;
|
|
59
|
-
function parseDotenvMnemonic(content) {
|
|
60
|
-
let found;
|
|
61
|
-
for (const line of content.split(/\r?\n/)) {
|
|
62
|
-
const match = DOTENV_LINE_RE.exec(line);
|
|
63
|
-
if (!match) continue;
|
|
64
|
-
let value = (match[1] ?? "").trim();
|
|
65
|
-
const quote = value[0];
|
|
66
|
-
if ((quote === '"' || quote === "'") && value.length >= 2 && value.endsWith(quote)) {
|
|
67
|
-
value = value.slice(1, -1);
|
|
68
|
-
} else {
|
|
69
|
-
const hash = value.search(/\s#/);
|
|
70
|
-
if (hash !== -1) value = value.slice(0, hash);
|
|
71
|
-
value = value.trim();
|
|
72
|
-
}
|
|
73
|
-
if (value !== "") found = value;
|
|
74
|
-
}
|
|
75
|
-
return found;
|
|
76
|
-
}
|
|
77
|
-
function findDotenvMnemonic(startDir, stopDir) {
|
|
78
|
-
let dir = startDir;
|
|
79
|
-
for (; ; ) {
|
|
80
|
-
const candidate = join(dir, ".env");
|
|
81
|
-
if (existsSync(candidate)) {
|
|
82
|
-
try {
|
|
83
|
-
const mnemonic = parseDotenvMnemonic(readFileSync(candidate, "utf8"));
|
|
84
|
-
if (mnemonic !== void 0) return { path: candidate, mnemonic };
|
|
85
|
-
} catch {
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (stopDir !== void 0 && dir === stopDir) return void 0;
|
|
89
|
-
const parent = dirname(dir);
|
|
90
|
-
if (parent === dir) return void 0;
|
|
91
|
-
dir = parent;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
var HOME_INDEPENDENT_SOURCES = /* @__PURE__ */ new Set([
|
|
95
|
-
"env",
|
|
96
|
-
"env-alias",
|
|
97
|
-
"dotenv"
|
|
98
|
-
]);
|
|
99
|
-
function resolveAccountIndex(env, source, file) {
|
|
100
|
-
const raw = env["RIG_ACCOUNT_INDEX"]?.trim();
|
|
101
|
-
if (raw) {
|
|
102
|
-
const parsed = Number(raw);
|
|
103
|
-
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`RIG_ACCOUNT_INDEX must be a non-negative integer, got ${JSON.stringify(raw)}`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
return parsed;
|
|
109
|
-
}
|
|
110
|
-
if (HOME_INDEPENDENT_SOURCES.has(source)) return 0;
|
|
111
|
-
return typeof file.mnemonicAccountIndex === "number" ? file.mnemonicAccountIndex : 0;
|
|
112
|
-
}
|
|
113
|
-
function resolveMnemonicSource(options, file, configPath) {
|
|
114
|
-
const { env } = options;
|
|
115
|
-
const rigEnv = env["RIG_MNEMONIC"]?.trim();
|
|
116
|
-
if (rigEnv) {
|
|
117
|
-
return { mnemonic: rigEnv, source: "env", sourceLabel: "RIG_MNEMONIC env" };
|
|
118
|
-
}
|
|
119
|
-
const aliasEnv = env["TOON_CLIENT_MNEMONIC"]?.trim();
|
|
120
|
-
if (aliasEnv) {
|
|
121
|
-
options.warn(
|
|
122
|
-
"rig: TOON_CLIENT_MNEMONIC is deprecated \u2014 rename the variable to RIG_MNEMONIC"
|
|
123
|
-
);
|
|
124
|
-
return {
|
|
125
|
-
mnemonic: aliasEnv,
|
|
126
|
-
source: "env-alias",
|
|
127
|
-
sourceLabel: "TOON_CLIENT_MNEMONIC env (deprecated alias)"
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const dotenv = findDotenvMnemonic(options.cwd, options.dotenvStopDir);
|
|
131
|
-
if (dotenv) {
|
|
132
|
-
return {
|
|
133
|
-
mnemonic: dotenv.mnemonic,
|
|
134
|
-
source: "dotenv",
|
|
135
|
-
sourceLabel: dotenv.path
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (typeof file.keystorePath === "string" && file.keystorePath !== "") {
|
|
139
|
-
return { keystorePath: file.keystorePath };
|
|
140
|
-
}
|
|
141
|
-
if (typeof file.mnemonic === "string" && file.mnemonic.trim() !== "") {
|
|
142
|
-
return {
|
|
143
|
-
mnemonic: file.mnemonic.trim(),
|
|
144
|
-
source: "config",
|
|
145
|
-
sourceLabel: configPath
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
throw new MissingIdentityError(configPath);
|
|
149
|
-
}
|
|
150
|
-
async function loadClientKeys() {
|
|
151
|
-
return await import("@toon-protocol/client");
|
|
152
|
-
}
|
|
153
|
-
async function resolveIdentity(options) {
|
|
154
|
-
const configPath = clientConfigPath(options.env);
|
|
155
|
-
const file = readClientConfigFile(configPath);
|
|
156
|
-
const picked = resolveMnemonicSource(options, file, configPath);
|
|
157
|
-
const keys = await loadClientKeys();
|
|
158
|
-
let source;
|
|
159
|
-
if ("keystorePath" in picked) {
|
|
160
|
-
const password = options.env["TOON_CLIENT_KEYSTORE_PASSWORD"] ?? (file.keystoreAutoPassword === true ? DEFAULT_KEYSTORE_PASSWORD : void 0);
|
|
161
|
-
if (!password) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
`keystorePath is set in ${configPath} but TOON_CLIENT_KEYSTORE_PASSWORD is not provided`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
source = {
|
|
167
|
-
mnemonic: keys.loadKeystore(picked.keystorePath, password),
|
|
168
|
-
source: "keystore",
|
|
169
|
-
sourceLabel: picked.keystorePath
|
|
170
|
-
};
|
|
171
|
-
} else {
|
|
172
|
-
source = picked;
|
|
173
|
-
}
|
|
174
|
-
const accountIndex = resolveAccountIndex(options.env, source.source, file);
|
|
175
|
-
const { pubkey } = keys.deriveNostrKeyFromMnemonic(source.mnemonic, accountIndex);
|
|
176
|
-
return { ...source, accountIndex, pubkey };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/standalone/topology-cache.ts
|
|
180
|
-
import { createHash } from "crypto";
|
|
181
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
182
|
-
import { dirname as dirname2 } from "path";
|
|
183
|
-
var TOPOLOGY_CACHE_FILENAME = "rig-topology-cache.json";
|
|
184
|
-
var DEFAULT_TOPOLOGY_TTL_MS = 15 * 60 * 1e3;
|
|
185
|
-
var TOPOLOGY_TTL_ENV = "RIG_TOPOLOGY_TTL_MS";
|
|
186
|
-
function topologyCacheTtlMs(env) {
|
|
187
|
-
const raw = env[TOPOLOGY_TTL_ENV];
|
|
188
|
-
if (raw === void 0 || raw === "") return DEFAULT_TOPOLOGY_TTL_MS;
|
|
189
|
-
const parsed = Number(raw);
|
|
190
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_TOPOLOGY_TTL_MS;
|
|
191
|
-
}
|
|
192
|
-
var TOPOLOGY_SCHEMA_VERSION = 3;
|
|
193
|
-
function topologyCacheKey(args) {
|
|
194
|
-
return createHash("sha256").update(
|
|
195
|
-
`v${TOPOLOGY_SCHEMA_VERSION}
|
|
196
|
-
${args.relayUrl}
|
|
197
|
-
${args.identity}
|
|
198
|
-
` + args.fingerprint
|
|
199
|
-
).digest("hex");
|
|
200
|
-
}
|
|
201
|
-
function explicitConfigFingerprint(env, file) {
|
|
202
|
-
const envKeys = [
|
|
203
|
-
"TOON_CLIENT_PROXY_URL",
|
|
204
|
-
"TOON_CLIENT_BTP_URL",
|
|
205
|
-
"TOON_CLIENT_DESTINATION",
|
|
206
|
-
"TOON_CLIENT_PUBLISH_DESTINATION",
|
|
207
|
-
"TOON_CLIENT_STORE_DESTINATION",
|
|
208
|
-
"TOON_CLIENT_CHAIN",
|
|
209
|
-
"TOON_CLIENT_NETWORK"
|
|
210
|
-
];
|
|
211
|
-
const fileKeys = [
|
|
212
|
-
"network",
|
|
213
|
-
"btpUrl",
|
|
214
|
-
"proxyUrl",
|
|
215
|
-
"destination",
|
|
216
|
-
"publishDestination",
|
|
217
|
-
"storeDestination",
|
|
218
|
-
"chain",
|
|
219
|
-
"supportedChains",
|
|
220
|
-
"settlementAddresses",
|
|
221
|
-
"preferredTokens",
|
|
222
|
-
"tokenNetworks",
|
|
223
|
-
"chainRpcUrls"
|
|
224
|
-
];
|
|
225
|
-
const picked = {};
|
|
226
|
-
for (const key of envKeys) {
|
|
227
|
-
if (env[key] !== void 0) picked[`env:${key}`] = env[key];
|
|
228
|
-
}
|
|
229
|
-
for (const key of fileKeys) {
|
|
230
|
-
if (file[key] !== void 0) picked[`file:${key}`] = file[key];
|
|
231
|
-
}
|
|
232
|
-
return JSON.stringify(picked);
|
|
233
|
-
}
|
|
234
|
-
var TopologyCache = class {
|
|
235
|
-
path;
|
|
236
|
-
ttlMs;
|
|
237
|
-
validate;
|
|
238
|
-
now;
|
|
239
|
-
constructor(options) {
|
|
240
|
-
this.path = options.path;
|
|
241
|
-
this.ttlMs = options.ttlMs;
|
|
242
|
-
this.validate = options.validate;
|
|
243
|
-
this.now = options.now ?? Date.now;
|
|
244
|
-
}
|
|
245
|
-
/** True when the cache is disabled (`RIG_TOPOLOGY_TTL_MS=0`). */
|
|
246
|
-
get disabled() {
|
|
247
|
-
return this.ttlMs <= 0;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* A fresh, structurally-valid entry for `key` — or undefined (expired,
|
|
251
|
-
* missing, invalid, corrupt file, or disabled). Also reports the entry age
|
|
252
|
-
* for the "topology from cache" stderr line.
|
|
253
|
-
*/
|
|
254
|
-
read(key) {
|
|
255
|
-
if (this.disabled) return void 0;
|
|
256
|
-
const entry = this.readFile().entries[key];
|
|
257
|
-
if (!entry) return void 0;
|
|
258
|
-
const cachedAt = Date.parse(entry.cachedAt);
|
|
259
|
-
if (!Number.isFinite(cachedAt)) return void 0;
|
|
260
|
-
const ageMs = this.now() - cachedAt;
|
|
261
|
-
if (ageMs < 0 || ageMs > this.ttlMs) return void 0;
|
|
262
|
-
if (!this.validate(entry.topology)) return void 0;
|
|
263
|
-
return { topology: entry.topology, ageMs };
|
|
264
|
-
}
|
|
265
|
-
/** Persist `topology` under `key` (prunes expired entries; best-effort). */
|
|
266
|
-
write(key, topology) {
|
|
267
|
-
if (this.disabled) return;
|
|
268
|
-
try {
|
|
269
|
-
const file = this.readFile();
|
|
270
|
-
const nowMs = this.now();
|
|
271
|
-
const fresh = Object.fromEntries(
|
|
272
|
-
Object.entries(file.entries).filter(([, entry]) => {
|
|
273
|
-
const at = Date.parse(entry.cachedAt);
|
|
274
|
-
return Number.isFinite(at) && nowMs - at <= this.ttlMs;
|
|
275
|
-
})
|
|
276
|
-
);
|
|
277
|
-
fresh[key] = {
|
|
278
|
-
cachedAt: new Date(nowMs).toISOString(),
|
|
279
|
-
topology
|
|
280
|
-
};
|
|
281
|
-
mkdirSync2(dirname2(this.path), { recursive: true });
|
|
282
|
-
writeFileSync2(
|
|
283
|
-
this.path,
|
|
284
|
-
JSON.stringify(
|
|
285
|
-
{ version: 1, entries: fresh },
|
|
286
|
-
null,
|
|
287
|
-
2
|
|
288
|
-
),
|
|
289
|
-
{ mode: 384 }
|
|
290
|
-
);
|
|
291
|
-
} catch {
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
/** Drop `key` (a cached topology failed to bootstrap). Best-effort. */
|
|
295
|
-
invalidate(key) {
|
|
296
|
-
try {
|
|
297
|
-
const file = this.readFile();
|
|
298
|
-
if (!(key in file.entries)) return;
|
|
299
|
-
const remaining = Object.fromEntries(
|
|
300
|
-
Object.entries(file.entries).filter(([k]) => k !== key)
|
|
301
|
-
);
|
|
302
|
-
if (Object.keys(remaining).length === 0) {
|
|
303
|
-
unlinkSync(this.path);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
writeFileSync2(
|
|
307
|
-
this.path,
|
|
308
|
-
JSON.stringify(
|
|
309
|
-
{ version: 1, entries: remaining },
|
|
310
|
-
null,
|
|
311
|
-
2
|
|
312
|
-
),
|
|
313
|
-
{ mode: 384 }
|
|
314
|
-
);
|
|
315
|
-
} catch {
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
readFile() {
|
|
319
|
-
const empty = { version: 1, entries: {} };
|
|
320
|
-
let raw;
|
|
321
|
-
try {
|
|
322
|
-
raw = readFileSync2(this.path, "utf8");
|
|
323
|
-
} catch {
|
|
324
|
-
return empty;
|
|
325
|
-
}
|
|
326
|
-
try {
|
|
327
|
-
const parsed = JSON.parse(raw);
|
|
328
|
-
if (typeof parsed !== "object" || parsed === null || parsed.version !== 1 || typeof parsed.entries !== "object" || parsed.entries === null) {
|
|
329
|
-
return empty;
|
|
330
|
-
}
|
|
331
|
-
return parsed;
|
|
332
|
-
} catch {
|
|
333
|
-
return empty;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
// src/standalone/mina-zkapp-store.ts
|
|
339
|
-
import {
|
|
340
|
-
chmodSync,
|
|
341
|
-
mkdirSync as mkdirSync3,
|
|
342
|
-
readFileSync as readFileSync3,
|
|
343
|
-
writeFileSync as writeFileSync3
|
|
344
|
-
} from "fs";
|
|
345
|
-
import { dirname as dirname3, join as join2 } from "path";
|
|
346
|
-
var FILENAME = "rig-mina-zkapps.json";
|
|
347
|
-
function keyFor(identity, chain) {
|
|
348
|
-
return `${identity}|${chain}`;
|
|
349
|
-
}
|
|
350
|
-
var MinaZkAppStore = class _MinaZkAppStore {
|
|
351
|
-
constructor(filePath) {
|
|
352
|
-
this.filePath = filePath;
|
|
353
|
-
}
|
|
354
|
-
filePath;
|
|
355
|
-
/** The store under a client home dir: `<dir>/keys/rig-mina-zkapps.json`. */
|
|
356
|
-
static forHome(dir) {
|
|
357
|
-
return new _MinaZkAppStore(join2(dir, "keys", FILENAME));
|
|
358
|
-
}
|
|
359
|
-
readAll() {
|
|
360
|
-
try {
|
|
361
|
-
return JSON.parse(readFileSync3(this.filePath, "utf8"));
|
|
362
|
-
} catch (err) {
|
|
363
|
-
if (err.code === "ENOENT") return {};
|
|
364
|
-
throw new Error(
|
|
365
|
-
`failed to read the Mina zkApp store at ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/** The recorded deployment for this identity/chain, if any. */
|
|
370
|
-
lookup(identity, chain) {
|
|
371
|
-
return this.readAll()[keyFor(identity, chain)];
|
|
372
|
-
}
|
|
373
|
-
/** Read-merge-write; the file holds private keys → 0600. */
|
|
374
|
-
save(record) {
|
|
375
|
-
const all = this.readAll();
|
|
376
|
-
all[keyFor(record.identity, record.chain)] = record;
|
|
377
|
-
mkdirSync3(dirname3(this.filePath), { recursive: true });
|
|
378
|
-
writeFileSync3(this.filePath, `${JSON.stringify(all, null, 2)}
|
|
379
|
-
`, {
|
|
380
|
-
encoding: "utf8",
|
|
381
|
-
mode: 384
|
|
382
|
-
});
|
|
383
|
-
chmodSync(this.filePath, 384);
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
export {
|
|
388
|
-
MissingIdentityError,
|
|
389
|
-
DEFAULT_KEYSTORE_PASSWORD,
|
|
390
|
-
clientConfigPath,
|
|
391
|
-
clientKeystorePath,
|
|
392
|
-
readClientConfigFile,
|
|
393
|
-
linkKeystoreInClientConfig,
|
|
394
|
-
resolveAccountIndex,
|
|
395
|
-
resolveIdentity,
|
|
396
|
-
TOPOLOGY_CACHE_FILENAME,
|
|
397
|
-
TOPOLOGY_TTL_ENV,
|
|
398
|
-
topologyCacheTtlMs,
|
|
399
|
-
topologyCacheKey,
|
|
400
|
-
explicitConfigFingerprint,
|
|
401
|
-
TopologyCache,
|
|
402
|
-
MinaZkAppStore
|
|
403
|
-
};
|
|
404
|
-
//# sourceMappingURL=chunk-PWNZWQOH.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/identity.ts","../src/standalone/topology-cache.ts","../src/standalone/mina-zkapp-store.ts"],"sourcesContent":["/**\n * Identity resolution for the standalone-only `rig` CLI (#248).\n *\n * One precedence chain, used by every command that signs or pays:\n *\n * 1. `RIG_MNEMONIC` environment variable\n * 2. `TOON_CLIENT_MNEMONIC` environment variable — DEPRECATED alias of the\n * env tier (a one-line stderr warning fires when it is the source)\n * 3. project-local `.env` — walked up from the working directory (through\n * the repo root to the filesystem root); ONLY the `RIG_MNEMONIC` line is\n * parsed out of it (never loaded into the process env, never required\n * to exist)\n * 4. the shared `~/.toon-client` state dir (`TOON_CLIENT_HOME` override):\n * encrypted keystore (`keystorePath` + `TOON_CLIENT_KEYSTORE_PASSWORD`,\n * auto-password fallback for daemon-provisioned keystores), then the\n * plain `mnemonic` config field — the pre-#248 standalone-context logic\n *\n * The BIP-44 account index follows the same env-over-config precedence:\n *\n * 1. `RIG_ACCOUNT_INDEX` environment variable (applies to every source)\n * 2. `mnemonicAccountIndex` from the shared config file — but ONLY when the\n * phrase itself came from that shared state dir (keystore/config tiers).\n * An env- or `.env`-sourced phrase derives at index 0 unless\n * `RIG_ACCOUNT_INDEX` says otherwise, so an explicit `RIG_MNEMONIC`\n * pins the same identity no matter which `TOON_CLIENT_HOME` is active\n * (#384 — a per-home config must never silently shift an explicitly\n * provided identity).\n * 3. account 0\n *\n * The phrase itself is never written anywhere by rig (not to git config, not\n * to any repo file) and never printed — callers report only the SOURCE and\n * the derived pubkey.\n *\n * Key derivation and keystore decryption live in the `@toon-protocol/client`\n * dependency, loaded via dynamic import so this module can be statically\n * imported by every command without dragging the client into runs that fail\n * earlier (usage errors, missing git repo, …).\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n/** Where the mnemonic came from (reported in output; the phrase never is). */\nexport type IdentitySourceKind =\n /** `RIG_MNEMONIC` environment variable. */\n | 'env'\n /** `TOON_CLIENT_MNEMONIC` environment variable (deprecated alias). */\n | 'env-alias'\n /** `RIG_MNEMONIC` parsed out of a project-local `.env` file. */\n | 'dotenv'\n /** Encrypted keystore referenced by the shared client config. */\n | 'keystore'\n /** Plain `mnemonic` field of the shared client config file. */\n | 'config';\n\n/** A resolved signing identity: source + derived pubkey (never the phrase). */\nexport interface ResolvedIdentity {\n /** The BIP-39 phrase. Handle with care; never log or persist it. */\n mnemonic: string;\n /** BIP-44 account index used for derivation — `RIG_ACCOUNT_INDEX` env,\n * else `mnemonicAccountIndex` from the shared config (home-sourced phrases\n * only), else 0. */\n accountIndex: number;\n source: IdentitySourceKind;\n /** Human-facing source label, e.g. `RIG_MNEMONIC env` or a file path. */\n sourceLabel: string;\n /** Derived Nostr pubkey (hex) — the repo owner / `toon.owner` value. */\n pubkey: string;\n}\n\n/** No mnemonic could be resolved anywhere on the chain. */\nexport class MissingIdentityError extends Error {\n constructor(configPath: string) {\n super(\n 'no identity found — rig needs a BIP-39 seed phrase to sign and pay.\\n' +\n 'Generate one now (no BIP-39 tooling needed):\\n' +\n ' • `rig identity create` — mint a fresh identity into the encrypted ' +\n 'keystore (shows the phrase once to back up)\\n' +\n 'Or point rig at an existing phrase (highest precedence first):\\n' +\n ' • RIG_MNEMONIC environment variable\\n' +\n ' • RIG_MNEMONIC=<phrase> in a project-local .env file (gitignore it!)\\n' +\n ` • the shared client config at ${configPath} (mnemonic or ` +\n 'keystorePath + TOON_CLIENT_KEYSTORE_PASSWORD)'\n );\n this.name = 'MissingIdentityError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared client config conventions (duplicated from client-mcp — the same\n// note as standalone/nonce-guard.ts: no @toon-protocol/client-mcp import)\n// ---------------------------------------------------------------------------\n\n/**\n * Duplicated daemon convention (client-mcp `daemon/config.ts`): the password\n * used to encrypt a keystore rig auto-provisions (`rig identity create`\n * with no `TOON_CLIENT_KEYSTORE_PASSWORD` set) so the identity reloads across\n * runs with no env var. At-rest obfuscation only.\n */\nexport const DEFAULT_KEYSTORE_PASSWORD = 'toon-client-default';\n\n/** Shared client state dir: `TOON_CLIENT_HOME`, else `~/.toon-client`. */\nexport function clientStateDir(env: NodeJS.ProcessEnv): string {\n return env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\n/** Shared client config file path (`<state-dir>/config.json`). */\nexport function clientConfigPath(env: NodeJS.ProcessEnv): string {\n return join(clientStateDir(env), 'config.json');\n}\n\n/** Default encrypted-keystore path (`<state-dir>/keystore.json`) — the same\n * convention the daemon's first-run provisioning uses. */\nexport function clientKeystorePath(env: NodeJS.ProcessEnv): string {\n return join(clientStateDir(env), 'keystore.json');\n}\n\n/** The subset of the shared client config file identity resolution reads. */\nexport interface ClientConfigIdentityFields {\n mnemonic?: unknown;\n mnemonicAccountIndex?: unknown;\n keystorePath?: unknown;\n keystoreAutoPassword?: unknown;\n}\n\nexport function readClientConfigFile(path: string): ClientConfigIdentityFields {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as ClientConfigIdentityFields;\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/**\n * Link a freshly written keystore into the shared client config so the\n * keystore tier of {@link resolveIdentity} (and the daemon) picks it up on the\n * next run — merging into any existing config, preserving every other field,\n * and writing owner-only (mode 0o600). `keystoreAutoPassword` records whether\n * rig chose the default password (so a user-set password still requires the\n * env var to decrypt, exactly like the daemon's first-run convention).\n */\nexport function linkKeystoreInClientConfig(\n env: NodeJS.ProcessEnv,\n keystorePath: string,\n autoPassword: boolean\n): string {\n const configPath = clientConfigPath(env);\n mkdirSync(dirname(configPath), { recursive: true });\n const existing = readClientConfigFile(configPath) as Record<string, unknown>;\n const merged: Record<string, unknown> = {\n ...existing,\n keystorePath,\n ...(autoPassword\n ? { keystoreAutoPassword: true }\n : // A user-set password: drop any stale auto flag so decryption\n // requires TOON_CLIENT_KEYSTORE_PASSWORD.\n { keystoreAutoPassword: false }),\n };\n writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\\n', {\n encoding: 'utf8',\n mode: 0o600,\n });\n return configPath;\n}\n\n// ---------------------------------------------------------------------------\n// .env parsing (RIG_MNEMONIC only — never arbitrary env loading)\n// ---------------------------------------------------------------------------\n\nconst DOTENV_LINE_RE = /^\\s*(?:export\\s+)?RIG_MNEMONIC\\s*=\\s*(.*)\\s*$/;\n\n/**\n * Extract ONLY the `RIG_MNEMONIC` value from `.env` file content. Supports\n * `export ` prefixes, single/double quotes, full-line `#` comments, and\n * inline ` #` comments on unquoted values. Multiple assignments: the last\n * one wins (shell-sourcing semantics). An empty value counts as unset.\n * Nothing is ever loaded into `process.env`.\n */\nexport function parseDotenvMnemonic(content: string): string | undefined {\n let found: string | undefined;\n for (const line of content.split(/\\r?\\n/)) {\n const match = DOTENV_LINE_RE.exec(line);\n if (!match) continue;\n let value = (match[1] ?? '').trim();\n const quote = value[0];\n if ((quote === '\"' || quote === \"'\") && value.length >= 2 && value.endsWith(quote)) {\n value = value.slice(1, -1);\n } else {\n // Unquoted: an inline comment starts at the first whitespace-preceded #.\n const hash = value.search(/\\s#/);\n if (hash !== -1) value = value.slice(0, hash);\n value = value.trim();\n }\n if (value !== '') found = value;\n }\n return found;\n}\n\n/**\n * Walk up from `startDir` (the working directory — the walk passes through\n * the repo root) to the filesystem root, returning the first `.env` file\n * that defines `RIG_MNEMONIC`. `stopDir` bounds the walk (tests).\n */\nexport function findDotenvMnemonic(\n startDir: string,\n stopDir?: string\n): { path: string; mnemonic: string } | undefined {\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, '.env');\n if (existsSync(candidate)) {\n try {\n const mnemonic = parseDotenvMnemonic(readFileSync(candidate, 'utf8'));\n if (mnemonic !== undefined) return { path: candidate, mnemonic };\n } catch {\n // Unreadable .env → keep walking; the file is never required.\n }\n }\n if (stopDir !== undefined && dir === stopDir) return undefined;\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Account index (env > per-home config for home-sourced phrases > 0)\n// ---------------------------------------------------------------------------\n\n/** Sources whose phrase lives OUTSIDE the shared state dir. For these the\n * per-home `mnemonicAccountIndex` must not apply — an explicit\n * `RIG_MNEMONIC` derives the same identity across every\n * `TOON_CLIENT_HOME` (#384). */\nconst HOME_INDEPENDENT_SOURCES: ReadonlySet<IdentitySourceKind> = new Set([\n 'env',\n 'env-alias',\n 'dotenv',\n]);\n\n/**\n * Resolve the BIP-44 account index for a phrase from `source`:\n * `RIG_ACCOUNT_INDEX` env always wins; the shared config's\n * `mnemonicAccountIndex` applies only when the phrase itself came from the\n * shared state dir (keystore/config tiers); default 0. Throws on a\n * malformed `RIG_ACCOUNT_INDEX` — silently deriving a different identity\n * is exactly the surprise this precedence exists to prevent.\n */\nexport function resolveAccountIndex(\n env: NodeJS.ProcessEnv,\n source: IdentitySourceKind,\n file: ClientConfigIdentityFields\n): number {\n const raw = env['RIG_ACCOUNT_INDEX']?.trim();\n if (raw) {\n const parsed = Number(raw);\n if (!Number.isInteger(parsed) || parsed < 0) {\n throw new Error(\n `RIG_ACCOUNT_INDEX must be a non-negative integer, got ${JSON.stringify(raw)}`\n );\n }\n return parsed;\n }\n if (HOME_INDEPENDENT_SOURCES.has(source)) return 0;\n return typeof file.mnemonicAccountIndex === 'number' ? file.mnemonicAccountIndex : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\nexport interface ResolveIdentityOptions {\n env: NodeJS.ProcessEnv;\n /** Working directory the `.env` walk starts from. */\n cwd: string;\n /** Stderr line sink for the deprecated-alias warning. */\n warn(line: string): void;\n /** Bound the `.env` walk-up (tests only). */\n dotenvStopDir?: string;\n}\n\n/** The resolved source before any derivation (dependency-free). */\ninterface MnemonicSource {\n mnemonic: string;\n source: IdentitySourceKind;\n sourceLabel: string;\n}\n\nfunction resolveMnemonicSource(\n options: ResolveIdentityOptions,\n file: ClientConfigIdentityFields,\n configPath: string\n): MnemonicSource | { keystorePath: string } {\n const { env } = options;\n\n const rigEnv = env['RIG_MNEMONIC']?.trim();\n if (rigEnv) {\n return { mnemonic: rigEnv, source: 'env', sourceLabel: 'RIG_MNEMONIC env' };\n }\n\n const aliasEnv = env['TOON_CLIENT_MNEMONIC']?.trim();\n if (aliasEnv) {\n options.warn(\n 'rig: TOON_CLIENT_MNEMONIC is deprecated — rename the variable to RIG_MNEMONIC'\n );\n return {\n mnemonic: aliasEnv,\n source: 'env-alias',\n sourceLabel: 'TOON_CLIENT_MNEMONIC env (deprecated alias)',\n };\n }\n\n const dotenv = findDotenvMnemonic(options.cwd, options.dotenvStopDir);\n if (dotenv) {\n return {\n mnemonic: dotenv.mnemonic,\n source: 'dotenv',\n sourceLabel: dotenv.path,\n };\n }\n\n if (typeof file.keystorePath === 'string' && file.keystorePath !== '') {\n return { keystorePath: file.keystorePath };\n }\n if (typeof file.mnemonic === 'string' && file.mnemonic.trim() !== '') {\n return {\n mnemonic: file.mnemonic.trim(),\n source: 'config',\n sourceLabel: configPath,\n };\n }\n throw new MissingIdentityError(configPath);\n}\n\n/**\n * Load the client key helpers. `@toon-protocol/client` is a regular runtime\n * dependency (#259); the import stays dynamic purely so commands that fail\n * earlier never pay its startup cost.\n */\nasync function loadClientKeys(): Promise<{\n loadKeystore(path: string, password: string): string;\n deriveNostrKeyFromMnemonic(\n mnemonic: string,\n accountIndex?: number\n ): { secretKey: Uint8Array; pubkey: string };\n}> {\n return await import('@toon-protocol/client');\n}\n\n/**\n * Resolve the CLI signing identity along the precedence chain and derive its\n * Nostr pubkey. Throws {@link MissingIdentityError} (with the full\n * three-option remediation) when nothing on the chain yields a phrase.\n */\nexport async function resolveIdentity(\n options: ResolveIdentityOptions\n): Promise<ResolvedIdentity> {\n const configPath = clientConfigPath(options.env);\n const file = readClientConfigFile(configPath);\n const picked = resolveMnemonicSource(options, file, configPath);\n\n const keys = await loadClientKeys();\n let source: MnemonicSource;\n if ('keystorePath' in picked) {\n const password =\n options.env['TOON_CLIENT_KEYSTORE_PASSWORD'] ??\n (file.keystoreAutoPassword === true ? DEFAULT_KEYSTORE_PASSWORD : undefined);\n if (!password) {\n throw new Error(\n `keystorePath is set in ${configPath} but TOON_CLIENT_KEYSTORE_PASSWORD is not provided`\n );\n }\n source = {\n mnemonic: keys.loadKeystore(picked.keystorePath, password),\n source: 'keystore',\n sourceLabel: picked.keystorePath,\n };\n } else {\n source = picked;\n }\n\n const accountIndex = resolveAccountIndex(options.env, source.source, file);\n const { pubkey } = keys.deriveNostrKeyFromMnemonic(source.mnemonic, accountIndex);\n return { ...source, accountIndex, pubkey };\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 * Schema tag mixed into every cache key. A rig release that changes WHAT the\n * resolution produces for the same inputs (not just its values) must bump\n * this: entries written by the previous release then miss instead of\n * shadowing the new behaviour for a whole TTL. Bumped to 2 when the default\n * resolution started placing a BTP uplink alongside the proxy — a cached\n * v1 topology has no `btpUrl`, so paid writes would have kept 401ing for 15\n * minutes after the upgrade.\n */\nexport const TOPOLOGY_SCHEMA_VERSION = 3;\n\n/**\n * Stable cache key: schema tag + relay-origin + identity pubkey + the\n * explicit-config fingerprint. Hashed so the key is filename/JSON-safe and\n * the mnemonic-side 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(\n `v${TOPOLOGY_SCHEMA_VERSION}\\n${args.relayUrl}\\n${args.identity}\\n` +\n args.fingerprint\n )\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","/**\n * Persistence for rig's auto-deployed per-pair Mina `PaymentChannel` zkApps.\n *\n * The Mina zkApp is single-pair (one deployment per client↔connector pair)\n * and costs ~1.1 MINA to deploy, so the zkApp's PRIVATE KEY must survive the\n * process: losing it strands the deployment (and any future co-signed zkApp\n * tx). Records live in `<TOON_CLIENT_HOME>/keys/rig-mina-zkapps.json`\n * (mode 0600 — it holds private keys), keyed `identity|chain`, next to the\n * hand-managed `mina-zkapp-*-deploy.json` records operators already keep.\n *\n * A stale record self-heals: the open path's ownership check (on-chain\n * channelHash vs the current pair) simply stops matching and a fresh zkApp is\n * deployed over a new record.\n */\n\nimport {\n chmodSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/** One auto-deployed zkApp record (everything needed to reuse or audit it). */\nexport interface RigMinaZkAppRecord {\n /** The rig identity (nostr pubkey hex) that deployed it. */\n identity: string;\n /** The settlement chain the deployment serves (e.g. `mina:devnet`). */\n chain: string;\n /** Deployed zkApp B62 address (== the channel id). */\n zkAppAddress: string;\n /** The zkApp's `EK…` base58 private key. */\n zkAppPrivateKey: string;\n /** The fee payer (client Mina B62) that funded the deployment. */\n feePayer?: string;\n /** Deploy tx hash, when the send surfaced one. */\n deployTxHash?: string;\n /** Verification-key hash of the compiled contract that was deployed. */\n vkHash?: string;\n /** ISO timestamp of the deployment. */\n deployedAt: string;\n /** Provenance note (package/build the contract came from). */\n source?: string;\n}\n\nconst FILENAME = 'rig-mina-zkapps.json';\n\n/** `identity|chain` — one live deployment per identity per chain. */\nfunction keyFor(identity: string, chain: string): string {\n return `${identity}|${chain}`;\n}\n\nexport class MinaZkAppStore {\n constructor(readonly filePath: string) {}\n\n /** The store under a client home dir: `<dir>/keys/rig-mina-zkapps.json`. */\n static forHome(dir: string): MinaZkAppStore {\n return new MinaZkAppStore(join(dir, 'keys', FILENAME));\n }\n\n private readAll(): Record<string, RigMinaZkAppRecord> {\n try {\n return JSON.parse(readFileSync(this.filePath, 'utf8')) as Record<\n string,\n RigMinaZkAppRecord\n >;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `failed to read the Mina zkApp store at ${this.filePath}: ` +\n `${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n /** The recorded deployment for this identity/chain, if any. */\n lookup(identity: string, chain: string): RigMinaZkAppRecord | undefined {\n return this.readAll()[keyFor(identity, chain)];\n }\n\n /** Read-merge-write; the file holds private keys → 0600. */\n save(record: RigMinaZkAppRecord): void {\n const all = this.readAll();\n all[keyFor(record.identity, record.chain)] = record;\n mkdirSync(dirname(this.filePath), { recursive: true });\n writeFileSync(this.filePath, `${JSON.stringify(all, null, 2)}\\n`, {\n encoding: 'utf8',\n mode: 0o600,\n });\n // writeFileSync's `mode` only applies on CREATE — re-tighten every save.\n chmodSync(this.filePath, 0o600);\n }\n}\n"],"mappings":";AAuCA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AA+BvB,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YAAY,YAAoB;AAC9B;AAAA,MACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAOqC,UAAU;AAAA,IAEjD;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAaO,IAAM,4BAA4B;AAGlC,SAAS,eAAe,KAAgC;AAC7D,SAAO,IAAI,kBAAkB,KAAK,KAAK,QAAQ,GAAG,cAAc;AAClE;AAGO,SAAS,iBAAiB,KAAgC;AAC/D,SAAO,KAAK,eAAe,GAAG,GAAG,aAAa;AAChD;AAIO,SAAS,mBAAmB,KAAgC;AACjE,SAAO,KAAK,eAAe,GAAG,GAAG,eAAe;AAClD;AAUO,SAAS,qBAAqB,MAA0C;AAC7E,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM,IAAI;AAAA,MACR,mCAAmC,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9F;AAAA,EACF;AACF;AAUO,SAAS,2BACd,KACA,cACA,cACQ;AACR,QAAM,aAAa,iBAAiB,GAAG;AACvC,YAAU,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,QAAM,WAAW,qBAAqB,UAAU;AAChD,QAAM,SAAkC;AAAA,IACtC,GAAG;AAAA,IACH;AAAA,IACA,GAAI,eACA,EAAE,sBAAsB,KAAK;AAAA;AAAA;AAAA,MAG7B,EAAE,sBAAsB,MAAM;AAAA;AAAA,EACpC;AACA,gBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM;AAAA,IAChE,UAAU;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AACD,SAAO;AACT;AAMA,IAAM,iBAAiB;AAShB,SAAS,oBAAoB,SAAqC;AACvE,MAAI;AACJ,aAAW,QAAQ,QAAQ,MAAM,OAAO,GAAG;AACzC,UAAM,QAAQ,eAAe,KAAK,IAAI;AACtC,QAAI,CAAC,MAAO;AACZ,QAAI,SAAS,MAAM,CAAC,KAAK,IAAI,KAAK;AAClC,UAAM,QAAQ,MAAM,CAAC;AACrB,SAAK,UAAU,OAAO,UAAU,QAAQ,MAAM,UAAU,KAAK,MAAM,SAAS,KAAK,GAAG;AAClF,cAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,IAC3B,OAAO;AAEL,YAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,UAAI,SAAS,GAAI,SAAQ,MAAM,MAAM,GAAG,IAAI;AAC5C,cAAQ,MAAM,KAAK;AAAA,IACrB;AACA,QAAI,UAAU,GAAI,SAAQ;AAAA,EAC5B;AACA,SAAO;AACT;AAOO,SAAS,mBACd,UACA,SACgD;AAChD,MAAI,MAAM;AACV,aAAS;AACP,UAAM,YAAY,KAAK,KAAK,MAAM;AAClC,QAAI,WAAW,SAAS,GAAG;AACzB,UAAI;AACF,cAAM,WAAW,oBAAoB,aAAa,WAAW,MAAM,CAAC;AACpE,YAAI,aAAa,OAAW,QAAO,EAAE,MAAM,WAAW,SAAS;AAAA,MACjE,QAAQ;AAAA,MAER;AAAA,IACF;AACA,QAAI,YAAY,UAAa,QAAQ,QAAS,QAAO;AACrD,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAUA,IAAM,2BAA4D,oBAAI,IAAI;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAUM,SAAS,oBACd,KACA,QACA,MACQ;AACR,QAAM,MAAM,IAAI,mBAAmB,GAAG,KAAK;AAC3C,MAAI,KAAK;AACP,UAAM,SAAS,OAAO,GAAG;AACzB,QAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,GAAG;AAC3C,YAAM,IAAI;AAAA,QACR,yDAAyD,KAAK,UAAU,GAAG,CAAC;AAAA,MAC9E;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,MAAI,yBAAyB,IAAI,MAAM,EAAG,QAAO;AACjD,SAAO,OAAO,KAAK,yBAAyB,WAAW,KAAK,uBAAuB;AACrF;AAuBA,SAAS,sBACP,SACA,MACA,YAC2C;AAC3C,QAAM,EAAE,IAAI,IAAI;AAEhB,QAAM,SAAS,IAAI,cAAc,GAAG,KAAK;AACzC,MAAI,QAAQ;AACV,WAAO,EAAE,UAAU,QAAQ,QAAQ,OAAO,aAAa,mBAAmB;AAAA,EAC5E;AAEA,QAAM,WAAW,IAAI,sBAAsB,GAAG,KAAK;AACnD,MAAI,UAAU;AACZ,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,aAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,SAAS,mBAAmB,QAAQ,KAAK,QAAQ,aAAa;AACpE,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,QAAQ;AAAA,MACR,aAAa,OAAO;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,iBAAiB,YAAY,KAAK,iBAAiB,IAAI;AACrE,WAAO,EAAE,cAAc,KAAK,aAAa;AAAA,EAC3C;AACA,MAAI,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,MAAM,IAAI;AACpE,WAAO;AAAA,MACL,UAAU,KAAK,SAAS,KAAK;AAAA,MAC7B,QAAQ;AAAA,MACR,aAAa;AAAA,IACf;AAAA,EACF;AACA,QAAM,IAAI,qBAAqB,UAAU;AAC3C;AAOA,eAAe,iBAMZ;AACD,SAAO,MAAM,OAAO,uBAAuB;AAC7C;AAOA,eAAsB,gBACpB,SAC2B;AAC3B,QAAM,aAAa,iBAAiB,QAAQ,GAAG;AAC/C,QAAM,OAAO,qBAAqB,UAAU;AAC5C,QAAM,SAAS,sBAAsB,SAAS,MAAM,UAAU;AAE9D,QAAM,OAAO,MAAM,eAAe;AAClC,MAAI;AACJ,MAAI,kBAAkB,QAAQ;AAC5B,UAAM,WACJ,QAAQ,IAAI,+BAA+B,MAC1C,KAAK,yBAAyB,OAAO,4BAA4B;AACpE,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR,0BAA0B,UAAU;AAAA,MACtC;AAAA,IACF;AACA,aAAS;AAAA,MACP,UAAU,KAAK,aAAa,OAAO,cAAc,QAAQ;AAAA,MACzD,QAAQ;AAAA,MACR,aAAa,OAAO;AAAA,IACtB;AAAA,EACF,OAAO;AACL,aAAS;AAAA,EACX;AAEA,QAAM,eAAe,oBAAoB,QAAQ,KAAK,OAAO,QAAQ,IAAI;AACzE,QAAM,EAAE,OAAO,IAAI,KAAK,2BAA2B,OAAO,UAAU,YAAY;AAChF,SAAO,EAAE,GAAG,QAAQ,cAAc,OAAO;AAC3C;;;ACvWA,SAAS,kBAAkB;AAC3B,SAAS,aAAAA,YAAW,gBAAAC,eAAc,iBAAAC,gBAAe,kBAAkB;AACnE,SAAS,WAAAC,gBAAe;AAGjB,IAAM,0BAA0B;AAGhC,IAAM,0BAA0B,KAAK,KAAK;AAG1C,IAAM,mBAAmB;AAGzB,SAAS,mBAAmB,KAAgC;AACjE,QAAM,MAAM,IAAI,gBAAgB;AAChC,MAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;AAC5C,QAAM,SAAS,OAAO,GAAG;AACzB,SAAO,OAAO,SAAS,MAAM,KAAK,UAAU,IACxC,SACA;AACN;AAWO,IAAM,0BAA0B;AAOhC,SAAS,iBAAiB,MAItB;AACT,SAAO,WAAW,QAAQ,EACvB;AAAA,IACC,IAAI,uBAAuB;AAAA,EAAK,KAAK,QAAQ;AAAA,EAAK,KAAK,QAAQ;AAAA,IAC7D,KAAK;AAAA,EACT,EACC,OAAO,KAAK;AACjB;AASO,SAAS,0BACd,KACA,MACQ;AACR,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,SAAkC,CAAC;AACzC,aAAW,OAAO,SAAS;AACzB,QAAI,IAAI,GAAG,MAAM,OAAW,QAAO,OAAO,GAAG,EAAE,IAAI,IAAI,GAAG;AAAA,EAC5D;AACA,aAAW,OAAO,UAAU;AAC1B,QAAI,KAAK,GAAG,MAAM,OAAW,QAAO,QAAQ,GAAG,EAAE,IAAI,KAAK,GAAG;AAAA,EAC/D;AAEA,SAAO,KAAK,UAAU,MAAM;AAC9B;AA8BO,IAAM,gBAAN,MAAuB;AAAA,EACnB;AAAA,EACA;AAAA,EACQ;AAAA,EACA;AAAA,EAEjB,YAAY,SAAkC;AAC5C,SAAK,OAAO,QAAQ;AACpB,SAAK,QAAQ,QAAQ;AACrB,SAAK,WAAW,QAAQ;AACxB,SAAK,MAAM,QAAQ,OAAO,KAAK;AAAA,EACjC;AAAA;AAAA,EAGA,IAAI,WAAoB;AACtB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,KAAyD;AAC5D,QAAI,KAAK,SAAU,QAAO;AAC1B,UAAM,QAAQ,KAAK,SAAS,EAAE,QAAQ,GAAG;AACzC,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;AAC1C,QAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,QAAO;AACvC,UAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B,QAAI,QAAQ,KAAK,QAAQ,KAAK,MAAO,QAAO;AAC5C,QAAI,CAAC,KAAK,SAAS,MAAM,QAAQ,EAAG,QAAO;AAC3C,WAAO,EAAE,UAAU,MAAM,UAAU,MAAM;AAAA,EAC3C;AAAA;AAAA,EAGA,MAAM,KAAa,UAAmB;AACpC,QAAI,KAAK,SAAU;AACnB,QAAI;AACF,YAAM,OAAO,KAAK,SAAS;AAC3B,YAAM,QAAQ,KAAK,IAAI;AACvB,YAAM,QAAQ,OAAO;AAAA,QACnB,OAAO,QAAQ,KAAK,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM;AACjD,gBAAM,KAAK,KAAK,MAAM,MAAM,QAAQ;AACpC,iBAAO,OAAO,SAAS,EAAE,KAAK,QAAQ,MAAM,KAAK;AAAA,QACnD,CAAC;AAAA,MACH;AACA,YAAM,GAAG,IAAI;AAAA,QACX,UAAU,IAAI,KAAK,KAAK,EAAE,YAAY;AAAA,QACtC;AAAA,MACF;AACA,MAAAH,WAAUG,SAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,MAAAD;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,UACH,EAAE,SAAS,GAAG,SAAS,MAAM;AAAA,UAC7B;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,MAAM,IAAM;AAAA,MAChB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGA,WAAW,KAAmB;AAC5B,QAAI;AACF,YAAM,OAAO,KAAK,SAAS;AAC3B,UAAI,EAAE,OAAO,KAAK,SAAU;AAC5B,YAAM,YAAY,OAAO;AAAA,QACvB,OAAO,QAAQ,KAAK,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,GAAG;AAAA,MACxD;AACA,UAAI,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACvC,mBAAW,KAAK,IAAI;AACpB;AAAA,MACF;AACA,MAAAA;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,UACH,EAAE,SAAS,GAAG,SAAS,UAAU;AAAA,UACjC;AAAA,UACA;AAAA,QACF;AAAA,QACA,EAAE,MAAM,IAAM;AAAA,MAChB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,WAAsB;AAC5B,UAAM,QAAmB,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE;AACnD,QAAI;AACJ,QAAI;AACF,YAAMD,cAAa,KAAK,MAAM,MAAM;AAAA,IACtC,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UACE,OAAO,WAAW,YAClB,WAAW,QACX,OAAO,YAAY,KACnB,OAAO,OAAO,YAAY,YAC1B,OAAO,YAAY,MACnB;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC9PA;AAAA,EACE;AAAA,EACA,aAAAG;AAAA,EACA,gBAAAC;AAAA,EACA,iBAAAC;AAAA,OACK;AACP,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAwB9B,IAAM,WAAW;AAGjB,SAAS,OAAO,UAAkB,OAAuB;AACvD,SAAO,GAAG,QAAQ,IAAI,KAAK;AAC7B;AAEO,IAAM,iBAAN,MAAM,gBAAe;AAAA,EAC1B,YAAqB,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAAnB;AAAA;AAAA,EAGrB,OAAO,QAAQ,KAA6B;AAC1C,WAAO,IAAI,gBAAeA,MAAK,KAAK,QAAQ,QAAQ,CAAC;AAAA,EACvD;AAAA,EAEQ,UAA8C;AACpD,QAAI;AACF,aAAO,KAAK,MAAMH,cAAa,KAAK,UAAU,MAAM,CAAC;AAAA,IAIvD,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM,IAAI;AAAA,QACR,0CAA0C,KAAK,QAAQ,KAClD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,UAAkB,OAA+C;AACtE,WAAO,KAAK,QAAQ,EAAE,OAAO,UAAU,KAAK,CAAC;AAAA,EAC/C;AAAA;AAAA,EAGA,KAAK,QAAkC;AACrC,UAAM,MAAM,KAAK,QAAQ;AACzB,QAAI,OAAO,OAAO,UAAU,OAAO,KAAK,CAAC,IAAI;AAC7C,IAAAD,WAAUG,SAAQ,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,IAAAD,eAAc,KAAK,UAAU,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA,GAAM;AAAA,MAChE,UAAU;AAAA,MACV,MAAM;AAAA,IACR,CAAC;AAED,cAAU,KAAK,UAAU,GAAK;AAAA,EAChC;AACF;","names":["mkdirSync","readFileSync","writeFileSync","dirname","mkdirSync","readFileSync","writeFileSync","dirname","join"]}
|