@rozoai/checkout 0.1.6 → 0.1.10

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.
@@ -1759,10 +1759,10 @@ var init_u64 = __esm({
1759
1759
 
1760
1760
  // node_modules/@noble/hashes/esm/cryptoNode.js
1761
1761
  import * as nc from "node:crypto";
1762
- var crypto2;
1762
+ var crypto3;
1763
1763
  var init_cryptoNode = __esm({
1764
1764
  "node_modules/@noble/hashes/esm/cryptoNode.js"() {
1765
- crypto2 = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
1765
+ crypto3 = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0;
1766
1766
  }
1767
1767
  });
1768
1768
 
@@ -1857,11 +1857,11 @@ function createHasher(hashCons) {
1857
1857
  return hashC;
1858
1858
  }
1859
1859
  function randomBytes(bytesLength = 32) {
1860
- if (crypto2 && typeof crypto2.getRandomValues === "function") {
1861
- return crypto2.getRandomValues(new Uint8Array(bytesLength));
1860
+ if (crypto3 && typeof crypto3.getRandomValues === "function") {
1861
+ return crypto3.getRandomValues(new Uint8Array(bytesLength));
1862
1862
  }
1863
- if (crypto2 && typeof crypto2.randomBytes === "function") {
1864
- return Uint8Array.from(crypto2.randomBytes(bytesLength));
1863
+ if (crypto3 && typeof crypto3.randomBytes === "function") {
1864
+ return Uint8Array.from(crypto3.randomBytes(bytesLength));
1865
1865
  }
1866
1866
  throw new Error("crypto.getRandomValues must be defined");
1867
1867
  }
@@ -10214,43 +10214,148 @@ function chainFamily(chainId) {
10214
10214
  // scripts/src/lib/keys.mjs
10215
10215
  import fs from "node:fs";
10216
10216
  import path from "node:path";
10217
- import { execFileSync } from "node:child_process";
10218
- var ENV_FILE_RE = /^\.env(\..+)?$/;
10219
- var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
10220
- function insideGitWorkTree(dir, whatFor) {
10217
+ import crypto2 from "node:crypto";
10218
+ function findGitRepo(dir) {
10219
+ for (const v of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"]) {
10220
+ if (process.env[v]) {
10221
+ throw new SkillError(
10222
+ "TRACKED_DOTENV_UNVERIFIABLE",
10223
+ `${v} is set, so the repository layout cannot be resolved by reading the filesystem alone. Unset it (or run from a plain checkout) before using hot-wallet keys.`
10224
+ );
10225
+ }
10226
+ }
10227
+ let cur = path.resolve(dir);
10228
+ for (; ; ) {
10229
+ const dotGit = path.join(cur, ".git");
10230
+ let st = null;
10231
+ try {
10232
+ st = fs.statSync(dotGit);
10233
+ } catch (err) {
10234
+ if (err?.code !== "ENOENT" && err?.code !== "ENOTDIR") {
10235
+ throw new SkillError(
10236
+ "TRACKED_DOTENV_UNVERIFIABLE",
10237
+ `A .git entry here could not be inspected (${err?.code || "error"}), so tracked status cannot be proved. Refusing.`
10238
+ );
10239
+ }
10240
+ st = null;
10241
+ }
10242
+ if (st) {
10243
+ if (st.isDirectory()) return { root: cur, gitDir: dotGit };
10244
+ let text;
10245
+ try {
10246
+ text = fs.readFileSync(dotGit, "utf8");
10247
+ } catch {
10248
+ throw new SkillError(
10249
+ "TRACKED_DOTENV_UNVERIFIABLE",
10250
+ "A .git entry exists here but could not be read, so tracked status cannot be proved. Refusing."
10251
+ );
10252
+ }
10253
+ const m = /^gitdir:\s*(.+)\s*$/m.exec(text);
10254
+ if (!m) {
10255
+ throw new SkillError(
10256
+ "TRACKED_DOTENV_UNVERIFIABLE",
10257
+ "A .git file exists here but is not a recognised gitdir pointer. Refusing."
10258
+ );
10259
+ }
10260
+ return { root: cur, gitDir: path.resolve(cur, m[1]) };
10261
+ }
10262
+ const parent = path.dirname(cur);
10263
+ if (parent === cur) return null;
10264
+ cur = parent;
10265
+ }
10266
+ }
10267
+ function trackedPathsFromIndex(buf, { hashLen = 20 } = {}) {
10268
+ const fail2 = (why) => new SkillError(
10269
+ "TRACKED_DOTENV_UNVERIFIABLE",
10270
+ `The git index could not be interpreted (${why}), so tracked status cannot be proved. Refusing.`
10271
+ );
10272
+ if (buf.length < 12 + hashLen || buf.toString("latin1", 0, 4) !== "DIRC") throw fail2("bad header");
10273
+ const algo = hashLen === 32 ? "sha256" : "sha1";
10274
+ const expected = buf.subarray(buf.length - hashLen);
10275
+ const actual = crypto2.createHash(algo).update(buf.subarray(0, buf.length - hashLen)).digest();
10276
+ if (!actual.equals(expected)) throw fail2(`${algo} checksum mismatch`);
10277
+ const version4 = buf.readUInt32BE(4);
10278
+ if (version4 !== 2 && version4 !== 3) throw fail2(`unsupported index version ${version4}`);
10279
+ const count = buf.readUInt32BE(8);
10280
+ const paths = /* @__PURE__ */ new Set();
10281
+ const fixed = 40 + hashLen + 2;
10282
+ let off = 12;
10283
+ for (let i = 0; i < count; i++) {
10284
+ const entryStart = off;
10285
+ if (off + fixed > buf.length - hashLen) throw fail2("truncated entry");
10286
+ const flags = buf.readUInt16BE(off + fixed - 2);
10287
+ let nameOff = off + fixed;
10288
+ if (flags & 16384) {
10289
+ if (version4 < 3) throw fail2("extended flags in v2 index");
10290
+ nameOff += 2;
10291
+ }
10292
+ const nameLen = flags & 4095;
10293
+ let end;
10294
+ if (nameLen < 4095) {
10295
+ end = nameOff + nameLen;
10296
+ if (end > buf.length - hashLen) throw fail2("truncated path");
10297
+ } else {
10298
+ end = buf.indexOf(0, nameOff);
10299
+ if (end === -1 || end > buf.length - hashLen) throw fail2("unterminated path");
10300
+ }
10301
+ paths.add(buf.toString("utf8", nameOff, end));
10302
+ const entryLen = end - entryStart;
10303
+ off = entryStart + (Math.floor(entryLen / 8) + 1) * 8;
10304
+ }
10305
+ while (off < buf.length - hashLen) {
10306
+ if (off + 8 > buf.length - hashLen) throw fail2("truncated extension header");
10307
+ const extName = buf.toString("latin1", off, off + 4);
10308
+ const extSize = buf.readUInt32BE(off + 4);
10309
+ const first = extName.charCodeAt(0);
10310
+ if (first >= 97 && first <= 122) {
10311
+ throw fail2(`mandatory index extension "${extName}" (e.g. core.splitIndex) is not supported`);
10312
+ }
10313
+ off += 8 + extSize;
10314
+ if (off > buf.length - hashLen) throw fail2("truncated extension");
10315
+ }
10316
+ return paths;
10317
+ }
10318
+ function repoHashLen(gitDir) {
10319
+ let configDir = gitDir;
10320
+ try {
10321
+ const common = fs.readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
10322
+ if (common) configDir = path.resolve(gitDir, common);
10323
+ } catch {
10324
+ }
10325
+ try {
10326
+ const cfg = fs.readFileSync(path.join(configDir, "config"), "utf8");
10327
+ if (/^\s*objectformat\s*=\s*sha256\s*$/im.test(cfg)) return 32;
10328
+ } catch {
10329
+ }
10330
+ return 20;
10331
+ }
10332
+ function trackedAmong(repo, files) {
10333
+ const indexPath = path.join(repo.gitDir, "index");
10334
+ let buf;
10221
10335
  try {
10222
- return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
10223
- cwd: dir,
10224
- stdio: ["ignore", "pipe", "pipe"],
10225
- encoding: "utf8"
10226
- }).trim() === "true";
10336
+ buf = fs.readFileSync(indexPath);
10227
10337
  } catch (err) {
10228
- const stderr = String(err?.stderr || "");
10229
- if (/not a git repository|does not appear to be a git repository/i.test(stderr)) return false;
10338
+ if (err?.code === "ENOENT") return [];
10230
10339
  throw new SkillError(
10231
10340
  "TRACKED_DOTENV_UNVERIFIABLE",
10232
- `git could not be consulted (${err?.code || "unknown error"}), so it cannot be proved that ${whatFor} is untracked. Refusing rather than assuming it is safe.`
10341
+ "The git index exists but could not be read, so tracked status cannot be proved. Refusing."
10233
10342
  );
10234
10343
  }
10344
+ const tracked = trackedPathsFromIndex(buf, { hashLen: repoHashLen(repo.gitDir) });
10345
+ return files.filter((f) => {
10346
+ const rel = path.relative(repo.root, path.resolve(f)).split(path.sep).join("/");
10347
+ return tracked.has(rel);
10348
+ });
10235
10349
  }
10350
+ var ENV_FILE_RE = /^\.env(\..+)?$/;
10351
+ var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
10236
10352
  function assertNotTrackedByGit(file) {
10237
10353
  const dir = path.dirname(path.resolve(file));
10238
- const base = path.basename(file);
10239
- if (!insideGitWorkTree(dir, "this key file")) return { checked: true, tracked: false };
10240
- let out;
10241
- try {
10242
- out = execFileSync("git", ["ls-files", "-z", "--", base], {
10243
- cwd: dir,
10244
- stdio: ["ignore", "pipe", "ignore"],
10245
- encoding: "utf8"
10246
- });
10247
- } catch {
10248
- throw new SkillError(
10249
- "TRACKED_DOTENV_UNVERIFIABLE",
10250
- "git could not report whether this key file is tracked. Refusing to use it."
10251
- );
10252
- }
10253
- if (out.split("\0").filter(Boolean).length) {
10354
+ const repo = findGitRepo(dir);
10355
+ if (!repo) return { checked: true, tracked: false };
10356
+ const hits = trackedAmong(repo, [file]);
10357
+ if (hits.length) {
10358
+ const base = path.basename(file);
10254
10359
  throw new SkillError(
10255
10360
  "TRACKED_KEYFILE",
10256
10361
  `${base} is tracked by git. A committed key is one push from being public \u2014 untrack it (git rm --cached ${base}) and gitignore it before using it to sign.`
@@ -10269,37 +10374,11 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
10269
10374
  );
10270
10375
  }
10271
10376
  if (candidates.length === 0) return { checked: true, tracked: false, candidates: [] };
10272
- let insideRepo;
10273
- try {
10274
- insideRepo = execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
10275
- cwd,
10276
- stdio: ["ignore", "pipe", "pipe"],
10277
- encoding: "utf8"
10278
- }).trim() === "true";
10279
- } catch (err) {
10280
- const stderr = String(err?.stderr || "");
10281
- const notARepo = /not a git repository|does not appear to be a git repository/i.test(stderr);
10282
- if (notARepo) return { checked: true, tracked: false, candidates };
10283
- throw new SkillError(
10284
- "TRACKED_DOTENV_UNVERIFIABLE",
10285
- `Found ${candidates.length} .env file(s) here, but git could not be consulted (${err?.code || "unknown error"}), so it cannot be proved they are untracked. Refusing to use hot-wallet keys in this directory.`
10286
- );
10287
- }
10288
- if (!insideRepo) return { checked: true, tracked: false, candidates };
10289
- let out;
10290
- try {
10291
- out = execFileSync("git", ["ls-files", "-z", "--", ...candidates], {
10292
- cwd,
10293
- stdio: ["ignore", "pipe", "ignore"],
10294
- encoding: "utf8"
10295
- });
10296
- } catch {
10297
- throw new SkillError(
10298
- "TRACKED_DOTENV_UNVERIFIABLE",
10299
- "git could not report whether the .env file(s) in this directory are tracked. Refusing to use hot-wallet keys rather than assuming they are safe."
10300
- );
10301
- }
10302
- const tracked = out.split("\0").filter(Boolean);
10377
+ const repo = findGitRepo(cwd);
10378
+ if (!repo) return { checked: true, tracked: false, candidates };
10379
+ const tracked = trackedAmong(repo, candidates.map((f) => path.join(cwd, f))).map(
10380
+ (f) => path.basename(f)
10381
+ );
10303
10382
  if (tracked.length) {
10304
10383
  throw new SkillError(
10305
10384
  "TRACKED_DOTENV",
@@ -10310,10 +10389,10 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
10310
10389
  }
10311
10390
 
10312
10391
  // scripts/src/lib/key-source.mjs
10313
- import fs2 from "node:fs";
10314
- import path2 from "node:path";
10315
- import os from "node:os";
10316
- import crypto3 from "node:crypto";
10392
+ import fs3 from "node:fs";
10393
+ import path3 from "node:path";
10394
+ import os2 from "node:os";
10395
+ import crypto4 from "node:crypto";
10317
10396
 
10318
10397
  // node_modules/viem/_esm/utils/getAction.js
10319
10398
  function getAction(client, actionFn, name) {
@@ -21278,18 +21357,121 @@ function http(url, config = {}) {
21278
21357
  init_encodeFunctionData();
21279
21358
  init_keccak256();
21280
21359
 
21360
+ // scripts/src/lib/dotenv.mjs
21361
+ import fs2 from "node:fs";
21362
+ import os from "node:os";
21363
+ import path2 from "node:path";
21364
+ var ALLOWED_KEYS = [
21365
+ "ROZO_CHECKOUT_EVM_KEY",
21366
+ "ROZO_CHECKOUT_SOL_KEY",
21367
+ "ROZO_CHECKOUT_EVM_KEYSTORE",
21368
+ "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
21369
+ ];
21370
+ var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
21371
+ function isAllowedKey(key) {
21372
+ return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
21373
+ }
21374
+ function parseDotenv(text) {
21375
+ const vars = {};
21376
+ const lines = String(text).split(/\r?\n/);
21377
+ lines.forEach((rawLine, i) => {
21378
+ const lineNo = i + 1;
21379
+ const line = rawLine.trim();
21380
+ if (!line || line.startsWith("#")) return;
21381
+ const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
21382
+ const eq = withoutExport.indexOf("=");
21383
+ if (eq === -1) {
21384
+ throw new SkillError(
21385
+ "BAD_ENV_FILE",
21386
+ `Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
21387
+ );
21388
+ }
21389
+ const key = withoutExport.slice(0, eq).trim();
21390
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
21391
+ throw new SkillError(
21392
+ "BAD_ENV_FILE",
21393
+ `Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
21394
+ );
21395
+ }
21396
+ let value = withoutExport.slice(eq + 1).trim();
21397
+ if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
21398
+ value = value.slice(1, -1);
21399
+ }
21400
+ vars[key] = value;
21401
+ });
21402
+ return vars;
21403
+ }
21404
+ function filterAllowed(vars) {
21405
+ const out = {};
21406
+ for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out[k] = v;
21407
+ return out;
21408
+ }
21409
+ function envFileCandidates({ cwd = process.cwd(), home = os.homedir() } = {}) {
21410
+ const candidates = [path2.join(cwd, ".env")];
21411
+ const owned = path2.join(home, ".rozo-checkout", ".env");
21412
+ if (owned !== candidates[0]) candidates.push(owned);
21413
+ return candidates;
21414
+ }
21415
+ function resolveEnvFile({ file, cwd = process.cwd(), home = os.homedir() } = {}) {
21416
+ if (file) {
21417
+ const p = path2.resolve(file);
21418
+ if (!fs2.existsSync(p)) {
21419
+ throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
21420
+ }
21421
+ return p;
21422
+ }
21423
+ return envFileCandidates({ cwd, home }).find((p) => fs2.existsSync(p)) ?? null;
21424
+ }
21425
+ function applyDotenv({
21426
+ file,
21427
+ cwd = process.cwd(),
21428
+ home = os.homedir(),
21429
+ env = process.env
21430
+ } = {}) {
21431
+ const target = resolveEnvFile({ file, cwd, home });
21432
+ if (!target) return null;
21433
+ let stat;
21434
+ try {
21435
+ stat = fs2.statSync(target);
21436
+ } catch (err) {
21437
+ throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
21438
+ }
21439
+ if (process.platform !== "win32" && stat.mode & 63) {
21440
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
21441
+ throw new SkillError(
21442
+ "ENV_FILE_PERMISSIONS",
21443
+ `That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
21444
+ );
21445
+ }
21446
+ assertNotTrackedByGit(target);
21447
+ const parsed = parseDotenv(fs2.readFileSync(target, "utf8"));
21448
+ const allowed = filterAllowed(parsed);
21449
+ const applied = [];
21450
+ for (const [k, v] of Object.entries(allowed)) {
21451
+ const existing = env[k];
21452
+ if (existing !== void 0 && String(existing).trim() !== "") continue;
21453
+ env[k] = v;
21454
+ applied.push(k);
21455
+ }
21456
+ return {
21457
+ path: target,
21458
+ applied,
21459
+ ignored: Object.keys(parsed).length - Object.keys(allowed).length
21460
+ };
21461
+ }
21462
+
21281
21463
  // scripts/src/lib/key-source.mjs
21282
21464
  var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
21283
21465
  var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
21284
21466
  var EVM_KEYSTORE_ENV = "ROZO_CHECKOUT_EVM_KEYSTORE";
21285
21467
  var KEYSTORE_PASSPHRASE_ENV = "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE";
21286
21468
  function defaultSolanaKeypairPath() {
21287
- return path2.join(os.homedir(), ".config", "solana", "id.json");
21469
+ return path3.join(os2.homedir(), ".config", "solana", "id.json");
21288
21470
  }
21289
21471
  function assertKeyfileSafe(file) {
21290
21472
  let stat;
21291
21473
  try {
21292
- stat = fs2.statSync(file);
21474
+ stat = fs3.statSync(file);
21293
21475
  } catch (err) {
21294
21476
  throw new SkillError(
21295
21477
  "KEYFILE_UNREADABLE",
@@ -21401,7 +21583,7 @@ function decryptKeystoreV3(keystore, passphrase) {
21401
21583
  if (!n || !r || !p || !dklen || !salt) {
21402
21584
  throw new SkillError("BAD_KEYSTORE", "The keystore scrypt parameters are incomplete.");
21403
21585
  }
21404
- derived = crypto3.scryptSync(pass, Buffer.from(salt, "hex"), dklen, {
21586
+ derived = crypto4.scryptSync(pass, Buffer.from(salt, "hex"), dklen, {
21405
21587
  N: n,
21406
21588
  r,
21407
21589
  p,
@@ -21417,7 +21599,7 @@ function decryptKeystoreV3(keystore, passphrase) {
21417
21599
  if (!iterations || !dklen || !salt) {
21418
21600
  throw new SkillError("BAD_KEYSTORE", "The keystore pbkdf2 parameters are incomplete.");
21419
21601
  }
21420
- derived = crypto3.pbkdf2Sync(pass, Buffer.from(salt, "hex"), iterations, dklen, "sha256");
21602
+ derived = crypto4.pbkdf2Sync(pass, Buffer.from(salt, "hex"), iterations, dklen, "sha256");
21421
21603
  } else {
21422
21604
  throw new SkillError("BAD_KEYSTORE", `Unsupported keystore KDF "${c.kdf}".`);
21423
21605
  }
@@ -21432,7 +21614,7 @@ function decryptKeystoreV3(keystore, passphrase) {
21432
21614
  if (c.cipher !== "aes-128-ctr") {
21433
21615
  throw new SkillError("BAD_KEYSTORE", `Unsupported keystore cipher "${c.cipher}".`);
21434
21616
  }
21435
- const decipher = crypto3.createDecipheriv(
21617
+ const decipher = crypto4.createDecipheriv(
21436
21618
  "aes-128-ctr",
21437
21619
  derived.subarray(0, 16),
21438
21620
  Buffer.from(c.cipherparams.iv, "hex")
@@ -21454,7 +21636,7 @@ function timingSafeEqualHex(a, b) {
21454
21636
  const ab = Buffer.from(String(a).toLowerCase(), "hex");
21455
21637
  const bb = Buffer.from(String(b).toLowerCase(), "hex");
21456
21638
  if (ab.length === 0 || ab.length !== bb.length) return false;
21457
- return crypto3.timingSafeEqual(ab, bb);
21639
+ return crypto4.timingSafeEqual(ab, bb);
21458
21640
  }
21459
21641
  function looksLikeKeystore(text) {
21460
21642
  const t = String(text).trimStart();
@@ -21466,12 +21648,16 @@ function looksLikeKeystore(text) {
21466
21648
  return false;
21467
21649
  }
21468
21650
  }
21469
- function planKeySource({ family, keyfile, env = process.env }) {
21651
+ function searchedSuffix(searched) {
21652
+ if (!searched || !searched.length) return "";
21653
+ return ` Looked for a .env in: ${searched.map(displayPath).join(", ")}.`;
21654
+ }
21655
+ function planKeySource({ family, keyfile, env = process.env, searched }) {
21470
21656
  if (keyfile) {
21471
21657
  const resolved = expandHome(keyfile);
21472
21658
  let text;
21473
21659
  try {
21474
- text = fs2.readFileSync(resolved, "utf8");
21660
+ text = fs3.readFileSync(resolved, "utf8");
21475
21661
  } catch (err) {
21476
21662
  throw new SkillError(
21477
21663
  "KEYFILE_UNREADABLE",
@@ -21495,13 +21681,13 @@ function planKeySource({ family, keyfile, env = process.env }) {
21495
21681
  }
21496
21682
  if (family === "solana") {
21497
21683
  const standard = defaultSolanaKeypairPath();
21498
- if (fs2.existsSync(standard)) {
21684
+ if (fs3.existsSync(standard)) {
21499
21685
  return { kind: "keypair-file", path: standard, label: displayPath(standard) };
21500
21686
  }
21501
21687
  if (env[SOL_KEY_ENV]) return { kind: "env", label: SOL_KEY_ENV };
21502
21688
  throw new SkillError(
21503
21689
  "NO_KEY_SOURCE",
21504
- `No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.`
21690
+ `No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.` + searchedSuffix(searched)
21505
21691
  );
21506
21692
  }
21507
21693
  if (family === "evm") {
@@ -21513,7 +21699,7 @@ function planKeySource({ family, keyfile, env = process.env }) {
21513
21699
  if (env[EVM_KEY_ENV]) return { kind: "env", label: EVM_KEY_ENV };
21514
21700
  throw new SkillError(
21515
21701
  "NO_KEY_SOURCE",
21516
- `No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.`
21702
+ `No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.${searchedSuffix(searched)}`
21517
21703
  );
21518
21704
  }
21519
21705
  throw new SkillError("NO_KEY_SOURCE", `No key source for family "${family}".`);
@@ -21531,7 +21717,7 @@ async function loadKeySource(plan, { family, env = process.env, askPassphrase }
21531
21717
  return { secretKey: decodeSolanaEnvKey(raw), label: plan.label, kind: plan.kind };
21532
21718
  }
21533
21719
  assertKeyfileSafe(plan.path);
21534
- const text = fs2.readFileSync(plan.path, "utf8");
21720
+ const text = fs3.readFileSync(plan.path, "utf8");
21535
21721
  if (plan.kind === "keypair-file") {
21536
21722
  return { secretKey: parseSolanaKeypairJson(text), label: plan.label, kind: plan.kind };
21537
21723
  }
@@ -21550,106 +21736,14 @@ async function loadKeySource(plan, { family, env = process.env, askPassphrase }
21550
21736
  }
21551
21737
  function expandHome(p) {
21552
21738
  const s = String(p);
21553
- if (s === "~") return os.homedir();
21554
- if (s.startsWith("~/")) return path2.join(os.homedir(), s.slice(2));
21739
+ if (s === "~") return os2.homedir();
21740
+ if (s.startsWith("~/")) return path3.join(os2.homedir(), s.slice(2));
21555
21741
  return s;
21556
21742
  }
21557
21743
  function displayPath(p) {
21558
- const home = os.homedir();
21744
+ const home = os2.homedir();
21559
21745
  const s = String(p);
21560
- return s.startsWith(home + path2.sep) ? `~${s.slice(home.length)}` : s;
21561
- }
21562
-
21563
- // scripts/src/lib/dotenv.mjs
21564
- import fs3 from "node:fs";
21565
- import path3 from "node:path";
21566
- var ALLOWED_KEYS = [
21567
- "ROZO_CHECKOUT_EVM_KEY",
21568
- "ROZO_CHECKOUT_SOL_KEY",
21569
- "ROZO_CHECKOUT_EVM_KEYSTORE",
21570
- "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
21571
- ];
21572
- var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
21573
- function isAllowedKey(key) {
21574
- return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
21575
- }
21576
- function parseDotenv(text) {
21577
- const vars = {};
21578
- const lines = String(text).split(/\r?\n/);
21579
- lines.forEach((rawLine, i) => {
21580
- const lineNo = i + 1;
21581
- const line = rawLine.trim();
21582
- if (!line || line.startsWith("#")) return;
21583
- const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
21584
- const eq = withoutExport.indexOf("=");
21585
- if (eq === -1) {
21586
- throw new SkillError(
21587
- "BAD_ENV_FILE",
21588
- `Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
21589
- );
21590
- }
21591
- const key = withoutExport.slice(0, eq).trim();
21592
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
21593
- throw new SkillError(
21594
- "BAD_ENV_FILE",
21595
- `Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
21596
- );
21597
- }
21598
- let value = withoutExport.slice(eq + 1).trim();
21599
- if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
21600
- value = value.slice(1, -1);
21601
- }
21602
- vars[key] = value;
21603
- });
21604
- return vars;
21605
- }
21606
- function filterAllowed(vars) {
21607
- const out = {};
21608
- for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out[k] = v;
21609
- return out;
21610
- }
21611
- function resolveEnvFile({ file, cwd = process.cwd() } = {}) {
21612
- if (file) {
21613
- const p = path3.resolve(file);
21614
- if (!fs3.existsSync(p)) {
21615
- throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
21616
- }
21617
- return p;
21618
- }
21619
- const fallback = path3.join(cwd, ".env");
21620
- return fs3.existsSync(fallback) ? fallback : null;
21621
- }
21622
- function applyDotenv({ file, cwd = process.cwd(), env = process.env } = {}) {
21623
- const target = resolveEnvFile({ file, cwd });
21624
- if (!target) return null;
21625
- let stat;
21626
- try {
21627
- stat = fs3.statSync(target);
21628
- } catch (err) {
21629
- throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
21630
- }
21631
- if (process.platform !== "win32" && stat.mode & 63) {
21632
- const mode = (stat.mode & 511).toString(8).padStart(3, "0");
21633
- throw new SkillError(
21634
- "ENV_FILE_PERMISSIONS",
21635
- `That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
21636
- );
21637
- }
21638
- assertNotTrackedByGit(target);
21639
- const parsed = parseDotenv(fs3.readFileSync(target, "utf8"));
21640
- const allowed = filterAllowed(parsed);
21641
- const applied = [];
21642
- for (const [k, v] of Object.entries(allowed)) {
21643
- const existing = env[k];
21644
- if (existing !== void 0 && String(existing).trim() !== "") continue;
21645
- env[k] = v;
21646
- applied.push(k);
21647
- }
21648
- return {
21649
- path: target,
21650
- applied,
21651
- ignored: Object.keys(parsed).length - Object.keys(allowed).length
21652
- };
21746
+ return s.startsWith(home + path3.sep) ? `~${s.slice(home.length)}` : s;
21653
21747
  }
21654
21748
 
21655
21749
  // scripts/src/lib/passphrase.mjs
@@ -22092,7 +22186,7 @@ function checkExpiry({
22092
22186
  // scripts/src/lib/blacklist.mjs
22093
22187
  import fs4 from "node:fs";
22094
22188
  import path4 from "node:path";
22095
- import crypto4 from "node:crypto";
22189
+ import crypto5 from "node:crypto";
22096
22190
  import { fileURLToPath } from "node:url";
22097
22191
  var BlacklistError = class extends Error {
22098
22192
  constructor(code, message) {
@@ -22127,7 +22221,7 @@ function parseBlacklist(doc) {
22127
22221
  }
22128
22222
  addresses.push(e.address);
22129
22223
  }
22130
- const digest = crypto4.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
22224
+ const digest = crypto5.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
22131
22225
  if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
22132
22226
  throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
22133
22227
  }
@@ -22215,8 +22309,8 @@ function assertNotBlacklisted(targets, blacklist) {
22215
22309
  // scripts/src/lib/state.mjs
22216
22310
  import fs5 from "node:fs";
22217
22311
  import path5 from "node:path";
22218
- import os2 from "node:os";
22219
- import crypto5 from "node:crypto";
22312
+ import os3 from "node:os";
22313
+ import crypto6 from "node:crypto";
22220
22314
  var LOCK_STALE_MS = 6e4;
22221
22315
  var LOCK_WAIT_MS = 1e4;
22222
22316
  var LOCK_POLL_MS = 25;
@@ -22246,7 +22340,7 @@ function withLock(fn) {
22246
22340
  try {
22247
22341
  const age = Date.now() - fs5.statSync(file).mtimeMs;
22248
22342
  if (age > LOCK_STALE_MS) {
22249
- const stolen = `${file}.stale.${crypto5.randomBytes(4).toString("hex")}`;
22343
+ const stolen = `${file}.stale.${crypto6.randomBytes(4).toString("hex")}`;
22250
22344
  try {
22251
22345
  fs5.renameSync(file, stolen);
22252
22346
  fs5.unlinkSync(stolen);
@@ -22276,7 +22370,7 @@ function withLock(fn) {
22276
22370
  }
22277
22371
  }
22278
22372
  function stateRoot() {
22279
- return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(os2.homedir(), ".rozo-checkout", "state");
22373
+ return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(os3.homedir(), ".rozo-checkout", "state");
22280
22374
  }
22281
22375
  function statePath(rozoPaymentId) {
22282
22376
  if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
@@ -22287,7 +22381,7 @@ function statePath(rozoPaymentId) {
22287
22381
  function writeAtomic(file, data) {
22288
22382
  const dir = path5.dirname(file);
22289
22383
  fs5.mkdirSync(dir, { recursive: true, mode: 448 });
22290
- const tmp = path5.join(dir, `.${path5.basename(file)}.${crypto5.randomBytes(6).toString("hex")}.tmp`);
22384
+ const tmp = path5.join(dir, `.${path5.basename(file)}.${crypto6.randomBytes(6).toString("hex")}.tmp`);
22291
22385
  const fd = fs5.openSync(tmp, "wx", 384);
22292
22386
  try {
22293
22387
  fs5.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
@@ -22326,7 +22420,7 @@ function depositDigest(source) {
22326
22420
  amountUnit: source?.amountUnit ?? null,
22327
22421
  lnInvoice: source?.lnInvoice ?? null
22328
22422
  });
22329
- return crypto5.createHash("sha256").update(canonical, "utf8").digest("hex");
22423
+ return crypto6.createHash("sha256").update(canonical, "utf8").digest("hex");
22330
22424
  }
22331
22425
  function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
22332
22426
  return withLock(() => {
@@ -22770,7 +22864,11 @@ async function main(argv) {
22770
22864
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
22771
22865
  assertNoTrackedDotEnv();
22772
22866
  const dotenv = applyDotenv({ file: args["env-file"] });
22773
- const plan = planKeySource({ family: "evm", keyfile: args.keyfile });
22867
+ const plan = planKeySource({
22868
+ family: "evm",
22869
+ keyfile: args.keyfile,
22870
+ searched: args["env-file"] ? null : envFileCandidates()
22871
+ });
22774
22872
  const loaded = await loadKeySource(plan, { family: "evm", askPassphrase: promptPassphrase });
22775
22873
  const account = privateKeyToAccount(loaded.privateKey);
22776
22874
  const sender = account.address;