@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.
- package/README.md +7 -2
- package/docs/QUICKSTART.es.md +2 -0
- package/docs/QUICKSTART.ja.md +2 -0
- package/docs/QUICKSTART.md +2 -0
- package/docs/QUICKSTART.zh.md +2 -0
- package/docs/README.es.md +2 -0
- package/docs/README.ja.md +2 -0
- package/docs/README.zh.md +2 -0
- package/docs/security-audit-notes.md +30 -0
- package/package.json +1 -1
- package/scripts/dist/cli.js +285 -182
- package/scripts/dist/send-evm.js +284 -186
- package/scripts/dist/send-sol.js +287 -189
- package/docs/review-history.md +0 -77
package/scripts/dist/send-sol.js
CHANGED
|
@@ -11988,10 +11988,10 @@ var require_dist = __commonJS({
|
|
|
11988
11988
|
});
|
|
11989
11989
|
|
|
11990
11990
|
// node_modules/uuid/dist/esm-node/rng.js
|
|
11991
|
-
import
|
|
11991
|
+
import crypto6 from "crypto";
|
|
11992
11992
|
function rng() {
|
|
11993
11993
|
if (poolPtr > rnds8Pool.length - 16) {
|
|
11994
|
-
|
|
11994
|
+
crypto6.randomFillSync(rnds8Pool);
|
|
11995
11995
|
poolPtr = 0;
|
|
11996
11996
|
}
|
|
11997
11997
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
@@ -12191,14 +12191,14 @@ var init_v35 = __esm({
|
|
|
12191
12191
|
});
|
|
12192
12192
|
|
|
12193
12193
|
// node_modules/uuid/dist/esm-node/md5.js
|
|
12194
|
-
import
|
|
12194
|
+
import crypto7 from "crypto";
|
|
12195
12195
|
function md5(bytes) {
|
|
12196
12196
|
if (Array.isArray(bytes)) {
|
|
12197
12197
|
bytes = Buffer.from(bytes);
|
|
12198
12198
|
} else if (typeof bytes === "string") {
|
|
12199
12199
|
bytes = Buffer.from(bytes, "utf8");
|
|
12200
12200
|
}
|
|
12201
|
-
return
|
|
12201
|
+
return crypto7.createHash("md5").update(bytes).digest();
|
|
12202
12202
|
}
|
|
12203
12203
|
var md5_default;
|
|
12204
12204
|
var init_md5 = __esm({
|
|
@@ -12243,14 +12243,14 @@ var init_v4 = __esm({
|
|
|
12243
12243
|
});
|
|
12244
12244
|
|
|
12245
12245
|
// node_modules/uuid/dist/esm-node/sha1.js
|
|
12246
|
-
import
|
|
12246
|
+
import crypto8 from "crypto";
|
|
12247
12247
|
function sha1(bytes) {
|
|
12248
12248
|
if (Array.isArray(bytes)) {
|
|
12249
12249
|
bytes = Buffer.from(bytes);
|
|
12250
12250
|
} else if (typeof bytes === "string") {
|
|
12251
12251
|
bytes = Buffer.from(bytes, "utf8");
|
|
12252
12252
|
}
|
|
12253
|
-
return
|
|
12253
|
+
return crypto8.createHash("sha1").update(bytes).digest();
|
|
12254
12254
|
}
|
|
12255
12255
|
var sha1_default;
|
|
12256
12256
|
var init_sha1 = __esm({
|
|
@@ -15507,14 +15507,14 @@ var require_node_gyp_build = __commonJS({
|
|
|
15507
15507
|
"node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
15508
15508
|
var fs6 = __require("fs");
|
|
15509
15509
|
var path6 = __require("path");
|
|
15510
|
-
var
|
|
15510
|
+
var os4 = __require("os");
|
|
15511
15511
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
15512
15512
|
var vars = process.config && process.config.variables || {};
|
|
15513
15513
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
15514
15514
|
var abi = process.versions.modules;
|
|
15515
15515
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
15516
|
-
var arch = process.env.npm_config_arch ||
|
|
15517
|
-
var platform = process.env.npm_config_platform ||
|
|
15516
|
+
var arch = process.env.npm_config_arch || os4.arch();
|
|
15517
|
+
var platform = process.env.npm_config_platform || os4.platform();
|
|
15518
15518
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
15519
15519
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
15520
15520
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -32359,43 +32359,148 @@ function chainFamily(chainId) {
|
|
|
32359
32359
|
// scripts/src/lib/keys.mjs
|
|
32360
32360
|
import fs from "node:fs";
|
|
32361
32361
|
import path from "node:path";
|
|
32362
|
-
import
|
|
32363
|
-
|
|
32364
|
-
|
|
32365
|
-
|
|
32362
|
+
import crypto2 from "node:crypto";
|
|
32363
|
+
function findGitRepo(dir) {
|
|
32364
|
+
for (const v of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"]) {
|
|
32365
|
+
if (process.env[v]) {
|
|
32366
|
+
throw new SkillError(
|
|
32367
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32368
|
+
`${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.`
|
|
32369
|
+
);
|
|
32370
|
+
}
|
|
32371
|
+
}
|
|
32372
|
+
let cur = path.resolve(dir);
|
|
32373
|
+
for (; ; ) {
|
|
32374
|
+
const dotGit = path.join(cur, ".git");
|
|
32375
|
+
let st = null;
|
|
32376
|
+
try {
|
|
32377
|
+
st = fs.statSync(dotGit);
|
|
32378
|
+
} catch (err) {
|
|
32379
|
+
if (err?.code !== "ENOENT" && err?.code !== "ENOTDIR") {
|
|
32380
|
+
throw new SkillError(
|
|
32381
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32382
|
+
`A .git entry here could not be inspected (${err?.code || "error"}), so tracked status cannot be proved. Refusing.`
|
|
32383
|
+
);
|
|
32384
|
+
}
|
|
32385
|
+
st = null;
|
|
32386
|
+
}
|
|
32387
|
+
if (st) {
|
|
32388
|
+
if (st.isDirectory()) return { root: cur, gitDir: dotGit };
|
|
32389
|
+
let text;
|
|
32390
|
+
try {
|
|
32391
|
+
text = fs.readFileSync(dotGit, "utf8");
|
|
32392
|
+
} catch {
|
|
32393
|
+
throw new SkillError(
|
|
32394
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32395
|
+
"A .git entry exists here but could not be read, so tracked status cannot be proved. Refusing."
|
|
32396
|
+
);
|
|
32397
|
+
}
|
|
32398
|
+
const m = /^gitdir:\s*(.+)\s*$/m.exec(text);
|
|
32399
|
+
if (!m) {
|
|
32400
|
+
throw new SkillError(
|
|
32401
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32402
|
+
"A .git file exists here but is not a recognised gitdir pointer. Refusing."
|
|
32403
|
+
);
|
|
32404
|
+
}
|
|
32405
|
+
return { root: cur, gitDir: path.resolve(cur, m[1]) };
|
|
32406
|
+
}
|
|
32407
|
+
const parent = path.dirname(cur);
|
|
32408
|
+
if (parent === cur) return null;
|
|
32409
|
+
cur = parent;
|
|
32410
|
+
}
|
|
32411
|
+
}
|
|
32412
|
+
function trackedPathsFromIndex(buf, { hashLen = 20 } = {}) {
|
|
32413
|
+
const fail2 = (why) => new SkillError(
|
|
32414
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32415
|
+
`The git index could not be interpreted (${why}), so tracked status cannot be proved. Refusing.`
|
|
32416
|
+
);
|
|
32417
|
+
if (buf.length < 12 + hashLen || buf.toString("latin1", 0, 4) !== "DIRC") throw fail2("bad header");
|
|
32418
|
+
const algo = hashLen === 32 ? "sha256" : "sha1";
|
|
32419
|
+
const expected = buf.subarray(buf.length - hashLen);
|
|
32420
|
+
const actual = crypto2.createHash(algo).update(buf.subarray(0, buf.length - hashLen)).digest();
|
|
32421
|
+
if (!actual.equals(expected)) throw fail2(`${algo} checksum mismatch`);
|
|
32422
|
+
const version4 = buf.readUInt32BE(4);
|
|
32423
|
+
if (version4 !== 2 && version4 !== 3) throw fail2(`unsupported index version ${version4}`);
|
|
32424
|
+
const count = buf.readUInt32BE(8);
|
|
32425
|
+
const paths = /* @__PURE__ */ new Set();
|
|
32426
|
+
const fixed = 40 + hashLen + 2;
|
|
32427
|
+
let off = 12;
|
|
32428
|
+
for (let i = 0; i < count; i++) {
|
|
32429
|
+
const entryStart = off;
|
|
32430
|
+
if (off + fixed > buf.length - hashLen) throw fail2("truncated entry");
|
|
32431
|
+
const flags = buf.readUInt16BE(off + fixed - 2);
|
|
32432
|
+
let nameOff = off + fixed;
|
|
32433
|
+
if (flags & 16384) {
|
|
32434
|
+
if (version4 < 3) throw fail2("extended flags in v2 index");
|
|
32435
|
+
nameOff += 2;
|
|
32436
|
+
}
|
|
32437
|
+
const nameLen = flags & 4095;
|
|
32438
|
+
let end;
|
|
32439
|
+
if (nameLen < 4095) {
|
|
32440
|
+
end = nameOff + nameLen;
|
|
32441
|
+
if (end > buf.length - hashLen) throw fail2("truncated path");
|
|
32442
|
+
} else {
|
|
32443
|
+
end = buf.indexOf(0, nameOff);
|
|
32444
|
+
if (end === -1 || end > buf.length - hashLen) throw fail2("unterminated path");
|
|
32445
|
+
}
|
|
32446
|
+
paths.add(buf.toString("utf8", nameOff, end));
|
|
32447
|
+
const entryLen = end - entryStart;
|
|
32448
|
+
off = entryStart + (Math.floor(entryLen / 8) + 1) * 8;
|
|
32449
|
+
}
|
|
32450
|
+
while (off < buf.length - hashLen) {
|
|
32451
|
+
if (off + 8 > buf.length - hashLen) throw fail2("truncated extension header");
|
|
32452
|
+
const extName = buf.toString("latin1", off, off + 4);
|
|
32453
|
+
const extSize = buf.readUInt32BE(off + 4);
|
|
32454
|
+
const first = extName.charCodeAt(0);
|
|
32455
|
+
if (first >= 97 && first <= 122) {
|
|
32456
|
+
throw fail2(`mandatory index extension "${extName}" (e.g. core.splitIndex) is not supported`);
|
|
32457
|
+
}
|
|
32458
|
+
off += 8 + extSize;
|
|
32459
|
+
if (off > buf.length - hashLen) throw fail2("truncated extension");
|
|
32460
|
+
}
|
|
32461
|
+
return paths;
|
|
32462
|
+
}
|
|
32463
|
+
function repoHashLen(gitDir) {
|
|
32464
|
+
let configDir = gitDir;
|
|
32465
|
+
try {
|
|
32466
|
+
const common = fs.readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
|
|
32467
|
+
if (common) configDir = path.resolve(gitDir, common);
|
|
32468
|
+
} catch {
|
|
32469
|
+
}
|
|
32470
|
+
try {
|
|
32471
|
+
const cfg = fs.readFileSync(path.join(configDir, "config"), "utf8");
|
|
32472
|
+
if (/^\s*objectformat\s*=\s*sha256\s*$/im.test(cfg)) return 32;
|
|
32473
|
+
} catch {
|
|
32474
|
+
}
|
|
32475
|
+
return 20;
|
|
32476
|
+
}
|
|
32477
|
+
function trackedAmong(repo, files) {
|
|
32478
|
+
const indexPath = path.join(repo.gitDir, "index");
|
|
32479
|
+
let buf;
|
|
32366
32480
|
try {
|
|
32367
|
-
|
|
32368
|
-
cwd: dir,
|
|
32369
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
32370
|
-
encoding: "utf8"
|
|
32371
|
-
}).trim() === "true";
|
|
32481
|
+
buf = fs.readFileSync(indexPath);
|
|
32372
32482
|
} catch (err) {
|
|
32373
|
-
|
|
32374
|
-
if (/not a git repository|does not appear to be a git repository/i.test(stderr)) return false;
|
|
32483
|
+
if (err?.code === "ENOENT") return [];
|
|
32375
32484
|
throw new SkillError(
|
|
32376
32485
|
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32377
|
-
|
|
32486
|
+
"The git index exists but could not be read, so tracked status cannot be proved. Refusing."
|
|
32378
32487
|
);
|
|
32379
32488
|
}
|
|
32489
|
+
const tracked = trackedPathsFromIndex(buf, { hashLen: repoHashLen(repo.gitDir) });
|
|
32490
|
+
return files.filter((f) => {
|
|
32491
|
+
const rel = path.relative(repo.root, path.resolve(f)).split(path.sep).join("/");
|
|
32492
|
+
return tracked.has(rel);
|
|
32493
|
+
});
|
|
32380
32494
|
}
|
|
32495
|
+
var ENV_FILE_RE = /^\.env(\..+)?$/;
|
|
32496
|
+
var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
|
|
32381
32497
|
function assertNotTrackedByGit(file) {
|
|
32382
32498
|
const dir = path.dirname(path.resolve(file));
|
|
32383
|
-
const
|
|
32384
|
-
if (!
|
|
32385
|
-
|
|
32386
|
-
|
|
32387
|
-
|
|
32388
|
-
cwd: dir,
|
|
32389
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
32390
|
-
encoding: "utf8"
|
|
32391
|
-
});
|
|
32392
|
-
} catch {
|
|
32393
|
-
throw new SkillError(
|
|
32394
|
-
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32395
|
-
"git could not report whether this key file is tracked. Refusing to use it."
|
|
32396
|
-
);
|
|
32397
|
-
}
|
|
32398
|
-
if (out.split("\0").filter(Boolean).length) {
|
|
32499
|
+
const repo = findGitRepo(dir);
|
|
32500
|
+
if (!repo) return { checked: true, tracked: false };
|
|
32501
|
+
const hits = trackedAmong(repo, [file]);
|
|
32502
|
+
if (hits.length) {
|
|
32503
|
+
const base = path.basename(file);
|
|
32399
32504
|
throw new SkillError(
|
|
32400
32505
|
"TRACKED_KEYFILE",
|
|
32401
32506
|
`${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.`
|
|
@@ -32414,37 +32519,11 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
|
32414
32519
|
);
|
|
32415
32520
|
}
|
|
32416
32521
|
if (candidates.length === 0) return { checked: true, tracked: false, candidates: [] };
|
|
32417
|
-
|
|
32418
|
-
|
|
32419
|
-
|
|
32420
|
-
|
|
32421
|
-
|
|
32422
|
-
encoding: "utf8"
|
|
32423
|
-
}).trim() === "true";
|
|
32424
|
-
} catch (err) {
|
|
32425
|
-
const stderr = String(err?.stderr || "");
|
|
32426
|
-
const notARepo = /not a git repository|does not appear to be a git repository/i.test(stderr);
|
|
32427
|
-
if (notARepo) return { checked: true, tracked: false, candidates };
|
|
32428
|
-
throw new SkillError(
|
|
32429
|
-
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32430
|
-
`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.`
|
|
32431
|
-
);
|
|
32432
|
-
}
|
|
32433
|
-
if (!insideRepo) return { checked: true, tracked: false, candidates };
|
|
32434
|
-
let out;
|
|
32435
|
-
try {
|
|
32436
|
-
out = execFileSync("git", ["ls-files", "-z", "--", ...candidates], {
|
|
32437
|
-
cwd,
|
|
32438
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
32439
|
-
encoding: "utf8"
|
|
32440
|
-
});
|
|
32441
|
-
} catch {
|
|
32442
|
-
throw new SkillError(
|
|
32443
|
-
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
32444
|
-
"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."
|
|
32445
|
-
);
|
|
32446
|
-
}
|
|
32447
|
-
const tracked = out.split("\0").filter(Boolean);
|
|
32522
|
+
const repo = findGitRepo(cwd);
|
|
32523
|
+
if (!repo) return { checked: true, tracked: false, candidates };
|
|
32524
|
+
const tracked = trackedAmong(repo, candidates.map((f) => path.join(cwd, f))).map(
|
|
32525
|
+
(f) => path.basename(f)
|
|
32526
|
+
);
|
|
32448
32527
|
if (tracked.length) {
|
|
32449
32528
|
throw new SkillError(
|
|
32450
32529
|
"TRACKED_DOTENV",
|
|
@@ -32455,26 +32534,129 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
|
32455
32534
|
}
|
|
32456
32535
|
|
|
32457
32536
|
// scripts/src/lib/key-source.mjs
|
|
32458
|
-
import
|
|
32459
|
-
import
|
|
32460
|
-
import
|
|
32461
|
-
import
|
|
32537
|
+
import fs3 from "node:fs";
|
|
32538
|
+
import path3 from "node:path";
|
|
32539
|
+
import os2 from "node:os";
|
|
32540
|
+
import crypto3 from "node:crypto";
|
|
32462
32541
|
|
|
32463
32542
|
// node_modules/viem/_esm/index.js
|
|
32464
32543
|
init_keccak256();
|
|
32465
32544
|
|
|
32545
|
+
// scripts/src/lib/dotenv.mjs
|
|
32546
|
+
import fs2 from "node:fs";
|
|
32547
|
+
import os from "node:os";
|
|
32548
|
+
import path2 from "node:path";
|
|
32549
|
+
var ALLOWED_KEYS = [
|
|
32550
|
+
"ROZO_CHECKOUT_EVM_KEY",
|
|
32551
|
+
"ROZO_CHECKOUT_SOL_KEY",
|
|
32552
|
+
"ROZO_CHECKOUT_EVM_KEYSTORE",
|
|
32553
|
+
"ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
|
|
32554
|
+
];
|
|
32555
|
+
var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
|
|
32556
|
+
function isAllowedKey(key) {
|
|
32557
|
+
return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
|
|
32558
|
+
}
|
|
32559
|
+
function parseDotenv(text) {
|
|
32560
|
+
const vars = {};
|
|
32561
|
+
const lines = String(text).split(/\r?\n/);
|
|
32562
|
+
lines.forEach((rawLine, i) => {
|
|
32563
|
+
const lineNo = i + 1;
|
|
32564
|
+
const line = rawLine.trim();
|
|
32565
|
+
if (!line || line.startsWith("#")) return;
|
|
32566
|
+
const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
32567
|
+
const eq = withoutExport.indexOf("=");
|
|
32568
|
+
if (eq === -1) {
|
|
32569
|
+
throw new SkillError(
|
|
32570
|
+
"BAD_ENV_FILE",
|
|
32571
|
+
`Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
|
|
32572
|
+
);
|
|
32573
|
+
}
|
|
32574
|
+
const key = withoutExport.slice(0, eq).trim();
|
|
32575
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
32576
|
+
throw new SkillError(
|
|
32577
|
+
"BAD_ENV_FILE",
|
|
32578
|
+
`Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
|
|
32579
|
+
);
|
|
32580
|
+
}
|
|
32581
|
+
let value = withoutExport.slice(eq + 1).trim();
|
|
32582
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
32583
|
+
value = value.slice(1, -1);
|
|
32584
|
+
}
|
|
32585
|
+
vars[key] = value;
|
|
32586
|
+
});
|
|
32587
|
+
return vars;
|
|
32588
|
+
}
|
|
32589
|
+
function filterAllowed(vars) {
|
|
32590
|
+
const out = {};
|
|
32591
|
+
for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out[k] = v;
|
|
32592
|
+
return out;
|
|
32593
|
+
}
|
|
32594
|
+
function envFileCandidates({ cwd = process.cwd(), home = os.homedir() } = {}) {
|
|
32595
|
+
const candidates = [path2.join(cwd, ".env")];
|
|
32596
|
+
const owned = path2.join(home, ".rozo-checkout", ".env");
|
|
32597
|
+
if (owned !== candidates[0]) candidates.push(owned);
|
|
32598
|
+
return candidates;
|
|
32599
|
+
}
|
|
32600
|
+
function resolveEnvFile({ file, cwd = process.cwd(), home = os.homedir() } = {}) {
|
|
32601
|
+
if (file) {
|
|
32602
|
+
const p = path2.resolve(file);
|
|
32603
|
+
if (!fs2.existsSync(p)) {
|
|
32604
|
+
throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
|
|
32605
|
+
}
|
|
32606
|
+
return p;
|
|
32607
|
+
}
|
|
32608
|
+
return envFileCandidates({ cwd, home }).find((p) => fs2.existsSync(p)) ?? null;
|
|
32609
|
+
}
|
|
32610
|
+
function applyDotenv({
|
|
32611
|
+
file,
|
|
32612
|
+
cwd = process.cwd(),
|
|
32613
|
+
home = os.homedir(),
|
|
32614
|
+
env = process.env
|
|
32615
|
+
} = {}) {
|
|
32616
|
+
const target = resolveEnvFile({ file, cwd, home });
|
|
32617
|
+
if (!target) return null;
|
|
32618
|
+
let stat;
|
|
32619
|
+
try {
|
|
32620
|
+
stat = fs2.statSync(target);
|
|
32621
|
+
} catch (err) {
|
|
32622
|
+
throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
|
|
32623
|
+
}
|
|
32624
|
+
if (process.platform !== "win32" && stat.mode & 63) {
|
|
32625
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
32626
|
+
throw new SkillError(
|
|
32627
|
+
"ENV_FILE_PERMISSIONS",
|
|
32628
|
+
`That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
|
|
32629
|
+
);
|
|
32630
|
+
}
|
|
32631
|
+
assertNotTrackedByGit(target);
|
|
32632
|
+
const parsed = parseDotenv(fs2.readFileSync(target, "utf8"));
|
|
32633
|
+
const allowed = filterAllowed(parsed);
|
|
32634
|
+
const applied = [];
|
|
32635
|
+
for (const [k, v] of Object.entries(allowed)) {
|
|
32636
|
+
const existing = env[k];
|
|
32637
|
+
if (existing !== void 0 && String(existing).trim() !== "") continue;
|
|
32638
|
+
env[k] = v;
|
|
32639
|
+
applied.push(k);
|
|
32640
|
+
}
|
|
32641
|
+
return {
|
|
32642
|
+
path: target,
|
|
32643
|
+
applied,
|
|
32644
|
+
ignored: Object.keys(parsed).length - Object.keys(allowed).length
|
|
32645
|
+
};
|
|
32646
|
+
}
|
|
32647
|
+
|
|
32466
32648
|
// scripts/src/lib/key-source.mjs
|
|
32467
32649
|
var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
|
|
32468
32650
|
var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
|
|
32469
32651
|
var EVM_KEYSTORE_ENV = "ROZO_CHECKOUT_EVM_KEYSTORE";
|
|
32470
32652
|
var KEYSTORE_PASSPHRASE_ENV = "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE";
|
|
32471
32653
|
function defaultSolanaKeypairPath() {
|
|
32472
|
-
return
|
|
32654
|
+
return path3.join(os2.homedir(), ".config", "solana", "id.json");
|
|
32473
32655
|
}
|
|
32474
32656
|
function assertKeyfileSafe(file) {
|
|
32475
32657
|
let stat;
|
|
32476
32658
|
try {
|
|
32477
|
-
stat =
|
|
32659
|
+
stat = fs3.statSync(file);
|
|
32478
32660
|
} catch (err) {
|
|
32479
32661
|
throw new SkillError(
|
|
32480
32662
|
"KEYFILE_UNREADABLE",
|
|
@@ -32586,7 +32768,7 @@ function decryptKeystoreV3(keystore, passphrase) {
|
|
|
32586
32768
|
if (!n || !r || !p || !dklen || !salt) {
|
|
32587
32769
|
throw new SkillError("BAD_KEYSTORE", "The keystore scrypt parameters are incomplete.");
|
|
32588
32770
|
}
|
|
32589
|
-
derived =
|
|
32771
|
+
derived = crypto3.scryptSync(pass, Buffer.from(salt, "hex"), dklen, {
|
|
32590
32772
|
N: n,
|
|
32591
32773
|
r,
|
|
32592
32774
|
p,
|
|
@@ -32602,7 +32784,7 @@ function decryptKeystoreV3(keystore, passphrase) {
|
|
|
32602
32784
|
if (!iterations || !dklen || !salt) {
|
|
32603
32785
|
throw new SkillError("BAD_KEYSTORE", "The keystore pbkdf2 parameters are incomplete.");
|
|
32604
32786
|
}
|
|
32605
|
-
derived =
|
|
32787
|
+
derived = crypto3.pbkdf2Sync(pass, Buffer.from(salt, "hex"), iterations, dklen, "sha256");
|
|
32606
32788
|
} else {
|
|
32607
32789
|
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore KDF "${c.kdf}".`);
|
|
32608
32790
|
}
|
|
@@ -32617,7 +32799,7 @@ function decryptKeystoreV3(keystore, passphrase) {
|
|
|
32617
32799
|
if (c.cipher !== "aes-128-ctr") {
|
|
32618
32800
|
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore cipher "${c.cipher}".`);
|
|
32619
32801
|
}
|
|
32620
|
-
const decipher =
|
|
32802
|
+
const decipher = crypto3.createDecipheriv(
|
|
32621
32803
|
"aes-128-ctr",
|
|
32622
32804
|
derived.subarray(0, 16),
|
|
32623
32805
|
Buffer.from(c.cipherparams.iv, "hex")
|
|
@@ -32639,7 +32821,7 @@ function timingSafeEqualHex(a, b) {
|
|
|
32639
32821
|
const ab = Buffer.from(String(a).toLowerCase(), "hex");
|
|
32640
32822
|
const bb = Buffer.from(String(b).toLowerCase(), "hex");
|
|
32641
32823
|
if (ab.length === 0 || ab.length !== bb.length) return false;
|
|
32642
|
-
return
|
|
32824
|
+
return crypto3.timingSafeEqual(ab, bb);
|
|
32643
32825
|
}
|
|
32644
32826
|
function looksLikeKeystore(text) {
|
|
32645
32827
|
const t = String(text).trimStart();
|
|
@@ -32651,12 +32833,16 @@ function looksLikeKeystore(text) {
|
|
|
32651
32833
|
return false;
|
|
32652
32834
|
}
|
|
32653
32835
|
}
|
|
32654
|
-
function
|
|
32836
|
+
function searchedSuffix(searched) {
|
|
32837
|
+
if (!searched || !searched.length) return "";
|
|
32838
|
+
return ` Looked for a .env in: ${searched.map(displayPath).join(", ")}.`;
|
|
32839
|
+
}
|
|
32840
|
+
function planKeySource({ family, keyfile, env = process.env, searched }) {
|
|
32655
32841
|
if (keyfile) {
|
|
32656
32842
|
const resolved = expandHome(keyfile);
|
|
32657
32843
|
let text;
|
|
32658
32844
|
try {
|
|
32659
|
-
text =
|
|
32845
|
+
text = fs3.readFileSync(resolved, "utf8");
|
|
32660
32846
|
} catch (err) {
|
|
32661
32847
|
throw new SkillError(
|
|
32662
32848
|
"KEYFILE_UNREADABLE",
|
|
@@ -32680,13 +32866,13 @@ function planKeySource({ family, keyfile, env = process.env }) {
|
|
|
32680
32866
|
}
|
|
32681
32867
|
if (family === "solana") {
|
|
32682
32868
|
const standard = defaultSolanaKeypairPath();
|
|
32683
|
-
if (
|
|
32869
|
+
if (fs3.existsSync(standard)) {
|
|
32684
32870
|
return { kind: "keypair-file", path: standard, label: displayPath(standard) };
|
|
32685
32871
|
}
|
|
32686
32872
|
if (env[SOL_KEY_ENV]) return { kind: "env", label: SOL_KEY_ENV };
|
|
32687
32873
|
throw new SkillError(
|
|
32688
32874
|
"NO_KEY_SOURCE",
|
|
32689
|
-
`No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.`
|
|
32875
|
+
`No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.` + searchedSuffix(searched)
|
|
32690
32876
|
);
|
|
32691
32877
|
}
|
|
32692
32878
|
if (family === "evm") {
|
|
@@ -32698,7 +32884,7 @@ function planKeySource({ family, keyfile, env = process.env }) {
|
|
|
32698
32884
|
if (env[EVM_KEY_ENV]) return { kind: "env", label: EVM_KEY_ENV };
|
|
32699
32885
|
throw new SkillError(
|
|
32700
32886
|
"NO_KEY_SOURCE",
|
|
32701
|
-
`No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}
|
|
32887
|
+
`No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.${searchedSuffix(searched)}`
|
|
32702
32888
|
);
|
|
32703
32889
|
}
|
|
32704
32890
|
throw new SkillError("NO_KEY_SOURCE", `No key source for family "${family}".`);
|
|
@@ -32716,7 +32902,7 @@ async function loadKeySource(plan, { family, env = process.env, askPassphrase }
|
|
|
32716
32902
|
return { secretKey: decodeSolanaEnvKey(raw), label: plan.label, kind: plan.kind };
|
|
32717
32903
|
}
|
|
32718
32904
|
assertKeyfileSafe(plan.path);
|
|
32719
|
-
const text =
|
|
32905
|
+
const text = fs3.readFileSync(plan.path, "utf8");
|
|
32720
32906
|
if (plan.kind === "keypair-file") {
|
|
32721
32907
|
return { secretKey: parseSolanaKeypairJson(text), label: plan.label, kind: plan.kind };
|
|
32722
32908
|
}
|
|
@@ -32735,106 +32921,14 @@ async function loadKeySource(plan, { family, env = process.env, askPassphrase }
|
|
|
32735
32921
|
}
|
|
32736
32922
|
function expandHome(p) {
|
|
32737
32923
|
const s = String(p);
|
|
32738
|
-
if (s === "~") return
|
|
32739
|
-
if (s.startsWith("~/")) return
|
|
32924
|
+
if (s === "~") return os2.homedir();
|
|
32925
|
+
if (s.startsWith("~/")) return path3.join(os2.homedir(), s.slice(2));
|
|
32740
32926
|
return s;
|
|
32741
32927
|
}
|
|
32742
32928
|
function displayPath(p) {
|
|
32743
|
-
const home =
|
|
32929
|
+
const home = os2.homedir();
|
|
32744
32930
|
const s = String(p);
|
|
32745
|
-
return s.startsWith(home +
|
|
32746
|
-
}
|
|
32747
|
-
|
|
32748
|
-
// scripts/src/lib/dotenv.mjs
|
|
32749
|
-
import fs3 from "node:fs";
|
|
32750
|
-
import path3 from "node:path";
|
|
32751
|
-
var ALLOWED_KEYS = [
|
|
32752
|
-
"ROZO_CHECKOUT_EVM_KEY",
|
|
32753
|
-
"ROZO_CHECKOUT_SOL_KEY",
|
|
32754
|
-
"ROZO_CHECKOUT_EVM_KEYSTORE",
|
|
32755
|
-
"ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
|
|
32756
|
-
];
|
|
32757
|
-
var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
|
|
32758
|
-
function isAllowedKey(key) {
|
|
32759
|
-
return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
|
|
32760
|
-
}
|
|
32761
|
-
function parseDotenv(text) {
|
|
32762
|
-
const vars = {};
|
|
32763
|
-
const lines = String(text).split(/\r?\n/);
|
|
32764
|
-
lines.forEach((rawLine, i) => {
|
|
32765
|
-
const lineNo = i + 1;
|
|
32766
|
-
const line = rawLine.trim();
|
|
32767
|
-
if (!line || line.startsWith("#")) return;
|
|
32768
|
-
const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
32769
|
-
const eq = withoutExport.indexOf("=");
|
|
32770
|
-
if (eq === -1) {
|
|
32771
|
-
throw new SkillError(
|
|
32772
|
-
"BAD_ENV_FILE",
|
|
32773
|
-
`Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
|
|
32774
|
-
);
|
|
32775
|
-
}
|
|
32776
|
-
const key = withoutExport.slice(0, eq).trim();
|
|
32777
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
32778
|
-
throw new SkillError(
|
|
32779
|
-
"BAD_ENV_FILE",
|
|
32780
|
-
`Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
|
|
32781
|
-
);
|
|
32782
|
-
}
|
|
32783
|
-
let value = withoutExport.slice(eq + 1).trim();
|
|
32784
|
-
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
32785
|
-
value = value.slice(1, -1);
|
|
32786
|
-
}
|
|
32787
|
-
vars[key] = value;
|
|
32788
|
-
});
|
|
32789
|
-
return vars;
|
|
32790
|
-
}
|
|
32791
|
-
function filterAllowed(vars) {
|
|
32792
|
-
const out = {};
|
|
32793
|
-
for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out[k] = v;
|
|
32794
|
-
return out;
|
|
32795
|
-
}
|
|
32796
|
-
function resolveEnvFile({ file, cwd = process.cwd() } = {}) {
|
|
32797
|
-
if (file) {
|
|
32798
|
-
const p = path3.resolve(file);
|
|
32799
|
-
if (!fs3.existsSync(p)) {
|
|
32800
|
-
throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
|
|
32801
|
-
}
|
|
32802
|
-
return p;
|
|
32803
|
-
}
|
|
32804
|
-
const fallback = path3.join(cwd, ".env");
|
|
32805
|
-
return fs3.existsSync(fallback) ? fallback : null;
|
|
32806
|
-
}
|
|
32807
|
-
function applyDotenv({ file, cwd = process.cwd(), env = process.env } = {}) {
|
|
32808
|
-
const target = resolveEnvFile({ file, cwd });
|
|
32809
|
-
if (!target) return null;
|
|
32810
|
-
let stat;
|
|
32811
|
-
try {
|
|
32812
|
-
stat = fs3.statSync(target);
|
|
32813
|
-
} catch (err) {
|
|
32814
|
-
throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
|
|
32815
|
-
}
|
|
32816
|
-
if (process.platform !== "win32" && stat.mode & 63) {
|
|
32817
|
-
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
32818
|
-
throw new SkillError(
|
|
32819
|
-
"ENV_FILE_PERMISSIONS",
|
|
32820
|
-
`That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
|
|
32821
|
-
);
|
|
32822
|
-
}
|
|
32823
|
-
assertNotTrackedByGit(target);
|
|
32824
|
-
const parsed = parseDotenv(fs3.readFileSync(target, "utf8"));
|
|
32825
|
-
const allowed = filterAllowed(parsed);
|
|
32826
|
-
const applied = [];
|
|
32827
|
-
for (const [k, v] of Object.entries(allowed)) {
|
|
32828
|
-
const existing = env[k];
|
|
32829
|
-
if (existing !== void 0 && String(existing).trim() !== "") continue;
|
|
32830
|
-
env[k] = v;
|
|
32831
|
-
applied.push(k);
|
|
32832
|
-
}
|
|
32833
|
-
return {
|
|
32834
|
-
path: target,
|
|
32835
|
-
applied,
|
|
32836
|
-
ignored: Object.keys(parsed).length - Object.keys(allowed).length
|
|
32837
|
-
};
|
|
32931
|
+
return s.startsWith(home + path3.sep) ? `~${s.slice(home.length)}` : s;
|
|
32838
32932
|
}
|
|
32839
32933
|
|
|
32840
32934
|
// scripts/src/lib/passphrase.mjs
|
|
@@ -33277,7 +33371,7 @@ function checkExpiry({
|
|
|
33277
33371
|
// scripts/src/lib/blacklist.mjs
|
|
33278
33372
|
import fs4 from "node:fs";
|
|
33279
33373
|
import path4 from "node:path";
|
|
33280
|
-
import
|
|
33374
|
+
import crypto4 from "node:crypto";
|
|
33281
33375
|
import { fileURLToPath } from "node:url";
|
|
33282
33376
|
var BlacklistError = class extends Error {
|
|
33283
33377
|
constructor(code, message) {
|
|
@@ -33312,7 +33406,7 @@ function parseBlacklist(doc) {
|
|
|
33312
33406
|
}
|
|
33313
33407
|
addresses.push(e.address);
|
|
33314
33408
|
}
|
|
33315
|
-
const digest =
|
|
33409
|
+
const digest = crypto4.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
|
|
33316
33410
|
if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
|
|
33317
33411
|
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
|
|
33318
33412
|
}
|
|
@@ -33400,8 +33494,8 @@ function assertNotBlacklisted(targets, blacklist) {
|
|
|
33400
33494
|
// scripts/src/lib/state.mjs
|
|
33401
33495
|
import fs5 from "node:fs";
|
|
33402
33496
|
import path5 from "node:path";
|
|
33403
|
-
import
|
|
33404
|
-
import
|
|
33497
|
+
import os3 from "node:os";
|
|
33498
|
+
import crypto5 from "node:crypto";
|
|
33405
33499
|
var LOCK_STALE_MS = 6e4;
|
|
33406
33500
|
var LOCK_WAIT_MS = 1e4;
|
|
33407
33501
|
var LOCK_POLL_MS = 25;
|
|
@@ -33431,7 +33525,7 @@ function withLock(fn) {
|
|
|
33431
33525
|
try {
|
|
33432
33526
|
const age = Date.now() - fs5.statSync(file).mtimeMs;
|
|
33433
33527
|
if (age > LOCK_STALE_MS) {
|
|
33434
|
-
const stolen = `${file}.stale.${
|
|
33528
|
+
const stolen = `${file}.stale.${crypto5.randomBytes(4).toString("hex")}`;
|
|
33435
33529
|
try {
|
|
33436
33530
|
fs5.renameSync(file, stolen);
|
|
33437
33531
|
fs5.unlinkSync(stolen);
|
|
@@ -33461,7 +33555,7 @@ function withLock(fn) {
|
|
|
33461
33555
|
}
|
|
33462
33556
|
}
|
|
33463
33557
|
function stateRoot() {
|
|
33464
|
-
return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(
|
|
33558
|
+
return process.env.ROZO_CHECKOUT_STATE_DIR || path5.join(os3.homedir(), ".rozo-checkout", "state");
|
|
33465
33559
|
}
|
|
33466
33560
|
function statePath(rozoPaymentId) {
|
|
33467
33561
|
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
|
|
@@ -33472,7 +33566,7 @@ function statePath(rozoPaymentId) {
|
|
|
33472
33566
|
function writeAtomic(file, data) {
|
|
33473
33567
|
const dir = path5.dirname(file);
|
|
33474
33568
|
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
33475
|
-
const tmp = path5.join(dir, `.${path5.basename(file)}.${
|
|
33569
|
+
const tmp = path5.join(dir, `.${path5.basename(file)}.${crypto5.randomBytes(6).toString("hex")}.tmp`);
|
|
33476
33570
|
const fd = fs5.openSync(tmp, "wx", 384);
|
|
33477
33571
|
try {
|
|
33478
33572
|
fs5.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
@@ -33511,7 +33605,7 @@ function depositDigest(source) {
|
|
|
33511
33605
|
amountUnit: source?.amountUnit ?? null,
|
|
33512
33606
|
lnInvoice: source?.lnInvoice ?? null
|
|
33513
33607
|
});
|
|
33514
|
-
return
|
|
33608
|
+
return crypto5.createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
33515
33609
|
}
|
|
33516
33610
|
function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
|
|
33517
33611
|
return withLock(() => {
|
|
@@ -35480,7 +35574,11 @@ async function main(argv) {
|
|
|
35480
35574
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
35481
35575
|
assertNoTrackedDotEnv();
|
|
35482
35576
|
const dotenv = applyDotenv({ file: args["env-file"] });
|
|
35483
|
-
const plan = planKeySource({
|
|
35577
|
+
const plan = planKeySource({
|
|
35578
|
+
family: "solana",
|
|
35579
|
+
keyfile: args.keyfile,
|
|
35580
|
+
searched: args["env-file"] ? null : envFileCandidates()
|
|
35581
|
+
});
|
|
35484
35582
|
const loaded = await loadKeySource(plan, { family: "solana", askPassphrase: promptPassphrase });
|
|
35485
35583
|
const secret = loaded.secretKey;
|
|
35486
35584
|
const keypair = secret.length === 64 ? import_web36.Keypair.fromSecretKey(secret) : import_web36.Keypair.fromSeed(secret);
|