@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.
@@ -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: path4 } = failure;
20461
- const msg = path4.length === 0 ? message : `At path: ${path4.join(".")} -- ${message}`;
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: path4, branch } = context;
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: path4[path4.length - 1],
20514
- path: path4,
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: path4 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20533
- const ctx = { path: path4, branch, mask: mask2 };
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 ? path4 : [...path4, k],
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 crypto5 from "crypto";
21260
+ import crypto6 from "crypto";
21261
21261
  function rng() {
21262
21262
  if (poolPtr > rnds8Pool.length - 16) {
21263
- crypto5.randomFillSync(rnds8Pool);
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 crypto6 from "crypto";
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 crypto6.createHash("md5").update(bytes).digest();
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 crypto7 from "crypto";
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 crypto7.createHash("sha1").update(bytes).digest();
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 path4 = url.path;
22476
- if (path4.length === 0) {
22475
+ const path7 = url.path;
22476
+ if (path7.length === 0) {
22477
22477
  return;
22478
22478
  }
22479
- if (url.scheme === "file" && path4.length === 1 && isNormalizedWindowsDriveLetter(path4[0])) {
22479
+ if (url.scheme === "file" && path7.length === 1 && isNormalizedWindowsDriveLetter(path7[0])) {
22480
22480
  return;
22481
22481
  }
22482
- path4.pop();
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 fs5 = __require("fs");
24778
- var path4 = __require("path");
24779
- var os3 = __require("os");
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 || os3.arch();
24786
- var platform = process.env.npm_config_platform || os3.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 = path4.resolve(dir || ".");
24795
+ dir = path7.resolve(dir || ".");
24796
24796
  try {
24797
- var name = runtimeRequire(path4.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
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(path4.join(dir, "build/Release"), matchBuild);
24802
+ var release = getFirst(path7.join(dir, "build/Release"), matchBuild);
24803
24803
  if (release) return release;
24804
- var debug = getFirst(path4.join(dir, "build/Debug"), matchBuild);
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(path4.dirname(process.execPath));
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(path4.join(dir2, "prebuilds")).map(parseTuple);
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 = path4.join(dir2, "prebuilds", tuple.name);
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 path4.join(prebuilds, winner.file);
24833
+ if (winner) return path7.join(prebuilds, winner.file);
24834
24834
  }
24835
24835
  };
24836
24836
  function readdirSync(dir) {
24837
24837
  try {
24838
- return fs5.readdirSync(dir);
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] && path4.join(dir, 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" && fs5.existsSync("/etc/alpine-release");
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 path4 = rest.substring(firstSlash + 1);
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
- path4 = path4.replace(/^(.+)\|/, "$1:");
41176
+ path7 = path7.replace(/^(.+)\|/, "$1:");
41177
41177
  if (sep == "\\") {
41178
- path4 = path4.replace(/\//g, "\\");
41178
+ path7 = path7.replace(/\//g, "\\");
41179
41179
  }
41180
- if (/^.+\:/.test(path4)) {
41180
+ if (/^.+\:/.test(path7)) {
41181
41181
  } else {
41182
- path4 = sep + path4;
41182
+ path7 = sep + path7;
41183
41183
  }
41184
- return host + path4;
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 fs5 = __require("fs");
41193
- var path4 = __require("path");
41192
+ var fs7 = __require("fs");
41193
+ var path7 = __require("path");
41194
41194
  var fileURLToPath2 = require_file_uri_to_path();
41195
- var join = path4.join;
41196
- var dirname = path4.dirname;
41197
- var exists = fs5.accessSync && function(path5) {
41195
+ var join = path7.join;
41196
+ var dirname = path7.dirname;
41197
+ var exists = fs7.accessSync && function(path8) {
41198
41198
  try {
41199
- fs5.accessSync(path5);
41199
+ fs7.accessSync(path8);
41200
41200
  } catch (e) {
41201
41201
  return false;
41202
41202
  }
41203
41203
  return true;
41204
- } || fs5.existsSync || path4.existsSync;
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 (path4.extname(opts.bindings) != ".node") {
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 readline from "node:readline";
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
- This is the only option that needs a key, read from
41926
- ROZO_CHECKOUT_EVM_KEY or ROZO_CHECKOUT_SOL_KEY. A single
41927
- payment may not exceed $1,100; there is no override.
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 EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
43642
- var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
43643
- function readKey(envName) {
43644
- const v = process.env[envName];
43645
- if (!v || !String(v).trim()) {
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
- "MISSING_KEY",
43648
- `${envName} is not set. Export it in the shell that runs this script; the script never accepts a key on the command line.`
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
- var ENV_FILE_RE = /^\.env(\..+)?$/;
43654
- var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
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/presend.mjs
43707
- async function preflight({
43708
- rozoPaymentId,
43709
- expectFamily,
43710
- senderAddress,
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 key = readKey(EVM_KEY_ENV);
55034
- if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
55035
- throw new SkillError(
55036
- "BAD_KEY_FORMAT",
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 secret = decodeSecretKey(readKey(SOL_KEY_ENV));
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 = readline.createInterface({ input: process.stdin, output: process.stdout });
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 = readline.createInterface({ input: process.stdin, output: process.stdout });
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) });