@rozoai/checkout 0.1.1 → 0.1.2
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 +115 -11
- package/docs/QUICKSTART.es.md +126 -11
- package/docs/QUICKSTART.ja.md +109 -11
- package/docs/QUICKSTART.md +120 -10
- package/docs/QUICKSTART.zh.md +98 -10
- package/docs/README.es.md +105 -8
- package/docs/README.ja.md +90 -8
- package/docs/README.zh.md +83 -9
- package/docs/safety.md +7 -3
- package/llms.txt +38 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +722 -288
- package/scripts/dist/send-evm.js +6695 -6237
- package/scripts/dist/send-sol.js +1727 -634
package/scripts/dist/cli.js
CHANGED
|
@@ -20457,8 +20457,8 @@ var require_dist = __commonJS({
|
|
|
20457
20457
|
constructor(failure, failures) {
|
|
20458
20458
|
let cached2;
|
|
20459
20459
|
const { message, explanation, ...rest } = failure;
|
|
20460
|
-
const { path:
|
|
20461
|
-
const msg =
|
|
20460
|
+
const { path: path7 } = failure;
|
|
20461
|
+
const msg = path7.length === 0 ? message : `At path: ${path7.join(".")} -- ${message}`;
|
|
20462
20462
|
super(explanation ?? msg);
|
|
20463
20463
|
if (explanation != null)
|
|
20464
20464
|
this.cause = msg;
|
|
@@ -20503,15 +20503,15 @@ var require_dist = __commonJS({
|
|
|
20503
20503
|
} else if (typeof result === "string") {
|
|
20504
20504
|
result = { message: result };
|
|
20505
20505
|
}
|
|
20506
|
-
const { path:
|
|
20506
|
+
const { path: path7, branch } = context;
|
|
20507
20507
|
const { type: type2 } = struct6;
|
|
20508
20508
|
const { refinement, message = `Expected a value of type \`${type2}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result;
|
|
20509
20509
|
return {
|
|
20510
20510
|
value,
|
|
20511
20511
|
type: type2,
|
|
20512
20512
|
refinement,
|
|
20513
|
-
key:
|
|
20514
|
-
path:
|
|
20513
|
+
key: path7[path7.length - 1],
|
|
20514
|
+
path: path7,
|
|
20515
20515
|
branch,
|
|
20516
20516
|
...result,
|
|
20517
20517
|
message
|
|
@@ -20529,8 +20529,8 @@ var require_dist = __commonJS({
|
|
|
20529
20529
|
}
|
|
20530
20530
|
}
|
|
20531
20531
|
function* run6(value, struct6, options = {}) {
|
|
20532
|
-
const { path:
|
|
20533
|
-
const ctx = { path:
|
|
20532
|
+
const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
|
|
20533
|
+
const ctx = { path: path7, branch, mask: mask2 };
|
|
20534
20534
|
if (coerce2) {
|
|
20535
20535
|
value = struct6.coercer(value, ctx);
|
|
20536
20536
|
}
|
|
@@ -20542,7 +20542,7 @@ var require_dist = __commonJS({
|
|
|
20542
20542
|
}
|
|
20543
20543
|
for (let [k, v, s] of struct6.entries(value, ctx)) {
|
|
20544
20544
|
const ts = run6(v, s, {
|
|
20545
|
-
path: k === void 0 ?
|
|
20545
|
+
path: k === void 0 ? path7 : [...path7, k],
|
|
20546
20546
|
branch: k === void 0 ? branch : [...branch, v],
|
|
20547
20547
|
coerce: coerce2,
|
|
20548
20548
|
mask: mask2,
|
|
@@ -21257,10 +21257,10 @@ var require_dist = __commonJS({
|
|
|
21257
21257
|
});
|
|
21258
21258
|
|
|
21259
21259
|
// node_modules/uuid/dist/esm-node/rng.js
|
|
21260
|
-
import
|
|
21260
|
+
import crypto6 from "crypto";
|
|
21261
21261
|
function rng() {
|
|
21262
21262
|
if (poolPtr > rnds8Pool.length - 16) {
|
|
21263
|
-
|
|
21263
|
+
crypto6.randomFillSync(rnds8Pool);
|
|
21264
21264
|
poolPtr = 0;
|
|
21265
21265
|
}
|
|
21266
21266
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
@@ -21460,14 +21460,14 @@ var init_v35 = __esm({
|
|
|
21460
21460
|
});
|
|
21461
21461
|
|
|
21462
21462
|
// node_modules/uuid/dist/esm-node/md5.js
|
|
21463
|
-
import
|
|
21463
|
+
import crypto7 from "crypto";
|
|
21464
21464
|
function md5(bytes) {
|
|
21465
21465
|
if (Array.isArray(bytes)) {
|
|
21466
21466
|
bytes = Buffer.from(bytes);
|
|
21467
21467
|
} else if (typeof bytes === "string") {
|
|
21468
21468
|
bytes = Buffer.from(bytes, "utf8");
|
|
21469
21469
|
}
|
|
21470
|
-
return
|
|
21470
|
+
return crypto7.createHash("md5").update(bytes).digest();
|
|
21471
21471
|
}
|
|
21472
21472
|
var md5_default;
|
|
21473
21473
|
var init_md5 = __esm({
|
|
@@ -21512,14 +21512,14 @@ var init_v4 = __esm({
|
|
|
21512
21512
|
});
|
|
21513
21513
|
|
|
21514
21514
|
// node_modules/uuid/dist/esm-node/sha1.js
|
|
21515
|
-
import
|
|
21515
|
+
import crypto8 from "crypto";
|
|
21516
21516
|
function sha1(bytes) {
|
|
21517
21517
|
if (Array.isArray(bytes)) {
|
|
21518
21518
|
bytes = Buffer.from(bytes);
|
|
21519
21519
|
} else if (typeof bytes === "string") {
|
|
21520
21520
|
bytes = Buffer.from(bytes, "utf8");
|
|
21521
21521
|
}
|
|
21522
|
-
return
|
|
21522
|
+
return crypto8.createHash("sha1").update(bytes).digest();
|
|
21523
21523
|
}
|
|
21524
21524
|
var sha1_default;
|
|
21525
21525
|
var init_sha1 = __esm({
|
|
@@ -22472,14 +22472,14 @@ var require_url_state_machine = __commonJS({
|
|
|
22472
22472
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
22473
22473
|
}
|
|
22474
22474
|
function shortenPath(url) {
|
|
22475
|
-
const
|
|
22476
|
-
if (
|
|
22475
|
+
const path7 = url.path;
|
|
22476
|
+
if (path7.length === 0) {
|
|
22477
22477
|
return;
|
|
22478
22478
|
}
|
|
22479
|
-
if (url.scheme === "file" &&
|
|
22479
|
+
if (url.scheme === "file" && path7.length === 1 && isNormalizedWindowsDriveLetter(path7[0])) {
|
|
22480
22480
|
return;
|
|
22481
22481
|
}
|
|
22482
|
-
|
|
22482
|
+
path7.pop();
|
|
22483
22483
|
}
|
|
22484
22484
|
function includesCredentials(url) {
|
|
22485
22485
|
return url.username !== "" || url.password !== "";
|
|
@@ -24774,16 +24774,16 @@ var require_constants = __commonJS({
|
|
|
24774
24774
|
// node_modules/node-gyp-build/node-gyp-build.js
|
|
24775
24775
|
var require_node_gyp_build = __commonJS({
|
|
24776
24776
|
"node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
24777
|
-
var
|
|
24778
|
-
var
|
|
24779
|
-
var
|
|
24777
|
+
var fs7 = __require("fs");
|
|
24778
|
+
var path7 = __require("path");
|
|
24779
|
+
var os4 = __require("os");
|
|
24780
24780
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
24781
24781
|
var vars = process.config && process.config.variables || {};
|
|
24782
24782
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
24783
24783
|
var abi2 = process.versions.modules;
|
|
24784
24784
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
24785
|
-
var arch = process.env.npm_config_arch ||
|
|
24786
|
-
var platform = process.env.npm_config_platform ||
|
|
24785
|
+
var arch = process.env.npm_config_arch || os4.arch();
|
|
24786
|
+
var platform = process.env.npm_config_platform || os4.platform();
|
|
24787
24787
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
24788
24788
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
24789
24789
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -24792,21 +24792,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
24792
24792
|
return runtimeRequire(load.resolve(dir));
|
|
24793
24793
|
}
|
|
24794
24794
|
load.resolve = load.path = function(dir) {
|
|
24795
|
-
dir =
|
|
24795
|
+
dir = path7.resolve(dir || ".");
|
|
24796
24796
|
try {
|
|
24797
|
-
var name = runtimeRequire(
|
|
24797
|
+
var name = runtimeRequire(path7.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
24798
24798
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
24799
24799
|
} catch (err) {
|
|
24800
24800
|
}
|
|
24801
24801
|
if (!prebuildsOnly) {
|
|
24802
|
-
var release = getFirst(
|
|
24802
|
+
var release = getFirst(path7.join(dir, "build/Release"), matchBuild);
|
|
24803
24803
|
if (release) return release;
|
|
24804
|
-
var debug = getFirst(
|
|
24804
|
+
var debug = getFirst(path7.join(dir, "build/Debug"), matchBuild);
|
|
24805
24805
|
if (debug) return debug;
|
|
24806
24806
|
}
|
|
24807
24807
|
var prebuild = resolve(dir);
|
|
24808
24808
|
if (prebuild) return prebuild;
|
|
24809
|
-
var nearby = resolve(
|
|
24809
|
+
var nearby = resolve(path7.dirname(process.execPath));
|
|
24810
24810
|
if (nearby) return nearby;
|
|
24811
24811
|
var target = [
|
|
24812
24812
|
"platform=" + platform,
|
|
@@ -24823,26 +24823,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
24823
24823
|
].filter(Boolean).join(" ");
|
|
24824
24824
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
24825
24825
|
function resolve(dir2) {
|
|
24826
|
-
var tuples = readdirSync(
|
|
24826
|
+
var tuples = readdirSync(path7.join(dir2, "prebuilds")).map(parseTuple);
|
|
24827
24827
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
24828
24828
|
if (!tuple) return;
|
|
24829
|
-
var prebuilds =
|
|
24829
|
+
var prebuilds = path7.join(dir2, "prebuilds", tuple.name);
|
|
24830
24830
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
24831
24831
|
var candidates = parsed.filter(matchTags(runtime, abi2));
|
|
24832
24832
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
24833
|
-
if (winner) return
|
|
24833
|
+
if (winner) return path7.join(prebuilds, winner.file);
|
|
24834
24834
|
}
|
|
24835
24835
|
};
|
|
24836
24836
|
function readdirSync(dir) {
|
|
24837
24837
|
try {
|
|
24838
|
-
return
|
|
24838
|
+
return fs7.readdirSync(dir);
|
|
24839
24839
|
} catch (err) {
|
|
24840
24840
|
return [];
|
|
24841
24841
|
}
|
|
24842
24842
|
}
|
|
24843
24843
|
function getFirst(dir, filter) {
|
|
24844
24844
|
var files = readdirSync(dir).filter(filter);
|
|
24845
|
-
return files[0] &&
|
|
24845
|
+
return files[0] && path7.join(dir, files[0]);
|
|
24846
24846
|
}
|
|
24847
24847
|
function matchBuild(name) {
|
|
24848
24848
|
return /\.node$/.test(name);
|
|
@@ -24929,7 +24929,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
24929
24929
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
24930
24930
|
}
|
|
24931
24931
|
function isAlpine(platform2) {
|
|
24932
|
-
return platform2 === "linux" &&
|
|
24932
|
+
return platform2 === "linux" && fs7.existsSync("/etc/alpine-release");
|
|
24933
24933
|
}
|
|
24934
24934
|
load.parseTags = parseTags;
|
|
24935
24935
|
load.matchTags = matchTags;
|
|
@@ -41168,20 +41168,20 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41168
41168
|
var rest = decodeURI(uri.substring(7));
|
|
41169
41169
|
var firstSlash = rest.indexOf("/");
|
|
41170
41170
|
var host = rest.substring(0, firstSlash);
|
|
41171
|
-
var
|
|
41171
|
+
var path7 = rest.substring(firstSlash + 1);
|
|
41172
41172
|
if ("localhost" == host) host = "";
|
|
41173
41173
|
if (host) {
|
|
41174
41174
|
host = sep + sep + host;
|
|
41175
41175
|
}
|
|
41176
|
-
|
|
41176
|
+
path7 = path7.replace(/^(.+)\|/, "$1:");
|
|
41177
41177
|
if (sep == "\\") {
|
|
41178
|
-
|
|
41178
|
+
path7 = path7.replace(/\//g, "\\");
|
|
41179
41179
|
}
|
|
41180
|
-
if (/^.+\:/.test(
|
|
41180
|
+
if (/^.+\:/.test(path7)) {
|
|
41181
41181
|
} else {
|
|
41182
|
-
|
|
41182
|
+
path7 = sep + path7;
|
|
41183
41183
|
}
|
|
41184
|
-
return host +
|
|
41184
|
+
return host + path7;
|
|
41185
41185
|
}
|
|
41186
41186
|
}
|
|
41187
41187
|
});
|
|
@@ -41189,19 +41189,19 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41189
41189
|
// node_modules/bindings/bindings.js
|
|
41190
41190
|
var require_bindings = __commonJS({
|
|
41191
41191
|
"node_modules/bindings/bindings.js"(exports, module) {
|
|
41192
|
-
var
|
|
41193
|
-
var
|
|
41192
|
+
var fs7 = __require("fs");
|
|
41193
|
+
var path7 = __require("path");
|
|
41194
41194
|
var fileURLToPath2 = require_file_uri_to_path();
|
|
41195
|
-
var join =
|
|
41196
|
-
var dirname =
|
|
41197
|
-
var exists =
|
|
41195
|
+
var join = path7.join;
|
|
41196
|
+
var dirname = path7.dirname;
|
|
41197
|
+
var exists = fs7.accessSync && function(path8) {
|
|
41198
41198
|
try {
|
|
41199
|
-
|
|
41199
|
+
fs7.accessSync(path8);
|
|
41200
41200
|
} catch (e) {
|
|
41201
41201
|
return false;
|
|
41202
41202
|
}
|
|
41203
41203
|
return true;
|
|
41204
|
-
} ||
|
|
41204
|
+
} || fs7.existsSync || path7.existsSync;
|
|
41205
41205
|
var defaults = {
|
|
41206
41206
|
arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
|
|
41207
41207
|
compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
|
|
@@ -41246,7 +41246,7 @@ var require_bindings = __commonJS({
|
|
|
41246
41246
|
if (!opts.module_root) {
|
|
41247
41247
|
opts.module_root = exports.getRoot(exports.getFileName());
|
|
41248
41248
|
}
|
|
41249
|
-
if (
|
|
41249
|
+
if (path7.extname(opts.bindings) != ".node") {
|
|
41250
41250
|
opts.bindings += ".node";
|
|
41251
41251
|
}
|
|
41252
41252
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
@@ -41403,7 +41403,7 @@ process.on("warning", (warning) => {
|
|
|
41403
41403
|
});
|
|
41404
41404
|
|
|
41405
41405
|
// scripts/src/cli.mjs
|
|
41406
|
-
import
|
|
41406
|
+
import readline2 from "node:readline";
|
|
41407
41407
|
import { createRequire } from "node:module";
|
|
41408
41408
|
|
|
41409
41409
|
// scripts/src/lib/output.mjs
|
|
@@ -41779,7 +41779,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
41779
41779
|
"watch",
|
|
41780
41780
|
"fresh"
|
|
41781
41781
|
]);
|
|
41782
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout", "payer"]);
|
|
41782
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout", "payer", "keyfile", "env-file"]);
|
|
41783
41783
|
function parseCliArgs(argv) {
|
|
41784
41784
|
const positional = [];
|
|
41785
41785
|
const flags = {};
|
|
@@ -41897,7 +41897,9 @@ function parseCliArgs(argv) {
|
|
|
41897
41897
|
timeout,
|
|
41898
41898
|
rpc: flags.rpc,
|
|
41899
41899
|
payer: flags.payer,
|
|
41900
|
-
fresh: flags.fresh === true
|
|
41900
|
+
fresh: flags.fresh === true,
|
|
41901
|
+
keyfile: flags.keyfile,
|
|
41902
|
+
envFile: flags["env-file"]
|
|
41901
41903
|
};
|
|
41902
41904
|
}
|
|
41903
41905
|
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link with BTC Lightning,
|
|
@@ -41922,9 +41924,15 @@ OPTIONS
|
|
|
41922
41924
|
--with <coin> which coin to pay with. Omit it on a terminal and you get a
|
|
41923
41925
|
numbered list to choose from; scripts and agents must pass it.
|
|
41924
41926
|
--send optional. Sign from a hot wallet instead of paying yourself.
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
payment may not exceed $1,100; there is
|
|
41927
|
+
The only option that needs a key. On Solana it uses your
|
|
41928
|
+
existing ~/.config/solana/id.json; on EVM an encrypted JSON
|
|
41929
|
+
keystore. A single payment may not exceed $1,100; there is
|
|
41930
|
+
no override.
|
|
41931
|
+
--keyfile <p> with --send: the key to sign with. A solana-keygen keypair
|
|
41932
|
+
file, or an encrypted V3 keystore for EVM (passphrase is
|
|
41933
|
+
prompted, never passed as a flag).
|
|
41934
|
+
--env-file <p> with --send: read hot-wallet settings from this file instead
|
|
41935
|
+
of ./.env. Only ROZO_CHECKOUT_* keys are read from it.
|
|
41928
41936
|
--yes, -y skip the interactive confirmation (required when not a TTY)
|
|
41929
41937
|
--dry-run with --send, show what would be signed and sign nothing
|
|
41930
41938
|
--json, -j machine-readable output
|
|
@@ -43637,21 +43645,51 @@ async function run3(argv = process.argv.slice(2)) {
|
|
|
43637
43645
|
|
|
43638
43646
|
// scripts/src/lib/keys.mjs
|
|
43639
43647
|
import fs4 from "node:fs";
|
|
43648
|
+
import path4 from "node:path";
|
|
43640
43649
|
import { execFileSync } from "node:child_process";
|
|
43641
|
-
var
|
|
43642
|
-
var
|
|
43643
|
-
function
|
|
43644
|
-
|
|
43645
|
-
|
|
43650
|
+
var ENV_FILE_RE = /^\.env(\..+)?$/;
|
|
43651
|
+
var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
|
|
43652
|
+
function insideGitWorkTree(dir, whatFor) {
|
|
43653
|
+
try {
|
|
43654
|
+
return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
43655
|
+
cwd: dir,
|
|
43656
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
43657
|
+
encoding: "utf8"
|
|
43658
|
+
}).trim() === "true";
|
|
43659
|
+
} catch (err) {
|
|
43660
|
+
const stderr = String(err?.stderr || "");
|
|
43661
|
+
if (/not a git repository|does not appear to be a git repository/i.test(stderr)) return false;
|
|
43646
43662
|
throw new SkillError(
|
|
43647
|
-
"
|
|
43648
|
-
|
|
43663
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
43664
|
+
`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.`
|
|
43649
43665
|
);
|
|
43650
43666
|
}
|
|
43651
|
-
return String(v).trim();
|
|
43652
43667
|
}
|
|
43653
|
-
|
|
43654
|
-
|
|
43668
|
+
function assertNotTrackedByGit(file) {
|
|
43669
|
+
const dir = path4.dirname(path4.resolve(file));
|
|
43670
|
+
const base = path4.basename(file);
|
|
43671
|
+
if (!insideGitWorkTree(dir, "this key file")) return { checked: true, tracked: false };
|
|
43672
|
+
let out2;
|
|
43673
|
+
try {
|
|
43674
|
+
out2 = execFileSync("git", ["ls-files", "-z", "--", base], {
|
|
43675
|
+
cwd: dir,
|
|
43676
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
43677
|
+
encoding: "utf8"
|
|
43678
|
+
});
|
|
43679
|
+
} catch {
|
|
43680
|
+
throw new SkillError(
|
|
43681
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
43682
|
+
"git could not report whether this key file is tracked. Refusing to use it."
|
|
43683
|
+
);
|
|
43684
|
+
}
|
|
43685
|
+
if (out2.split("\0").filter(Boolean).length) {
|
|
43686
|
+
throw new SkillError(
|
|
43687
|
+
"TRACKED_KEYFILE",
|
|
43688
|
+
`${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.`
|
|
43689
|
+
);
|
|
43690
|
+
}
|
|
43691
|
+
return { checked: true, tracked: false };
|
|
43692
|
+
}
|
|
43655
43693
|
function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
43656
43694
|
let candidates = [];
|
|
43657
43695
|
try {
|
|
@@ -43703,172 +43741,11 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
|
43703
43741
|
return { checked: true, tracked: false, candidates };
|
|
43704
43742
|
}
|
|
43705
43743
|
|
|
43706
|
-
// scripts/src/lib/
|
|
43707
|
-
|
|
43708
|
-
|
|
43709
|
-
|
|
43710
|
-
|
|
43711
|
-
send = false,
|
|
43712
|
-
dryRun = false
|
|
43713
|
-
}) {
|
|
43714
|
-
if (!send && !dryRun) {
|
|
43715
|
-
throw new SkillError(
|
|
43716
|
-
"SEND_NOT_OPTED_IN",
|
|
43717
|
-
"Refusing to move funds without the explicit --send flag. Add --dry-run to see exactly what would be signed instead."
|
|
43718
|
-
);
|
|
43719
|
-
}
|
|
43720
|
-
let blacklist;
|
|
43721
|
-
try {
|
|
43722
|
-
blacklist = loadBlacklist();
|
|
43723
|
-
} catch (err) {
|
|
43724
|
-
throw new SkillError(
|
|
43725
|
-
"BLACKLIST_UNAVAILABLE",
|
|
43726
|
-
`Compromised-address list unusable: ${err.message} Refusing to send.`
|
|
43727
|
-
);
|
|
43728
|
-
}
|
|
43729
|
-
const state = readState(rozoPaymentId);
|
|
43730
|
-
if (!state) {
|
|
43731
|
-
throw new SkillError(
|
|
43732
|
-
"NO_ORDER_STATE",
|
|
43733
|
-
"No local record for this order. Run create-order.js first, in the same environment."
|
|
43734
|
-
);
|
|
43735
|
-
}
|
|
43736
|
-
if (state.send) {
|
|
43737
|
-
throw new SkillError(
|
|
43738
|
-
"ALREADY_SENT",
|
|
43739
|
-
`A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice.`,
|
|
43740
|
-
{ send: state.send }
|
|
43741
|
-
);
|
|
43742
|
-
}
|
|
43743
|
-
if (!state.confirmation) {
|
|
43744
|
-
throw new SkillError(
|
|
43745
|
-
"NOT_CONFIRMED",
|
|
43746
|
-
"This order has never been confirmed. Run `create-order.js --url <link> --chain <id> --token <SYM> --confirm` first, show the user the deposit block it prints, and get an explicit yes before sending."
|
|
43747
|
-
);
|
|
43748
|
-
}
|
|
43749
|
-
const payment = await getPayment(rozoPaymentId);
|
|
43750
|
-
const source = payment?.source || {};
|
|
43751
|
-
const family = chainFamily(source.chainId);
|
|
43752
|
-
if (family !== expectFamily) {
|
|
43753
|
-
throw new SkillError(
|
|
43754
|
-
"WRONG_SENDER_SCRIPT",
|
|
43755
|
-
`This order settles on a ${family ?? "unknown"} chain; use the matching send script.`
|
|
43756
|
-
);
|
|
43757
|
-
}
|
|
43758
|
-
const guard = reuseGuard({
|
|
43759
|
-
payment,
|
|
43760
|
-
requested: { chainId: state.source.chainId, tokenSymbol: state.source.tokenSymbol }
|
|
43761
|
-
});
|
|
43762
|
-
if (!guard.ok) {
|
|
43763
|
-
throw new SkillError(guard.code, guard.reason, {
|
|
43764
|
-
...guard.evidence,
|
|
43765
|
-
moneyDetected: guard.moneyDetected
|
|
43766
|
-
});
|
|
43767
|
-
}
|
|
43768
|
-
const deposit = validateDepositInstructions(source);
|
|
43769
|
-
if (!deposit.ok) throw new SkillError(deposit.code, deposit.reason);
|
|
43770
|
-
const liveDigest = depositDigest(source);
|
|
43771
|
-
if (liveDigest !== state.confirmation.depositDigest) {
|
|
43772
|
-
throw new SkillError(
|
|
43773
|
-
"CONFIRMATION_STALE",
|
|
43774
|
-
"The live deposit instructions differ from the ones that were confirmed. Re-run create-order.js --confirm, show the user the new details, and get a fresh yes."
|
|
43775
|
-
);
|
|
43776
|
-
}
|
|
43777
|
-
if (source.receiverAddress !== state.receiverAddress) {
|
|
43778
|
-
throw new SkillError(
|
|
43779
|
-
"DEPOSIT_CHANGED",
|
|
43780
|
-
"The live deposit address differs from the one recorded at create time. Refusing to send."
|
|
43781
|
-
);
|
|
43782
|
-
}
|
|
43783
|
-
if (String(source.amount) !== String(state.amount)) {
|
|
43784
|
-
throw new SkillError(
|
|
43785
|
-
"DEPOSIT_CHANGED",
|
|
43786
|
-
`The live deposit amount (${source.amount}) differs from the recorded one (${state.amount}).`
|
|
43787
|
-
);
|
|
43788
|
-
}
|
|
43789
|
-
if ((source.receiverMemo ?? null) !== (state.receiverMemo ?? null)) {
|
|
43790
|
-
throw new SkillError("DEPOSIT_CHANGED", "The live deposit memo differs from the recorded one.");
|
|
43791
|
-
}
|
|
43792
|
-
const statusNow = await invoiceStatus({ linkId: state.linkId });
|
|
43793
|
-
const expiry = checkExpiry({
|
|
43794
|
-
now: Date.now(),
|
|
43795
|
-
chainId: source.chainId,
|
|
43796
|
-
intentExpiresAt: payment?.expiresAt,
|
|
43797
|
-
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
43798
|
-
});
|
|
43799
|
-
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
43800
|
-
const payable = checkPayable(statusNow, Date.now());
|
|
43801
|
-
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
43802
|
-
assertNotBlacklisted(
|
|
43803
|
-
[
|
|
43804
|
-
{ address: source.receiverAddress, family, role: "deposit address" },
|
|
43805
|
-
{ address: senderAddress, family, role: "sender wallet" }
|
|
43806
|
-
],
|
|
43807
|
-
blacklist
|
|
43808
|
-
);
|
|
43809
|
-
return {
|
|
43810
|
-
payment,
|
|
43811
|
-
source,
|
|
43812
|
-
state,
|
|
43813
|
-
expiry,
|
|
43814
|
-
blacklist,
|
|
43815
|
-
amountAtomic: deposit.amountAtomic,
|
|
43816
|
-
deposit,
|
|
43817
|
-
statusNow
|
|
43818
|
-
};
|
|
43819
|
-
}
|
|
43820
|
-
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt }) {
|
|
43821
|
-
const statusNow = await invoiceStatus({ linkId });
|
|
43822
|
-
const payable = checkPayable(statusNow, Date.now());
|
|
43823
|
-
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
43824
|
-
const expiry = checkExpiry({
|
|
43825
|
-
now: Date.now(),
|
|
43826
|
-
chainId,
|
|
43827
|
-
intentExpiresAt,
|
|
43828
|
-
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
43829
|
-
});
|
|
43830
|
-
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
43831
|
-
return { statusNow, payable, expiry };
|
|
43832
|
-
}
|
|
43833
|
-
|
|
43834
|
-
// scripts/src/lib/outcomes.mjs
|
|
43835
|
-
function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen = void 0 }) {
|
|
43836
|
-
const seen = receiptSeen ?? (receiptStatus !== null && receiptStatus !== void 0);
|
|
43837
|
-
if (executionError) {
|
|
43838
|
-
return {
|
|
43839
|
-
state: "failed",
|
|
43840
|
-
success: false,
|
|
43841
|
-
exitCode: EXIT_ERROR,
|
|
43842
|
-
code: "TX_FAILED",
|
|
43843
|
-
recordStatus: "failed"
|
|
43844
|
-
};
|
|
43845
|
-
}
|
|
43846
|
-
if (!seen) {
|
|
43847
|
-
return {
|
|
43848
|
-
state: "unconfirmed",
|
|
43849
|
-
success: true,
|
|
43850
|
-
exitCode: EXIT_UNCONFIRMED,
|
|
43851
|
-
code: null,
|
|
43852
|
-
recordStatus: "submitted"
|
|
43853
|
-
};
|
|
43854
|
-
}
|
|
43855
|
-
if (receiptStatus === "success") {
|
|
43856
|
-
return {
|
|
43857
|
-
state: "confirmed",
|
|
43858
|
-
success: true,
|
|
43859
|
-
exitCode: EXIT_OK,
|
|
43860
|
-
code: null,
|
|
43861
|
-
recordStatus: "confirmed"
|
|
43862
|
-
};
|
|
43863
|
-
}
|
|
43864
|
-
return {
|
|
43865
|
-
state: "reverted",
|
|
43866
|
-
success: false,
|
|
43867
|
-
exitCode: EXIT_ERROR,
|
|
43868
|
-
code: "TX_REVERTED",
|
|
43869
|
-
recordStatus: "failed"
|
|
43870
|
-
};
|
|
43871
|
-
}
|
|
43744
|
+
// scripts/src/lib/key-source.mjs
|
|
43745
|
+
import fs5 from "node:fs";
|
|
43746
|
+
import path5 from "node:path";
|
|
43747
|
+
import os3 from "node:os";
|
|
43748
|
+
import crypto5 from "node:crypto";
|
|
43872
43749
|
|
|
43873
43750
|
// node_modules/viem/_esm/utils/getAction.js
|
|
43874
43751
|
function getAction(client, actionFn, name) {
|
|
@@ -54833,6 +54710,593 @@ function http(url, config = {}) {
|
|
|
54833
54710
|
init_encodeFunctionData();
|
|
54834
54711
|
init_keccak256();
|
|
54835
54712
|
|
|
54713
|
+
// scripts/src/lib/key-source.mjs
|
|
54714
|
+
var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
|
|
54715
|
+
var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
|
|
54716
|
+
var EVM_KEYSTORE_ENV = "ROZO_CHECKOUT_EVM_KEYSTORE";
|
|
54717
|
+
var KEYSTORE_PASSPHRASE_ENV = "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE";
|
|
54718
|
+
function defaultSolanaKeypairPath() {
|
|
54719
|
+
return path5.join(os3.homedir(), ".config", "solana", "id.json");
|
|
54720
|
+
}
|
|
54721
|
+
function assertKeyfileSafe(file) {
|
|
54722
|
+
let stat;
|
|
54723
|
+
try {
|
|
54724
|
+
stat = fs5.statSync(file);
|
|
54725
|
+
} catch (err) {
|
|
54726
|
+
throw new SkillError(
|
|
54727
|
+
"KEYFILE_UNREADABLE",
|
|
54728
|
+
`Cannot read key file (${err.code || "unknown error"}). Check the path.`
|
|
54729
|
+
);
|
|
54730
|
+
}
|
|
54731
|
+
if (!stat.isFile()) {
|
|
54732
|
+
throw new SkillError("KEYFILE_UNREADABLE", "That key file path is not a regular file.");
|
|
54733
|
+
}
|
|
54734
|
+
if (process.platform !== "win32" && stat.mode & 63) {
|
|
54735
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
54736
|
+
throw new SkillError(
|
|
54737
|
+
"KEYFILE_PERMISSIONS",
|
|
54738
|
+
`That key file is readable by other users (mode ${mode}). Run: chmod 600 ${file}`
|
|
54739
|
+
);
|
|
54740
|
+
}
|
|
54741
|
+
assertNotTrackedByGit(file);
|
|
54742
|
+
return true;
|
|
54743
|
+
}
|
|
54744
|
+
function parseSolanaKeypairJson(text) {
|
|
54745
|
+
let arr;
|
|
54746
|
+
try {
|
|
54747
|
+
arr = JSON.parse(String(text));
|
|
54748
|
+
} catch {
|
|
54749
|
+
throw new SkillError(
|
|
54750
|
+
"BAD_KEYPAIR_FILE",
|
|
54751
|
+
"That file is not a Solana keypair: expected a JSON array of bytes."
|
|
54752
|
+
);
|
|
54753
|
+
}
|
|
54754
|
+
if (!Array.isArray(arr)) {
|
|
54755
|
+
throw new SkillError("BAD_KEYPAIR_FILE", "A Solana keypair file must contain a JSON array.");
|
|
54756
|
+
}
|
|
54757
|
+
if (arr.length !== 64 && arr.length !== 32) {
|
|
54758
|
+
throw new SkillError(
|
|
54759
|
+
"BAD_KEYPAIR_FILE",
|
|
54760
|
+
`A Solana keypair must be 64 bytes (or a 32-byte seed); this file has ${arr.length}.`
|
|
54761
|
+
);
|
|
54762
|
+
}
|
|
54763
|
+
for (const b of arr) {
|
|
54764
|
+
if (!Number.isInteger(b) || b < 0 || b > 255) {
|
|
54765
|
+
throw new SkillError("BAD_KEYPAIR_FILE", "A Solana keypair must contain byte values 0-255.");
|
|
54766
|
+
}
|
|
54767
|
+
}
|
|
54768
|
+
return Uint8Array.from(arr);
|
|
54769
|
+
}
|
|
54770
|
+
function normalizeEvmPrivateKey(raw) {
|
|
54771
|
+
const s = String(raw ?? "").trim();
|
|
54772
|
+
if (!s) {
|
|
54773
|
+
throw new SkillError("MISSING_KEY", "No EVM private key supplied.");
|
|
54774
|
+
}
|
|
54775
|
+
const body = /^0[xX]/.test(s) ? s.slice(2) : s;
|
|
54776
|
+
if (!/^[0-9a-fA-F]{64}$/.test(body)) {
|
|
54777
|
+
throw new SkillError(
|
|
54778
|
+
"BAD_KEY_FORMAT",
|
|
54779
|
+
`An EVM private key must be 64 hex characters, with or without a 0x prefix (got ${body.length} characters after any prefix). Value withheld.`
|
|
54780
|
+
);
|
|
54781
|
+
}
|
|
54782
|
+
return `0x${body.toLowerCase()}`;
|
|
54783
|
+
}
|
|
54784
|
+
function decodeSolanaEnvKey(raw) {
|
|
54785
|
+
const s = String(raw ?? "").trim();
|
|
54786
|
+
if (!s) throw new SkillError("MISSING_KEY", `${SOL_KEY_ENV} is empty.`);
|
|
54787
|
+
if (s.startsWith("[")) return parseSolanaKeypairJson(s);
|
|
54788
|
+
const bytes = base58Decode(s);
|
|
54789
|
+
if (bytes.length !== 64 && bytes.length !== 32) {
|
|
54790
|
+
throw new SkillError(
|
|
54791
|
+
"BAD_KEY_FORMAT",
|
|
54792
|
+
`${SOL_KEY_ENV} must be a base58 secret key or a JSON byte array.`
|
|
54793
|
+
);
|
|
54794
|
+
}
|
|
54795
|
+
return bytes;
|
|
54796
|
+
}
|
|
54797
|
+
var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
54798
|
+
function base58Decode(str) {
|
|
54799
|
+
let num2 = 0n;
|
|
54800
|
+
for (const ch of str) {
|
|
54801
|
+
const idx = B58.indexOf(ch);
|
|
54802
|
+
if (idx < 0) throw new SkillError("BAD_KEY_FORMAT", "Not valid base58.");
|
|
54803
|
+
num2 = num2 * 58n + BigInt(idx);
|
|
54804
|
+
}
|
|
54805
|
+
const bytes = [];
|
|
54806
|
+
while (num2 > 0n) {
|
|
54807
|
+
bytes.unshift(Number(num2 % 256n));
|
|
54808
|
+
num2 /= 256n;
|
|
54809
|
+
}
|
|
54810
|
+
for (const ch of str) {
|
|
54811
|
+
if (ch === "1") bytes.unshift(0);
|
|
54812
|
+
else break;
|
|
54813
|
+
}
|
|
54814
|
+
return Uint8Array.from(bytes);
|
|
54815
|
+
}
|
|
54816
|
+
function decryptKeystoreV3(keystore, passphrase) {
|
|
54817
|
+
const ks = typeof keystore === "string" ? safeParseJson(keystore) : keystore;
|
|
54818
|
+
if (!ks || typeof ks !== "object" || !ks.crypto) {
|
|
54819
|
+
throw new SkillError(
|
|
54820
|
+
"BAD_KEYSTORE",
|
|
54821
|
+
'That file is not a V3 keystore (no "crypto" section). Export an encrypted JSON keystore from your wallet, or use a raw key in the environment.'
|
|
54822
|
+
);
|
|
54823
|
+
}
|
|
54824
|
+
if (ks.version !== void 0 && Number(ks.version) !== 3) {
|
|
54825
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore version ${ks.version}; expected 3.`);
|
|
54826
|
+
}
|
|
54827
|
+
const c2 = ks.crypto;
|
|
54828
|
+
const kdfparams = c2.kdfparams || {};
|
|
54829
|
+
const pass = Buffer.from(String(passphrase ?? ""), "utf8");
|
|
54830
|
+
let derived;
|
|
54831
|
+
if (c2.kdf === "scrypt") {
|
|
54832
|
+
const { n, r, p, dklen, salt } = kdfparams;
|
|
54833
|
+
if (!n || !r || !p || !dklen || !salt) {
|
|
54834
|
+
throw new SkillError("BAD_KEYSTORE", "The keystore scrypt parameters are incomplete.");
|
|
54835
|
+
}
|
|
54836
|
+
derived = crypto5.scryptSync(pass, Buffer.from(salt, "hex"), dklen, {
|
|
54837
|
+
N: n,
|
|
54838
|
+
r,
|
|
54839
|
+
p,
|
|
54840
|
+
// geth's default N=262144 needs roughly 256 MB; the Node default cap is
|
|
54841
|
+
// far lower, so raise it with headroom rather than failing on a normal file.
|
|
54842
|
+
maxmem: 256 * n * r + 64 * 1024 * 1024
|
|
54843
|
+
});
|
|
54844
|
+
} else if (c2.kdf === "pbkdf2") {
|
|
54845
|
+
const { c: iterations, dklen, salt, prf } = kdfparams;
|
|
54846
|
+
if (prf && prf !== "hmac-sha256") {
|
|
54847
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore PRF "${prf}".`);
|
|
54848
|
+
}
|
|
54849
|
+
if (!iterations || !dklen || !salt) {
|
|
54850
|
+
throw new SkillError("BAD_KEYSTORE", "The keystore pbkdf2 parameters are incomplete.");
|
|
54851
|
+
}
|
|
54852
|
+
derived = crypto5.pbkdf2Sync(pass, Buffer.from(salt, "hex"), iterations, dklen, "sha256");
|
|
54853
|
+
} else {
|
|
54854
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore KDF "${c2.kdf}".`);
|
|
54855
|
+
}
|
|
54856
|
+
const ciphertext = Buffer.from(c2.ciphertext, "hex");
|
|
54857
|
+
const mac = keccak256(Buffer.concat([derived.subarray(16, 32), ciphertext])).slice(2);
|
|
54858
|
+
if (!timingSafeEqualHex(mac, String(c2.mac || ""))) {
|
|
54859
|
+
throw new SkillError(
|
|
54860
|
+
"KEYSTORE_BAD_PASSPHRASE",
|
|
54861
|
+
"Wrong passphrase for that keystore (the MAC did not verify)."
|
|
54862
|
+
);
|
|
54863
|
+
}
|
|
54864
|
+
if (c2.cipher !== "aes-128-ctr") {
|
|
54865
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore cipher "${c2.cipher}".`);
|
|
54866
|
+
}
|
|
54867
|
+
const decipher = crypto5.createDecipheriv(
|
|
54868
|
+
"aes-128-ctr",
|
|
54869
|
+
derived.subarray(0, 16),
|
|
54870
|
+
Buffer.from(c2.cipherparams.iv, "hex")
|
|
54871
|
+
);
|
|
54872
|
+
const secret = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
54873
|
+
if (secret.length !== 32) {
|
|
54874
|
+
throw new SkillError("BAD_KEYSTORE", "The decrypted key is not 32 bytes.");
|
|
54875
|
+
}
|
|
54876
|
+
return `0x${secret.toString("hex")}`;
|
|
54877
|
+
}
|
|
54878
|
+
function safeParseJson(text) {
|
|
54879
|
+
try {
|
|
54880
|
+
return JSON.parse(text);
|
|
54881
|
+
} catch {
|
|
54882
|
+
throw new SkillError("BAD_KEYSTORE", "That keystore file is not valid JSON.");
|
|
54883
|
+
}
|
|
54884
|
+
}
|
|
54885
|
+
function timingSafeEqualHex(a, b) {
|
|
54886
|
+
const ab = Buffer.from(String(a).toLowerCase(), "hex");
|
|
54887
|
+
const bb = Buffer.from(String(b).toLowerCase(), "hex");
|
|
54888
|
+
if (ab.length === 0 || ab.length !== bb.length) return false;
|
|
54889
|
+
return crypto5.timingSafeEqual(ab, bb);
|
|
54890
|
+
}
|
|
54891
|
+
function looksLikeKeystore(text) {
|
|
54892
|
+
const t = String(text).trimStart();
|
|
54893
|
+
if (!t.startsWith("{")) return false;
|
|
54894
|
+
try {
|
|
54895
|
+
const o = JSON.parse(t);
|
|
54896
|
+
return Boolean(o && typeof o === "object" && o.crypto);
|
|
54897
|
+
} catch {
|
|
54898
|
+
return false;
|
|
54899
|
+
}
|
|
54900
|
+
}
|
|
54901
|
+
function planKeySource({ family, keyfile, env = process.env }) {
|
|
54902
|
+
if (keyfile) {
|
|
54903
|
+
const resolved = expandHome(keyfile);
|
|
54904
|
+
let text;
|
|
54905
|
+
try {
|
|
54906
|
+
text = fs5.readFileSync(resolved, "utf8");
|
|
54907
|
+
} catch (err) {
|
|
54908
|
+
throw new SkillError(
|
|
54909
|
+
"KEYFILE_UNREADABLE",
|
|
54910
|
+
`Cannot read ${displayPath(resolved)} (${err.code || "unknown error"}).`
|
|
54911
|
+
);
|
|
54912
|
+
}
|
|
54913
|
+
const kind = looksLikeKeystore(text) ? "keystore" : "keypair-file";
|
|
54914
|
+
if (kind === "keypair-file" && family === "evm") {
|
|
54915
|
+
throw new SkillError(
|
|
54916
|
+
"BAD_KEYSTORE",
|
|
54917
|
+
"That file is not a V3 keystore. For EVM, --keyfile expects an encrypted JSON keystore."
|
|
54918
|
+
);
|
|
54919
|
+
}
|
|
54920
|
+
if (kind === "keystore" && family === "solana") {
|
|
54921
|
+
throw new SkillError(
|
|
54922
|
+
"BAD_KEYPAIR_FILE",
|
|
54923
|
+
"That looks like an EVM keystore. For Solana, --keyfile expects a solana-keygen keypair file (a JSON array of bytes)."
|
|
54924
|
+
);
|
|
54925
|
+
}
|
|
54926
|
+
return { kind, path: resolved, label: displayPath(resolved) };
|
|
54927
|
+
}
|
|
54928
|
+
if (family === "solana") {
|
|
54929
|
+
const standard = defaultSolanaKeypairPath();
|
|
54930
|
+
if (fs5.existsSync(standard)) {
|
|
54931
|
+
return { kind: "keypair-file", path: standard, label: displayPath(standard) };
|
|
54932
|
+
}
|
|
54933
|
+
if (env[SOL_KEY_ENV]) return { kind: "env", label: SOL_KEY_ENV };
|
|
54934
|
+
throw new SkillError(
|
|
54935
|
+
"NO_KEY_SOURCE",
|
|
54936
|
+
`No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.`
|
|
54937
|
+
);
|
|
54938
|
+
}
|
|
54939
|
+
if (family === "evm") {
|
|
54940
|
+
const fromEnv = env[EVM_KEYSTORE_ENV];
|
|
54941
|
+
if (fromEnv) {
|
|
54942
|
+
const resolved = expandHome(fromEnv);
|
|
54943
|
+
return { kind: "keystore", path: resolved, label: displayPath(resolved) };
|
|
54944
|
+
}
|
|
54945
|
+
if (env[EVM_KEY_ENV]) return { kind: "env", label: EVM_KEY_ENV };
|
|
54946
|
+
throw new SkillError(
|
|
54947
|
+
"NO_KEY_SOURCE",
|
|
54948
|
+
`No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.`
|
|
54949
|
+
);
|
|
54950
|
+
}
|
|
54951
|
+
throw new SkillError("NO_KEY_SOURCE", `No key source for family "${family}".`);
|
|
54952
|
+
}
|
|
54953
|
+
async function loadKeySource(plan, { family, env = process.env, askPassphrase } = {}) {
|
|
54954
|
+
if (plan.kind === "env") {
|
|
54955
|
+
const name = family === "evm" ? EVM_KEY_ENV : SOL_KEY_ENV;
|
|
54956
|
+
const raw = env[name];
|
|
54957
|
+
if (!raw || !String(raw).trim()) {
|
|
54958
|
+
throw new SkillError("MISSING_KEY", `${name} is not set.`);
|
|
54959
|
+
}
|
|
54960
|
+
if (family === "evm") {
|
|
54961
|
+
return { privateKey: normalizeEvmPrivateKey(raw), label: plan.label, kind: plan.kind };
|
|
54962
|
+
}
|
|
54963
|
+
return { secretKey: decodeSolanaEnvKey(raw), label: plan.label, kind: plan.kind };
|
|
54964
|
+
}
|
|
54965
|
+
assertKeyfileSafe(plan.path);
|
|
54966
|
+
const text = fs5.readFileSync(plan.path, "utf8");
|
|
54967
|
+
if (plan.kind === "keypair-file") {
|
|
54968
|
+
return { secretKey: parseSolanaKeypairJson(text), label: plan.label, kind: plan.kind };
|
|
54969
|
+
}
|
|
54970
|
+
let passphrase = env[KEYSTORE_PASSPHRASE_ENV];
|
|
54971
|
+
if (passphrase === void 0 || passphrase === "") {
|
|
54972
|
+
if (!askPassphrase) {
|
|
54973
|
+
throw new SkillError(
|
|
54974
|
+
"KEYSTORE_PASSPHRASE_REQUIRED",
|
|
54975
|
+
`That keystore needs a passphrase. Run this on a terminal to be prompted, or set ${KEYSTORE_PASSPHRASE_ENV} for unattended use.`
|
|
54976
|
+
);
|
|
54977
|
+
}
|
|
54978
|
+
passphrase = await askPassphrase();
|
|
54979
|
+
}
|
|
54980
|
+
const privateKey = decryptKeystoreV3(text, passphrase);
|
|
54981
|
+
return { privateKey, label: plan.label, kind: plan.kind };
|
|
54982
|
+
}
|
|
54983
|
+
function expandHome(p) {
|
|
54984
|
+
const s = String(p);
|
|
54985
|
+
if (s === "~") return os3.homedir();
|
|
54986
|
+
if (s.startsWith("~/")) return path5.join(os3.homedir(), s.slice(2));
|
|
54987
|
+
return s;
|
|
54988
|
+
}
|
|
54989
|
+
function displayPath(p) {
|
|
54990
|
+
const home = os3.homedir();
|
|
54991
|
+
const s = String(p);
|
|
54992
|
+
return s.startsWith(home + path5.sep) ? `~${s.slice(home.length)}` : s;
|
|
54993
|
+
}
|
|
54994
|
+
|
|
54995
|
+
// scripts/src/lib/dotenv.mjs
|
|
54996
|
+
import fs6 from "node:fs";
|
|
54997
|
+
import path6 from "node:path";
|
|
54998
|
+
var ALLOWED_KEYS = [
|
|
54999
|
+
"ROZO_CHECKOUT_EVM_KEY",
|
|
55000
|
+
"ROZO_CHECKOUT_SOL_KEY",
|
|
55001
|
+
"ROZO_CHECKOUT_EVM_KEYSTORE",
|
|
55002
|
+
"ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
|
|
55003
|
+
];
|
|
55004
|
+
var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
|
|
55005
|
+
function isAllowedKey(key) {
|
|
55006
|
+
return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
|
|
55007
|
+
}
|
|
55008
|
+
function parseDotenv(text) {
|
|
55009
|
+
const vars = {};
|
|
55010
|
+
const lines = String(text).split(/\r?\n/);
|
|
55011
|
+
lines.forEach((rawLine, i) => {
|
|
55012
|
+
const lineNo = i + 1;
|
|
55013
|
+
const line = rawLine.trim();
|
|
55014
|
+
if (!line || line.startsWith("#")) return;
|
|
55015
|
+
const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
55016
|
+
const eq = withoutExport.indexOf("=");
|
|
55017
|
+
if (eq === -1) {
|
|
55018
|
+
throw new SkillError(
|
|
55019
|
+
"BAD_ENV_FILE",
|
|
55020
|
+
`Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
|
|
55021
|
+
);
|
|
55022
|
+
}
|
|
55023
|
+
const key = withoutExport.slice(0, eq).trim();
|
|
55024
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
55025
|
+
throw new SkillError(
|
|
55026
|
+
"BAD_ENV_FILE",
|
|
55027
|
+
`Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
|
|
55028
|
+
);
|
|
55029
|
+
}
|
|
55030
|
+
let value = withoutExport.slice(eq + 1).trim();
|
|
55031
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
55032
|
+
value = value.slice(1, -1);
|
|
55033
|
+
}
|
|
55034
|
+
vars[key] = value;
|
|
55035
|
+
});
|
|
55036
|
+
return vars;
|
|
55037
|
+
}
|
|
55038
|
+
function filterAllowed(vars) {
|
|
55039
|
+
const out2 = {};
|
|
55040
|
+
for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out2[k] = v;
|
|
55041
|
+
return out2;
|
|
55042
|
+
}
|
|
55043
|
+
function resolveEnvFile({ file, cwd = process.cwd() } = {}) {
|
|
55044
|
+
if (file) {
|
|
55045
|
+
const p = path6.resolve(file);
|
|
55046
|
+
if (!fs6.existsSync(p)) {
|
|
55047
|
+
throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
|
|
55048
|
+
}
|
|
55049
|
+
return p;
|
|
55050
|
+
}
|
|
55051
|
+
const fallback = path6.join(cwd, ".env");
|
|
55052
|
+
return fs6.existsSync(fallback) ? fallback : null;
|
|
55053
|
+
}
|
|
55054
|
+
function applyDotenv({ file, cwd = process.cwd(), env = process.env } = {}) {
|
|
55055
|
+
const target = resolveEnvFile({ file, cwd });
|
|
55056
|
+
if (!target) return null;
|
|
55057
|
+
let stat;
|
|
55058
|
+
try {
|
|
55059
|
+
stat = fs6.statSync(target);
|
|
55060
|
+
} catch (err) {
|
|
55061
|
+
throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
|
|
55062
|
+
}
|
|
55063
|
+
if (process.platform !== "win32" && stat.mode & 63) {
|
|
55064
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
55065
|
+
throw new SkillError(
|
|
55066
|
+
"ENV_FILE_PERMISSIONS",
|
|
55067
|
+
`That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
|
|
55068
|
+
);
|
|
55069
|
+
}
|
|
55070
|
+
assertNotTrackedByGit(target);
|
|
55071
|
+
const parsed = parseDotenv(fs6.readFileSync(target, "utf8"));
|
|
55072
|
+
const allowed = filterAllowed(parsed);
|
|
55073
|
+
const applied = [];
|
|
55074
|
+
for (const [k, v] of Object.entries(allowed)) {
|
|
55075
|
+
const existing = env[k];
|
|
55076
|
+
if (existing !== void 0 && String(existing).trim() !== "") continue;
|
|
55077
|
+
env[k] = v;
|
|
55078
|
+
applied.push(k);
|
|
55079
|
+
}
|
|
55080
|
+
return {
|
|
55081
|
+
path: target,
|
|
55082
|
+
applied,
|
|
55083
|
+
ignored: Object.keys(parsed).length - Object.keys(allowed).length
|
|
55084
|
+
};
|
|
55085
|
+
}
|
|
55086
|
+
|
|
55087
|
+
// scripts/src/lib/passphrase.mjs
|
|
55088
|
+
import readline from "node:readline";
|
|
55089
|
+
function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
55090
|
+
if (!process.stdin.isTTY) {
|
|
55091
|
+
throw new SkillError(
|
|
55092
|
+
"KEYSTORE_PASSPHRASE_REQUIRED",
|
|
55093
|
+
"A keystore passphrase is needed but this is not a terminal. Set ROZO_CHECKOUT_KEYSTORE_PASSPHRASE for unattended use."
|
|
55094
|
+
);
|
|
55095
|
+
}
|
|
55096
|
+
return new Promise((resolve, reject) => {
|
|
55097
|
+
const rl = readline.createInterface({
|
|
55098
|
+
input: process.stdin,
|
|
55099
|
+
output: process.stdout,
|
|
55100
|
+
terminal: true
|
|
55101
|
+
});
|
|
55102
|
+
let muted = false;
|
|
55103
|
+
const originalWrite = rl._writeToOutput?.bind(rl);
|
|
55104
|
+
rl._writeToOutput = (chunk) => {
|
|
55105
|
+
if (!muted) {
|
|
55106
|
+
if (originalWrite) originalWrite(chunk);
|
|
55107
|
+
else process.stdout.write(chunk);
|
|
55108
|
+
return;
|
|
55109
|
+
}
|
|
55110
|
+
if (chunk.includes(prompt)) process.stdout.write(prompt);
|
|
55111
|
+
};
|
|
55112
|
+
rl.question(prompt, (answer) => {
|
|
55113
|
+
rl._writeToOutput = originalWrite;
|
|
55114
|
+
rl.close();
|
|
55115
|
+
process.stdout.write("\n");
|
|
55116
|
+
const value = String(answer ?? "");
|
|
55117
|
+
if (!value) {
|
|
55118
|
+
reject(new SkillError("KEYSTORE_PASSPHRASE_REQUIRED", "No passphrase entered."));
|
|
55119
|
+
return;
|
|
55120
|
+
}
|
|
55121
|
+
resolve(value);
|
|
55122
|
+
});
|
|
55123
|
+
muted = true;
|
|
55124
|
+
rl.on("SIGINT", () => {
|
|
55125
|
+
rl._writeToOutput = originalWrite;
|
|
55126
|
+
rl.close();
|
|
55127
|
+
process.stdout.write("\n");
|
|
55128
|
+
reject(new SkillError("CANCELLED", "Cancelled."));
|
|
55129
|
+
});
|
|
55130
|
+
});
|
|
55131
|
+
}
|
|
55132
|
+
|
|
55133
|
+
// scripts/src/lib/presend.mjs
|
|
55134
|
+
async function preflight({
|
|
55135
|
+
rozoPaymentId,
|
|
55136
|
+
expectFamily,
|
|
55137
|
+
senderAddress,
|
|
55138
|
+
send = false,
|
|
55139
|
+
dryRun = false
|
|
55140
|
+
}) {
|
|
55141
|
+
if (!send && !dryRun) {
|
|
55142
|
+
throw new SkillError(
|
|
55143
|
+
"SEND_NOT_OPTED_IN",
|
|
55144
|
+
"Refusing to move funds without the explicit --send flag. Add --dry-run to see exactly what would be signed instead."
|
|
55145
|
+
);
|
|
55146
|
+
}
|
|
55147
|
+
let blacklist;
|
|
55148
|
+
try {
|
|
55149
|
+
blacklist = loadBlacklist();
|
|
55150
|
+
} catch (err) {
|
|
55151
|
+
throw new SkillError(
|
|
55152
|
+
"BLACKLIST_UNAVAILABLE",
|
|
55153
|
+
`Compromised-address list unusable: ${err.message} Refusing to send.`
|
|
55154
|
+
);
|
|
55155
|
+
}
|
|
55156
|
+
const state = readState(rozoPaymentId);
|
|
55157
|
+
if (!state) {
|
|
55158
|
+
throw new SkillError(
|
|
55159
|
+
"NO_ORDER_STATE",
|
|
55160
|
+
"No local record for this order. Run create-order.js first, in the same environment."
|
|
55161
|
+
);
|
|
55162
|
+
}
|
|
55163
|
+
if (state.send) {
|
|
55164
|
+
throw new SkillError(
|
|
55165
|
+
"ALREADY_SENT",
|
|
55166
|
+
`A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice.`,
|
|
55167
|
+
{ send: state.send }
|
|
55168
|
+
);
|
|
55169
|
+
}
|
|
55170
|
+
if (!state.confirmation) {
|
|
55171
|
+
throw new SkillError(
|
|
55172
|
+
"NOT_CONFIRMED",
|
|
55173
|
+
"This order has never been confirmed. Run `create-order.js --url <link> --chain <id> --token <SYM> --confirm` first, show the user the deposit block it prints, and get an explicit yes before sending."
|
|
55174
|
+
);
|
|
55175
|
+
}
|
|
55176
|
+
const payment = await getPayment(rozoPaymentId);
|
|
55177
|
+
const source = payment?.source || {};
|
|
55178
|
+
const family = chainFamily(source.chainId);
|
|
55179
|
+
if (family !== expectFamily) {
|
|
55180
|
+
throw new SkillError(
|
|
55181
|
+
"WRONG_SENDER_SCRIPT",
|
|
55182
|
+
`This order settles on a ${family ?? "unknown"} chain; use the matching send script.`
|
|
55183
|
+
);
|
|
55184
|
+
}
|
|
55185
|
+
const guard = reuseGuard({
|
|
55186
|
+
payment,
|
|
55187
|
+
requested: { chainId: state.source.chainId, tokenSymbol: state.source.tokenSymbol }
|
|
55188
|
+
});
|
|
55189
|
+
if (!guard.ok) {
|
|
55190
|
+
throw new SkillError(guard.code, guard.reason, {
|
|
55191
|
+
...guard.evidence,
|
|
55192
|
+
moneyDetected: guard.moneyDetected
|
|
55193
|
+
});
|
|
55194
|
+
}
|
|
55195
|
+
const deposit = validateDepositInstructions(source);
|
|
55196
|
+
if (!deposit.ok) throw new SkillError(deposit.code, deposit.reason);
|
|
55197
|
+
const liveDigest = depositDigest(source);
|
|
55198
|
+
if (liveDigest !== state.confirmation.depositDigest) {
|
|
55199
|
+
throw new SkillError(
|
|
55200
|
+
"CONFIRMATION_STALE",
|
|
55201
|
+
"The live deposit instructions differ from the ones that were confirmed. Re-run create-order.js --confirm, show the user the new details, and get a fresh yes."
|
|
55202
|
+
);
|
|
55203
|
+
}
|
|
55204
|
+
if (source.receiverAddress !== state.receiverAddress) {
|
|
55205
|
+
throw new SkillError(
|
|
55206
|
+
"DEPOSIT_CHANGED",
|
|
55207
|
+
"The live deposit address differs from the one recorded at create time. Refusing to send."
|
|
55208
|
+
);
|
|
55209
|
+
}
|
|
55210
|
+
if (String(source.amount) !== String(state.amount)) {
|
|
55211
|
+
throw new SkillError(
|
|
55212
|
+
"DEPOSIT_CHANGED",
|
|
55213
|
+
`The live deposit amount (${source.amount}) differs from the recorded one (${state.amount}).`
|
|
55214
|
+
);
|
|
55215
|
+
}
|
|
55216
|
+
if ((source.receiverMemo ?? null) !== (state.receiverMemo ?? null)) {
|
|
55217
|
+
throw new SkillError("DEPOSIT_CHANGED", "The live deposit memo differs from the recorded one.");
|
|
55218
|
+
}
|
|
55219
|
+
const statusNow = await invoiceStatus({ linkId: state.linkId });
|
|
55220
|
+
const expiry = checkExpiry({
|
|
55221
|
+
now: Date.now(),
|
|
55222
|
+
chainId: source.chainId,
|
|
55223
|
+
intentExpiresAt: payment?.expiresAt,
|
|
55224
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
55225
|
+
});
|
|
55226
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
55227
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
55228
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
55229
|
+
assertNotBlacklisted(
|
|
55230
|
+
[
|
|
55231
|
+
{ address: source.receiverAddress, family, role: "deposit address" },
|
|
55232
|
+
{ address: senderAddress, family, role: "sender wallet" }
|
|
55233
|
+
],
|
|
55234
|
+
blacklist
|
|
55235
|
+
);
|
|
55236
|
+
return {
|
|
55237
|
+
payment,
|
|
55238
|
+
source,
|
|
55239
|
+
state,
|
|
55240
|
+
expiry,
|
|
55241
|
+
blacklist,
|
|
55242
|
+
amountAtomic: deposit.amountAtomic,
|
|
55243
|
+
deposit,
|
|
55244
|
+
statusNow
|
|
55245
|
+
};
|
|
55246
|
+
}
|
|
55247
|
+
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt }) {
|
|
55248
|
+
const statusNow = await invoiceStatus({ linkId });
|
|
55249
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
55250
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
55251
|
+
const expiry = checkExpiry({
|
|
55252
|
+
now: Date.now(),
|
|
55253
|
+
chainId,
|
|
55254
|
+
intentExpiresAt,
|
|
55255
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
55256
|
+
});
|
|
55257
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
55258
|
+
return { statusNow, payable, expiry };
|
|
55259
|
+
}
|
|
55260
|
+
|
|
55261
|
+
// scripts/src/lib/outcomes.mjs
|
|
55262
|
+
function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen = void 0 }) {
|
|
55263
|
+
const seen = receiptSeen ?? (receiptStatus !== null && receiptStatus !== void 0);
|
|
55264
|
+
if (executionError) {
|
|
55265
|
+
return {
|
|
55266
|
+
state: "failed",
|
|
55267
|
+
success: false,
|
|
55268
|
+
exitCode: EXIT_ERROR,
|
|
55269
|
+
code: "TX_FAILED",
|
|
55270
|
+
recordStatus: "failed"
|
|
55271
|
+
};
|
|
55272
|
+
}
|
|
55273
|
+
if (!seen) {
|
|
55274
|
+
return {
|
|
55275
|
+
state: "unconfirmed",
|
|
55276
|
+
success: true,
|
|
55277
|
+
exitCode: EXIT_UNCONFIRMED,
|
|
55278
|
+
code: null,
|
|
55279
|
+
recordStatus: "submitted"
|
|
55280
|
+
};
|
|
55281
|
+
}
|
|
55282
|
+
if (receiptStatus === "success") {
|
|
55283
|
+
return {
|
|
55284
|
+
state: "confirmed",
|
|
55285
|
+
success: true,
|
|
55286
|
+
exitCode: EXIT_OK,
|
|
55287
|
+
code: null,
|
|
55288
|
+
recordStatus: "confirmed"
|
|
55289
|
+
};
|
|
55290
|
+
}
|
|
55291
|
+
return {
|
|
55292
|
+
state: "reverted",
|
|
55293
|
+
success: false,
|
|
55294
|
+
exitCode: EXIT_ERROR,
|
|
55295
|
+
code: "TX_REVERTED",
|
|
55296
|
+
recordStatus: "failed"
|
|
55297
|
+
};
|
|
55298
|
+
}
|
|
55299
|
+
|
|
54836
55300
|
// node_modules/viem/_esm/accounts/privateKeyToAccount.js
|
|
54837
55301
|
init_secp256k1();
|
|
54838
55302
|
init_toHex();
|
|
@@ -55030,15 +55494,12 @@ async function main4(argv) {
|
|
|
55030
55494
|
const args = parseArgs(argv);
|
|
55031
55495
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
55032
55496
|
assertNoTrackedDotEnv();
|
|
55033
|
-
const
|
|
55034
|
-
|
|
55035
|
-
|
|
55036
|
-
|
|
55037
|
-
`${EVM_KEY_ENV} must be a 0x-prefixed 32-byte hex private key.`
|
|
55038
|
-
);
|
|
55039
|
-
}
|
|
55040
|
-
const account = privateKeyToAccount(key);
|
|
55497
|
+
const dotenv = applyDotenv({ file: args["env-file"] });
|
|
55498
|
+
const plan = planKeySource({ family: "evm", keyfile: args.keyfile });
|
|
55499
|
+
const loaded = await loadKeySource(plan, { family: "evm", askPassphrase: promptPassphrase });
|
|
55500
|
+
const account = privateKeyToAccount(loaded.privateKey);
|
|
55041
55501
|
const sender = account.address;
|
|
55502
|
+
const keySource = loaded.label;
|
|
55042
55503
|
const dryRun = Boolean(args["dry-run"]);
|
|
55043
55504
|
const { source, state, expiry, amountAtomic, payment } = await preflight({
|
|
55044
55505
|
rozoPaymentId,
|
|
@@ -55119,6 +55580,9 @@ async function main4(argv) {
|
|
|
55119
55580
|
fromMasked: maskAddress2(sender)
|
|
55120
55581
|
},
|
|
55121
55582
|
confirmedAt: state.confirmation?.confirmedAt ?? null,
|
|
55583
|
+
keySource,
|
|
55584
|
+
// Key NAMES only — a value from a .env is never echoed.
|
|
55585
|
+
envFile: dotenv ? { path: dotenv.path, applied: dotenv.applied } : null,
|
|
55122
55586
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4),
|
|
55123
55587
|
note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
|
|
55124
55588
|
});
|
|
@@ -55238,7 +55702,8 @@ async function main4(argv) {
|
|
|
55238
55702
|
tokenSymbol: source.tokenSymbol,
|
|
55239
55703
|
amount: source.amount,
|
|
55240
55704
|
toMasked: maskAddress2(to),
|
|
55241
|
-
fromMasked: maskAddress2(sender)
|
|
55705
|
+
fromMasked: maskAddress2(sender),
|
|
55706
|
+
keySource
|
|
55242
55707
|
},
|
|
55243
55708
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
55244
55709
|
guidance: succeeded ? "On-chain confirmation is not settlement. Poll status.js until the state is `settled`." : "The transfer reverted, so no funds moved \u2014 but this order stays locked against a second automated send. Investigate before retrying anything."
|
|
@@ -56968,59 +57433,18 @@ async function getAssociatedTokenAddress(mint, owner, allowOwnerOffCurve = false
|
|
|
56968
57433
|
// scripts/src/send-sol.mjs
|
|
56969
57434
|
var MEMO_PROGRAM_ID = new import_web36.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
56970
57435
|
var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
|
|
56971
|
-
function decodeSecretKey(raw) {
|
|
56972
|
-
const s = raw.trim();
|
|
56973
|
-
if (s.startsWith("[")) {
|
|
56974
|
-
let arr;
|
|
56975
|
-
try {
|
|
56976
|
-
arr = JSON.parse(s);
|
|
56977
|
-
} catch {
|
|
56978
|
-
throw new SkillError("BAD_KEY_FORMAT", `${SOL_KEY_ENV} looks like a JSON array but is malformed.`);
|
|
56979
|
-
}
|
|
56980
|
-
if (!Array.isArray(arr) || arr.length !== 64 && arr.length !== 32) {
|
|
56981
|
-
throw new SkillError("BAD_KEY_FORMAT", `${SOL_KEY_ENV} array must be 32 or 64 bytes.`);
|
|
56982
|
-
}
|
|
56983
|
-
return Uint8Array.from(arr);
|
|
56984
|
-
}
|
|
56985
|
-
try {
|
|
56986
|
-
const bytes = base58Decode(s);
|
|
56987
|
-
if (bytes.length !== 64 && bytes.length !== 32) {
|
|
56988
|
-
throw new Error("unexpected length");
|
|
56989
|
-
}
|
|
56990
|
-
return bytes;
|
|
56991
|
-
} catch {
|
|
56992
|
-
throw new SkillError(
|
|
56993
|
-
"BAD_KEY_FORMAT",
|
|
56994
|
-
`${SOL_KEY_ENV} must be a base58 secret key or a JSON byte array.`
|
|
56995
|
-
);
|
|
56996
|
-
}
|
|
56997
|
-
}
|
|
56998
57436
|
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
56999
|
-
function base58Decode(str) {
|
|
57000
|
-
let num2 = 0n;
|
|
57001
|
-
for (const ch of str) {
|
|
57002
|
-
const idx = B58_ALPHABET.indexOf(ch);
|
|
57003
|
-
if (idx < 0) throw new Error("bad base58");
|
|
57004
|
-
num2 = num2 * 58n + BigInt(idx);
|
|
57005
|
-
}
|
|
57006
|
-
const bytes = [];
|
|
57007
|
-
while (num2 > 0n) {
|
|
57008
|
-
bytes.unshift(Number(num2 % 256n));
|
|
57009
|
-
num2 /= 256n;
|
|
57010
|
-
}
|
|
57011
|
-
for (const ch of str) {
|
|
57012
|
-
if (ch === "1") bytes.unshift(0);
|
|
57013
|
-
else break;
|
|
57014
|
-
}
|
|
57015
|
-
return Uint8Array.from(bytes);
|
|
57016
|
-
}
|
|
57017
57437
|
async function main5(argv) {
|
|
57018
57438
|
const args = parseArgs(argv);
|
|
57019
57439
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
57020
57440
|
assertNoTrackedDotEnv();
|
|
57021
|
-
const
|
|
57441
|
+
const dotenv = applyDotenv({ file: args["env-file"] });
|
|
57442
|
+
const plan = planKeySource({ family: "solana", keyfile: args.keyfile });
|
|
57443
|
+
const loaded = await loadKeySource(plan, { family: "solana", askPassphrase: promptPassphrase });
|
|
57444
|
+
const secret = loaded.secretKey;
|
|
57022
57445
|
const keypair = secret.length === 64 ? import_web36.Keypair.fromSecretKey(secret) : import_web36.Keypair.fromSeed(secret);
|
|
57023
57446
|
const sender = keypair.publicKey.toBase58();
|
|
57447
|
+
const keySource = loaded.label;
|
|
57024
57448
|
const dryRun = Boolean(args["dry-run"]);
|
|
57025
57449
|
const { source, state, expiry, amountAtomic, payment } = await preflight({
|
|
57026
57450
|
rozoPaymentId,
|
|
@@ -57120,6 +57544,9 @@ async function main5(argv) {
|
|
|
57120
57544
|
withMemo: Boolean(source.receiverMemo)
|
|
57121
57545
|
},
|
|
57122
57546
|
confirmedAt: state.confirmation?.confirmedAt ?? null,
|
|
57547
|
+
keySource,
|
|
57548
|
+
// Key NAMES only — a value from a .env is never echoed.
|
|
57549
|
+
envFile: dotenv ? { path: dotenv.path, applied: dotenv.applied } : null,
|
|
57123
57550
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4),
|
|
57124
57551
|
note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
|
|
57125
57552
|
});
|
|
@@ -57275,7 +57702,8 @@ async function main5(argv) {
|
|
|
57275
57702
|
amount: source.amount,
|
|
57276
57703
|
toMasked: maskAddress2(source.receiverAddress),
|
|
57277
57704
|
fromMasked: maskAddress2(sender),
|
|
57278
|
-
withMemo: Boolean(source.receiverMemo)
|
|
57705
|
+
withMemo: Boolean(source.receiverMemo),
|
|
57706
|
+
keySource
|
|
57279
57707
|
},
|
|
57280
57708
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
57281
57709
|
guidance: "On-chain confirmation is not settlement. Poll status.js until the state is `settled`."
|
|
@@ -57350,7 +57778,7 @@ function targetToArgs(target) {
|
|
|
57350
57778
|
return ["--link-id", linkId];
|
|
57351
57779
|
}
|
|
57352
57780
|
async function ask(question) {
|
|
57353
|
-
const rl =
|
|
57781
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
57354
57782
|
try {
|
|
57355
57783
|
return await new Promise((resolve) => rl.question(question, resolve));
|
|
57356
57784
|
} finally {
|
|
@@ -57439,7 +57867,7 @@ async function pickSource({ invoiceUsd, saved, fresh }) {
|
|
|
57439
57867
|
throw new CliError("BAD_CHOICE", "No valid choice after three attempts.");
|
|
57440
57868
|
}
|
|
57441
57869
|
async function askYesNo(question) {
|
|
57442
|
-
const rl =
|
|
57870
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
57443
57871
|
try {
|
|
57444
57872
|
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
57445
57873
|
return /^(y|yes)$/i.test(String(answer).trim());
|
|
@@ -57623,6 +58051,8 @@ async function cmdPay(opts) {
|
|
|
57623
58051
|
if (opts.dryRun) sendArgs.push("--dry-run");
|
|
57624
58052
|
else sendArgs.push("--send");
|
|
57625
58053
|
if (opts.rpc) sendArgs.push("--rpc", opts.rpc);
|
|
58054
|
+
if (opts.keyfile) sendArgs.push("--keyfile", opts.keyfile);
|
|
58055
|
+
if (opts.envFile) sendArgs.push("--env-file", opts.envFile);
|
|
57626
58056
|
if (!opts.json) out(dim(opts.dryRun ? " Preparing (dry run)\u2026" : " Sending\u2026"));
|
|
57627
58057
|
const sent = await step(sender, sendArgs);
|
|
57628
58058
|
sendResult = sent.payload;
|
|
@@ -57636,10 +58066,14 @@ async function cmdPay(opts) {
|
|
|
57636
58066
|
printMoneyWarning(sent.payload);
|
|
57637
58067
|
return sent.exitCode;
|
|
57638
58068
|
} else if (opts.dryRun) {
|
|
58069
|
+
if (sent.payload.keySource) out(` ${dim(`would sign with ${sent.payload.keySource}`)}`);
|
|
57639
58070
|
out(` ${green("\u2713")} Dry run only \u2014 nothing was signed or broadcast.`);
|
|
57640
58071
|
out();
|
|
57641
58072
|
return sent.exitCode;
|
|
57642
58073
|
} else {
|
|
58074
|
+
if (sent.payload.sent?.keySource) {
|
|
58075
|
+
out(` ${dim(`signing with ${sent.payload.sent.keySource}`)}`);
|
|
58076
|
+
}
|
|
57643
58077
|
out(` ${green("\u2713")} Sent. tx ${sent.payload.txHash}`);
|
|
57644
58078
|
out();
|
|
57645
58079
|
savePrefs({ lastPreset: chosenPreset || presetFor(chainId, tokenSymbol) });
|