@rozoai/checkout 0.1.0 → 0.1.1
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 +189 -215
- package/{QUICKSTART.es.md → docs/QUICKSTART.es.md} +55 -24
- package/{QUICKSTART.ja.md → docs/QUICKSTART.ja.md} +52 -22
- package/{QUICKSTART.md → docs/QUICKSTART.md} +54 -24
- package/{QUICKSTART.zh.md → docs/QUICKSTART.zh.md} +41 -18
- package/docs/README.es.md +251 -0
- package/docs/README.ja.md +255 -0
- package/docs/README.zh.md +233 -0
- package/docs/how-it-works.md +43 -0
- package/docs/safety.md +74 -0
- package/llms.txt +17 -7
- package/package.json +3 -8
- package/scripts/dist/cli.js +853 -481
- package/scripts/dist/send-evm.js +11 -40
- package/scripts/dist/send-sol.js +11 -40
- package/README.es.md +0 -287
- package/README.ja.md +0 -265
- package/README.zh.md +0 -187
package/scripts/dist/cli.js
CHANGED
|
@@ -20457,8 +20457,8 @@ var require_dist = __commonJS({
|
|
|
20457
20457
|
constructor(failure, failures) {
|
|
20458
20458
|
let cached2;
|
|
20459
20459
|
const { message, explanation, ...rest } = failure;
|
|
20460
|
-
const { path:
|
|
20461
|
-
const msg =
|
|
20460
|
+
const { path: path4 } = failure;
|
|
20461
|
+
const msg = path4.length === 0 ? message : `At path: ${path4.join(".")} -- ${message}`;
|
|
20462
20462
|
super(explanation ?? msg);
|
|
20463
20463
|
if (explanation != null)
|
|
20464
20464
|
this.cause = msg;
|
|
@@ -20503,15 +20503,15 @@ var require_dist = __commonJS({
|
|
|
20503
20503
|
} else if (typeof result === "string") {
|
|
20504
20504
|
result = { message: result };
|
|
20505
20505
|
}
|
|
20506
|
-
const { path:
|
|
20506
|
+
const { path: path4, branch } = context;
|
|
20507
20507
|
const { type: type2 } = struct6;
|
|
20508
20508
|
const { refinement, message = `Expected a value of type \`${type2}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result;
|
|
20509
20509
|
return {
|
|
20510
20510
|
value,
|
|
20511
20511
|
type: type2,
|
|
20512
20512
|
refinement,
|
|
20513
|
-
key:
|
|
20514
|
-
path:
|
|
20513
|
+
key: path4[path4.length - 1],
|
|
20514
|
+
path: path4,
|
|
20515
20515
|
branch,
|
|
20516
20516
|
...result,
|
|
20517
20517
|
message
|
|
@@ -20529,8 +20529,8 @@ var require_dist = __commonJS({
|
|
|
20529
20529
|
}
|
|
20530
20530
|
}
|
|
20531
20531
|
function* run6(value, struct6, options = {}) {
|
|
20532
|
-
const { path:
|
|
20533
|
-
const ctx = { path:
|
|
20532
|
+
const { path: path4 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
|
|
20533
|
+
const ctx = { path: path4, branch, mask: mask2 };
|
|
20534
20534
|
if (coerce2) {
|
|
20535
20535
|
value = struct6.coercer(value, ctx);
|
|
20536
20536
|
}
|
|
@@ -20542,7 +20542,7 @@ var require_dist = __commonJS({
|
|
|
20542
20542
|
}
|
|
20543
20543
|
for (let [k, v, s] of struct6.entries(value, ctx)) {
|
|
20544
20544
|
const ts = run6(v, s, {
|
|
20545
|
-
path: k === void 0 ?
|
|
20545
|
+
path: k === void 0 ? path4 : [...path4, k],
|
|
20546
20546
|
branch: k === void 0 ? branch : [...branch, v],
|
|
20547
20547
|
coerce: coerce2,
|
|
20548
20548
|
mask: mask2,
|
|
@@ -22472,14 +22472,14 @@ var require_url_state_machine = __commonJS({
|
|
|
22472
22472
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
22473
22473
|
}
|
|
22474
22474
|
function shortenPath(url) {
|
|
22475
|
-
const
|
|
22476
|
-
if (
|
|
22475
|
+
const path4 = url.path;
|
|
22476
|
+
if (path4.length === 0) {
|
|
22477
22477
|
return;
|
|
22478
22478
|
}
|
|
22479
|
-
if (url.scheme === "file" &&
|
|
22479
|
+
if (url.scheme === "file" && path4.length === 1 && isNormalizedWindowsDriveLetter(path4[0])) {
|
|
22480
22480
|
return;
|
|
22481
22481
|
}
|
|
22482
|
-
|
|
22482
|
+
path4.pop();
|
|
22483
22483
|
}
|
|
22484
22484
|
function includesCredentials(url) {
|
|
22485
22485
|
return url.username !== "" || url.password !== "";
|
|
@@ -24774,16 +24774,16 @@ var require_constants = __commonJS({
|
|
|
24774
24774
|
// node_modules/node-gyp-build/node-gyp-build.js
|
|
24775
24775
|
var require_node_gyp_build = __commonJS({
|
|
24776
24776
|
"node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
24777
|
-
var
|
|
24778
|
-
var
|
|
24779
|
-
var
|
|
24777
|
+
var fs5 = __require("fs");
|
|
24778
|
+
var path4 = __require("path");
|
|
24779
|
+
var os3 = __require("os");
|
|
24780
24780
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
24781
24781
|
var vars = process.config && process.config.variables || {};
|
|
24782
24782
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
24783
24783
|
var abi2 = process.versions.modules;
|
|
24784
24784
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
24785
|
-
var arch = process.env.npm_config_arch ||
|
|
24786
|
-
var platform = process.env.npm_config_platform ||
|
|
24785
|
+
var arch = process.env.npm_config_arch || os3.arch();
|
|
24786
|
+
var platform = process.env.npm_config_platform || os3.platform();
|
|
24787
24787
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
24788
24788
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
24789
24789
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -24792,21 +24792,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
24792
24792
|
return runtimeRequire(load.resolve(dir));
|
|
24793
24793
|
}
|
|
24794
24794
|
load.resolve = load.path = function(dir) {
|
|
24795
|
-
dir =
|
|
24795
|
+
dir = path4.resolve(dir || ".");
|
|
24796
24796
|
try {
|
|
24797
|
-
var name = runtimeRequire(
|
|
24797
|
+
var name = runtimeRequire(path4.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
24798
24798
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
24799
24799
|
} catch (err) {
|
|
24800
24800
|
}
|
|
24801
24801
|
if (!prebuildsOnly) {
|
|
24802
|
-
var release = getFirst(
|
|
24802
|
+
var release = getFirst(path4.join(dir, "build/Release"), matchBuild);
|
|
24803
24803
|
if (release) return release;
|
|
24804
|
-
var debug = getFirst(
|
|
24804
|
+
var debug = getFirst(path4.join(dir, "build/Debug"), matchBuild);
|
|
24805
24805
|
if (debug) return debug;
|
|
24806
24806
|
}
|
|
24807
24807
|
var prebuild = resolve(dir);
|
|
24808
24808
|
if (prebuild) return prebuild;
|
|
24809
|
-
var nearby = resolve(
|
|
24809
|
+
var nearby = resolve(path4.dirname(process.execPath));
|
|
24810
24810
|
if (nearby) return nearby;
|
|
24811
24811
|
var target = [
|
|
24812
24812
|
"platform=" + platform,
|
|
@@ -24823,26 +24823,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
24823
24823
|
].filter(Boolean).join(" ");
|
|
24824
24824
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
24825
24825
|
function resolve(dir2) {
|
|
24826
|
-
var tuples = readdirSync(
|
|
24826
|
+
var tuples = readdirSync(path4.join(dir2, "prebuilds")).map(parseTuple);
|
|
24827
24827
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
24828
24828
|
if (!tuple) return;
|
|
24829
|
-
var prebuilds =
|
|
24829
|
+
var prebuilds = path4.join(dir2, "prebuilds", tuple.name);
|
|
24830
24830
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
24831
24831
|
var candidates = parsed.filter(matchTags(runtime, abi2));
|
|
24832
24832
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
24833
|
-
if (winner) return
|
|
24833
|
+
if (winner) return path4.join(prebuilds, winner.file);
|
|
24834
24834
|
}
|
|
24835
24835
|
};
|
|
24836
24836
|
function readdirSync(dir) {
|
|
24837
24837
|
try {
|
|
24838
|
-
return
|
|
24838
|
+
return fs5.readdirSync(dir);
|
|
24839
24839
|
} catch (err) {
|
|
24840
24840
|
return [];
|
|
24841
24841
|
}
|
|
24842
24842
|
}
|
|
24843
24843
|
function getFirst(dir, filter) {
|
|
24844
24844
|
var files = readdirSync(dir).filter(filter);
|
|
24845
|
-
return files[0] &&
|
|
24845
|
+
return files[0] && path4.join(dir, files[0]);
|
|
24846
24846
|
}
|
|
24847
24847
|
function matchBuild(name) {
|
|
24848
24848
|
return /\.node$/.test(name);
|
|
@@ -24929,7 +24929,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
24929
24929
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
24930
24930
|
}
|
|
24931
24931
|
function isAlpine(platform2) {
|
|
24932
|
-
return platform2 === "linux" &&
|
|
24932
|
+
return platform2 === "linux" && fs5.existsSync("/etc/alpine-release");
|
|
24933
24933
|
}
|
|
24934
24934
|
load.parseTags = parseTags;
|
|
24935
24935
|
load.matchTags = matchTags;
|
|
@@ -41168,20 +41168,20 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41168
41168
|
var rest = decodeURI(uri.substring(7));
|
|
41169
41169
|
var firstSlash = rest.indexOf("/");
|
|
41170
41170
|
var host = rest.substring(0, firstSlash);
|
|
41171
|
-
var
|
|
41171
|
+
var path4 = rest.substring(firstSlash + 1);
|
|
41172
41172
|
if ("localhost" == host) host = "";
|
|
41173
41173
|
if (host) {
|
|
41174
41174
|
host = sep + sep + host;
|
|
41175
41175
|
}
|
|
41176
|
-
|
|
41176
|
+
path4 = path4.replace(/^(.+)\|/, "$1:");
|
|
41177
41177
|
if (sep == "\\") {
|
|
41178
|
-
|
|
41178
|
+
path4 = path4.replace(/\//g, "\\");
|
|
41179
41179
|
}
|
|
41180
|
-
if (/^.+\:/.test(
|
|
41180
|
+
if (/^.+\:/.test(path4)) {
|
|
41181
41181
|
} else {
|
|
41182
|
-
|
|
41182
|
+
path4 = sep + path4;
|
|
41183
41183
|
}
|
|
41184
|
-
return host +
|
|
41184
|
+
return host + path4;
|
|
41185
41185
|
}
|
|
41186
41186
|
}
|
|
41187
41187
|
});
|
|
@@ -41189,19 +41189,19 @@ var require_file_uri_to_path = __commonJS({
|
|
|
41189
41189
|
// node_modules/bindings/bindings.js
|
|
41190
41190
|
var require_bindings = __commonJS({
|
|
41191
41191
|
"node_modules/bindings/bindings.js"(exports, module) {
|
|
41192
|
-
var
|
|
41193
|
-
var
|
|
41192
|
+
var fs5 = __require("fs");
|
|
41193
|
+
var path4 = __require("path");
|
|
41194
41194
|
var fileURLToPath2 = require_file_uri_to_path();
|
|
41195
|
-
var join =
|
|
41196
|
-
var dirname =
|
|
41197
|
-
var exists =
|
|
41195
|
+
var join = path4.join;
|
|
41196
|
+
var dirname = path4.dirname;
|
|
41197
|
+
var exists = fs5.accessSync && function(path5) {
|
|
41198
41198
|
try {
|
|
41199
|
-
|
|
41199
|
+
fs5.accessSync(path5);
|
|
41200
41200
|
} catch (e) {
|
|
41201
41201
|
return false;
|
|
41202
41202
|
}
|
|
41203
41203
|
return true;
|
|
41204
|
-
} ||
|
|
41204
|
+
} || fs5.existsSync || path4.existsSync;
|
|
41205
41205
|
var defaults = {
|
|
41206
41206
|
arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
|
|
41207
41207
|
compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
|
|
@@ -41246,7 +41246,7 @@ var require_bindings = __commonJS({
|
|
|
41246
41246
|
if (!opts.module_root) {
|
|
41247
41247
|
opts.module_root = exports.getRoot(exports.getFileName());
|
|
41248
41248
|
}
|
|
41249
|
-
if (
|
|
41249
|
+
if (path4.extname(opts.bindings) != ".node") {
|
|
41250
41250
|
opts.bindings += ".node";
|
|
41251
41251
|
}
|
|
41252
41252
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
@@ -41685,6 +41685,44 @@ var CHAIN_ALIASES = {
|
|
|
41685
41685
|
bitcoin: "lightning",
|
|
41686
41686
|
btc: "lightning"
|
|
41687
41687
|
};
|
|
41688
|
+
var PICKER_OPTIONS = [
|
|
41689
|
+
{ token: "USDT", chain: "Solana", chainId: "900" },
|
|
41690
|
+
{ token: "USDT", chain: "BNB Chain", chainId: "56" },
|
|
41691
|
+
{ token: "USDT", chain: "Ethereum", chainId: "1" },
|
|
41692
|
+
{ token: "USDT", chain: "Polygon", chainId: "137" },
|
|
41693
|
+
{ token: "USDC", chain: "Solana", chainId: "900" },
|
|
41694
|
+
{ token: "USDC", chain: "BNB Chain", chainId: "56" },
|
|
41695
|
+
{ token: "USDC", chain: "Ethereum", chainId: "1" },
|
|
41696
|
+
{ token: "USDC", chain: "Polygon", chainId: "137" },
|
|
41697
|
+
{ token: "USDC", chain: "Base", chainId: "8453" },
|
|
41698
|
+
{ token: "USDC", chain: "Stellar", chainId: "1500" },
|
|
41699
|
+
{ token: "BTC", chain: "Lightning", chainId: "lightning" }
|
|
41700
|
+
].map((o, i) => ({
|
|
41701
|
+
number: i + 1,
|
|
41702
|
+
...o,
|
|
41703
|
+
tokenSymbol: o.token,
|
|
41704
|
+
// The flag a user could have typed instead, echoed back so they learn it.
|
|
41705
|
+
preset: `${o.token.toLowerCase()}-${{ "900": "solana", "56": "bnb", "1": "ethereum", "137": "polygon", "8453": "base", "1500": "stellar", lightning: "lightning" }[o.chainId]}`
|
|
41706
|
+
}));
|
|
41707
|
+
function resolvePickerChoice(input) {
|
|
41708
|
+
const raw = String(input ?? "").trim();
|
|
41709
|
+
if (!raw) throw new CliError("BAD_CHOICE", "Nothing entered. Type the number of a coin.");
|
|
41710
|
+
if (/^\d+$/.test(raw)) {
|
|
41711
|
+
const option2 = PICKER_OPTIONS.find((o) => o.number === Number(raw));
|
|
41712
|
+
if (!option2) {
|
|
41713
|
+
throw new CliError(
|
|
41714
|
+
"BAD_CHOICE",
|
|
41715
|
+
`${raw} is not on the list. Choose 1-${PICKER_OPTIONS.length}.`
|
|
41716
|
+
);
|
|
41717
|
+
}
|
|
41718
|
+
return { chainId: option2.chainId, tokenSymbol: option2.tokenSymbol, preset: option2.preset };
|
|
41719
|
+
}
|
|
41720
|
+
const { chainId, tokenSymbol } = resolvePreset(raw);
|
|
41721
|
+
const option = PICKER_OPTIONS.find(
|
|
41722
|
+
(o) => o.chainId === chainId && o.tokenSymbol === tokenSymbol
|
|
41723
|
+
);
|
|
41724
|
+
return { chainId, tokenSymbol, preset: option ? option.preset : raw.toLowerCase() };
|
|
41725
|
+
}
|
|
41688
41726
|
var CliError = class extends Error {
|
|
41689
41727
|
constructor(code, message, details) {
|
|
41690
41728
|
super(message);
|
|
@@ -41737,11 +41775,11 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
41737
41775
|
"help",
|
|
41738
41776
|
"version",
|
|
41739
41777
|
"dry-run",
|
|
41740
|
-
"yes-large",
|
|
41741
41778
|
"no-watch",
|
|
41742
|
-
"watch"
|
|
41779
|
+
"watch",
|
|
41780
|
+
"fresh"
|
|
41743
41781
|
]);
|
|
41744
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout"]);
|
|
41782
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["with", "chain", "token", "rpc", "timeout", "payer"]);
|
|
41745
41783
|
function parseCliArgs(argv) {
|
|
41746
41784
|
const positional = [];
|
|
41747
41785
|
const flags = {};
|
|
@@ -41838,8 +41876,10 @@ function parseCliArgs(argv) {
|
|
|
41838
41876
|
);
|
|
41839
41877
|
}
|
|
41840
41878
|
source = { chainId, tokenSymbol };
|
|
41841
|
-
} else {
|
|
41879
|
+
} else if (flags.with !== void 0) {
|
|
41842
41880
|
source = resolvePreset(flags.with);
|
|
41881
|
+
} else {
|
|
41882
|
+
source = null;
|
|
41843
41883
|
}
|
|
41844
41884
|
const timeout = flags.timeout === void 0 ? 900 : Number(flags.timeout);
|
|
41845
41885
|
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
@@ -41853,16 +41893,18 @@ function parseCliArgs(argv) {
|
|
|
41853
41893
|
send: flags.send === true,
|
|
41854
41894
|
yes: flags.yes === true,
|
|
41855
41895
|
dryRun: flags["dry-run"] === true,
|
|
41856
|
-
yesLarge: flags["yes-large"] === true,
|
|
41857
41896
|
watch: flags["no-watch"] !== true,
|
|
41858
41897
|
timeout,
|
|
41859
|
-
rpc: flags.rpc
|
|
41898
|
+
rpc: flags.rpc,
|
|
41899
|
+
payer: flags.payer,
|
|
41900
|
+
fresh: flags.fresh === true
|
|
41860
41901
|
};
|
|
41861
41902
|
}
|
|
41862
41903
|
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link with BTC Lightning,
|
|
41863
41904
|
or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.
|
|
41864
41905
|
|
|
41865
41906
|
USAGE
|
|
41907
|
+
npx @rozoai/checkout pay <coinbase-link> (pick a coin from a list)
|
|
41866
41908
|
npx @rozoai/checkout pay <coinbase-link> --with <coin>
|
|
41867
41909
|
npx @rozoai/checkout quote <coinbase-link>
|
|
41868
41910
|
npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
|
|
@@ -41873,21 +41915,31 @@ COINS (--with)
|
|
|
41873
41915
|
usdc-base usdc-stellar btc-lightning
|
|
41874
41916
|
or give them raw: --chain 900 --token USDT
|
|
41875
41917
|
|
|
41918
|
+
By default, pay just prints an address for you to pay from any wallet:
|
|
41919
|
+
no private key, no environment variable, no configuration.
|
|
41920
|
+
|
|
41876
41921
|
OPTIONS
|
|
41877
|
-
--with <coin> which coin to pay with
|
|
41878
|
-
|
|
41879
|
-
|
|
41922
|
+
--with <coin> which coin to pay with. Omit it on a terminal and you get a
|
|
41923
|
+
numbered list to choose from; scripts and agents must pass it.
|
|
41924
|
+
--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.
|
|
41880
41928
|
--yes, -y skip the interactive confirmation (required when not a TTY)
|
|
41881
41929
|
--dry-run with --send, show what would be signed and sign nothing
|
|
41882
|
-
--yes-large allow a single payment above the $100 cap
|
|
41883
41930
|
--json, -j machine-readable output
|
|
41884
41931
|
--no-watch stop after showing the deposit instructions
|
|
41885
41932
|
--timeout <s> how long to poll for settlement (default 900)
|
|
41933
|
+
--payer <addr> optional. Check what this wallet holds and mark the coin
|
|
41934
|
+
list accordingly. Display help only; it never changes what
|
|
41935
|
+
gets signed.
|
|
41936
|
+
--fresh ignore the remembered wallet address and coin for this run
|
|
41886
41937
|
--rpc <url> override the RPC endpoint for --send
|
|
41887
41938
|
--help, -h this text
|
|
41888
41939
|
--version, -v print the version
|
|
41889
41940
|
|
|
41890
41941
|
EXAMPLES
|
|
41942
|
+
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01...
|
|
41891
41943
|
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01... --with usdt-solana
|
|
41892
41944
|
npx @rozoai/checkout pay pl_01... --with btc-lightning
|
|
41893
41945
|
npx @rozoai/checkout status 11111111-2222-4333-8444-555555555555
|
|
@@ -41895,6 +41947,572 @@ EXAMPLES
|
|
|
41895
41947
|
Creating an order moves no money; an unfunded order simply expires. Nothing is
|
|
41896
41948
|
paid until you confirm, and --send is required before anything is signed.`;
|
|
41897
41949
|
|
|
41950
|
+
// scripts/src/lib/wallet-check.mjs
|
|
41951
|
+
var INTENT_API_BASE = process.env.ROZO_CHECKOUT_INTENT_API || "https://intentapi.rozo.ai";
|
|
41952
|
+
var BALANCE_TIMEOUT_MS = 3e3;
|
|
41953
|
+
var APP_ID = "rozoCheckoutCli";
|
|
41954
|
+
var DEST_CHAIN_ID = 8453;
|
|
41955
|
+
var EVM_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
41956
|
+
var STELLAR_RE = /^G[A-Z2-7]{55}$/;
|
|
41957
|
+
var SOLANA_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
41958
|
+
function detectAddressFamily(address) {
|
|
41959
|
+
const a = String(address ?? "").trim();
|
|
41960
|
+
if (!a) return null;
|
|
41961
|
+
if (EVM_RE.test(a)) return "evm";
|
|
41962
|
+
if (STELLAR_RE.test(a)) return "stellar";
|
|
41963
|
+
if (SOLANA_RE.test(a)) return "solana";
|
|
41964
|
+
return null;
|
|
41965
|
+
}
|
|
41966
|
+
function familyCoversChain(family, chainId) {
|
|
41967
|
+
const id = String(chainId);
|
|
41968
|
+
if (id === "lightning") return false;
|
|
41969
|
+
if (id === "900") return family === "solana";
|
|
41970
|
+
if (id === "1500") return family === "stellar";
|
|
41971
|
+
return family === "evm";
|
|
41972
|
+
}
|
|
41973
|
+
function buildBalanceRequest({ family, address, usdRequired }) {
|
|
41974
|
+
const usd = Number(usdRequired);
|
|
41975
|
+
if (!Number.isFinite(usd) || usd <= 0) {
|
|
41976
|
+
throw new SkillError("BAD_USD_REQUIRED", "A positive USD amount is required.");
|
|
41977
|
+
}
|
|
41978
|
+
const wrap3 = (payload) => encodeURIComponent(JSON.stringify({ 0: payload }));
|
|
41979
|
+
if (family === "evm") {
|
|
41980
|
+
return `${INTENT_API_BASE}/getWalletPaymentOptions?input=${wrap3({
|
|
41981
|
+
payerAddress: address,
|
|
41982
|
+
usdRequired: usd,
|
|
41983
|
+
destChainId: DEST_CHAIN_ID,
|
|
41984
|
+
appId: APP_ID
|
|
41985
|
+
})}`;
|
|
41986
|
+
}
|
|
41987
|
+
if (family === "solana") {
|
|
41988
|
+
return `${INTENT_API_BASE}/getSolanaPaymentOptions?input=${wrap3({
|
|
41989
|
+
pubKey: address,
|
|
41990
|
+
usdRequired: usd,
|
|
41991
|
+
appId: APP_ID
|
|
41992
|
+
})}`;
|
|
41993
|
+
}
|
|
41994
|
+
if (family === "stellar") {
|
|
41995
|
+
return `${INTENT_API_BASE}/getStellarPaymentOptions?input=${wrap3({
|
|
41996
|
+
stellarAddress: address,
|
|
41997
|
+
usdRequired: usd,
|
|
41998
|
+
appId: APP_ID
|
|
41999
|
+
})}`;
|
|
42000
|
+
}
|
|
42001
|
+
throw new SkillError("BAD_ADDRESS_FAMILY", `No balance endpoint for family "${family}".`);
|
|
42002
|
+
}
|
|
42003
|
+
function parsePaymentOptions(json) {
|
|
42004
|
+
const data = Array.isArray(json) ? json[0]?.result?.data : json?.result?.data ?? json?.data;
|
|
42005
|
+
if (!Array.isArray(data)) return [];
|
|
42006
|
+
const out2 = [];
|
|
42007
|
+
for (const o of data) {
|
|
42008
|
+
const token = o?.balance?.token ?? o?.required?.token;
|
|
42009
|
+
const chainId = token?.chainId;
|
|
42010
|
+
const symbol = token?.symbol;
|
|
42011
|
+
if (chainId === void 0 || !symbol) continue;
|
|
42012
|
+
const balanceUsd = Number(o?.balance?.usd);
|
|
42013
|
+
const requiredUsd = Number(o?.required?.usd);
|
|
42014
|
+
out2.push({
|
|
42015
|
+
chainId: String(chainId),
|
|
42016
|
+
tokenSymbol: String(symbol).toUpperCase(),
|
|
42017
|
+
balanceUsd: Number.isFinite(balanceUsd) ? balanceUsd : null,
|
|
42018
|
+
requiredUsd: Number.isFinite(requiredUsd) ? requiredUsd : null,
|
|
42019
|
+
// The backend's own verdict wins when present; otherwise compare.
|
|
42020
|
+
affordable: o?.disabledReason === void 0 || o?.disabledReason === null ? true : false,
|
|
42021
|
+
disabledReason: o?.disabledReason ?? null
|
|
42022
|
+
});
|
|
42023
|
+
}
|
|
42024
|
+
return out2;
|
|
42025
|
+
}
|
|
42026
|
+
function markPickerOptions(pickerOptions, { family = null, options = [] } = {}) {
|
|
42027
|
+
const index2 = /* @__PURE__ */ new Map();
|
|
42028
|
+
for (const o of options) index2.set(`${o.chainId}:${o.tokenSymbol}`, o);
|
|
42029
|
+
const marked = pickerOptions.map((row) => {
|
|
42030
|
+
if (!family || !familyCoversChain(family, row.chainId)) {
|
|
42031
|
+
return { ...row, mark: "unchecked", balanceUsd: null, note: null };
|
|
42032
|
+
}
|
|
42033
|
+
const hit = index2.get(`${row.chainId}:${row.tokenSymbol}`);
|
|
42034
|
+
if (!hit) {
|
|
42035
|
+
return { ...row, mark: "insufficient", balanceUsd: 0, note: "no balance found" };
|
|
42036
|
+
}
|
|
42037
|
+
return {
|
|
42038
|
+
...row,
|
|
42039
|
+
mark: hit.affordable ? "affordable" : "insufficient",
|
|
42040
|
+
balanceUsd: hit.balanceUsd,
|
|
42041
|
+
note: hit.disabledReason
|
|
42042
|
+
};
|
|
42043
|
+
});
|
|
42044
|
+
const rank = { affordable: 0, unchecked: 1, insufficient: 2 };
|
|
42045
|
+
return marked.map((row, i) => ({ row, i })).sort((a, b) => rank[a.row.mark] - rank[b.row.mark] || a.i - b.i).map(({ row }) => row);
|
|
42046
|
+
}
|
|
42047
|
+
async function fetchWalletOptions({ address, usdRequired, timeoutMs = BALANCE_TIMEOUT_MS }) {
|
|
42048
|
+
const family = detectAddressFamily(address);
|
|
42049
|
+
if (!family) return { ok: false, family: null, options: [], reason: "unrecognised address format" };
|
|
42050
|
+
let url;
|
|
42051
|
+
try {
|
|
42052
|
+
url = buildBalanceRequest({ family, address, usdRequired });
|
|
42053
|
+
} catch (err) {
|
|
42054
|
+
return { ok: false, family, options: [], reason: err.message };
|
|
42055
|
+
}
|
|
42056
|
+
const controller = new AbortController();
|
|
42057
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
42058
|
+
try {
|
|
42059
|
+
const res = await fetch(url, {
|
|
42060
|
+
headers: { accept: "application/json" },
|
|
42061
|
+
signal: controller.signal
|
|
42062
|
+
});
|
|
42063
|
+
if (!res.ok) {
|
|
42064
|
+
return { ok: false, family, options: [], reason: `balance service returned HTTP ${res.status}` };
|
|
42065
|
+
}
|
|
42066
|
+
const json = await res.json();
|
|
42067
|
+
return { ok: true, family, options: parsePaymentOptions(json), reason: null };
|
|
42068
|
+
} catch (err) {
|
|
42069
|
+
const reason = err?.name === "AbortError" ? "balance check timed out" : "balance service unreachable";
|
|
42070
|
+
return { ok: false, family, options: [], reason };
|
|
42071
|
+
} finally {
|
|
42072
|
+
clearTimeout(timer);
|
|
42073
|
+
}
|
|
42074
|
+
}
|
|
42075
|
+
function isProvablyShort({ chainId, tokenSymbol }, { ok, family, options }) {
|
|
42076
|
+
if (!ok || !family) return false;
|
|
42077
|
+
if (!familyCoversChain(family, chainId)) return false;
|
|
42078
|
+
const hit = options.find(
|
|
42079
|
+
(o) => o.chainId === String(chainId) && o.tokenSymbol === String(tokenSymbol).toUpperCase()
|
|
42080
|
+
);
|
|
42081
|
+
if (!hit) return false;
|
|
42082
|
+
return hit.affordable === false;
|
|
42083
|
+
}
|
|
42084
|
+
|
|
42085
|
+
// scripts/src/lib/prefs.mjs
|
|
42086
|
+
import fs2 from "node:fs";
|
|
42087
|
+
import path2 from "node:path";
|
|
42088
|
+
import os2 from "node:os";
|
|
42089
|
+
|
|
42090
|
+
// scripts/src/lib/state.mjs
|
|
42091
|
+
import fs from "node:fs";
|
|
42092
|
+
import path from "node:path";
|
|
42093
|
+
import os from "node:os";
|
|
42094
|
+
import crypto2 from "node:crypto";
|
|
42095
|
+
var LOCK_STALE_MS = 6e4;
|
|
42096
|
+
var LOCK_WAIT_MS = 1e4;
|
|
42097
|
+
var LOCK_POLL_MS = 25;
|
|
42098
|
+
function lockPath() {
|
|
42099
|
+
return path.join(stateRoot(), ".send.lock");
|
|
42100
|
+
}
|
|
42101
|
+
function sleepSync(ms) {
|
|
42102
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
42103
|
+
}
|
|
42104
|
+
function tryAcquire(file) {
|
|
42105
|
+
try {
|
|
42106
|
+
const fd = fs.openSync(file, "wx", 384);
|
|
42107
|
+
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
42108
|
+
fs.closeSync(fd);
|
|
42109
|
+
return true;
|
|
42110
|
+
} catch (err) {
|
|
42111
|
+
if (err.code !== "EEXIST") throw err;
|
|
42112
|
+
return false;
|
|
42113
|
+
}
|
|
42114
|
+
}
|
|
42115
|
+
function withLock(fn) {
|
|
42116
|
+
const file = lockPath();
|
|
42117
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
|
|
42118
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
42119
|
+
for (; ; ) {
|
|
42120
|
+
if (tryAcquire(file)) break;
|
|
42121
|
+
try {
|
|
42122
|
+
const age = Date.now() - fs.statSync(file).mtimeMs;
|
|
42123
|
+
if (age > LOCK_STALE_MS) {
|
|
42124
|
+
const stolen = `${file}.stale.${crypto2.randomBytes(4).toString("hex")}`;
|
|
42125
|
+
try {
|
|
42126
|
+
fs.renameSync(file, stolen);
|
|
42127
|
+
fs.unlinkSync(stolen);
|
|
42128
|
+
} catch {
|
|
42129
|
+
}
|
|
42130
|
+
continue;
|
|
42131
|
+
}
|
|
42132
|
+
} catch (err) {
|
|
42133
|
+
if (err.code !== "ENOENT") throw err;
|
|
42134
|
+
continue;
|
|
42135
|
+
}
|
|
42136
|
+
if (Date.now() > deadline) {
|
|
42137
|
+
throw new SkillError(
|
|
42138
|
+
"LOCK_TIMEOUT",
|
|
42139
|
+
"Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
|
|
42140
|
+
);
|
|
42141
|
+
}
|
|
42142
|
+
sleepSync(LOCK_POLL_MS);
|
|
42143
|
+
}
|
|
42144
|
+
try {
|
|
42145
|
+
return fn();
|
|
42146
|
+
} finally {
|
|
42147
|
+
try {
|
|
42148
|
+
fs.unlinkSync(file);
|
|
42149
|
+
} catch {
|
|
42150
|
+
}
|
|
42151
|
+
}
|
|
42152
|
+
}
|
|
42153
|
+
function stateRoot() {
|
|
42154
|
+
return process.env.ROZO_CHECKOUT_STATE_DIR || path.join(os.homedir(), ".rozo-checkout", "state");
|
|
42155
|
+
}
|
|
42156
|
+
function statePath(rozoPaymentId) {
|
|
42157
|
+
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
|
|
42158
|
+
throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
|
|
42159
|
+
}
|
|
42160
|
+
return path.join(stateRoot(), `${rozoPaymentId}.json`);
|
|
42161
|
+
}
|
|
42162
|
+
function writeAtomic(file, data) {
|
|
42163
|
+
const dir = path.dirname(file);
|
|
42164
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
42165
|
+
const tmp = path.join(dir, `.${path.basename(file)}.${crypto2.randomBytes(6).toString("hex")}.tmp`);
|
|
42166
|
+
const fd = fs.openSync(tmp, "wx", 384);
|
|
42167
|
+
try {
|
|
42168
|
+
fs.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
42169
|
+
fs.fsyncSync(fd);
|
|
42170
|
+
} finally {
|
|
42171
|
+
fs.closeSync(fd);
|
|
42172
|
+
}
|
|
42173
|
+
fs.renameSync(tmp, file);
|
|
42174
|
+
}
|
|
42175
|
+
function readState(rozoPaymentId) {
|
|
42176
|
+
const file = statePath(rozoPaymentId);
|
|
42177
|
+
let raw;
|
|
42178
|
+
try {
|
|
42179
|
+
raw = fs.readFileSync(file, "utf8");
|
|
42180
|
+
} catch (err) {
|
|
42181
|
+
if (err.code === "ENOENT") return null;
|
|
42182
|
+
throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
|
|
42183
|
+
}
|
|
42184
|
+
try {
|
|
42185
|
+
return JSON.parse(raw);
|
|
42186
|
+
} catch {
|
|
42187
|
+
throw new SkillError(
|
|
42188
|
+
"STATE_CORRUPT",
|
|
42189
|
+
"The local state file for this order is corrupt. Refusing to act; inspect it manually."
|
|
42190
|
+
);
|
|
42191
|
+
}
|
|
42192
|
+
}
|
|
42193
|
+
function createOrderRecord(record) {
|
|
42194
|
+
return withLock(() => createOrderRecordUnlocked(record));
|
|
42195
|
+
}
|
|
42196
|
+
function createOrderRecordUnlocked(record) {
|
|
42197
|
+
const { rozoPaymentId } = record;
|
|
42198
|
+
const existing = readState(rozoPaymentId);
|
|
42199
|
+
const next = {
|
|
42200
|
+
version: 1,
|
|
42201
|
+
rozoPaymentId,
|
|
42202
|
+
linkId: record.linkId,
|
|
42203
|
+
paymentLink: record.paymentLink ?? null,
|
|
42204
|
+
merchant: record.merchant ?? null,
|
|
42205
|
+
invoiceAmount: record.invoiceAmount ?? null,
|
|
42206
|
+
source: record.source,
|
|
42207
|
+
receiverAddress: record.receiverAddress,
|
|
42208
|
+
receiverMemo: record.receiverMemo ?? null,
|
|
42209
|
+
amount: record.amount,
|
|
42210
|
+
amountUnit: record.amountUnit ?? null,
|
|
42211
|
+
expiresAt: record.expiresAt ?? null,
|
|
42212
|
+
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
42213
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42214
|
+
confirmation: existing?.confirmation ?? null,
|
|
42215
|
+
send: existing?.send ?? null
|
|
42216
|
+
};
|
|
42217
|
+
writeAtomic(statePath(rozoPaymentId), next);
|
|
42218
|
+
return next;
|
|
42219
|
+
}
|
|
42220
|
+
function depositDigest(source) {
|
|
42221
|
+
const canonical = JSON.stringify({
|
|
42222
|
+
chainId: String(source?.chainId ?? ""),
|
|
42223
|
+
tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
|
|
42224
|
+
tokenAddress: String(source?.tokenAddress ?? ""),
|
|
42225
|
+
receiverAddress: String(source?.receiverAddress ?? ""),
|
|
42226
|
+
receiverMemo: source?.receiverMemo ?? null,
|
|
42227
|
+
amount: String(source?.amount ?? ""),
|
|
42228
|
+
amountUnit: source?.amountUnit ?? null,
|
|
42229
|
+
lnInvoice: source?.lnInvoice ?? null
|
|
42230
|
+
});
|
|
42231
|
+
return crypto2.createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
42232
|
+
}
|
|
42233
|
+
function recordConfirmation(rozoPaymentId, { source, invoiceAmount, tier }) {
|
|
42234
|
+
return withLock(() => {
|
|
42235
|
+
const state = readState(rozoPaymentId);
|
|
42236
|
+
if (!state) {
|
|
42237
|
+
throw new SkillError("NO_ORDER_STATE", "Cannot confirm an order with no local record.");
|
|
42238
|
+
}
|
|
42239
|
+
const next = {
|
|
42240
|
+
...state,
|
|
42241
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42242
|
+
confirmation: {
|
|
42243
|
+
confirmedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42244
|
+
depositDigest: depositDigest(source),
|
|
42245
|
+
invoiceAmount: invoiceAmount ?? state.invoiceAmount ?? null,
|
|
42246
|
+
tier: tier ?? null
|
|
42247
|
+
}
|
|
42248
|
+
};
|
|
42249
|
+
writeAtomic(statePath(rozoPaymentId), next);
|
|
42250
|
+
return next;
|
|
42251
|
+
});
|
|
42252
|
+
}
|
|
42253
|
+
function claimSend(rozoPaymentId, intent, { skipCaps = false } = {}) {
|
|
42254
|
+
return withLock(() => {
|
|
42255
|
+
const state = readState(rozoPaymentId);
|
|
42256
|
+
if (!state) {
|
|
42257
|
+
throw new SkillError(
|
|
42258
|
+
"NO_ORDER_STATE",
|
|
42259
|
+
"No local record for this order. Run create-order.js in this same session first."
|
|
42260
|
+
);
|
|
42261
|
+
}
|
|
42262
|
+
if (state.send) {
|
|
42263
|
+
throw new SkillError(
|
|
42264
|
+
"ALREADY_SENT",
|
|
42265
|
+
`A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice. Check the backend for the pay-in before doing anything else.`,
|
|
42266
|
+
{ send: state.send }
|
|
42267
|
+
);
|
|
42268
|
+
}
|
|
42269
|
+
const caps = skipCaps ? null : assertPaymentLimit(state.invoiceAmount);
|
|
42270
|
+
const next = {
|
|
42271
|
+
...state,
|
|
42272
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42273
|
+
send: {
|
|
42274
|
+
status: "claimed",
|
|
42275
|
+
claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42276
|
+
chainId: intent.chainId,
|
|
42277
|
+
tokenSymbol: intent.tokenSymbol,
|
|
42278
|
+
from: intent.from,
|
|
42279
|
+
to: intent.to,
|
|
42280
|
+
amountAtomic: intent.amountAtomic,
|
|
42281
|
+
memo: intent.memo ?? null,
|
|
42282
|
+
nonceBefore: intent.nonceBefore ?? null,
|
|
42283
|
+
expectedTxHash: intent.expectedTxHash ?? null,
|
|
42284
|
+
txHash: null
|
|
42285
|
+
}
|
|
42286
|
+
};
|
|
42287
|
+
writeAtomic(statePath(rozoPaymentId), next);
|
|
42288
|
+
return { state: next, caps };
|
|
42289
|
+
});
|
|
42290
|
+
}
|
|
42291
|
+
function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
|
|
42292
|
+
return withLock(() => {
|
|
42293
|
+
const state = readState(rozoPaymentId);
|
|
42294
|
+
if (!state || !state.send) {
|
|
42295
|
+
throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
|
|
42296
|
+
}
|
|
42297
|
+
const next = {
|
|
42298
|
+
...state,
|
|
42299
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42300
|
+
send: {
|
|
42301
|
+
...state.send,
|
|
42302
|
+
status,
|
|
42303
|
+
txHash: txHash ?? state.send.txHash,
|
|
42304
|
+
note,
|
|
42305
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42306
|
+
}
|
|
42307
|
+
};
|
|
42308
|
+
writeAtomic(statePath(rozoPaymentId), next);
|
|
42309
|
+
return next;
|
|
42310
|
+
});
|
|
42311
|
+
}
|
|
42312
|
+
function findByLinkId(linkId) {
|
|
42313
|
+
const dir = stateRoot();
|
|
42314
|
+
let files = [];
|
|
42315
|
+
try {
|
|
42316
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
42317
|
+
} catch {
|
|
42318
|
+
return null;
|
|
42319
|
+
}
|
|
42320
|
+
let best = null;
|
|
42321
|
+
for (const f of files) {
|
|
42322
|
+
try {
|
|
42323
|
+
const s = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
42324
|
+
if (s?.linkId !== linkId) continue;
|
|
42325
|
+
if (!best || String(s.createdAt) > String(best.createdAt)) best = s;
|
|
42326
|
+
} catch {
|
|
42327
|
+
}
|
|
42328
|
+
}
|
|
42329
|
+
return best;
|
|
42330
|
+
}
|
|
42331
|
+
var MAX_PAYMENT_USD = 1100;
|
|
42332
|
+
function assertPaymentLimit(invoiceUsd) {
|
|
42333
|
+
if (invoiceUsd === null || invoiceUsd === void 0 || String(invoiceUsd).trim() === "") {
|
|
42334
|
+
throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
|
|
42335
|
+
}
|
|
42336
|
+
const usd = Number(invoiceUsd);
|
|
42337
|
+
if (!Number.isFinite(usd) || usd < 0) {
|
|
42338
|
+
throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate the payment limit without a USD amount.");
|
|
42339
|
+
}
|
|
42340
|
+
if (usd > MAX_PAYMENT_USD) {
|
|
42341
|
+
throw new SkillError(
|
|
42342
|
+
"CAP_PER_TX",
|
|
42343
|
+
`$${usd} is above the $${MAX_PAYMENT_USD} per-payment limit for automated sending. This limit has no override. Pay this invoice from a wallet you control instead \u2014 that path needs no key and has no limit.`
|
|
42344
|
+
);
|
|
42345
|
+
}
|
|
42346
|
+
return { usd, limitUsd: MAX_PAYMENT_USD };
|
|
42347
|
+
}
|
|
42348
|
+
|
|
42349
|
+
// scripts/src/lib/prefs.mjs
|
|
42350
|
+
function prefsPath() {
|
|
42351
|
+
const root = process.env.ROZO_CHECKOUT_STATE_DIR ? path2.dirname(stateRoot()) : path2.join(os2.homedir(), ".rozo-checkout");
|
|
42352
|
+
return path2.join(root, "prefs.json");
|
|
42353
|
+
}
|
|
42354
|
+
var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "updatedAt"];
|
|
42355
|
+
function readPrefs() {
|
|
42356
|
+
let raw;
|
|
42357
|
+
try {
|
|
42358
|
+
raw = fs2.readFileSync(prefsPath(), "utf8");
|
|
42359
|
+
} catch {
|
|
42360
|
+
return null;
|
|
42361
|
+
}
|
|
42362
|
+
let doc;
|
|
42363
|
+
try {
|
|
42364
|
+
doc = JSON.parse(raw);
|
|
42365
|
+
} catch {
|
|
42366
|
+
return null;
|
|
42367
|
+
}
|
|
42368
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
42369
|
+
const out2 = {};
|
|
42370
|
+
for (const k of ALLOWED) {
|
|
42371
|
+
const v = doc[k];
|
|
42372
|
+
if (typeof v === "string" && v.trim()) out2[k] = v.trim();
|
|
42373
|
+
}
|
|
42374
|
+
return Object.keys(out2).length ? out2 : null;
|
|
42375
|
+
}
|
|
42376
|
+
function savePrefs(update) {
|
|
42377
|
+
const existing = readPrefs() || {};
|
|
42378
|
+
const next = { ...existing };
|
|
42379
|
+
for (const k of ALLOWED) {
|
|
42380
|
+
if (k === "updatedAt") continue;
|
|
42381
|
+
const v = update?.[k];
|
|
42382
|
+
if (typeof v === "string" && v.trim()) next[k] = v.trim();
|
|
42383
|
+
}
|
|
42384
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42385
|
+
try {
|
|
42386
|
+
writeAtomic(prefsPath(), next);
|
|
42387
|
+
} catch {
|
|
42388
|
+
return null;
|
|
42389
|
+
}
|
|
42390
|
+
return next;
|
|
42391
|
+
}
|
|
42392
|
+
|
|
42393
|
+
// scripts/src/lib/blacklist.mjs
|
|
42394
|
+
import fs3 from "node:fs";
|
|
42395
|
+
import path3 from "node:path";
|
|
42396
|
+
import crypto3 from "node:crypto";
|
|
42397
|
+
import { fileURLToPath } from "node:url";
|
|
42398
|
+
var BlacklistError = class extends Error {
|
|
42399
|
+
constructor(code, message) {
|
|
42400
|
+
super(message);
|
|
42401
|
+
this.code = code;
|
|
42402
|
+
}
|
|
42403
|
+
};
|
|
42404
|
+
function normalizeAddress(address, family) {
|
|
42405
|
+
if (typeof address !== "string") return null;
|
|
42406
|
+
const trimmed = address.trim();
|
|
42407
|
+
if (!trimmed) return null;
|
|
42408
|
+
if (family === "evm" || /^0x[0-9a-fA-F]{40}$/.test(trimmed)) {
|
|
42409
|
+
return trimmed.toLowerCase();
|
|
42410
|
+
}
|
|
42411
|
+
return trimmed;
|
|
42412
|
+
}
|
|
42413
|
+
function parseBlacklist(doc) {
|
|
42414
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
42415
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist document is not an object.");
|
|
42416
|
+
}
|
|
42417
|
+
const { provenance, entries } = doc;
|
|
42418
|
+
if (!provenance || typeof provenance !== "object") {
|
|
42419
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance header is missing.");
|
|
42420
|
+
}
|
|
42421
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
42422
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist is empty or not an array.");
|
|
42423
|
+
}
|
|
42424
|
+
const addresses = [];
|
|
42425
|
+
for (const e of entries) {
|
|
42426
|
+
if (!e || typeof e !== "object" || typeof e.address !== "string" || !e.address.trim()) {
|
|
42427
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist entry has no address.");
|
|
42428
|
+
}
|
|
42429
|
+
addresses.push(e.address);
|
|
42430
|
+
}
|
|
42431
|
+
const digest = crypto3.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
|
|
42432
|
+
if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
|
|
42433
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
|
|
42434
|
+
}
|
|
42435
|
+
if (digest !== provenance.addressesSha256) {
|
|
42436
|
+
throw new BlacklistError(
|
|
42437
|
+
"BLACKLIST_UNAVAILABLE",
|
|
42438
|
+
"Blacklist digest mismatch \u2014 the vendored address list was modified without re-signing."
|
|
42439
|
+
);
|
|
42440
|
+
}
|
|
42441
|
+
if (provenance.addressCount !== void 0 && provenance.addressCount !== entries.length) {
|
|
42442
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist addressCount does not match entries.");
|
|
42443
|
+
}
|
|
42444
|
+
const index2 = /* @__PURE__ */ new Map();
|
|
42445
|
+
for (const e of entries) {
|
|
42446
|
+
const key = normalizeAddress(e.address, e.family);
|
|
42447
|
+
if (key) index2.set(key, e);
|
|
42448
|
+
const lower = e.address.trim().toLowerCase();
|
|
42449
|
+
if (/^0x[0-9a-f]{40}$/.test(lower)) index2.set(lower, e);
|
|
42450
|
+
}
|
|
42451
|
+
return { entries, index: index2, provenance, digest };
|
|
42452
|
+
}
|
|
42453
|
+
function candidatePaths(moduleUrl) {
|
|
42454
|
+
const here = path3.dirname(fileURLToPath(moduleUrl));
|
|
42455
|
+
return [
|
|
42456
|
+
path3.join(here, "blacklist.json"),
|
|
42457
|
+
path3.join(here, "..", "src", "lib", "blacklist.json"),
|
|
42458
|
+
path3.join(here, "..", "..", "src", "lib", "blacklist.json")
|
|
42459
|
+
];
|
|
42460
|
+
}
|
|
42461
|
+
var cached = null;
|
|
42462
|
+
function loadBlacklist(explicitPath) {
|
|
42463
|
+
if (!explicitPath && cached) return cached;
|
|
42464
|
+
const paths = explicitPath ? [explicitPath] : candidatePaths(import.meta.url);
|
|
42465
|
+
let lastErr = null;
|
|
42466
|
+
for (const p of paths) {
|
|
42467
|
+
let raw;
|
|
42468
|
+
try {
|
|
42469
|
+
raw = fs3.readFileSync(p, "utf8");
|
|
42470
|
+
} catch (err) {
|
|
42471
|
+
lastErr = err;
|
|
42472
|
+
continue;
|
|
42473
|
+
}
|
|
42474
|
+
let doc;
|
|
42475
|
+
try {
|
|
42476
|
+
doc = JSON.parse(raw);
|
|
42477
|
+
} catch {
|
|
42478
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", `Blacklist file is not valid JSON: ${p}`);
|
|
42479
|
+
}
|
|
42480
|
+
const parsed = parseBlacklist(doc);
|
|
42481
|
+
parsed.sourceFile = p;
|
|
42482
|
+
if (!explicitPath) cached = parsed;
|
|
42483
|
+
return parsed;
|
|
42484
|
+
}
|
|
42485
|
+
throw new BlacklistError(
|
|
42486
|
+
"BLACKLIST_UNAVAILABLE",
|
|
42487
|
+
`Blacklist file not found (looked in ${paths.length} locations). Refusing to proceed.` + (lastErr ? ` Last error: ${lastErr.code || lastErr.message}` : "")
|
|
42488
|
+
);
|
|
42489
|
+
}
|
|
42490
|
+
function checkAddress(address, family, blacklist) {
|
|
42491
|
+
const bl = blacklist || loadBlacklist();
|
|
42492
|
+
const normalized = normalizeAddress(address, family);
|
|
42493
|
+
if (!normalized) {
|
|
42494
|
+
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Cannot check an empty address.");
|
|
42495
|
+
}
|
|
42496
|
+
const entry = bl.index.get(normalized) || bl.index.get(normalized.toLowerCase()) || null;
|
|
42497
|
+
return { hit: Boolean(entry), entry, normalized };
|
|
42498
|
+
}
|
|
42499
|
+
function assertNotBlacklisted(targets, blacklist) {
|
|
42500
|
+
const bl = blacklist || loadBlacklist();
|
|
42501
|
+
for (const t of targets) {
|
|
42502
|
+
if (!t || !t.address) continue;
|
|
42503
|
+
const { hit, entry } = checkAddress(t.address, t.family, bl);
|
|
42504
|
+
if (hit) {
|
|
42505
|
+
const err = new BlacklistError(
|
|
42506
|
+
"BLACKLIST_HIT",
|
|
42507
|
+
`${t.role || "address"} is on the compromised-wallet blacklist (reported ${entry.reportedOn}: ${entry.note}). Refusing.`
|
|
42508
|
+
);
|
|
42509
|
+
err.role = t.role || "address";
|
|
42510
|
+
throw err;
|
|
42511
|
+
}
|
|
42512
|
+
}
|
|
42513
|
+
return true;
|
|
42514
|
+
}
|
|
42515
|
+
|
|
41898
42516
|
// scripts/src/lib/ids.mjs
|
|
41899
42517
|
var IdError = class extends Error {
|
|
41900
42518
|
constructor(code, message) {
|
|
@@ -41935,7 +42553,7 @@ function assertRozoPaymentId(value) {
|
|
|
41935
42553
|
function isRozoPaymentId(value) {
|
|
41936
42554
|
return UUID_RE.test(String(value || "").trim());
|
|
41937
42555
|
}
|
|
41938
|
-
function
|
|
42556
|
+
function maskAddress2(address) {
|
|
41939
42557
|
const s = String(address ?? "").trim();
|
|
41940
42558
|
if (!s) return "(none)";
|
|
41941
42559
|
if (s.length <= 12) return s;
|
|
@@ -42664,416 +43282,6 @@ async function run(argv = process.argv.slice(2)) {
|
|
|
42664
43282
|
return main(argv);
|
|
42665
43283
|
}
|
|
42666
43284
|
|
|
42667
|
-
// scripts/src/lib/blacklist.mjs
|
|
42668
|
-
import fs from "node:fs";
|
|
42669
|
-
import path from "node:path";
|
|
42670
|
-
import crypto2 from "node:crypto";
|
|
42671
|
-
import { fileURLToPath } from "node:url";
|
|
42672
|
-
var BlacklistError = class extends Error {
|
|
42673
|
-
constructor(code, message) {
|
|
42674
|
-
super(message);
|
|
42675
|
-
this.code = code;
|
|
42676
|
-
}
|
|
42677
|
-
};
|
|
42678
|
-
function normalizeAddress(address, family) {
|
|
42679
|
-
if (typeof address !== "string") return null;
|
|
42680
|
-
const trimmed = address.trim();
|
|
42681
|
-
if (!trimmed) return null;
|
|
42682
|
-
if (family === "evm" || /^0x[0-9a-fA-F]{40}$/.test(trimmed)) {
|
|
42683
|
-
return trimmed.toLowerCase();
|
|
42684
|
-
}
|
|
42685
|
-
return trimmed;
|
|
42686
|
-
}
|
|
42687
|
-
function parseBlacklist(doc) {
|
|
42688
|
-
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
42689
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist document is not an object.");
|
|
42690
|
-
}
|
|
42691
|
-
const { provenance, entries } = doc;
|
|
42692
|
-
if (!provenance || typeof provenance !== "object") {
|
|
42693
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance header is missing.");
|
|
42694
|
-
}
|
|
42695
|
-
if (!Array.isArray(entries) || entries.length === 0) {
|
|
42696
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist is empty or not an array.");
|
|
42697
|
-
}
|
|
42698
|
-
const addresses = [];
|
|
42699
|
-
for (const e of entries) {
|
|
42700
|
-
if (!e || typeof e !== "object" || typeof e.address !== "string" || !e.address.trim()) {
|
|
42701
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist entry has no address.");
|
|
42702
|
-
}
|
|
42703
|
-
addresses.push(e.address);
|
|
42704
|
-
}
|
|
42705
|
-
const digest = crypto2.createHash("sha256").update(JSON.stringify(addresses), "utf8").digest("hex");
|
|
42706
|
-
if (typeof provenance.addressesSha256 !== "string" || !provenance.addressesSha256) {
|
|
42707
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist provenance digest is missing.");
|
|
42708
|
-
}
|
|
42709
|
-
if (digest !== provenance.addressesSha256) {
|
|
42710
|
-
throw new BlacklistError(
|
|
42711
|
-
"BLACKLIST_UNAVAILABLE",
|
|
42712
|
-
"Blacklist digest mismatch \u2014 the vendored address list was modified without re-signing."
|
|
42713
|
-
);
|
|
42714
|
-
}
|
|
42715
|
-
if (provenance.addressCount !== void 0 && provenance.addressCount !== entries.length) {
|
|
42716
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Blacklist addressCount does not match entries.");
|
|
42717
|
-
}
|
|
42718
|
-
const index2 = /* @__PURE__ */ new Map();
|
|
42719
|
-
for (const e of entries) {
|
|
42720
|
-
const key = normalizeAddress(e.address, e.family);
|
|
42721
|
-
if (key) index2.set(key, e);
|
|
42722
|
-
const lower = e.address.trim().toLowerCase();
|
|
42723
|
-
if (/^0x[0-9a-f]{40}$/.test(lower)) index2.set(lower, e);
|
|
42724
|
-
}
|
|
42725
|
-
return { entries, index: index2, provenance, digest };
|
|
42726
|
-
}
|
|
42727
|
-
function candidatePaths(moduleUrl) {
|
|
42728
|
-
const here = path.dirname(fileURLToPath(moduleUrl));
|
|
42729
|
-
return [
|
|
42730
|
-
path.join(here, "blacklist.json"),
|
|
42731
|
-
path.join(here, "..", "src", "lib", "blacklist.json"),
|
|
42732
|
-
path.join(here, "..", "..", "src", "lib", "blacklist.json")
|
|
42733
|
-
];
|
|
42734
|
-
}
|
|
42735
|
-
var cached = null;
|
|
42736
|
-
function loadBlacklist(explicitPath) {
|
|
42737
|
-
if (!explicitPath && cached) return cached;
|
|
42738
|
-
const paths = explicitPath ? [explicitPath] : candidatePaths(import.meta.url);
|
|
42739
|
-
let lastErr = null;
|
|
42740
|
-
for (const p of paths) {
|
|
42741
|
-
let raw;
|
|
42742
|
-
try {
|
|
42743
|
-
raw = fs.readFileSync(p, "utf8");
|
|
42744
|
-
} catch (err) {
|
|
42745
|
-
lastErr = err;
|
|
42746
|
-
continue;
|
|
42747
|
-
}
|
|
42748
|
-
let doc;
|
|
42749
|
-
try {
|
|
42750
|
-
doc = JSON.parse(raw);
|
|
42751
|
-
} catch {
|
|
42752
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", `Blacklist file is not valid JSON: ${p}`);
|
|
42753
|
-
}
|
|
42754
|
-
const parsed = parseBlacklist(doc);
|
|
42755
|
-
parsed.sourceFile = p;
|
|
42756
|
-
if (!explicitPath) cached = parsed;
|
|
42757
|
-
return parsed;
|
|
42758
|
-
}
|
|
42759
|
-
throw new BlacklistError(
|
|
42760
|
-
"BLACKLIST_UNAVAILABLE",
|
|
42761
|
-
`Blacklist file not found (looked in ${paths.length} locations). Refusing to proceed.` + (lastErr ? ` Last error: ${lastErr.code || lastErr.message}` : "")
|
|
42762
|
-
);
|
|
42763
|
-
}
|
|
42764
|
-
function checkAddress(address, family, blacklist) {
|
|
42765
|
-
const bl = blacklist || loadBlacklist();
|
|
42766
|
-
const normalized = normalizeAddress(address, family);
|
|
42767
|
-
if (!normalized) {
|
|
42768
|
-
throw new BlacklistError("BLACKLIST_UNAVAILABLE", "Cannot check an empty address.");
|
|
42769
|
-
}
|
|
42770
|
-
const entry = bl.index.get(normalized) || bl.index.get(normalized.toLowerCase()) || null;
|
|
42771
|
-
return { hit: Boolean(entry), entry, normalized };
|
|
42772
|
-
}
|
|
42773
|
-
function assertNotBlacklisted(targets, blacklist) {
|
|
42774
|
-
const bl = blacklist || loadBlacklist();
|
|
42775
|
-
for (const t of targets) {
|
|
42776
|
-
if (!t || !t.address) continue;
|
|
42777
|
-
const { hit, entry } = checkAddress(t.address, t.family, bl);
|
|
42778
|
-
if (hit) {
|
|
42779
|
-
const err = new BlacklistError(
|
|
42780
|
-
"BLACKLIST_HIT",
|
|
42781
|
-
`${t.role || "address"} is on the compromised-wallet blacklist (reported ${entry.reportedOn}: ${entry.note}). Refusing.`
|
|
42782
|
-
);
|
|
42783
|
-
err.role = t.role || "address";
|
|
42784
|
-
throw err;
|
|
42785
|
-
}
|
|
42786
|
-
}
|
|
42787
|
-
return true;
|
|
42788
|
-
}
|
|
42789
|
-
|
|
42790
|
-
// scripts/src/lib/state.mjs
|
|
42791
|
-
import fs2 from "node:fs";
|
|
42792
|
-
import path2 from "node:path";
|
|
42793
|
-
import os from "node:os";
|
|
42794
|
-
import crypto3 from "node:crypto";
|
|
42795
|
-
var LOCK_STALE_MS = 6e4;
|
|
42796
|
-
var LOCK_WAIT_MS = 1e4;
|
|
42797
|
-
var LOCK_POLL_MS = 25;
|
|
42798
|
-
function lockPath() {
|
|
42799
|
-
return path2.join(stateRoot(), ".send.lock");
|
|
42800
|
-
}
|
|
42801
|
-
function sleepSync(ms) {
|
|
42802
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
42803
|
-
}
|
|
42804
|
-
function tryAcquire(file) {
|
|
42805
|
-
try {
|
|
42806
|
-
const fd = fs2.openSync(file, "wx", 384);
|
|
42807
|
-
fs2.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
42808
|
-
fs2.closeSync(fd);
|
|
42809
|
-
return true;
|
|
42810
|
-
} catch (err) {
|
|
42811
|
-
if (err.code !== "EEXIST") throw err;
|
|
42812
|
-
return false;
|
|
42813
|
-
}
|
|
42814
|
-
}
|
|
42815
|
-
function withLock(fn) {
|
|
42816
|
-
const file = lockPath();
|
|
42817
|
-
fs2.mkdirSync(path2.dirname(file), { recursive: true, mode: 448 });
|
|
42818
|
-
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
42819
|
-
for (; ; ) {
|
|
42820
|
-
if (tryAcquire(file)) break;
|
|
42821
|
-
try {
|
|
42822
|
-
const age = Date.now() - fs2.statSync(file).mtimeMs;
|
|
42823
|
-
if (age > LOCK_STALE_MS) {
|
|
42824
|
-
const stolen = `${file}.stale.${crypto3.randomBytes(4).toString("hex")}`;
|
|
42825
|
-
try {
|
|
42826
|
-
fs2.renameSync(file, stolen);
|
|
42827
|
-
fs2.unlinkSync(stolen);
|
|
42828
|
-
} catch {
|
|
42829
|
-
}
|
|
42830
|
-
continue;
|
|
42831
|
-
}
|
|
42832
|
-
} catch (err) {
|
|
42833
|
-
if (err.code !== "ENOENT") throw err;
|
|
42834
|
-
continue;
|
|
42835
|
-
}
|
|
42836
|
-
if (Date.now() > deadline) {
|
|
42837
|
-
throw new SkillError(
|
|
42838
|
-
"LOCK_TIMEOUT",
|
|
42839
|
-
"Another rozo-checkout process is holding the send lock. Refusing to proceed rather than risk a concurrent second send."
|
|
42840
|
-
);
|
|
42841
|
-
}
|
|
42842
|
-
sleepSync(LOCK_POLL_MS);
|
|
42843
|
-
}
|
|
42844
|
-
try {
|
|
42845
|
-
return fn();
|
|
42846
|
-
} finally {
|
|
42847
|
-
try {
|
|
42848
|
-
fs2.unlinkSync(file);
|
|
42849
|
-
} catch {
|
|
42850
|
-
}
|
|
42851
|
-
}
|
|
42852
|
-
}
|
|
42853
|
-
function stateRoot() {
|
|
42854
|
-
return process.env.ROZO_CHECKOUT_STATE_DIR || path2.join(os.homedir(), ".rozo-checkout", "state");
|
|
42855
|
-
}
|
|
42856
|
-
function statePath(rozoPaymentId) {
|
|
42857
|
-
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
|
|
42858
|
-
throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
|
|
42859
|
-
}
|
|
42860
|
-
return path2.join(stateRoot(), `${rozoPaymentId}.json`);
|
|
42861
|
-
}
|
|
42862
|
-
function writeAtomic(file, data) {
|
|
42863
|
-
const dir = path2.dirname(file);
|
|
42864
|
-
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
42865
|
-
const tmp = path2.join(dir, `.${path2.basename(file)}.${crypto3.randomBytes(6).toString("hex")}.tmp`);
|
|
42866
|
-
const fd = fs2.openSync(tmp, "wx", 384);
|
|
42867
|
-
try {
|
|
42868
|
-
fs2.writeFileSync(fd, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
42869
|
-
fs2.fsyncSync(fd);
|
|
42870
|
-
} finally {
|
|
42871
|
-
fs2.closeSync(fd);
|
|
42872
|
-
}
|
|
42873
|
-
fs2.renameSync(tmp, file);
|
|
42874
|
-
}
|
|
42875
|
-
function readState(rozoPaymentId) {
|
|
42876
|
-
const file = statePath(rozoPaymentId);
|
|
42877
|
-
let raw;
|
|
42878
|
-
try {
|
|
42879
|
-
raw = fs2.readFileSync(file, "utf8");
|
|
42880
|
-
} catch (err) {
|
|
42881
|
-
if (err.code === "ENOENT") return null;
|
|
42882
|
-
throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
|
|
42883
|
-
}
|
|
42884
|
-
try {
|
|
42885
|
-
return JSON.parse(raw);
|
|
42886
|
-
} catch {
|
|
42887
|
-
throw new SkillError(
|
|
42888
|
-
"STATE_CORRUPT",
|
|
42889
|
-
"The local state file for this order is corrupt. Refusing to act; inspect it manually."
|
|
42890
|
-
);
|
|
42891
|
-
}
|
|
42892
|
-
}
|
|
42893
|
-
function createOrderRecord(record) {
|
|
42894
|
-
return withLock(() => createOrderRecordUnlocked(record));
|
|
42895
|
-
}
|
|
42896
|
-
function createOrderRecordUnlocked(record) {
|
|
42897
|
-
const { rozoPaymentId } = record;
|
|
42898
|
-
const existing = readState(rozoPaymentId);
|
|
42899
|
-
const next = {
|
|
42900
|
-
version: 1,
|
|
42901
|
-
rozoPaymentId,
|
|
42902
|
-
linkId: record.linkId,
|
|
42903
|
-
paymentLink: record.paymentLink ?? null,
|
|
42904
|
-
merchant: record.merchant ?? null,
|
|
42905
|
-
invoiceAmount: record.invoiceAmount ?? null,
|
|
42906
|
-
source: record.source,
|
|
42907
|
-
receiverAddress: record.receiverAddress,
|
|
42908
|
-
receiverMemo: record.receiverMemo ?? null,
|
|
42909
|
-
amount: record.amount,
|
|
42910
|
-
amountUnit: record.amountUnit ?? null,
|
|
42911
|
-
expiresAt: record.expiresAt ?? null,
|
|
42912
|
-
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
42913
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42914
|
-
confirmation: existing?.confirmation ?? null,
|
|
42915
|
-
send: existing?.send ?? null
|
|
42916
|
-
};
|
|
42917
|
-
writeAtomic(statePath(rozoPaymentId), next);
|
|
42918
|
-
return next;
|
|
42919
|
-
}
|
|
42920
|
-
function depositDigest(source) {
|
|
42921
|
-
const canonical = JSON.stringify({
|
|
42922
|
-
chainId: String(source?.chainId ?? ""),
|
|
42923
|
-
tokenSymbol: String(source?.tokenSymbol ?? "").toUpperCase(),
|
|
42924
|
-
tokenAddress: String(source?.tokenAddress ?? ""),
|
|
42925
|
-
receiverAddress: String(source?.receiverAddress ?? ""),
|
|
42926
|
-
receiverMemo: source?.receiverMemo ?? null,
|
|
42927
|
-
amount: String(source?.amount ?? ""),
|
|
42928
|
-
amountUnit: source?.amountUnit ?? null,
|
|
42929
|
-
lnInvoice: source?.lnInvoice ?? null
|
|
42930
|
-
});
|
|
42931
|
-
return crypto3.createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
42932
|
-
}
|
|
42933
|
-
function recordConfirmation(rozoPaymentId, { source, invoiceAmount, tier }) {
|
|
42934
|
-
return withLock(() => {
|
|
42935
|
-
const state = readState(rozoPaymentId);
|
|
42936
|
-
if (!state) {
|
|
42937
|
-
throw new SkillError("NO_ORDER_STATE", "Cannot confirm an order with no local record.");
|
|
42938
|
-
}
|
|
42939
|
-
const next = {
|
|
42940
|
-
...state,
|
|
42941
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42942
|
-
confirmation: {
|
|
42943
|
-
confirmedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42944
|
-
depositDigest: depositDigest(source),
|
|
42945
|
-
invoiceAmount: invoiceAmount ?? state.invoiceAmount ?? null,
|
|
42946
|
-
tier: tier ?? null
|
|
42947
|
-
}
|
|
42948
|
-
};
|
|
42949
|
-
writeAtomic(statePath(rozoPaymentId), next);
|
|
42950
|
-
return next;
|
|
42951
|
-
});
|
|
42952
|
-
}
|
|
42953
|
-
function claimSend(rozoPaymentId, intent, { allowLarge = false, skipCaps = false } = {}) {
|
|
42954
|
-
return withLock(() => {
|
|
42955
|
-
const state = readState(rozoPaymentId);
|
|
42956
|
-
if (!state) {
|
|
42957
|
-
throw new SkillError(
|
|
42958
|
-
"NO_ORDER_STATE",
|
|
42959
|
-
"No local record for this order. Run create-order.js in this same session first."
|
|
42960
|
-
);
|
|
42961
|
-
}
|
|
42962
|
-
if (state.send) {
|
|
42963
|
-
throw new SkillError(
|
|
42964
|
-
"ALREADY_SENT",
|
|
42965
|
-
`A send was already recorded for this order at ${state.send.claimedAt} (status: ${state.send.status}). Refusing to send twice. Check the backend for the pay-in before doing anything else.`,
|
|
42966
|
-
{ send: state.send }
|
|
42967
|
-
);
|
|
42968
|
-
}
|
|
42969
|
-
const caps = skipCaps ? null : assertSpendCapsUnlocked(state.invoiceAmount, { allowLarge, excludeId: rozoPaymentId });
|
|
42970
|
-
const next = {
|
|
42971
|
-
...state,
|
|
42972
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42973
|
-
send: {
|
|
42974
|
-
status: "claimed",
|
|
42975
|
-
claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42976
|
-
chainId: intent.chainId,
|
|
42977
|
-
tokenSymbol: intent.tokenSymbol,
|
|
42978
|
-
from: intent.from,
|
|
42979
|
-
to: intent.to,
|
|
42980
|
-
amountAtomic: intent.amountAtomic,
|
|
42981
|
-
memo: intent.memo ?? null,
|
|
42982
|
-
nonceBefore: intent.nonceBefore ?? null,
|
|
42983
|
-
expectedTxHash: intent.expectedTxHash ?? null,
|
|
42984
|
-
txHash: null
|
|
42985
|
-
}
|
|
42986
|
-
};
|
|
42987
|
-
writeAtomic(statePath(rozoPaymentId), next);
|
|
42988
|
-
return { state: next, caps };
|
|
42989
|
-
});
|
|
42990
|
-
}
|
|
42991
|
-
function recordSendResult(rozoPaymentId, { status, txHash = null, note = null }) {
|
|
42992
|
-
return withLock(() => {
|
|
42993
|
-
const state = readState(rozoPaymentId);
|
|
42994
|
-
if (!state || !state.send) {
|
|
42995
|
-
throw new SkillError("NO_SEND_CLAIM", "No send claim to update for this order.");
|
|
42996
|
-
}
|
|
42997
|
-
const next = {
|
|
42998
|
-
...state,
|
|
42999
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43000
|
-
send: {
|
|
43001
|
-
...state.send,
|
|
43002
|
-
status,
|
|
43003
|
-
txHash: txHash ?? state.send.txHash,
|
|
43004
|
-
note,
|
|
43005
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
43006
|
-
}
|
|
43007
|
-
};
|
|
43008
|
-
writeAtomic(statePath(rozoPaymentId), next);
|
|
43009
|
-
return next;
|
|
43010
|
-
});
|
|
43011
|
-
}
|
|
43012
|
-
function findByLinkId(linkId) {
|
|
43013
|
-
const dir = stateRoot();
|
|
43014
|
-
let files = [];
|
|
43015
|
-
try {
|
|
43016
|
-
files = fs2.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
43017
|
-
} catch {
|
|
43018
|
-
return null;
|
|
43019
|
-
}
|
|
43020
|
-
let best = null;
|
|
43021
|
-
for (const f of files) {
|
|
43022
|
-
try {
|
|
43023
|
-
const s = JSON.parse(fs2.readFileSync(path2.join(dir, f), "utf8"));
|
|
43024
|
-
if (s?.linkId !== linkId) continue;
|
|
43025
|
-
if (!best || String(s.createdAt) > String(best.createdAt)) best = s;
|
|
43026
|
-
} catch {
|
|
43027
|
-
}
|
|
43028
|
-
}
|
|
43029
|
-
return best;
|
|
43030
|
-
}
|
|
43031
|
-
function sessionSpendUsd({ excludeId = null } = {}) {
|
|
43032
|
-
const dir = stateRoot();
|
|
43033
|
-
let files = [];
|
|
43034
|
-
try {
|
|
43035
|
-
files = fs2.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
43036
|
-
} catch {
|
|
43037
|
-
return 0;
|
|
43038
|
-
}
|
|
43039
|
-
let total = 0;
|
|
43040
|
-
for (const f of files) {
|
|
43041
|
-
if (excludeId && f === `${excludeId}.json`) continue;
|
|
43042
|
-
try {
|
|
43043
|
-
const s = JSON.parse(fs2.readFileSync(path2.join(dir, f), "utf8"));
|
|
43044
|
-
if (s?.send && ["claimed", "submitted", "confirmed", "ambiguous"].includes(s.send.status)) {
|
|
43045
|
-
const usd = Number(s.invoiceAmount);
|
|
43046
|
-
total += Number.isFinite(usd) ? usd : MAX_TX_USD;
|
|
43047
|
-
}
|
|
43048
|
-
} catch {
|
|
43049
|
-
total += MAX_TX_USD;
|
|
43050
|
-
}
|
|
43051
|
-
}
|
|
43052
|
-
return total;
|
|
43053
|
-
}
|
|
43054
|
-
var MAX_TX_USD = 100;
|
|
43055
|
-
var MAX_SESSION_USD = 200;
|
|
43056
|
-
function assertSpendCapsUnlocked(invoiceUsd, { allowLarge = false, excludeId = null } = {}) {
|
|
43057
|
-
const usd = Number(invoiceUsd);
|
|
43058
|
-
if (!Number.isFinite(usd) || usd < 0) {
|
|
43059
|
-
throw new SkillError("BAD_INVOICE_AMOUNT", "Cannot evaluate spend caps without a USD amount.");
|
|
43060
|
-
}
|
|
43061
|
-
if (usd > MAX_TX_USD && !allowLarge) {
|
|
43062
|
-
throw new SkillError(
|
|
43063
|
-
"CAP_PER_TX",
|
|
43064
|
-
`$${usd} exceeds the $${MAX_TX_USD} per-transaction cap. Re-run with --yes-large to override.`
|
|
43065
|
-
);
|
|
43066
|
-
}
|
|
43067
|
-
const prior = sessionSpendUsd({ excludeId });
|
|
43068
|
-
if (prior + usd > MAX_SESSION_USD) {
|
|
43069
|
-
throw new SkillError(
|
|
43070
|
-
"CAP_SESSION",
|
|
43071
|
-
`This send would bring cumulative spend to $${(prior + usd).toFixed(2)}, past the $${MAX_SESSION_USD} session cap. Clear or archive the state directory to start a new session.`
|
|
43072
|
-
);
|
|
43073
|
-
}
|
|
43074
|
-
return { priorUsd: prior, totalUsd: prior + usd };
|
|
43075
|
-
}
|
|
43076
|
-
|
|
43077
43285
|
// scripts/src/create-order.mjs
|
|
43078
43286
|
function confirmTier(usdAmount) {
|
|
43079
43287
|
const usd = Number(usdAmount);
|
|
@@ -43279,7 +43487,7 @@ async function main2(argv) {
|
|
|
43279
43487
|
token: source.tokenSymbol,
|
|
43280
43488
|
amount: formatAmount(source),
|
|
43281
43489
|
isSats: isSatsUnit(source.amountUnit),
|
|
43282
|
-
payToMasked:
|
|
43490
|
+
payToMasked: maskAddress2(depositInfo.payTo),
|
|
43283
43491
|
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
43284
43492
|
hasMemo: Boolean(source.receiverMemo),
|
|
43285
43493
|
memoRequirement
|
|
@@ -43380,7 +43588,7 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
43380
43588
|
receipt: verdict.receipt,
|
|
43381
43589
|
txHash: source.txHash ?? null,
|
|
43382
43590
|
confirmedAt: source.confirmedAt ?? null,
|
|
43383
|
-
senderAddressMasked: source.senderAddress ?
|
|
43591
|
+
senderAddressMasked: source.senderAddress ? maskAddress2(source.senderAddress) : null,
|
|
43384
43592
|
chain: source.chainId ? chainName(source.chainId) : null
|
|
43385
43593
|
},
|
|
43386
43594
|
payout: {
|
|
@@ -43428,7 +43636,7 @@ async function run3(argv = process.argv.slice(2)) {
|
|
|
43428
43636
|
}
|
|
43429
43637
|
|
|
43430
43638
|
// scripts/src/lib/keys.mjs
|
|
43431
|
-
import
|
|
43639
|
+
import fs4 from "node:fs";
|
|
43432
43640
|
import { execFileSync } from "node:child_process";
|
|
43433
43641
|
var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
|
|
43434
43642
|
var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
|
|
@@ -43447,7 +43655,7 @@ var PUBLIC_ENV_RE = /^\.env\.(example|sample|template)$/;
|
|
|
43447
43655
|
function assertNoTrackedDotEnv(cwd = process.cwd()) {
|
|
43448
43656
|
let candidates = [];
|
|
43449
43657
|
try {
|
|
43450
|
-
candidates =
|
|
43658
|
+
candidates = fs4.readdirSync(cwd).filter((f) => ENV_FILE_RE.test(f) && !PUBLIC_ENV_RE.test(f));
|
|
43451
43659
|
} catch (err) {
|
|
43452
43660
|
throw new SkillError(
|
|
43453
43661
|
"TRACKED_DOTENV_UNVERIFIABLE",
|
|
@@ -54907,8 +55115,8 @@ async function main4(argv) {
|
|
|
54907
55115
|
tokenSymbol: source.tokenSymbol,
|
|
54908
55116
|
amountAtomic: amountAtomic.toString(),
|
|
54909
55117
|
amount: source.amount,
|
|
54910
|
-
toMasked:
|
|
54911
|
-
fromMasked:
|
|
55118
|
+
toMasked: maskAddress2(to),
|
|
55119
|
+
fromMasked: maskAddress2(sender)
|
|
54912
55120
|
},
|
|
54913
55121
|
confirmedAt: state.confirmation?.confirmedAt ?? null,
|
|
54914
55122
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4),
|
|
@@ -54945,8 +55153,7 @@ async function main4(argv) {
|
|
|
54945
55153
|
memo: null,
|
|
54946
55154
|
nonceBefore,
|
|
54947
55155
|
expectedTxHash
|
|
54948
|
-
}
|
|
54949
|
-
{ allowLarge: Boolean(args["yes-large"]) }
|
|
55156
|
+
}
|
|
54950
55157
|
);
|
|
54951
55158
|
let txHash = null;
|
|
54952
55159
|
try {
|
|
@@ -55030,8 +55237,8 @@ async function main4(argv) {
|
|
|
55030
55237
|
chain: chainName(chainId),
|
|
55031
55238
|
tokenSymbol: source.tokenSymbol,
|
|
55032
55239
|
amount: source.amount,
|
|
55033
|
-
toMasked:
|
|
55034
|
-
fromMasked:
|
|
55240
|
+
toMasked: maskAddress2(to),
|
|
55241
|
+
fromMasked: maskAddress2(sender)
|
|
55035
55242
|
},
|
|
55036
55243
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
55037
55244
|
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."
|
|
@@ -56907,8 +57114,8 @@ async function main5(argv) {
|
|
|
56907
57114
|
tokenSymbol: source.tokenSymbol,
|
|
56908
57115
|
amountAtomic: amountAtomic.toString(),
|
|
56909
57116
|
amount: source.amount,
|
|
56910
|
-
toMasked:
|
|
56911
|
-
fromMasked:
|
|
57117
|
+
toMasked: maskAddress2(source.receiverAddress),
|
|
57118
|
+
fromMasked: maskAddress2(sender),
|
|
56912
57119
|
toIsTokenAccount,
|
|
56913
57120
|
withMemo: Boolean(source.receiverMemo)
|
|
56914
57121
|
},
|
|
@@ -56959,8 +57166,7 @@ async function main5(argv) {
|
|
|
56959
57166
|
amountAtomic: amountAtomic.toString(),
|
|
56960
57167
|
memo: source.receiverMemo ?? null,
|
|
56961
57168
|
expectedTxHash: signature
|
|
56962
|
-
}
|
|
56963
|
-
{ allowLarge: Boolean(args["yes-large"]) }
|
|
57169
|
+
}
|
|
56964
57170
|
);
|
|
56965
57171
|
let sent = null;
|
|
56966
57172
|
try {
|
|
@@ -57067,8 +57273,8 @@ async function main5(argv) {
|
|
|
57067
57273
|
chain: chainName(900),
|
|
57068
57274
|
tokenSymbol: source.tokenSymbol,
|
|
57069
57275
|
amount: source.amount,
|
|
57070
|
-
toMasked:
|
|
57071
|
-
fromMasked:
|
|
57276
|
+
toMasked: maskAddress2(source.receiverAddress),
|
|
57277
|
+
fromMasked: maskAddress2(sender),
|
|
57072
57278
|
withMemo: Boolean(source.receiverMemo)
|
|
57073
57279
|
},
|
|
57074
57280
|
nextStep: `status.js --rozo-payment-id ${rozoPaymentId} --watch`,
|
|
@@ -57094,6 +57300,12 @@ async function run5(argv = process.argv.slice(2)) {
|
|
|
57094
57300
|
}
|
|
57095
57301
|
|
|
57096
57302
|
// scripts/src/cli.mjs
|
|
57303
|
+
var SkillErrorLike = class extends Error {
|
|
57304
|
+
constructor(code, message) {
|
|
57305
|
+
super(message);
|
|
57306
|
+
this.code = code;
|
|
57307
|
+
}
|
|
57308
|
+
};
|
|
57097
57309
|
var VERSION = (() => {
|
|
57098
57310
|
try {
|
|
57099
57311
|
return createRequire(import.meta.url)("../../package.json").version;
|
|
@@ -57137,6 +57349,95 @@ function targetToArgs(target) {
|
|
|
57137
57349
|
const { linkId } = extractLinkId(target);
|
|
57138
57350
|
return ["--link-id", linkId];
|
|
57139
57351
|
}
|
|
57352
|
+
async function ask(question) {
|
|
57353
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
57354
|
+
try {
|
|
57355
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
57356
|
+
} finally {
|
|
57357
|
+
rl.close();
|
|
57358
|
+
}
|
|
57359
|
+
}
|
|
57360
|
+
function vetPayerAddress(address) {
|
|
57361
|
+
const family = detectAddressFamily(address);
|
|
57362
|
+
if (!family) {
|
|
57363
|
+
out(` ${yellow("That does not look like an EVM, Solana or Stellar address. Skipping the check.")}`);
|
|
57364
|
+
return null;
|
|
57365
|
+
}
|
|
57366
|
+
try {
|
|
57367
|
+
assertNotBlacklisted([{ address, family, role: "wallet address" }], loadBlacklist());
|
|
57368
|
+
} catch (err) {
|
|
57369
|
+
out(` ${red("\u2717")} ${err.message}`);
|
|
57370
|
+
return null;
|
|
57371
|
+
}
|
|
57372
|
+
return family;
|
|
57373
|
+
}
|
|
57374
|
+
async function askPayerAddress(saved) {
|
|
57375
|
+
const savedMask = saved?.lastPayerAddress ? maskAddress(saved.lastPayerAddress) : null;
|
|
57376
|
+
const prompt = savedMask ? ` Wallet address [${savedMask}, Enter to reuse, 'n' for none]: ` : " Wallet address you'll pay from (optional \u2014 checks balances; Enter to skip): ";
|
|
57377
|
+
const answer = String(await ask(prompt)).trim();
|
|
57378
|
+
if (/^n(o)?$/i.test(answer)) return null;
|
|
57379
|
+
const address = answer || saved?.lastPayerAddress || null;
|
|
57380
|
+
if (!address) return null;
|
|
57381
|
+
const family = vetPayerAddress(address);
|
|
57382
|
+
return family ? { address, family } : null;
|
|
57383
|
+
}
|
|
57384
|
+
async function pickSource({ invoiceUsd, saved, fresh }) {
|
|
57385
|
+
const payer = await askPayerAddress(fresh ? null : saved);
|
|
57386
|
+
let marked = PICKER_OPTIONS.map((o) => ({ ...o, mark: "unchecked", balanceUsd: null }));
|
|
57387
|
+
let checked = null;
|
|
57388
|
+
if (payer) {
|
|
57389
|
+
out(dim(" Checking balances\u2026"));
|
|
57390
|
+
const result = await fetchWalletOptions({ address: payer.address, usdRequired: invoiceUsd });
|
|
57391
|
+
if (result.ok) {
|
|
57392
|
+
marked = markPickerOptions(PICKER_OPTIONS, result);
|
|
57393
|
+
checked = result;
|
|
57394
|
+
} else {
|
|
57395
|
+
out(` ${yellow(`Could not check balances (${result.reason}). Showing all coins.`)}`);
|
|
57396
|
+
}
|
|
57397
|
+
}
|
|
57398
|
+
out();
|
|
57399
|
+
out(` ${bold("Which coin do you want to pay with?")}`);
|
|
57400
|
+
const savedPreset = fresh ? null : saved?.lastPreset ?? null;
|
|
57401
|
+
let group = null;
|
|
57402
|
+
for (const o of marked) {
|
|
57403
|
+
if (o.token !== group) {
|
|
57404
|
+
group = o.token;
|
|
57405
|
+
out();
|
|
57406
|
+
out(` ${dim(o.token)}`);
|
|
57407
|
+
}
|
|
57408
|
+
const badge = o.mark === "affordable" ? green(`\u2713 $${Number(o.balanceUsd ?? 0).toFixed(2)} available`) : o.mark === "insufficient" ? dim("\u2717 insufficient") : dim("\u2014 not checked");
|
|
57409
|
+
const star = savedPreset === o.preset ? bold(" (last used)") : "";
|
|
57410
|
+
out(` ${String(o.number).padStart(2)}. ${o.chain.padEnd(12)} ${badge}${star}`);
|
|
57411
|
+
}
|
|
57412
|
+
out();
|
|
57413
|
+
const defaultChoice = savedPreset ? ` [Enter for ${savedPreset}]` : "";
|
|
57414
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
57415
|
+
const answer = String(await ask(` Number 1-${PICKER_OPTIONS.length}${defaultChoice}: `)).trim();
|
|
57416
|
+
const raw = answer || savedPreset;
|
|
57417
|
+
if (!raw) {
|
|
57418
|
+
out(` ${yellow("Type the number of a coin.")}`);
|
|
57419
|
+
continue;
|
|
57420
|
+
}
|
|
57421
|
+
try {
|
|
57422
|
+
const choice = resolvePickerChoice(raw);
|
|
57423
|
+
if (checked && isProvablyShort(choice, checked)) {
|
|
57424
|
+
out(` ${yellow("That wallet appears not to hold enough for this invoice.")}`);
|
|
57425
|
+
out(` ${dim("You can still choose it \u2014 you may be paying from a different wallet.")}`);
|
|
57426
|
+
}
|
|
57427
|
+
out(` ${dim(`\u2192 same as: --with ${choice.preset}`)}`);
|
|
57428
|
+
out();
|
|
57429
|
+
return {
|
|
57430
|
+
chainId: choice.chainId,
|
|
57431
|
+
tokenSymbol: choice.tokenSymbol,
|
|
57432
|
+
preset: choice.preset,
|
|
57433
|
+
payer
|
|
57434
|
+
};
|
|
57435
|
+
} catch (err) {
|
|
57436
|
+
out(` ${yellow(err.message)}`);
|
|
57437
|
+
}
|
|
57438
|
+
}
|
|
57439
|
+
throw new CliError("BAD_CHOICE", "No valid choice after three attempts.");
|
|
57440
|
+
}
|
|
57140
57441
|
async function askYesNo(question) {
|
|
57141
57442
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
57142
57443
|
try {
|
|
@@ -57187,6 +57488,12 @@ async function cmdStatus(opts) {
|
|
|
57187
57488
|
out();
|
|
57188
57489
|
return exitCode;
|
|
57189
57490
|
}
|
|
57491
|
+
function presetFor(chainId, tokenSymbol) {
|
|
57492
|
+
const hit = PICKER_OPTIONS.find(
|
|
57493
|
+
(o) => o.chainId === String(chainId) && o.tokenSymbol === String(tokenSymbol).toUpperCase()
|
|
57494
|
+
);
|
|
57495
|
+
return hit ? hit.preset : null;
|
|
57496
|
+
}
|
|
57190
57497
|
function stateLabel(state) {
|
|
57191
57498
|
if (state === "settled") return green(bold(state));
|
|
57192
57499
|
if (["underpaid", "stuck_after_payment", "unknown"].includes(state)) return red(bold(state));
|
|
@@ -57195,6 +57502,48 @@ function stateLabel(state) {
|
|
|
57195
57502
|
}
|
|
57196
57503
|
async function cmdPay(opts) {
|
|
57197
57504
|
let sendResult = null;
|
|
57505
|
+
const saved = opts.fresh ? null : readPrefs();
|
|
57506
|
+
let chosenPreset = null;
|
|
57507
|
+
let payer = null;
|
|
57508
|
+
let invoiceUsd = null;
|
|
57509
|
+
if (!opts.source || opts.payer) {
|
|
57510
|
+
const q = await step(run, ["--url", opts.target]);
|
|
57511
|
+
if (!q.payload.success) {
|
|
57512
|
+
if (opts.json) printJson(q.payload);
|
|
57513
|
+
else printError(q.payload);
|
|
57514
|
+
return q.exitCode;
|
|
57515
|
+
}
|
|
57516
|
+
invoiceUsd = Number(q.payload.invoice?.amount);
|
|
57517
|
+
}
|
|
57518
|
+
if (!opts.source) {
|
|
57519
|
+
if (!process.stdin.isTTY || opts.json) {
|
|
57520
|
+
throw new CliError(
|
|
57521
|
+
"MISSING_PRESET",
|
|
57522
|
+
"No coin specified. Pass --with (e.g. --with usdt-solana), or run this on a terminal to choose from a list. There is no default."
|
|
57523
|
+
);
|
|
57524
|
+
}
|
|
57525
|
+
const picked = await pickSource({ invoiceUsd, saved, fresh: opts.fresh });
|
|
57526
|
+
opts.source = { chainId: picked.chainId, tokenSymbol: picked.tokenSymbol };
|
|
57527
|
+
chosenPreset = picked.preset;
|
|
57528
|
+
payer = picked.payer;
|
|
57529
|
+
} else if (opts.payer) {
|
|
57530
|
+
const family = detectAddressFamily(opts.payer);
|
|
57531
|
+
if (!family) {
|
|
57532
|
+
throw new CliError("BAD_PAYER_ADDRESS", "That is not a recognisable EVM, Solana or Stellar address.");
|
|
57533
|
+
}
|
|
57534
|
+
assertNotBlacklisted([{ address: opts.payer, family, role: "wallet address" }], loadBlacklist());
|
|
57535
|
+
const result = await fetchWalletOptions({ address: opts.payer, usdRequired: invoiceUsd });
|
|
57536
|
+
if (isProvablyShort(opts.source, result)) {
|
|
57537
|
+
throw new SkillErrorLike(
|
|
57538
|
+
"INSUFFICIENT_BALANCE",
|
|
57539
|
+
`${opts.payer.slice(0, 6)}\u2026${opts.payer.slice(-4)} does not appear to hold enough ${opts.source.tokenSymbol} for a $${invoiceUsd} invoice. Choose another coin, or omit --payer to proceed anyway from a different wallet.`
|
|
57540
|
+
);
|
|
57541
|
+
}
|
|
57542
|
+
if (!result.ok && !opts.json) {
|
|
57543
|
+
out(` ${yellow(`Could not check balances (${result.reason}). Continuing.`)}`);
|
|
57544
|
+
}
|
|
57545
|
+
payer = { address: opts.payer, family };
|
|
57546
|
+
}
|
|
57198
57547
|
const { chainId, tokenSymbol } = opts.source;
|
|
57199
57548
|
const baseArgs = ["--url", opts.target, "--chain", chainId, "--token", tokenSymbol];
|
|
57200
57549
|
const created = await step(run2, baseArgs);
|
|
@@ -57208,6 +57557,11 @@ async function cmdPay(opts) {
|
|
|
57208
57557
|
}
|
|
57209
57558
|
const p = created.payload;
|
|
57210
57559
|
const rozoPaymentId = p.rozoPaymentId;
|
|
57560
|
+
savePrefs({
|
|
57561
|
+
lastPayerAddress: payer?.address,
|
|
57562
|
+
lastAddressFamily: payer?.family,
|
|
57563
|
+
lastPreset: chosenPreset || presetFor(chainId, tokenSymbol)
|
|
57564
|
+
});
|
|
57211
57565
|
if (!opts.json) {
|
|
57212
57566
|
out();
|
|
57213
57567
|
out(` ${bold(p.merchant ?? "Unknown merchant")}`);
|
|
@@ -57268,7 +57622,6 @@ async function cmdPay(opts) {
|
|
|
57268
57622
|
const sendArgs = ["--rozo-payment-id", rozoPaymentId];
|
|
57269
57623
|
if (opts.dryRun) sendArgs.push("--dry-run");
|
|
57270
57624
|
else sendArgs.push("--send");
|
|
57271
|
-
if (opts.yesLarge) sendArgs.push("--yes-large");
|
|
57272
57625
|
if (opts.rpc) sendArgs.push("--rpc", opts.rpc);
|
|
57273
57626
|
if (!opts.json) out(dim(opts.dryRun ? " Preparing (dry run)\u2026" : " Sending\u2026"));
|
|
57274
57627
|
const sent = await step(sender, sendArgs);
|
|
@@ -57289,6 +57642,7 @@ async function cmdPay(opts) {
|
|
|
57289
57642
|
} else {
|
|
57290
57643
|
out(` ${green("\u2713")} Sent. tx ${sent.payload.txHash}`);
|
|
57291
57644
|
out();
|
|
57645
|
+
savePrefs({ lastPreset: chosenPreset || presetFor(chainId, tokenSymbol) });
|
|
57292
57646
|
}
|
|
57293
57647
|
if (sent.exitCode !== EXIT_OK && sent.exitCode !== 3) return sent.exitCode;
|
|
57294
57648
|
} else if (!opts.json) {
|
|
@@ -57342,6 +57696,11 @@ function printDeposit(deposit, opts) {
|
|
|
57342
57696
|
if (deposit.receiverMemo) out(` Memo ${bold(deposit.receiverMemo)} ${red("(required)")}`);
|
|
57343
57697
|
}
|
|
57344
57698
|
out();
|
|
57699
|
+
const unit = isSatsUnit(deposit.amountUnit) ? "sats" : deposit.tokenSymbol;
|
|
57700
|
+
const where = deposit.lnInvoice ? "over Lightning" : `on ${deposit.chain}`;
|
|
57701
|
+
out(
|
|
57702
|
+
` Make sure the wallet you pay from holds at least ${bold(`${deposit.amount} ${unit}`)} ${where}.`
|
|
57703
|
+
);
|
|
57345
57704
|
out(dim(" Copy the address from this block; do not retype it."));
|
|
57346
57705
|
out(dim(" Send it exactly once."));
|
|
57347
57706
|
out();
|
|
@@ -57376,7 +57735,20 @@ async function main6() {
|
|
|
57376
57735
|
case "status":
|
|
57377
57736
|
return cmdStatus(opts);
|
|
57378
57737
|
case "pay":
|
|
57379
|
-
|
|
57738
|
+
try {
|
|
57739
|
+
return await cmdPay(opts);
|
|
57740
|
+
} catch (err) {
|
|
57741
|
+
if (err instanceof CliError) {
|
|
57742
|
+
const payload = formatFailure(err);
|
|
57743
|
+
if (opts.json) printJson(payload);
|
|
57744
|
+
else {
|
|
57745
|
+
printError(payload);
|
|
57746
|
+
out(dim("\n rozo-checkout --help for usage."));
|
|
57747
|
+
}
|
|
57748
|
+
return EXIT_USAGE;
|
|
57749
|
+
}
|
|
57750
|
+
throw err;
|
|
57751
|
+
}
|
|
57380
57752
|
default:
|
|
57381
57753
|
out(HELP);
|
|
57382
57754
|
return EXIT_USAGE;
|