@rozoai/checkout 0.1.1 → 0.1.3
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 +137 -12
- package/docs/QUICKSTART.es.md +128 -11
- package/docs/QUICKSTART.ja.md +110 -11
- package/docs/QUICKSTART.md +126 -12
- package/docs/QUICKSTART.zh.md +99 -11
- package/docs/README.es.md +114 -9
- package/docs/README.ja.md +98 -9
- package/docs/README.zh.md +90 -10
- package/docs/safety.md +7 -3
- package/llms.txt +45 -3
- package/package.json +1 -1
- package/scripts/dist/cli.js +803 -295
- package/scripts/dist/create-order.js +53 -2
- package/scripts/dist/quote.js +4 -0
- package/scripts/dist/send-evm.js +6699 -6237
- package/scripts/dist/send-sol.js +1731 -634
- package/scripts/dist/status.js +39 -1
package/scripts/dist/cli.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
var __create = Object.create;
|
|
5
9
|
var __defProp = Object.defineProperty;
|
|
6
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -20457,8 +20461,8 @@ var require_dist = __commonJS({
|
|
|
20457
20461
|
constructor(failure, failures) {
|
|
20458
20462
|
let cached2;
|
|
20459
20463
|
const { message, explanation, ...rest } = failure;
|
|
20460
|
-
const { path:
|
|
20461
|
-
const msg =
|
|
20464
|
+
const { path: path7 } = failure;
|
|
20465
|
+
const msg = path7.length === 0 ? message : `At path: ${path7.join(".")} -- ${message}`;
|
|
20462
20466
|
super(explanation ?? msg);
|
|
20463
20467
|
if (explanation != null)
|
|
20464
20468
|
this.cause = msg;
|
|
@@ -20503,15 +20507,15 @@ var require_dist = __commonJS({
|
|
|
20503
20507
|
} else if (typeof result === "string") {
|
|
20504
20508
|
result = { message: result };
|
|
20505
20509
|
}
|
|
20506
|
-
const { path:
|
|
20510
|
+
const { path: path7, branch } = context;
|
|
20507
20511
|
const { type: type2 } = struct6;
|
|
20508
20512
|
const { refinement, message = `Expected a value of type \`${type2}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result;
|
|
20509
20513
|
return {
|
|
20510
20514
|
value,
|
|
20511
20515
|
type: type2,
|
|
20512
20516
|
refinement,
|
|
20513
|
-
key:
|
|
20514
|
-
path:
|
|
20517
|
+
key: path7[path7.length - 1],
|
|
20518
|
+
path: path7,
|
|
20515
20519
|
branch,
|
|
20516
20520
|
...result,
|
|
20517
20521
|
message
|
|
@@ -20529,8 +20533,8 @@ var require_dist = __commonJS({
|
|
|
20529
20533
|
}
|
|
20530
20534
|
}
|
|
20531
20535
|
function* run6(value, struct6, options = {}) {
|
|
20532
|
-
const { path:
|
|
20533
|
-
const ctx = { path:
|
|
20536
|
+
const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
|
|
20537
|
+
const ctx = { path: path7, branch, mask: mask2 };
|
|
20534
20538
|
if (coerce2) {
|
|
20535
20539
|
value = struct6.coercer(value, ctx);
|
|
20536
20540
|
}
|
|
@@ -20542,7 +20546,7 @@ var require_dist = __commonJS({
|
|
|
20542
20546
|
}
|
|
20543
20547
|
for (let [k, v, s] of struct6.entries(value, ctx)) {
|
|
20544
20548
|
const ts = run6(v, s, {
|
|
20545
|
-
path: k === void 0 ?
|
|
20549
|
+
path: k === void 0 ? path7 : [...path7, k],
|
|
20546
20550
|
branch: k === void 0 ? branch : [...branch, v],
|
|
20547
20551
|
coerce: coerce2,
|
|
20548
20552
|
mask: mask2,
|
|
@@ -21257,10 +21261,10 @@ var require_dist = __commonJS({
|
|
|
21257
21261
|
});
|
|
21258
21262
|
|
|
21259
21263
|
// node_modules/uuid/dist/esm-node/rng.js
|
|
21260
|
-
import
|
|
21264
|
+
import crypto6 from "crypto";
|
|
21261
21265
|
function rng() {
|
|
21262
21266
|
if (poolPtr > rnds8Pool.length - 16) {
|
|
21263
|
-
|
|
21267
|
+
crypto6.randomFillSync(rnds8Pool);
|
|
21264
21268
|
poolPtr = 0;
|
|
21265
21269
|
}
|
|
21266
21270
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
@@ -21460,14 +21464,14 @@ var init_v35 = __esm({
|
|
|
21460
21464
|
});
|
|
21461
21465
|
|
|
21462
21466
|
// node_modules/uuid/dist/esm-node/md5.js
|
|
21463
|
-
import
|
|
21467
|
+
import crypto7 from "crypto";
|
|
21464
21468
|
function md5(bytes) {
|
|
21465
21469
|
if (Array.isArray(bytes)) {
|
|
21466
21470
|
bytes = Buffer.from(bytes);
|
|
21467
21471
|
} else if (typeof bytes === "string") {
|
|
21468
21472
|
bytes = Buffer.from(bytes, "utf8");
|
|
21469
21473
|
}
|
|
21470
|
-
return
|
|
21474
|
+
return crypto7.createHash("md5").update(bytes).digest();
|
|
21471
21475
|
}
|
|
21472
21476
|
var md5_default;
|
|
21473
21477
|
var init_md5 = __esm({
|
|
@@ -21512,14 +21516,14 @@ var init_v4 = __esm({
|
|
|
21512
21516
|
});
|
|
21513
21517
|
|
|
21514
21518
|
// node_modules/uuid/dist/esm-node/sha1.js
|
|
21515
|
-
import
|
|
21519
|
+
import crypto8 from "crypto";
|
|
21516
21520
|
function sha1(bytes) {
|
|
21517
21521
|
if (Array.isArray(bytes)) {
|
|
21518
21522
|
bytes = Buffer.from(bytes);
|
|
21519
21523
|
} else if (typeof bytes === "string") {
|
|
21520
21524
|
bytes = Buffer.from(bytes, "utf8");
|
|
21521
21525
|
}
|
|
21522
|
-
return
|
|
21526
|
+
return crypto8.createHash("sha1").update(bytes).digest();
|
|
21523
21527
|
}
|
|
21524
21528
|
var sha1_default;
|
|
21525
21529
|
var init_sha1 = __esm({
|
|
@@ -22472,14 +22476,14 @@ var require_url_state_machine = __commonJS({
|
|
|
22472
22476
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
22473
22477
|
}
|
|
22474
22478
|
function shortenPath(url) {
|
|
22475
|
-
const
|
|
22476
|
-
if (
|
|
22479
|
+
const path7 = url.path;
|
|
22480
|
+
if (path7.length === 0) {
|
|
22477
22481
|
return;
|
|
22478
22482
|
}
|
|
22479
|
-
if (url.scheme === "file" &&
|
|
22483
|
+
if (url.scheme === "file" && path7.length === 1 && isNormalizedWindowsDriveLetter(path7[0])) {
|
|
22480
22484
|
return;
|
|
22481
22485
|
}
|
|
22482
|
-
|
|
22486
|
+
path7.pop();
|
|
22483
22487
|
}
|
|
22484
22488
|
function includesCredentials(url) {
|
|
22485
22489
|
return url.username !== "" || url.password !== "";
|
|
@@ -24774,16 +24778,16 @@ var require_constants = __commonJS({
|
|
|
24774
24778
|
// node_modules/node-gyp-build/node-gyp-build.js
|
|
24775
24779
|
var require_node_gyp_build = __commonJS({
|
|
24776
24780
|
"node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
24777
|
-
var
|
|
24778
|
-
var
|
|
24779
|
-
var
|
|
24781
|
+
var fs7 = __require("fs");
|
|
24782
|
+
var path7 = __require("path");
|
|
24783
|
+
var os4 = __require("os");
|
|
24780
24784
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
24781
24785
|
var vars = process.config && process.config.variables || {};
|
|
24782
24786
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
24783
24787
|
var abi2 = process.versions.modules;
|
|
24784
24788
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
24785
|
-
var arch = process.env.npm_config_arch ||
|
|
24786
|
-
var platform = process.env.npm_config_platform ||
|
|
24789
|
+
var arch = process.env.npm_config_arch || os4.arch();
|
|
24790
|
+
var platform = process.env.npm_config_platform || os4.platform();
|
|
24787
24791
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
24788
24792
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
24789
24793
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -24792,21 +24796,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
24792
24796
|
return runtimeRequire(load.resolve(dir));
|
|
24793
24797
|
}
|
|
24794
24798
|
load.resolve = load.path = function(dir) {
|
|
24795
|
-
dir =
|
|
24799
|
+
dir = path7.resolve(dir || ".");
|
|
24796
24800
|
try {
|
|
24797
|
-
var name = runtimeRequire(
|
|
24801
|
+
var name = runtimeRequire(path7.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
24798
24802
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
24799
24803
|
} catch (err) {
|
|
24800
24804
|
}
|
|
24801
24805
|
if (!prebuildsOnly) {
|
|
24802
|
-
var release = getFirst(
|
|
24806
|
+
var release = getFirst(path7.join(dir, "build/Release"), matchBuild);
|
|
24803
24807
|
if (release) return release;
|
|
24804
|
-
var debug = getFirst(
|
|
24808
|
+
var debug = getFirst(path7.join(dir, "build/Debug"), matchBuild);
|
|
24805
24809
|
if (debug) return debug;
|
|
24806
24810
|
}
|
|
24807
24811
|
var prebuild = resolve(dir);
|
|
24808
24812
|
if (prebuild) return prebuild;
|
|
24809
|
-
var nearby = resolve(
|
|
24813
|
+
var nearby = resolve(path7.dirname(process.execPath));
|
|
24810
24814
|
if (nearby) return nearby;
|
|
24811
24815
|
var target = [
|
|
24812
24816
|
"platform=" + platform,
|
|
@@ -24823,26 +24827,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
24823
24827
|
].filter(Boolean).join(" ");
|
|
24824
24828
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
24825
24829
|
function resolve(dir2) {
|
|
24826
|
-
var tuples = readdirSync(
|
|
24830
|
+
var tuples = readdirSync(path7.join(dir2, "prebuilds")).map(parseTuple);
|
|
24827
24831
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
24828
24832
|
if (!tuple) return;
|
|
24829
|
-
var prebuilds =
|
|
24833
|
+
var prebuilds = path7.join(dir2, "prebuilds", tuple.name);
|
|
24830
24834
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
24831
24835
|
var candidates = parsed.filter(matchTags(runtime, abi2));
|
|
24832
24836
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
24833
|
-
if (winner) return
|
|
24837
|
+
if (winner) return path7.join(prebuilds, winner.file);
|
|
24834
24838
|
}
|
|
24835
24839
|
};
|
|
24836
24840
|
function readdirSync(dir) {
|
|
24837
24841
|
try {
|
|
24838
|
-
return
|
|
24842
|
+
return fs7.readdirSync(dir);
|
|
24839
24843
|
} catch (err) {
|
|
24840
24844
|
return [];
|
|
24841
24845
|
}
|
|
24842
24846
|
}
|
|
24843
24847
|
function getFirst(dir, filter) {
|
|
24844
24848
|
var files = readdirSync(dir).filter(filter);
|
|
24845
|
-
return files[0] &&
|
|
24849
|
+
return files[0] && path7.join(dir, files[0]);
|
|
24846
24850
|
}
|
|
24847
24851
|
function matchBuild(name) {
|
|
24848
24852
|
return /\.node$/.test(name);
|
|
@@ -24929,7 +24933,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
24929
24933
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
24930
24934
|
}
|
|
24931
24935
|
function isAlpine(platform2) {
|
|
24932
|
-
return platform2 === "linux" &&
|
|
24936
|
+
return platform2 === "linux" && fs7.existsSync("/etc/alpine-release");
|
|
24933
24937
|
}
|
|
24934
24938
|
load.parseTags = parseTags;
|
|
24935
24939
|
load.matchTags = matchTags;
|
|
@@ -41168,20 +41172,20 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41168
41172
|
var rest = decodeURI(uri.substring(7));
|
|
41169
41173
|
var firstSlash = rest.indexOf("/");
|
|
41170
41174
|
var host = rest.substring(0, firstSlash);
|
|
41171
|
-
var
|
|
41175
|
+
var path7 = rest.substring(firstSlash + 1);
|
|
41172
41176
|
if ("localhost" == host) host = "";
|
|
41173
41177
|
if (host) {
|
|
41174
41178
|
host = sep + sep + host;
|
|
41175
41179
|
}
|
|
41176
|
-
|
|
41180
|
+
path7 = path7.replace(/^(.+)\|/, "$1:");
|
|
41177
41181
|
if (sep == "\\") {
|
|
41178
|
-
|
|
41182
|
+
path7 = path7.replace(/\//g, "\\");
|
|
41179
41183
|
}
|
|
41180
|
-
if (/^.+\:/.test(
|
|
41184
|
+
if (/^.+\:/.test(path7)) {
|
|
41181
41185
|
} else {
|
|
41182
|
-
|
|
41186
|
+
path7 = sep + path7;
|
|
41183
41187
|
}
|
|
41184
|
-
return host +
|
|
41188
|
+
return host + path7;
|
|
41185
41189
|
}
|
|
41186
41190
|
}
|
|
41187
41191
|
});
|
|
@@ -41189,19 +41193,19 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41189
41193
|
// node_modules/bindings/bindings.js
|
|
41190
41194
|
var require_bindings = __commonJS({
|
|
41191
41195
|
"node_modules/bindings/bindings.js"(exports, module) {
|
|
41192
|
-
var
|
|
41193
|
-
var
|
|
41196
|
+
var fs7 = __require("fs");
|
|
41197
|
+
var path7 = __require("path");
|
|
41194
41198
|
var fileURLToPath2 = require_file_uri_to_path();
|
|
41195
|
-
var join =
|
|
41196
|
-
var dirname =
|
|
41197
|
-
var exists =
|
|
41199
|
+
var join = path7.join;
|
|
41200
|
+
var dirname = path7.dirname;
|
|
41201
|
+
var exists = fs7.accessSync && function(path8) {
|
|
41198
41202
|
try {
|
|
41199
|
-
|
|
41203
|
+
fs7.accessSync(path8);
|
|
41200
41204
|
} catch (e) {
|
|
41201
41205
|
return false;
|
|
41202
41206
|
}
|
|
41203
41207
|
return true;
|
|
41204
|
-
} ||
|
|
41208
|
+
} || fs7.existsSync || path7.existsSync;
|
|
41205
41209
|
var defaults = {
|
|
41206
41210
|
arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
|
|
41207
41211
|
compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
|
|
@@ -41246,7 +41250,7 @@ var require_bindings = __commonJS({
|
|
|
41246
41250
|
if (!opts.module_root) {
|
|
41247
41251
|
opts.module_root = exports.getRoot(exports.getFileName());
|
|
41248
41252
|
}
|
|
41249
|
-
if (
|
|
41253
|
+
if (path7.extname(opts.bindings) != ".node") {
|
|
41250
41254
|
opts.bindings += ".node";
|
|
41251
41255
|
}
|
|
41252
41256
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
@@ -41403,7 +41407,7 @@ process.on("warning", (warning) => {
|
|
|
41403
41407
|
});
|
|
41404
41408
|
|
|
41405
41409
|
// scripts/src/cli.mjs
|
|
41406
|
-
import
|
|
41410
|
+
import readline2 from "node:readline";
|
|
41407
41411
|
import { createRequire } from "node:module";
|
|
41408
41412
|
|
|
41409
41413
|
// scripts/src/lib/output.mjs
|
|
@@ -41655,6 +41659,7 @@ function formatAmount(source) {
|
|
|
41655
41659
|
function chainName(chainId) {
|
|
41656
41660
|
return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
|
|
41657
41661
|
}
|
|
41662
|
+
var STELLAR_MEMO_TYPE = "MEMO_TEXT";
|
|
41658
41663
|
function chainFamily(chainId) {
|
|
41659
41664
|
return CHAIN_FAMILY[String(chainId)] || CHAIN_FAMILY[chainId] || null;
|
|
41660
41665
|
}
|
|
@@ -41779,7 +41784,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
41779
41784
|
"watch",
|
|
41780
41785
|
"fresh"
|
|
41781
41786
|
]);
|
|
41782
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout", "payer"]);
|
|
41787
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout", "payer", "keyfile", "env-file"]);
|
|
41783
41788
|
function parseCliArgs(argv) {
|
|
41784
41789
|
const positional = [];
|
|
41785
41790
|
const flags = {};
|
|
@@ -41897,7 +41902,9 @@ function parseCliArgs(argv) {
|
|
|
41897
41902
|
timeout,
|
|
41898
41903
|
rpc: flags.rpc,
|
|
41899
41904
|
payer: flags.payer,
|
|
41900
|
-
fresh: flags.fresh === true
|
|
41905
|
+
fresh: flags.fresh === true,
|
|
41906
|
+
keyfile: flags.keyfile,
|
|
41907
|
+
envFile: flags["env-file"]
|
|
41901
41908
|
};
|
|
41902
41909
|
}
|
|
41903
41910
|
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link with BTC Lightning,
|
|
@@ -41922,9 +41929,15 @@ OPTIONS
|
|
|
41922
41929
|
--with <coin> which coin to pay with. Omit it on a terminal and you get a
|
|
41923
41930
|
numbered list to choose from; scripts and agents must pass it.
|
|
41924
41931
|
--send optional. Sign from a hot wallet instead of paying yourself.
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
payment may not exceed $1,100; there is
|
|
41932
|
+
The only option that needs a key. On Solana it uses your
|
|
41933
|
+
existing ~/.config/solana/id.json; on EVM an encrypted JSON
|
|
41934
|
+
keystore. A single payment may not exceed $1,100; there is
|
|
41935
|
+
no override.
|
|
41936
|
+
--keyfile <p> with --send: the key to sign with. A solana-keygen keypair
|
|
41937
|
+
file, or an encrypted V3 keystore for EVM (passphrase is
|
|
41938
|
+
prompted, never passed as a flag).
|
|
41939
|
+
--env-file <p> with --send: read hot-wallet settings from this file instead
|
|
41940
|
+
of ./.env. Only ROZO_CHECKOUT_* keys are read from it.
|
|
41928
41941
|
--yes, -y skip the interactive confirmation (required when not a TTY)
|
|
41929
41942
|
--dry-run with --send, show what would be signed and sign nothing
|
|
41930
41943
|
--json, -j machine-readable output
|
|
@@ -42693,6 +42706,16 @@ var MARGINS_MS = {
|
|
|
42693
42706
|
};
|
|
42694
42707
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
42695
42708
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
42709
|
+
function formatRemaining(ms) {
|
|
42710
|
+
if (!Number.isFinite(ms)) return "unknown";
|
|
42711
|
+
if (ms <= 0) return "expired";
|
|
42712
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
42713
|
+
if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
|
|
42714
|
+
if (totalMinutes < 60) return `${totalMinutes}m`;
|
|
42715
|
+
const h = Math.floor(totalMinutes / 60);
|
|
42716
|
+
const m = totalMinutes % 60;
|
|
42717
|
+
return m ? `${h}h ${m}m` : `${h}h`;
|
|
42718
|
+
}
|
|
42696
42719
|
function marginFor(chainId) {
|
|
42697
42720
|
const key = String(chainId);
|
|
42698
42721
|
return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
|
|
@@ -43334,6 +43357,33 @@ async function main2(argv) {
|
|
|
43334
43357
|
const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
|
|
43335
43358
|
const verify = verifyCreateAgainstQuote({ snapshot: snapshot2, created, requested });
|
|
43336
43359
|
if (!verify.ok) {
|
|
43360
|
+
const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
|
|
43361
|
+
if (created.reused && onlySourceDrift) {
|
|
43362
|
+
const existing = created.source || {};
|
|
43363
|
+
const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
|
|
43364
|
+
emit(
|
|
43365
|
+
{
|
|
43366
|
+
success: false,
|
|
43367
|
+
step: "reuse-source-mismatch",
|
|
43368
|
+
error: {
|
|
43369
|
+
code: "REUSED_SOURCE_MISMATCH",
|
|
43370
|
+
message: `This link already has an unpaid order, created for ${existing.tokenSymbol ?? "?"} on chain ${existing.chainId ?? "?"}. You asked for ${tokenSymbol} on chain ${chainId}. Only one order exists per link at a time, so nothing new was created.`
|
|
43371
|
+
},
|
|
43372
|
+
linkId: created.linkId ?? parsedLinkId,
|
|
43373
|
+
rozoPaymentId,
|
|
43374
|
+
paymentLink: created.paymentLink ?? null,
|
|
43375
|
+
existingOrder: {
|
|
43376
|
+
rozoPaymentId,
|
|
43377
|
+
chainId: existing.chainId ?? null,
|
|
43378
|
+
tokenSymbol: existing.tokenSymbol ?? null,
|
|
43379
|
+
expiresAt: created.expiresAt ?? null,
|
|
43380
|
+
expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
|
|
43381
|
+
},
|
|
43382
|
+
guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
|
|
43383
|
+
},
|
|
43384
|
+
EXIT_ERROR
|
|
43385
|
+
);
|
|
43386
|
+
}
|
|
43337
43387
|
emit(
|
|
43338
43388
|
{
|
|
43339
43389
|
success: false,
|
|
@@ -43455,7 +43505,8 @@ async function main2(argv) {
|
|
|
43455
43505
|
step: "create-order",
|
|
43456
43506
|
confirmed,
|
|
43457
43507
|
reused: Boolean(created.reused),
|
|
43458
|
-
reusedNote: created.reused ?
|
|
43508
|
+
reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
|
|
43509
|
+
orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
|
|
43459
43510
|
linkId: created.linkId ?? parsedLinkId,
|
|
43460
43511
|
rozoPaymentId,
|
|
43461
43512
|
paymentLink: created.paymentLink ?? null,
|
|
@@ -43473,12 +43524,16 @@ async function main2(argv) {
|
|
|
43473
43524
|
// Lightning has no deposit address: the BOLT11 IS the instruction.
|
|
43474
43525
|
receiverAddress: lightning ? null : source.receiverAddress,
|
|
43475
43526
|
receiverMemo: source.receiverMemo ?? null,
|
|
43527
|
+
// Stellar memos are TEXT even when they look numeric. Sending one as
|
|
43528
|
+
// MEMO_ID produces a different memo and the payment will not match.
|
|
43529
|
+
receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
43476
43530
|
amount: source.amount,
|
|
43477
43531
|
amountUnit: source.amountUnit ?? null,
|
|
43478
43532
|
isSats: isSatsUnit(source.amountUnit),
|
|
43479
43533
|
lnInvoice: bolt11 || null,
|
|
43480
43534
|
payTo: depositInfo.payTo,
|
|
43481
|
-
expiresAt: payment?.expiresAt ?? null
|
|
43535
|
+
expiresAt: payment?.expiresAt ?? null,
|
|
43536
|
+
expiresIn: formatRemaining(expiry.msRemaining)
|
|
43482
43537
|
} : null,
|
|
43483
43538
|
depositWithheld: !confirmed,
|
|
43484
43539
|
// Safe for prose / chat.
|
|
@@ -43490,12 +43545,16 @@ async function main2(argv) {
|
|
|
43490
43545
|
payToMasked: maskAddress2(depositInfo.payTo),
|
|
43491
43546
|
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
43492
43547
|
hasMemo: Boolean(source.receiverMemo),
|
|
43548
|
+
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
43493
43549
|
memoRequirement
|
|
43494
43550
|
},
|
|
43495
43551
|
expiry: {
|
|
43496
43552
|
intentExpiresAt: payment?.expiresAt ?? null,
|
|
43497
43553
|
coinbaseExpiry: snapshot2?.coinbase?.preApprovalExpiry ?? null,
|
|
43498
43554
|
effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
|
|
43555
|
+
// A duration, not just a timestamp: this is what a payer actually needs.
|
|
43556
|
+
expiresIn: formatRemaining(expiry.msRemaining),
|
|
43557
|
+
msRemaining: expiry.msRemaining,
|
|
43499
43558
|
marginMinutes: Math.round(expiry.marginMs / 6e4),
|
|
43500
43559
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
|
|
43501
43560
|
},
|
|
@@ -43591,6 +43650,16 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
43591
43650
|
senderAddressMasked: source.senderAddress ? maskAddress2(source.senderAddress) : null,
|
|
43592
43651
|
chain: source.chainId ? chainName(source.chainId) : null
|
|
43593
43652
|
},
|
|
43653
|
+
expiry: (() => {
|
|
43654
|
+
const iso = payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
|
|
43655
|
+
if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
|
|
43656
|
+
const ms = Date.parse(iso) - Date.now();
|
|
43657
|
+
return {
|
|
43658
|
+
expiresAt: iso,
|
|
43659
|
+
expiresIn: formatRemaining(ms),
|
|
43660
|
+
msRemaining: Number.isFinite(ms) ? ms : null
|
|
43661
|
+
};
|
|
43662
|
+
})(),
|
|
43594
43663
|
payout: {
|
|
43595
43664
|
txHash: payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null,
|
|
43596
43665
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
@@ -43616,7 +43685,7 @@ async function main3(argv) {
|
|
|
43616
43685
|
if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
|
|
43617
43686
|
result = next;
|
|
43618
43687
|
}
|
|
43619
|
-
const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded.
|
|
43688
|
+
const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded, so nothing was lost. Start a fresh order with: rozo-checkout pay <coinbase-link> --with <coin> (or create-order.js --url <link> --chain <id> --token <SYMBOL>)" : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
|
|
43620
43689
|
const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
|
|
43621
43690
|
const failed = result.escalate || result.unknown || !result.authoritativeView;
|
|
43622
43691
|
emit(
|
|
@@ -43637,21 +43706,51 @@ async function run3(argv = process.argv.slice(2)) {
|
|
|
43637
43706
|
|
|
43638
43707
|
// scripts/src/lib/keys.mjs
|
|
43639
43708
|
import fs4 from "node:fs";
|
|
43709
|
+
import path4 from "node:path";
|
|
43640
43710
|
import { execFileSync } from "node:child_process";
|
|
43641
|
-
var
|
|
43642
|
-
var
|
|
43643
|
-
function
|
|
43644
|
-
|
|
43645
|
-
|
|
43711
|
+
var ENV_FILE_RE = /^\.env(\..+)?$/;
|
|
43712
|
+
var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
|
|
43713
|
+
function insideGitWorkTree(dir, whatFor) {
|
|
43714
|
+
try {
|
|
43715
|
+
return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
43716
|
+
cwd: dir,
|
|
43717
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
43718
|
+
encoding: "utf8"
|
|
43719
|
+
}).trim() === "true";
|
|
43720
|
+
} catch (err) {
|
|
43721
|
+
const stderr = String(err?.stderr || "");
|
|
43722
|
+
if (/not a git repository|does not appear to be a git repository/i.test(stderr)) return false;
|
|
43646
43723
|
throw new SkillError(
|
|
43647
|
-
"
|
|
43648
|
-
|
|
43724
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
43725
|
+
`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
43726
|
);
|
|
43650
43727
|
}
|
|
43651
|
-
return String(v).trim();
|
|
43652
43728
|
}
|
|
43653
|
-
|
|
43654
|
-
|
|
43729
|
+
function assertNotTrackedByGit(file) {
|
|
43730
|
+
const dir = path4.dirname(path4.resolve(file));
|
|
43731
|
+
const base = path4.basename(file);
|
|
43732
|
+
if (!insideGitWorkTree(dir, "this key file")) return { checked: true, tracked: false };
|
|
43733
|
+
let out2;
|
|
43734
|
+
try {
|
|
43735
|
+
out2 = execFileSync("git", ["ls-files", "-z", "--", base], {
|
|
43736
|
+
cwd: dir,
|
|
43737
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
43738
|
+
encoding: "utf8"
|
|
43739
|
+
});
|
|
43740
|
+
} catch {
|
|
43741
|
+
throw new SkillError(
|
|
43742
|
+
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
43743
|
+
"git could not report whether this key file is tracked. Refusing to use it."
|
|
43744
|
+
);
|
|
43745
|
+
}
|
|
43746
|
+
if (out2.split("\0").filter(Boolean).length) {
|
|
43747
|
+
throw new SkillError(
|
|
43748
|
+
"TRACKED_KEYFILE",
|
|
43749
|
+
`${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.`
|
|
43750
|
+
);
|
|
43751
|
+
}
|
|
43752
|
+
return { checked: true, tracked: false };
|
|
43753
|
+
}
|
|
43655
43754
|
function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
43656
43755
|
let candidates = [];
|
|
43657
43756
|
try {
|
|
@@ -43703,172 +43802,11 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
|
43703
43802
|
return { checked: true, tracked: false, candidates };
|
|
43704
43803
|
}
|
|
43705
43804
|
|
|
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
|
-
}
|
|
43805
|
+
// scripts/src/lib/key-source.mjs
|
|
43806
|
+
import fs5 from "node:fs";
|
|
43807
|
+
import path5 from "node:path";
|
|
43808
|
+
import os3 from "node:os";
|
|
43809
|
+
import crypto5 from "node:crypto";
|
|
43872
43810
|
|
|
43873
43811
|
// node_modules/viem/_esm/utils/getAction.js
|
|
43874
43812
|
function getAction(client, actionFn, name) {
|
|
@@ -54833,6 +54771,593 @@ function http(url, config = {}) {
|
|
|
54833
54771
|
init_encodeFunctionData();
|
|
54834
54772
|
init_keccak256();
|
|
54835
54773
|
|
|
54774
|
+
// scripts/src/lib/key-source.mjs
|
|
54775
|
+
var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
|
|
54776
|
+
var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
|
|
54777
|
+
var EVM_KEYSTORE_ENV = "ROZO_CHECKOUT_EVM_KEYSTORE";
|
|
54778
|
+
var KEYSTORE_PASSPHRASE_ENV = "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE";
|
|
54779
|
+
function defaultSolanaKeypairPath() {
|
|
54780
|
+
return path5.join(os3.homedir(), ".config", "solana", "id.json");
|
|
54781
|
+
}
|
|
54782
|
+
function assertKeyfileSafe(file) {
|
|
54783
|
+
let stat;
|
|
54784
|
+
try {
|
|
54785
|
+
stat = fs5.statSync(file);
|
|
54786
|
+
} catch (err) {
|
|
54787
|
+
throw new SkillError(
|
|
54788
|
+
"KEYFILE_UNREADABLE",
|
|
54789
|
+
`Cannot read key file (${err.code || "unknown error"}). Check the path.`
|
|
54790
|
+
);
|
|
54791
|
+
}
|
|
54792
|
+
if (!stat.isFile()) {
|
|
54793
|
+
throw new SkillError("KEYFILE_UNREADABLE", "That key file path is not a regular file.");
|
|
54794
|
+
}
|
|
54795
|
+
if (process.platform !== "win32" && stat.mode & 63) {
|
|
54796
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
54797
|
+
throw new SkillError(
|
|
54798
|
+
"KEYFILE_PERMISSIONS",
|
|
54799
|
+
`That key file is readable by other users (mode ${mode}). Run: chmod 600 ${file}`
|
|
54800
|
+
);
|
|
54801
|
+
}
|
|
54802
|
+
assertNotTrackedByGit(file);
|
|
54803
|
+
return true;
|
|
54804
|
+
}
|
|
54805
|
+
function parseSolanaKeypairJson(text) {
|
|
54806
|
+
let arr;
|
|
54807
|
+
try {
|
|
54808
|
+
arr = JSON.parse(String(text));
|
|
54809
|
+
} catch {
|
|
54810
|
+
throw new SkillError(
|
|
54811
|
+
"BAD_KEYPAIR_FILE",
|
|
54812
|
+
"That file is not a Solana keypair: expected a JSON array of bytes."
|
|
54813
|
+
);
|
|
54814
|
+
}
|
|
54815
|
+
if (!Array.isArray(arr)) {
|
|
54816
|
+
throw new SkillError("BAD_KEYPAIR_FILE", "A Solana keypair file must contain a JSON array.");
|
|
54817
|
+
}
|
|
54818
|
+
if (arr.length !== 64 && arr.length !== 32) {
|
|
54819
|
+
throw new SkillError(
|
|
54820
|
+
"BAD_KEYPAIR_FILE",
|
|
54821
|
+
`A Solana keypair must be 64 bytes (or a 32-byte seed); this file has ${arr.length}.`
|
|
54822
|
+
);
|
|
54823
|
+
}
|
|
54824
|
+
for (const b of arr) {
|
|
54825
|
+
if (!Number.isInteger(b) || b < 0 || b > 255) {
|
|
54826
|
+
throw new SkillError("BAD_KEYPAIR_FILE", "A Solana keypair must contain byte values 0-255.");
|
|
54827
|
+
}
|
|
54828
|
+
}
|
|
54829
|
+
return Uint8Array.from(arr);
|
|
54830
|
+
}
|
|
54831
|
+
function normalizeEvmPrivateKey(raw) {
|
|
54832
|
+
const s = String(raw ?? "").trim();
|
|
54833
|
+
if (!s) {
|
|
54834
|
+
throw new SkillError("MISSING_KEY", "No EVM private key supplied.");
|
|
54835
|
+
}
|
|
54836
|
+
const body = /^0[xX]/.test(s) ? s.slice(2) : s;
|
|
54837
|
+
if (!/^[0-9a-fA-F]{64}$/.test(body)) {
|
|
54838
|
+
throw new SkillError(
|
|
54839
|
+
"BAD_KEY_FORMAT",
|
|
54840
|
+
`An EVM private key must be 64 hex characters, with or without a 0x prefix (got ${body.length} characters after any prefix). Value withheld.`
|
|
54841
|
+
);
|
|
54842
|
+
}
|
|
54843
|
+
return `0x${body.toLowerCase()}`;
|
|
54844
|
+
}
|
|
54845
|
+
function decodeSolanaEnvKey(raw) {
|
|
54846
|
+
const s = String(raw ?? "").trim();
|
|
54847
|
+
if (!s) throw new SkillError("MISSING_KEY", `${SOL_KEY_ENV} is empty.`);
|
|
54848
|
+
if (s.startsWith("[")) return parseSolanaKeypairJson(s);
|
|
54849
|
+
const bytes = base58Decode(s);
|
|
54850
|
+
if (bytes.length !== 64 && bytes.length !== 32) {
|
|
54851
|
+
throw new SkillError(
|
|
54852
|
+
"BAD_KEY_FORMAT",
|
|
54853
|
+
`${SOL_KEY_ENV} must be a base58 secret key or a JSON byte array.`
|
|
54854
|
+
);
|
|
54855
|
+
}
|
|
54856
|
+
return bytes;
|
|
54857
|
+
}
|
|
54858
|
+
var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
54859
|
+
function base58Decode(str) {
|
|
54860
|
+
let num2 = 0n;
|
|
54861
|
+
for (const ch of str) {
|
|
54862
|
+
const idx = B58.indexOf(ch);
|
|
54863
|
+
if (idx < 0) throw new SkillError("BAD_KEY_FORMAT", "Not valid base58.");
|
|
54864
|
+
num2 = num2 * 58n + BigInt(idx);
|
|
54865
|
+
}
|
|
54866
|
+
const bytes = [];
|
|
54867
|
+
while (num2 > 0n) {
|
|
54868
|
+
bytes.unshift(Number(num2 % 256n));
|
|
54869
|
+
num2 /= 256n;
|
|
54870
|
+
}
|
|
54871
|
+
for (const ch of str) {
|
|
54872
|
+
if (ch === "1") bytes.unshift(0);
|
|
54873
|
+
else break;
|
|
54874
|
+
}
|
|
54875
|
+
return Uint8Array.from(bytes);
|
|
54876
|
+
}
|
|
54877
|
+
function decryptKeystoreV3(keystore, passphrase) {
|
|
54878
|
+
const ks = typeof keystore === "string" ? safeParseJson(keystore) : keystore;
|
|
54879
|
+
if (!ks || typeof ks !== "object" || !ks.crypto) {
|
|
54880
|
+
throw new SkillError(
|
|
54881
|
+
"BAD_KEYSTORE",
|
|
54882
|
+
'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.'
|
|
54883
|
+
);
|
|
54884
|
+
}
|
|
54885
|
+
if (ks.version !== void 0 && Number(ks.version) !== 3) {
|
|
54886
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore version ${ks.version}; expected 3.`);
|
|
54887
|
+
}
|
|
54888
|
+
const c2 = ks.crypto;
|
|
54889
|
+
const kdfparams = c2.kdfparams || {};
|
|
54890
|
+
const pass = Buffer.from(String(passphrase ?? ""), "utf8");
|
|
54891
|
+
let derived;
|
|
54892
|
+
if (c2.kdf === "scrypt") {
|
|
54893
|
+
const { n, r, p, dklen, salt } = kdfparams;
|
|
54894
|
+
if (!n || !r || !p || !dklen || !salt) {
|
|
54895
|
+
throw new SkillError("BAD_KEYSTORE", "The keystore scrypt parameters are incomplete.");
|
|
54896
|
+
}
|
|
54897
|
+
derived = crypto5.scryptSync(pass, Buffer.from(salt, "hex"), dklen, {
|
|
54898
|
+
N: n,
|
|
54899
|
+
r,
|
|
54900
|
+
p,
|
|
54901
|
+
// geth's default N=262144 needs roughly 256 MB; the Node default cap is
|
|
54902
|
+
// far lower, so raise it with headroom rather than failing on a normal file.
|
|
54903
|
+
maxmem: 256 * n * r + 64 * 1024 * 1024
|
|
54904
|
+
});
|
|
54905
|
+
} else if (c2.kdf === "pbkdf2") {
|
|
54906
|
+
const { c: iterations, dklen, salt, prf } = kdfparams;
|
|
54907
|
+
if (prf && prf !== "hmac-sha256") {
|
|
54908
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore PRF "${prf}".`);
|
|
54909
|
+
}
|
|
54910
|
+
if (!iterations || !dklen || !salt) {
|
|
54911
|
+
throw new SkillError("BAD_KEYSTORE", "The keystore pbkdf2 parameters are incomplete.");
|
|
54912
|
+
}
|
|
54913
|
+
derived = crypto5.pbkdf2Sync(pass, Buffer.from(salt, "hex"), iterations, dklen, "sha256");
|
|
54914
|
+
} else {
|
|
54915
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore KDF "${c2.kdf}".`);
|
|
54916
|
+
}
|
|
54917
|
+
const ciphertext = Buffer.from(c2.ciphertext, "hex");
|
|
54918
|
+
const mac = keccak256(Buffer.concat([derived.subarray(16, 32), ciphertext])).slice(2);
|
|
54919
|
+
if (!timingSafeEqualHex(mac, String(c2.mac || ""))) {
|
|
54920
|
+
throw new SkillError(
|
|
54921
|
+
"KEYSTORE_BAD_PASSPHRASE",
|
|
54922
|
+
"Wrong passphrase for that keystore (the MAC did not verify)."
|
|
54923
|
+
);
|
|
54924
|
+
}
|
|
54925
|
+
if (c2.cipher !== "aes-128-ctr") {
|
|
54926
|
+
throw new SkillError("BAD_KEYSTORE", `Unsupported keystore cipher "${c2.cipher}".`);
|
|
54927
|
+
}
|
|
54928
|
+
const decipher = crypto5.createDecipheriv(
|
|
54929
|
+
"aes-128-ctr",
|
|
54930
|
+
derived.subarray(0, 16),
|
|
54931
|
+
Buffer.from(c2.cipherparams.iv, "hex")
|
|
54932
|
+
);
|
|
54933
|
+
const secret = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
54934
|
+
if (secret.length !== 32) {
|
|
54935
|
+
throw new SkillError("BAD_KEYSTORE", "The decrypted key is not 32 bytes.");
|
|
54936
|
+
}
|
|
54937
|
+
return `0x${secret.toString("hex")}`;
|
|
54938
|
+
}
|
|
54939
|
+
function safeParseJson(text) {
|
|
54940
|
+
try {
|
|
54941
|
+
return JSON.parse(text);
|
|
54942
|
+
} catch {
|
|
54943
|
+
throw new SkillError("BAD_KEYSTORE", "That keystore file is not valid JSON.");
|
|
54944
|
+
}
|
|
54945
|
+
}
|
|
54946
|
+
function timingSafeEqualHex(a, b) {
|
|
54947
|
+
const ab = Buffer.from(String(a).toLowerCase(), "hex");
|
|
54948
|
+
const bb = Buffer.from(String(b).toLowerCase(), "hex");
|
|
54949
|
+
if (ab.length === 0 || ab.length !== bb.length) return false;
|
|
54950
|
+
return crypto5.timingSafeEqual(ab, bb);
|
|
54951
|
+
}
|
|
54952
|
+
function looksLikeKeystore(text) {
|
|
54953
|
+
const t = String(text).trimStart();
|
|
54954
|
+
if (!t.startsWith("{")) return false;
|
|
54955
|
+
try {
|
|
54956
|
+
const o = JSON.parse(t);
|
|
54957
|
+
return Boolean(o && typeof o === "object" && o.crypto);
|
|
54958
|
+
} catch {
|
|
54959
|
+
return false;
|
|
54960
|
+
}
|
|
54961
|
+
}
|
|
54962
|
+
function planKeySource({ family, keyfile, env = process.env }) {
|
|
54963
|
+
if (keyfile) {
|
|
54964
|
+
const resolved = expandHome(keyfile);
|
|
54965
|
+
let text;
|
|
54966
|
+
try {
|
|
54967
|
+
text = fs5.readFileSync(resolved, "utf8");
|
|
54968
|
+
} catch (err) {
|
|
54969
|
+
throw new SkillError(
|
|
54970
|
+
"KEYFILE_UNREADABLE",
|
|
54971
|
+
`Cannot read ${displayPath(resolved)} (${err.code || "unknown error"}).`
|
|
54972
|
+
);
|
|
54973
|
+
}
|
|
54974
|
+
const kind = looksLikeKeystore(text) ? "keystore" : "keypair-file";
|
|
54975
|
+
if (kind === "keypair-file" && family === "evm") {
|
|
54976
|
+
throw new SkillError(
|
|
54977
|
+
"BAD_KEYSTORE",
|
|
54978
|
+
"That file is not a V3 keystore. For EVM, --keyfile expects an encrypted JSON keystore."
|
|
54979
|
+
);
|
|
54980
|
+
}
|
|
54981
|
+
if (kind === "keystore" && family === "solana") {
|
|
54982
|
+
throw new SkillError(
|
|
54983
|
+
"BAD_KEYPAIR_FILE",
|
|
54984
|
+
"That looks like an EVM keystore. For Solana, --keyfile expects a solana-keygen keypair file (a JSON array of bytes)."
|
|
54985
|
+
);
|
|
54986
|
+
}
|
|
54987
|
+
return { kind, path: resolved, label: displayPath(resolved) };
|
|
54988
|
+
}
|
|
54989
|
+
if (family === "solana") {
|
|
54990
|
+
const standard = defaultSolanaKeypairPath();
|
|
54991
|
+
if (fs5.existsSync(standard)) {
|
|
54992
|
+
return { kind: "keypair-file", path: standard, label: displayPath(standard) };
|
|
54993
|
+
}
|
|
54994
|
+
if (env[SOL_KEY_ENV]) return { kind: "env", label: SOL_KEY_ENV };
|
|
54995
|
+
throw new SkillError(
|
|
54996
|
+
"NO_KEY_SOURCE",
|
|
54997
|
+
`No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.`
|
|
54998
|
+
);
|
|
54999
|
+
}
|
|
55000
|
+
if (family === "evm") {
|
|
55001
|
+
const fromEnv = env[EVM_KEYSTORE_ENV];
|
|
55002
|
+
if (fromEnv) {
|
|
55003
|
+
const resolved = expandHome(fromEnv);
|
|
55004
|
+
return { kind: "keystore", path: resolved, label: displayPath(resolved) };
|
|
55005
|
+
}
|
|
55006
|
+
if (env[EVM_KEY_ENV]) return { kind: "env", label: EVM_KEY_ENV };
|
|
55007
|
+
throw new SkillError(
|
|
55008
|
+
"NO_KEY_SOURCE",
|
|
55009
|
+
`No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.`
|
|
55010
|
+
);
|
|
55011
|
+
}
|
|
55012
|
+
throw new SkillError("NO_KEY_SOURCE", `No key source for family "${family}".`);
|
|
55013
|
+
}
|
|
55014
|
+
async function loadKeySource(plan, { family, env = process.env, askPassphrase } = {}) {
|
|
55015
|
+
if (plan.kind === "env") {
|
|
55016
|
+
const name = family === "evm" ? EVM_KEY_ENV : SOL_KEY_ENV;
|
|
55017
|
+
const raw = env[name];
|
|
55018
|
+
if (!raw || !String(raw).trim()) {
|
|
55019
|
+
throw new SkillError("MISSING_KEY", `${name} is not set.`);
|
|
55020
|
+
}
|
|
55021
|
+
if (family === "evm") {
|
|
55022
|
+
return { privateKey: normalizeEvmPrivateKey(raw), label: plan.label, kind: plan.kind };
|
|
55023
|
+
}
|
|
55024
|
+
return { secretKey: decodeSolanaEnvKey(raw), label: plan.label, kind: plan.kind };
|
|
55025
|
+
}
|
|
55026
|
+
assertKeyfileSafe(plan.path);
|
|
55027
|
+
const text = fs5.readFileSync(plan.path, "utf8");
|
|
55028
|
+
if (plan.kind === "keypair-file") {
|
|
55029
|
+
return { secretKey: parseSolanaKeypairJson(text), label: plan.label, kind: plan.kind };
|
|
55030
|
+
}
|
|
55031
|
+
let passphrase = env[KEYSTORE_PASSPHRASE_ENV];
|
|
55032
|
+
if (passphrase === void 0 || passphrase === "") {
|
|
55033
|
+
if (!askPassphrase) {
|
|
55034
|
+
throw new SkillError(
|
|
55035
|
+
"KEYSTORE_PASSPHRASE_REQUIRED",
|
|
55036
|
+
`That keystore needs a passphrase. Run this on a terminal to be prompted, or set ${KEYSTORE_PASSPHRASE_ENV} for unattended use.`
|
|
55037
|
+
);
|
|
55038
|
+
}
|
|
55039
|
+
passphrase = await askPassphrase();
|
|
55040
|
+
}
|
|
55041
|
+
const privateKey = decryptKeystoreV3(text, passphrase);
|
|
55042
|
+
return { privateKey, label: plan.label, kind: plan.kind };
|
|
55043
|
+
}
|
|
55044
|
+
function expandHome(p) {
|
|
55045
|
+
const s = String(p);
|
|
55046
|
+
if (s === "~") return os3.homedir();
|
|
55047
|
+
if (s.startsWith("~/")) return path5.join(os3.homedir(), s.slice(2));
|
|
55048
|
+
return s;
|
|
55049
|
+
}
|
|
55050
|
+
function displayPath(p) {
|
|
55051
|
+
const home = os3.homedir();
|
|
55052
|
+
const s = String(p);
|
|
55053
|
+
return s.startsWith(home + path5.sep) ? `~${s.slice(home.length)}` : s;
|
|
55054
|
+
}
|
|
55055
|
+
|
|
55056
|
+
// scripts/src/lib/dotenv.mjs
|
|
55057
|
+
import fs6 from "node:fs";
|
|
55058
|
+
import path6 from "node:path";
|
|
55059
|
+
var ALLOWED_KEYS = [
|
|
55060
|
+
"ROZO_CHECKOUT_EVM_KEY",
|
|
55061
|
+
"ROZO_CHECKOUT_SOL_KEY",
|
|
55062
|
+
"ROZO_CHECKOUT_EVM_KEYSTORE",
|
|
55063
|
+
"ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
|
|
55064
|
+
];
|
|
55065
|
+
var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
|
|
55066
|
+
function isAllowedKey(key) {
|
|
55067
|
+
return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
|
|
55068
|
+
}
|
|
55069
|
+
function parseDotenv(text) {
|
|
55070
|
+
const vars = {};
|
|
55071
|
+
const lines = String(text).split(/\r?\n/);
|
|
55072
|
+
lines.forEach((rawLine, i) => {
|
|
55073
|
+
const lineNo = i + 1;
|
|
55074
|
+
const line = rawLine.trim();
|
|
55075
|
+
if (!line || line.startsWith("#")) return;
|
|
55076
|
+
const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
55077
|
+
const eq = withoutExport.indexOf("=");
|
|
55078
|
+
if (eq === -1) {
|
|
55079
|
+
throw new SkillError(
|
|
55080
|
+
"BAD_ENV_FILE",
|
|
55081
|
+
`Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
|
|
55082
|
+
);
|
|
55083
|
+
}
|
|
55084
|
+
const key = withoutExport.slice(0, eq).trim();
|
|
55085
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
55086
|
+
throw new SkillError(
|
|
55087
|
+
"BAD_ENV_FILE",
|
|
55088
|
+
`Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
|
|
55089
|
+
);
|
|
55090
|
+
}
|
|
55091
|
+
let value = withoutExport.slice(eq + 1).trim();
|
|
55092
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
55093
|
+
value = value.slice(1, -1);
|
|
55094
|
+
}
|
|
55095
|
+
vars[key] = value;
|
|
55096
|
+
});
|
|
55097
|
+
return vars;
|
|
55098
|
+
}
|
|
55099
|
+
function filterAllowed(vars) {
|
|
55100
|
+
const out2 = {};
|
|
55101
|
+
for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out2[k] = v;
|
|
55102
|
+
return out2;
|
|
55103
|
+
}
|
|
55104
|
+
function resolveEnvFile({ file, cwd = process.cwd() } = {}) {
|
|
55105
|
+
if (file) {
|
|
55106
|
+
const p = path6.resolve(file);
|
|
55107
|
+
if (!fs6.existsSync(p)) {
|
|
55108
|
+
throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
|
|
55109
|
+
}
|
|
55110
|
+
return p;
|
|
55111
|
+
}
|
|
55112
|
+
const fallback = path6.join(cwd, ".env");
|
|
55113
|
+
return fs6.existsSync(fallback) ? fallback : null;
|
|
55114
|
+
}
|
|
55115
|
+
function applyDotenv({ file, cwd = process.cwd(), env = process.env } = {}) {
|
|
55116
|
+
const target = resolveEnvFile({ file, cwd });
|
|
55117
|
+
if (!target) return null;
|
|
55118
|
+
let stat;
|
|
55119
|
+
try {
|
|
55120
|
+
stat = fs6.statSync(target);
|
|
55121
|
+
} catch (err) {
|
|
55122
|
+
throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
|
|
55123
|
+
}
|
|
55124
|
+
if (process.platform !== "win32" && stat.mode & 63) {
|
|
55125
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
55126
|
+
throw new SkillError(
|
|
55127
|
+
"ENV_FILE_PERMISSIONS",
|
|
55128
|
+
`That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
|
|
55129
|
+
);
|
|
55130
|
+
}
|
|
55131
|
+
assertNotTrackedByGit(target);
|
|
55132
|
+
const parsed = parseDotenv(fs6.readFileSync(target, "utf8"));
|
|
55133
|
+
const allowed = filterAllowed(parsed);
|
|
55134
|
+
const applied = [];
|
|
55135
|
+
for (const [k, v] of Object.entries(allowed)) {
|
|
55136
|
+
const existing = env[k];
|
|
55137
|
+
if (existing !== void 0 && String(existing).trim() !== "") continue;
|
|
55138
|
+
env[k] = v;
|
|
55139
|
+
applied.push(k);
|
|
55140
|
+
}
|
|
55141
|
+
return {
|
|
55142
|
+
path: target,
|
|
55143
|
+
applied,
|
|
55144
|
+
ignored: Object.keys(parsed).length - Object.keys(allowed).length
|
|
55145
|
+
};
|
|
55146
|
+
}
|
|
55147
|
+
|
|
55148
|
+
// scripts/src/lib/passphrase.mjs
|
|
55149
|
+
import readline from "node:readline";
|
|
55150
|
+
function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
55151
|
+
if (!process.stdin.isTTY) {
|
|
55152
|
+
throw new SkillError(
|
|
55153
|
+
"KEYSTORE_PASSPHRASE_REQUIRED",
|
|
55154
|
+
"A keystore passphrase is needed but this is not a terminal. Set ROZO_CHECKOUT_KEYSTORE_PASSPHRASE for unattended use."
|
|
55155
|
+
);
|
|
55156
|
+
}
|
|
55157
|
+
return new Promise((resolve, reject) => {
|
|
55158
|
+
const rl = readline.createInterface({
|
|
55159
|
+
input: process.stdin,
|
|
55160
|
+
output: process.stdout,
|
|
55161
|
+
terminal: true
|
|
55162
|
+
});
|
|
55163
|
+
let muted = false;
|
|
55164
|
+
const originalWrite = rl._writeToOutput?.bind(rl);
|
|
55165
|
+
rl._writeToOutput = (chunk) => {
|
|
55166
|
+
if (!muted) {
|
|
55167
|
+
if (originalWrite) originalWrite(chunk);
|
|
55168
|
+
else process.stdout.write(chunk);
|
|
55169
|
+
return;
|
|
55170
|
+
}
|
|
55171
|
+
if (chunk.includes(prompt)) process.stdout.write(prompt);
|
|
55172
|
+
};
|
|
55173
|
+
rl.question(prompt, (answer) => {
|
|
55174
|
+
rl._writeToOutput = originalWrite;
|
|
55175
|
+
rl.close();
|
|
55176
|
+
process.stdout.write("\n");
|
|
55177
|
+
const value = String(answer ?? "");
|
|
55178
|
+
if (!value) {
|
|
55179
|
+
reject(new SkillError("KEYSTORE_PASSPHRASE_REQUIRED", "No passphrase entered."));
|
|
55180
|
+
return;
|
|
55181
|
+
}
|
|
55182
|
+
resolve(value);
|
|
55183
|
+
});
|
|
55184
|
+
muted = true;
|
|
55185
|
+
rl.on("SIGINT", () => {
|
|
55186
|
+
rl._writeToOutput = originalWrite;
|
|
55187
|
+
rl.close();
|
|
55188
|
+
process.stdout.write("\n");
|
|
55189
|
+
reject(new SkillError("CANCELLED", "Cancelled."));
|
|
55190
|
+
});
|
|
55191
|
+
});
|
|
55192
|
+
}
|
|
55193
|
+
|
|
55194
|
+
// scripts/src/lib/presend.mjs
|
|
55195
|
+
async function preflight({
|
|
55196
|
+
rozoPaymentId,
|
|
55197
|
+
expectFamily,
|
|
55198
|
+
senderAddress,
|
|
55199
|
+
send = false,
|
|
55200
|
+
dryRun = false
|
|
55201
|
+
}) {
|
|
55202
|
+
if (!send && !dryRun) {
|
|
55203
|
+
throw new SkillError(
|
|
55204
|
+
"SEND_NOT_OPTED_IN",
|
|
55205
|
+
"Refusing to move funds without the explicit --send flag. Add --dry-run to see exactly what would be signed instead."
|
|
55206
|
+
);
|
|
55207
|
+
}
|
|
55208
|
+
let blacklist;
|
|
55209
|
+
try {
|
|
55210
|
+
blacklist = loadBlacklist();
|
|
55211
|
+
} catch (err) {
|
|
55212
|
+
throw new SkillError(
|
|
55213
|
+
"BLACKLIST_UNAVAILABLE",
|
|
55214
|
+
`Compromised-address list unusable: ${err.message} Refusing to send.`
|
|
55215
|
+
);
|
|
55216
|
+
}
|
|
55217
|
+
const state = readState(rozoPaymentId);
|
|
55218
|
+
if (!state) {
|
|
55219
|
+
throw new SkillError(
|
|
55220
|
+
"NO_ORDER_STATE",
|
|
55221
|
+
"No local record for this order. Run create-order.js first, in the same environment."
|
|
55222
|
+
);
|
|
55223
|
+
}
|
|
55224
|
+
if (state.send) {
|
|
55225
|
+
throw new SkillError(
|
|
55226
|
+
"ALREADY_SENT",
|
|
55227
|
+
`A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice.`,
|
|
55228
|
+
{ send: state.send }
|
|
55229
|
+
);
|
|
55230
|
+
}
|
|
55231
|
+
if (!state.confirmation) {
|
|
55232
|
+
throw new SkillError(
|
|
55233
|
+
"NOT_CONFIRMED",
|
|
55234
|
+
"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."
|
|
55235
|
+
);
|
|
55236
|
+
}
|
|
55237
|
+
const payment = await getPayment(rozoPaymentId);
|
|
55238
|
+
const source = payment?.source || {};
|
|
55239
|
+
const family = chainFamily(source.chainId);
|
|
55240
|
+
if (family !== expectFamily) {
|
|
55241
|
+
throw new SkillError(
|
|
55242
|
+
"WRONG_SENDER_SCRIPT",
|
|
55243
|
+
`This order settles on a ${family ?? "unknown"} chain; use the matching send script.`
|
|
55244
|
+
);
|
|
55245
|
+
}
|
|
55246
|
+
const guard = reuseGuard({
|
|
55247
|
+
payment,
|
|
55248
|
+
requested: { chainId: state.source.chainId, tokenSymbol: state.source.tokenSymbol }
|
|
55249
|
+
});
|
|
55250
|
+
if (!guard.ok) {
|
|
55251
|
+
throw new SkillError(guard.code, guard.reason, {
|
|
55252
|
+
...guard.evidence,
|
|
55253
|
+
moneyDetected: guard.moneyDetected
|
|
55254
|
+
});
|
|
55255
|
+
}
|
|
55256
|
+
const deposit = validateDepositInstructions(source);
|
|
55257
|
+
if (!deposit.ok) throw new SkillError(deposit.code, deposit.reason);
|
|
55258
|
+
const liveDigest = depositDigest(source);
|
|
55259
|
+
if (liveDigest !== state.confirmation.depositDigest) {
|
|
55260
|
+
throw new SkillError(
|
|
55261
|
+
"CONFIRMATION_STALE",
|
|
55262
|
+
"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."
|
|
55263
|
+
);
|
|
55264
|
+
}
|
|
55265
|
+
if (source.receiverAddress !== state.receiverAddress) {
|
|
55266
|
+
throw new SkillError(
|
|
55267
|
+
"DEPOSIT_CHANGED",
|
|
55268
|
+
"The live deposit address differs from the one recorded at create time. Refusing to send."
|
|
55269
|
+
);
|
|
55270
|
+
}
|
|
55271
|
+
if (String(source.amount) !== String(state.amount)) {
|
|
55272
|
+
throw new SkillError(
|
|
55273
|
+
"DEPOSIT_CHANGED",
|
|
55274
|
+
`The live deposit amount (${source.amount}) differs from the recorded one (${state.amount}).`
|
|
55275
|
+
);
|
|
55276
|
+
}
|
|
55277
|
+
if ((source.receiverMemo ?? null) !== (state.receiverMemo ?? null)) {
|
|
55278
|
+
throw new SkillError("DEPOSIT_CHANGED", "The live deposit memo differs from the recorded one.");
|
|
55279
|
+
}
|
|
55280
|
+
const statusNow = await invoiceStatus({ linkId: state.linkId });
|
|
55281
|
+
const expiry = checkExpiry({
|
|
55282
|
+
now: Date.now(),
|
|
55283
|
+
chainId: source.chainId,
|
|
55284
|
+
intentExpiresAt: payment?.expiresAt,
|
|
55285
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
55286
|
+
});
|
|
55287
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
55288
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
55289
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
55290
|
+
assertNotBlacklisted(
|
|
55291
|
+
[
|
|
55292
|
+
{ address: source.receiverAddress, family, role: "deposit address" },
|
|
55293
|
+
{ address: senderAddress, family, role: "sender wallet" }
|
|
55294
|
+
],
|
|
55295
|
+
blacklist
|
|
55296
|
+
);
|
|
55297
|
+
return {
|
|
55298
|
+
payment,
|
|
55299
|
+
source,
|
|
55300
|
+
state,
|
|
55301
|
+
expiry,
|
|
55302
|
+
blacklist,
|
|
55303
|
+
amountAtomic: deposit.amountAtomic,
|
|
55304
|
+
deposit,
|
|
55305
|
+
statusNow
|
|
55306
|
+
};
|
|
55307
|
+
}
|
|
55308
|
+
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt }) {
|
|
55309
|
+
const statusNow = await invoiceStatus({ linkId });
|
|
55310
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
55311
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
55312
|
+
const expiry = checkExpiry({
|
|
55313
|
+
now: Date.now(),
|
|
55314
|
+
chainId,
|
|
55315
|
+
intentExpiresAt,
|
|
55316
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
55317
|
+
});
|
|
55318
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
55319
|
+
return { statusNow, payable, expiry };
|
|
55320
|
+
}
|
|
55321
|
+
|
|
55322
|
+
// scripts/src/lib/outcomes.mjs
|
|
55323
|
+
function broadcastOutcome({ receiptStatus, executionError = null, receiptSeen = void 0 }) {
|
|
55324
|
+
const seen = receiptSeen ?? (receiptStatus !== null && receiptStatus !== void 0);
|
|
55325
|
+
if (executionError) {
|
|
55326
|
+
return {
|
|
55327
|
+
state: "failed",
|
|
55328
|
+
success: false,
|
|
55329
|
+
exitCode: EXIT_ERROR,
|
|
55330
|
+
code: "TX_FAILED",
|
|
55331
|
+
recordStatus: "failed"
|
|
55332
|
+
};
|
|
55333
|
+
}
|
|
55334
|
+
if (!seen) {
|
|
55335
|
+
return {
|
|
55336
|
+
state: "unconfirmed",
|
|
55337
|
+
success: true,
|
|
55338
|
+
exitCode: EXIT_UNCONFIRMED,
|
|
55339
|
+
code: null,
|
|
55340
|
+
recordStatus: "submitted"
|
|
55341
|
+
};
|
|
55342
|
+
}
|
|
55343
|
+
if (receiptStatus === "success") {
|
|
55344
|
+
return {
|
|
55345
|
+
state: "confirmed",
|
|
55346
|
+
success: true,
|
|
55347
|
+
exitCode: EXIT_OK,
|
|
55348
|
+
code: null,
|
|
55349
|
+
recordStatus: "confirmed"
|
|
55350
|
+
};
|
|
55351
|
+
}
|
|
55352
|
+
return {
|
|
55353
|
+
state: "reverted",
|
|
55354
|
+
success: false,
|
|
55355
|
+
exitCode: EXIT_ERROR,
|
|
55356
|
+
code: "TX_REVERTED",
|
|
55357
|
+
recordStatus: "failed"
|
|
55358
|
+
};
|
|
55359
|
+
}
|
|
55360
|
+
|
|
54836
55361
|
// node_modules/viem/_esm/accounts/privateKeyToAccount.js
|
|
54837
55362
|
init_secp256k1();
|
|
54838
55363
|
init_toHex();
|
|
@@ -55030,15 +55555,12 @@ async function main4(argv) {
|
|
|
55030
55555
|
const args = parseArgs(argv);
|
|
55031
55556
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
55032
55557
|
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);
|
|
55558
|
+
const dotenv = applyDotenv({ file: args["env-file"] });
|
|
55559
|
+
const plan = planKeySource({ family: "evm", keyfile: args.keyfile });
|
|
55560
|
+
const loaded = await loadKeySource(plan, { family: "evm", askPassphrase: promptPassphrase });
|
|
55561
|
+
const account = privateKeyToAccount(loaded.privateKey);
|
|
55041
55562
|
const sender = account.address;
|
|
55563
|
+
const keySource = loaded.label;
|
|
55042
55564
|
const dryRun = Boolean(args["dry-run"]);
|
|
55043
55565
|
const { source, state, expiry, amountAtomic, payment } = await preflight({
|
|
55044
55566
|
rozoPaymentId,
|
|
@@ -55119,6 +55641,9 @@ async function main4(argv) {
|
|
|
55119
55641
|
fromMasked: maskAddress2(sender)
|
|
55120
55642
|
},
|
|
55121
55643
|
confirmedAt: state.confirmation?.confirmedAt ?? null,
|
|
55644
|
+
keySource,
|
|
55645
|
+
// Key NAMES only — a value from a .env is never echoed.
|
|
55646
|
+
envFile: dotenv ? { path: dotenv.path, applied: dotenv.applied } : null,
|
|
55122
55647
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4),
|
|
55123
55648
|
note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
|
|
55124
55649
|
});
|
|
@@ -55238,7 +55763,8 @@ async function main4(argv) {
|
|
|
55238
55763
|
tokenSymbol: source.tokenSymbol,
|
|
55239
55764
|
amount: source.amount,
|
|
55240
55765
|
toMasked: maskAddress2(to),
|
|
55241
|
-
fromMasked: maskAddress2(sender)
|
|
55766
|
+
fromMasked: maskAddress2(sender),
|
|
55767
|
+
keySource
|
|
55242
55768
|
},
|
|
55243
55769
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
55244
55770
|
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 +57494,18 @@ async function getAssociatedTokenAddress(mint, owner, allowOwnerOffCurve = false
|
|
|
56968
57494
|
// scripts/src/send-sol.mjs
|
|
56969
57495
|
var MEMO_PROGRAM_ID = new import_web36.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
56970
57496
|
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
57497
|
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
57498
|
async function main5(argv) {
|
|
57018
57499
|
const args = parseArgs(argv);
|
|
57019
57500
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
57020
57501
|
assertNoTrackedDotEnv();
|
|
57021
|
-
const
|
|
57502
|
+
const dotenv = applyDotenv({ file: args["env-file"] });
|
|
57503
|
+
const plan = planKeySource({ family: "solana", keyfile: args.keyfile });
|
|
57504
|
+
const loaded = await loadKeySource(plan, { family: "solana", askPassphrase: promptPassphrase });
|
|
57505
|
+
const secret = loaded.secretKey;
|
|
57022
57506
|
const keypair = secret.length === 64 ? import_web36.Keypair.fromSecretKey(secret) : import_web36.Keypair.fromSeed(secret);
|
|
57023
57507
|
const sender = keypair.publicKey.toBase58();
|
|
57508
|
+
const keySource = loaded.label;
|
|
57024
57509
|
const dryRun = Boolean(args["dry-run"]);
|
|
57025
57510
|
const { source, state, expiry, amountAtomic, payment } = await preflight({
|
|
57026
57511
|
rozoPaymentId,
|
|
@@ -57120,6 +57605,9 @@ async function main5(argv) {
|
|
|
57120
57605
|
withMemo: Boolean(source.receiverMemo)
|
|
57121
57606
|
},
|
|
57122
57607
|
confirmedAt: state.confirmation?.confirmedAt ?? null,
|
|
57608
|
+
keySource,
|
|
57609
|
+
// Key NAMES only — a value from a .env is never echoed.
|
|
57610
|
+
envFile: dotenv ? { path: dotenv.path, applied: dotenv.applied } : null,
|
|
57123
57611
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4),
|
|
57124
57612
|
note: "Nothing was signed or broadcast. Add --send (without --dry-run) to execute."
|
|
57125
57613
|
});
|
|
@@ -57275,7 +57763,8 @@ async function main5(argv) {
|
|
|
57275
57763
|
amount: source.amount,
|
|
57276
57764
|
toMasked: maskAddress2(source.receiverAddress),
|
|
57277
57765
|
fromMasked: maskAddress2(sender),
|
|
57278
|
-
withMemo: Boolean(source.receiverMemo)
|
|
57766
|
+
withMemo: Boolean(source.receiverMemo),
|
|
57767
|
+
keySource
|
|
57279
57768
|
},
|
|
57280
57769
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
57281
57770
|
guidance: "On-chain confirmation is not settlement. Poll status.js until the state is `settled`."
|
|
@@ -57350,7 +57839,7 @@ function targetToArgs(target) {
|
|
|
57350
57839
|
return ["--link-id", linkId];
|
|
57351
57840
|
}
|
|
57352
57841
|
async function ask(question) {
|
|
57353
|
-
const rl =
|
|
57842
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
57354
57843
|
try {
|
|
57355
57844
|
return await new Promise((resolve) => rl.question(question, resolve));
|
|
57356
57845
|
} finally {
|
|
@@ -57439,7 +57928,7 @@ async function pickSource({ invoiceUsd, saved, fresh }) {
|
|
|
57439
57928
|
throw new CliError("BAD_CHOICE", "No valid choice after three attempts.");
|
|
57440
57929
|
}
|
|
57441
57930
|
async function askYesNo(question) {
|
|
57442
|
-
const rl =
|
|
57931
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
57443
57932
|
try {
|
|
57444
57933
|
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
57445
57934
|
return /^(y|yes)$/i.test(String(answer).trim());
|
|
@@ -57482,6 +57971,9 @@ async function cmdStatus(opts) {
|
|
|
57482
57971
|
out();
|
|
57483
57972
|
out(` State ${stateLabel(payload.state)}`);
|
|
57484
57973
|
if (payload.detail) out(` ${dim(payload.detail)}`);
|
|
57974
|
+
if (payload.expiry?.expiresIn && !payload.terminal) {
|
|
57975
|
+
out(` Expires in ${payload.expiry.expiresIn}`);
|
|
57976
|
+
}
|
|
57485
57977
|
if (payload.payin?.txHash) out(` Pay-in ${payload.payin.txHash}`);
|
|
57486
57978
|
if (payload.guidance) out(` ${payload.escalate ? red(payload.guidance) : dim(payload.guidance)}`);
|
|
57487
57979
|
printMoneyWarning(payload);
|
|
@@ -57568,9 +58060,14 @@ async function cmdPay(opts) {
|
|
|
57568
58060
|
out(` Invoice ${bold(`${p.invoice?.amount} ${p.invoice?.currency ?? "USD"}`)}`);
|
|
57569
58061
|
out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}`);
|
|
57570
58062
|
out(` To ${p.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
|
|
57571
|
-
if (p.display?.hasMemo)
|
|
57572
|
-
|
|
57573
|
-
|
|
58063
|
+
if (p.display?.hasMemo) {
|
|
58064
|
+
out(` Memo ${p.display.receiverMemoMasked} ${dim(`(${p.display.memoType})`)}`);
|
|
58065
|
+
}
|
|
58066
|
+
out(` Expires in ${bold(p.expiry?.expiresIn ?? "?")} ${dim(`(${p.expiry?.effectiveDeadlineIso})`)}`);
|
|
58067
|
+
if (p.reused) {
|
|
58068
|
+
out(` ${dim(`Reusing the existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}`);
|
|
58069
|
+
}
|
|
58070
|
+
out(` ${dim("An order you never fund simply expires and costs nothing.")}`);
|
|
57574
58071
|
out();
|
|
57575
58072
|
out(dim(` The amount you send includes bridge and network fees, so it is`));
|
|
57576
58073
|
out(dim(` normally larger than the invoice.`));
|
|
@@ -57623,6 +58120,8 @@ async function cmdPay(opts) {
|
|
|
57623
58120
|
if (opts.dryRun) sendArgs.push("--dry-run");
|
|
57624
58121
|
else sendArgs.push("--send");
|
|
57625
58122
|
if (opts.rpc) sendArgs.push("--rpc", opts.rpc);
|
|
58123
|
+
if (opts.keyfile) sendArgs.push("--keyfile", opts.keyfile);
|
|
58124
|
+
if (opts.envFile) sendArgs.push("--env-file", opts.envFile);
|
|
57626
58125
|
if (!opts.json) out(dim(opts.dryRun ? " Preparing (dry run)\u2026" : " Sending\u2026"));
|
|
57627
58126
|
const sent = await step(sender, sendArgs);
|
|
57628
58127
|
sendResult = sent.payload;
|
|
@@ -57636,10 +58135,14 @@ async function cmdPay(opts) {
|
|
|
57636
58135
|
printMoneyWarning(sent.payload);
|
|
57637
58136
|
return sent.exitCode;
|
|
57638
58137
|
} else if (opts.dryRun) {
|
|
58138
|
+
if (sent.payload.keySource) out(` ${dim(`would sign with ${sent.payload.keySource}`)}`);
|
|
57639
58139
|
out(` ${green("\u2713")} Dry run only \u2014 nothing was signed or broadcast.`);
|
|
57640
58140
|
out();
|
|
57641
58141
|
return sent.exitCode;
|
|
57642
58142
|
} else {
|
|
58143
|
+
if (sent.payload.sent?.keySource) {
|
|
58144
|
+
out(` ${dim(`signing with ${sent.payload.sent.keySource}`)}`);
|
|
58145
|
+
}
|
|
57643
58146
|
out(` ${green("\u2713")} Sent. tx ${sent.payload.txHash}`);
|
|
57644
58147
|
out();
|
|
57645
58148
|
savePrefs({ lastPreset: chosenPreset || presetFor(chainId, tokenSymbol) });
|
|
@@ -57693,7 +58196,12 @@ function printDeposit(deposit, opts) {
|
|
|
57693
58196
|
out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)}`);
|
|
57694
58197
|
out(` Chain ${bold(deposit.chain)}`);
|
|
57695
58198
|
out(` Address ${bold(deposit.receiverAddress)}`);
|
|
57696
|
-
if (deposit.receiverMemo)
|
|
58199
|
+
if (deposit.receiverMemo) {
|
|
58200
|
+
out(
|
|
58201
|
+
` Memo ${bold(deposit.receiverMemo)} ${red(`(required, ${deposit.receiverMemoType})`)}`
|
|
58202
|
+
);
|
|
58203
|
+
}
|
|
58204
|
+
if (deposit.expiresIn) out(` Expires in ${bold(deposit.expiresIn)}`);
|
|
57697
58205
|
}
|
|
57698
58206
|
out();
|
|
57699
58207
|
const unit = isSatsUnit(deposit.amountUnit) ? "sats" : deposit.tokenSymbol;
|