@rozoai/checkout 0.1.11 → 0.1.14
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 +26 -0
- package/llms.txt +30 -1
- package/package.json +1 -1
- package/scripts/dist/cli.js +829 -78
- package/scripts/dist/create-order.js +78 -5
- package/scripts/dist/quote.js +12 -2
- package/scripts/dist/send-evm.js +111 -14
- package/scripts/dist/send-sol.js +111 -14
- package/scripts/dist/status.js +171 -20
package/scripts/dist/cli.js
CHANGED
|
@@ -20541,7 +20541,7 @@ var require_dist = __commonJS({
|
|
|
20541
20541
|
}
|
|
20542
20542
|
}
|
|
20543
20543
|
}
|
|
20544
|
-
function*
|
|
20544
|
+
function* run7(value, struct2, options = {}) {
|
|
20545
20545
|
const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
|
|
20546
20546
|
const ctx = { path: path7, branch, mask: mask2 };
|
|
20547
20547
|
if (coerce2) {
|
|
@@ -20554,7 +20554,7 @@ var require_dist = __commonJS({
|
|
|
20554
20554
|
yield [failure, void 0];
|
|
20555
20555
|
}
|
|
20556
20556
|
for (let [k, v, s] of struct2.entries(value, ctx)) {
|
|
20557
|
-
const ts =
|
|
20557
|
+
const ts = run7(v, s, {
|
|
20558
20558
|
path: k === void 0 ? path7 : [...path7, k],
|
|
20559
20559
|
branch: k === void 0 ? branch : [...branch, v],
|
|
20560
20560
|
coerce: coerce2,
|
|
@@ -20682,7 +20682,7 @@ var require_dist = __commonJS({
|
|
|
20682
20682
|
return !result[0];
|
|
20683
20683
|
}
|
|
20684
20684
|
function validate7(value, struct2, options = {}) {
|
|
20685
|
-
const tuples =
|
|
20685
|
+
const tuples = run7(value, struct2, options);
|
|
20686
20686
|
const tuple2 = shiftIterator(tuples);
|
|
20687
20687
|
if (tuple2[0]) {
|
|
20688
20688
|
const error = new StructError(tuple2[0], function* () {
|
|
@@ -21095,7 +21095,7 @@ var require_dist = __commonJS({
|
|
|
21095
21095
|
validator(value, ctx) {
|
|
21096
21096
|
const failures = [];
|
|
21097
21097
|
for (const S of Structs) {
|
|
21098
|
-
const [...tuples] =
|
|
21098
|
+
const [...tuples] = run7(value, S, ctx);
|
|
21099
21099
|
const [first] = tuples;
|
|
21100
21100
|
if (!first[0]) {
|
|
21101
21101
|
return [];
|
|
@@ -24067,29 +24067,29 @@ var require_lib3 = __commonJS({
|
|
|
24067
24067
|
}
|
|
24068
24068
|
let accum = [];
|
|
24069
24069
|
let accumBytes = 0;
|
|
24070
|
-
let
|
|
24070
|
+
let abort2 = false;
|
|
24071
24071
|
return new Body.Promise(function(resolve, reject) {
|
|
24072
24072
|
let resTimeout;
|
|
24073
24073
|
if (_this4.timeout) {
|
|
24074
24074
|
resTimeout = setTimeout(function() {
|
|
24075
|
-
|
|
24075
|
+
abort2 = true;
|
|
24076
24076
|
reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, "body-timeout"));
|
|
24077
24077
|
}, _this4.timeout);
|
|
24078
24078
|
}
|
|
24079
24079
|
body.on("error", function(err) {
|
|
24080
24080
|
if (err.name === "AbortError") {
|
|
24081
|
-
|
|
24081
|
+
abort2 = true;
|
|
24082
24082
|
reject(err);
|
|
24083
24083
|
} else {
|
|
24084
24084
|
reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, "system", err));
|
|
24085
24085
|
}
|
|
24086
24086
|
});
|
|
24087
24087
|
body.on("data", function(chunk) {
|
|
24088
|
-
if (
|
|
24088
|
+
if (abort2 || chunk === null) {
|
|
24089
24089
|
return;
|
|
24090
24090
|
}
|
|
24091
24091
|
if (_this4.size && accumBytes + chunk.length > _this4.size) {
|
|
24092
|
-
|
|
24092
|
+
abort2 = true;
|
|
24093
24093
|
reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, "max-size"));
|
|
24094
24094
|
return;
|
|
24095
24095
|
}
|
|
@@ -24097,7 +24097,7 @@ var require_lib3 = __commonJS({
|
|
|
24097
24097
|
accum.push(chunk);
|
|
24098
24098
|
});
|
|
24099
24099
|
body.on("end", function() {
|
|
24100
|
-
if (
|
|
24100
|
+
if (abort2) {
|
|
24101
24101
|
return;
|
|
24102
24102
|
}
|
|
24103
24103
|
clearTimeout(resTimeout);
|
|
@@ -24783,7 +24783,7 @@ var require_lib3 = __commonJS({
|
|
|
24783
24783
|
const send = (options.protocol === "https:" ? https : http2).request;
|
|
24784
24784
|
const signal = request2.signal;
|
|
24785
24785
|
let response = null;
|
|
24786
|
-
const
|
|
24786
|
+
const abort2 = function abort3() {
|
|
24787
24787
|
let error = new AbortError("The user aborted a request.");
|
|
24788
24788
|
reject(error);
|
|
24789
24789
|
if (request2.body && request2.body instanceof Stream.Readable) {
|
|
@@ -24793,11 +24793,11 @@ var require_lib3 = __commonJS({
|
|
|
24793
24793
|
response.body.emit("error", error);
|
|
24794
24794
|
};
|
|
24795
24795
|
if (signal && signal.aborted) {
|
|
24796
|
-
|
|
24796
|
+
abort2();
|
|
24797
24797
|
return;
|
|
24798
24798
|
}
|
|
24799
24799
|
const abortAndFinalize = function abortAndFinalize2() {
|
|
24800
|
-
|
|
24800
|
+
abort2();
|
|
24801
24801
|
finalize();
|
|
24802
24802
|
};
|
|
24803
24803
|
const req = send(options);
|
|
@@ -41445,7 +41445,47 @@ process.on("warning", (warning) => {
|
|
|
41445
41445
|
import readline2 from "node:readline";
|
|
41446
41446
|
import { createRequire as createRequire2 } from "node:module";
|
|
41447
41447
|
|
|
41448
|
+
// scripts/src/lib/support.mjs
|
|
41449
|
+
var SUPPORT = Object.freeze({
|
|
41450
|
+
email: "hi@rozo.ai",
|
|
41451
|
+
x: "https://x.com/ROZOai",
|
|
41452
|
+
discord: "https://discord.gg/EfWejgTbuU"
|
|
41453
|
+
});
|
|
41454
|
+
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
41455
|
+
var CONTACT_EMAIL_MAX_LENGTH = 254;
|
|
41456
|
+
var EMAIL_RE = /^[a-z0-9][^\s@]*@[^\s@.]+(\.[^\s@.]+)+$/;
|
|
41457
|
+
function normalizeContactEmail(raw) {
|
|
41458
|
+
if (raw === void 0 || raw === null) return null;
|
|
41459
|
+
if (typeof raw !== "string") return void 0;
|
|
41460
|
+
const email = raw.trim().toLowerCase();
|
|
41461
|
+
if (email.length === 0) return null;
|
|
41462
|
+
if (email.length > CONTACT_EMAIL_MAX_LENGTH) return void 0;
|
|
41463
|
+
if (/[\x00-\x1f\x7f]/.test(email) || !EMAIL_RE.test(email)) return void 0;
|
|
41464
|
+
return email;
|
|
41465
|
+
}
|
|
41466
|
+
function contactEmailAttached(email, reused) {
|
|
41467
|
+
return Boolean(email) && !reused;
|
|
41468
|
+
}
|
|
41469
|
+
function maskEmail(email) {
|
|
41470
|
+
const at = String(email).lastIndexOf("@");
|
|
41471
|
+
if (at <= 0) return "***";
|
|
41472
|
+
return `${email[0]}***${email.slice(at)}`;
|
|
41473
|
+
}
|
|
41474
|
+
function contactEmailFromArgs(args) {
|
|
41475
|
+
if (args.email === void 0) return null;
|
|
41476
|
+
const email = args.email === true ? void 0 : normalizeContactEmail(String(args.email));
|
|
41477
|
+
if (email === void 0) {
|
|
41478
|
+
const err = new Error(
|
|
41479
|
+
"--email must be a valid email address, for example name@example.com. It is optional: leave it out to continue without one."
|
|
41480
|
+
);
|
|
41481
|
+
err.code = "INVALID_EMAIL";
|
|
41482
|
+
throw err;
|
|
41483
|
+
}
|
|
41484
|
+
return email;
|
|
41485
|
+
}
|
|
41486
|
+
|
|
41448
41487
|
// scripts/src/lib/output.mjs
|
|
41488
|
+
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
41449
41489
|
var EXIT_OK = 0;
|
|
41450
41490
|
var EXIT_ERROR = 1;
|
|
41451
41491
|
var EXIT_USAGE = 2;
|
|
@@ -41457,7 +41497,8 @@ function redact(text) {
|
|
|
41457
41497
|
s = s.replace(/\b[0-9a-fA-F]{64}\b/g, "<redacted>");
|
|
41458
41498
|
s = s.replace(/\b[1-9A-HJ-NP-Za-km-z]{80,90}\b/g, "<redacted>");
|
|
41459
41499
|
s = s.replace(/\[(?:\s*\d{1,3}\s*,){40,}\s*\d{1,3}\s*\]/g, "[<redacted>]");
|
|
41460
|
-
s = s.replace(/\b([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^\s"'<>]+)/g, (
|
|
41500
|
+
s = s.replace(/\b([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^\s"'<>]+)/g, (m, scheme, rest) => {
|
|
41501
|
+
if (PUBLIC_SUPPORT_URLS.has(m.replace(/[.,;:!?)]+$/, ""))) return m;
|
|
41461
41502
|
const withoutUserinfo = rest.includes("@") ? rest.slice(rest.indexOf("@") + 1) : rest;
|
|
41462
41503
|
const host = withoutUserinfo.split(/[/?#]/)[0];
|
|
41463
41504
|
const hadMore = withoutUserinfo.length > host.length;
|
|
@@ -41704,6 +41745,14 @@ function isSupportedSource(chainId, tokenSymbol) {
|
|
|
41704
41745
|
return SUPPORTED_SOURCES.some((s) => s.chainId === cid && s.tokens.includes(sym));
|
|
41705
41746
|
}
|
|
41706
41747
|
|
|
41748
|
+
// scripts/src/lib/utm.mjs
|
|
41749
|
+
function normalizeUtmSource(value) {
|
|
41750
|
+
if (typeof value !== "string") return null;
|
|
41751
|
+
const s = value.trim().toLowerCase();
|
|
41752
|
+
if (!s || s.length > 100 || !/^[a-z0-9][a-z0-9._-]*$/.test(s)) return null;
|
|
41753
|
+
return s;
|
|
41754
|
+
}
|
|
41755
|
+
|
|
41707
41756
|
// scripts/src/lib/cli-args.mjs
|
|
41708
41757
|
var COMMANDS = ["pay", "quote", "status", "help", "version"];
|
|
41709
41758
|
var CHAIN_ALIASES = {
|
|
@@ -41819,7 +41868,41 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
41819
41868
|
"watch",
|
|
41820
41869
|
"fresh"
|
|
41821
41870
|
]);
|
|
41822
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
41871
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
41872
|
+
"with",
|
|
41873
|
+
"from",
|
|
41874
|
+
"chain",
|
|
41875
|
+
"token",
|
|
41876
|
+
"rpc",
|
|
41877
|
+
"timeout",
|
|
41878
|
+
"payer",
|
|
41879
|
+
"keyfile",
|
|
41880
|
+
"env-file",
|
|
41881
|
+
// Bitrefill invoice (pay) and provider override (status)
|
|
41882
|
+
"bitrefill-invoice",
|
|
41883
|
+
"to",
|
|
41884
|
+
"amount",
|
|
41885
|
+
"expires-at",
|
|
41886
|
+
"provider",
|
|
41887
|
+
// Optional channel label for reporting (pay, quote)
|
|
41888
|
+
"utm-source",
|
|
41889
|
+
// Optional contact email so ROZO can reach the payer (pay)
|
|
41890
|
+
"email"
|
|
41891
|
+
]);
|
|
41892
|
+
function resolveFromPreset(value) {
|
|
41893
|
+
try {
|
|
41894
|
+
return resolvePreset(value);
|
|
41895
|
+
} catch (err) {
|
|
41896
|
+
const raw = String(value ?? "").trim();
|
|
41897
|
+
const idx = raw.indexOf("-");
|
|
41898
|
+
if (idx <= 0) throw err;
|
|
41899
|
+
try {
|
|
41900
|
+
return resolvePreset(`${raw.slice(idx + 1)}-${raw.slice(0, idx)}`);
|
|
41901
|
+
} catch {
|
|
41902
|
+
throw err;
|
|
41903
|
+
}
|
|
41904
|
+
}
|
|
41905
|
+
}
|
|
41823
41906
|
function parseCliArgs(argv) {
|
|
41824
41907
|
const positional = [];
|
|
41825
41908
|
const flags = {};
|
|
@@ -41873,9 +41956,19 @@ function parseCliArgs(argv) {
|
|
|
41873
41956
|
}
|
|
41874
41957
|
const target = positional[1];
|
|
41875
41958
|
const json = Boolean(flags.json);
|
|
41959
|
+
let utmSource;
|
|
41960
|
+
if (flags["utm-source"] !== void 0 && (command === "pay" || command === "quote")) {
|
|
41961
|
+
utmSource = normalizeUtmSource(String(flags["utm-source"]));
|
|
41962
|
+
if (!utmSource) {
|
|
41963
|
+
throw new CliError(
|
|
41964
|
+
"BAD_VALUE",
|
|
41965
|
+
'--utm-source must be 1-100 characters of a-z, 0-9, ".", "_" or "-", starting with a letter or digit.'
|
|
41966
|
+
);
|
|
41967
|
+
}
|
|
41968
|
+
}
|
|
41876
41969
|
if (command === "quote") {
|
|
41877
41970
|
if (!target) throw new CliError("MISSING_TARGET", "Usage: rozo-checkout quote <coinbase-link>");
|
|
41878
|
-
return { command, target, json };
|
|
41971
|
+
return { command, target, json, ...utmSource ? { utmSource } : {} };
|
|
41879
41972
|
}
|
|
41880
41973
|
if (command === "status") {
|
|
41881
41974
|
if (!target) {
|
|
@@ -41884,15 +41977,43 @@ function parseCliArgs(argv) {
|
|
|
41884
41977
|
"Usage: rozo-checkout status <rozoPaymentId | coinbase-link>"
|
|
41885
41978
|
);
|
|
41886
41979
|
}
|
|
41980
|
+
if (flags.provider !== void 0 && !["coinbase", "bitrefill"].includes(String(flags.provider))) {
|
|
41981
|
+
throw new CliError("BAD_VALUE", "--provider must be coinbase or bitrefill.");
|
|
41982
|
+
}
|
|
41887
41983
|
return {
|
|
41888
41984
|
command,
|
|
41889
41985
|
target,
|
|
41890
41986
|
json,
|
|
41987
|
+
provider: flags.provider,
|
|
41891
41988
|
watch: flags.watch === true,
|
|
41892
41989
|
timeout: flags.timeout === void 0 ? 600 : Number(flags.timeout)
|
|
41893
41990
|
};
|
|
41894
41991
|
}
|
|
41895
|
-
|
|
41992
|
+
const bitrefillFlags = ["bitrefill-invoice", "to", "amount", "expires-at"].filter(
|
|
41993
|
+
(k) => flags[k] !== void 0
|
|
41994
|
+
);
|
|
41995
|
+
let bitrefill = null;
|
|
41996
|
+
if (bitrefillFlags.length) {
|
|
41997
|
+
if (flags["bitrefill-invoice"] === void 0 || flags.to === void 0 || flags.amount === void 0 || flags["expires-at"] === void 0) {
|
|
41998
|
+
throw new CliError(
|
|
41999
|
+
"MISSING_VALUE",
|
|
42000
|
+
"A Bitrefill invoice needs --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --expires-at <ISO>."
|
|
42001
|
+
);
|
|
42002
|
+
}
|
|
42003
|
+
if (target) {
|
|
42004
|
+
throw new CliError("CONFLICTING_FLAGS", "Pass either a Coinbase link or --bitrefill-invoice, not both.");
|
|
42005
|
+
}
|
|
42006
|
+
bitrefill = {
|
|
42007
|
+
invoiceId: String(flags["bitrefill-invoice"]).trim(),
|
|
42008
|
+
address: String(flags.to).trim(),
|
|
42009
|
+
amount: String(flags.amount).trim(),
|
|
42010
|
+
expiresAt: String(flags["expires-at"]).trim()
|
|
42011
|
+
};
|
|
42012
|
+
}
|
|
42013
|
+
if (flags.from !== void 0 && flags.with !== void 0) {
|
|
42014
|
+
throw new CliError("CONFLICTING_FLAGS", "Use either --with or --from, not both.");
|
|
42015
|
+
}
|
|
42016
|
+
if (!target && !bitrefill) {
|
|
41896
42017
|
throw new CliError(
|
|
41897
42018
|
"MISSING_TARGET",
|
|
41898
42019
|
"Usage: rozo-checkout pay <coinbase-link> --with usdt-solana"
|
|
@@ -41903,7 +42024,7 @@ function parseCliArgs(argv) {
|
|
|
41903
42024
|
if (flags.chain === void 0 || flags.token === void 0) {
|
|
41904
42025
|
throw new CliError("MISSING_VALUE", "--chain and --token must be given together.");
|
|
41905
42026
|
}
|
|
41906
|
-
if (flags.with !== void 0) {
|
|
42027
|
+
if (flags.with !== void 0 || flags.from !== void 0) {
|
|
41907
42028
|
throw new CliError("CONFLICTING_FLAGS", "Use either --with, or --chain plus --token.");
|
|
41908
42029
|
}
|
|
41909
42030
|
const chainId = String(flags.chain).trim();
|
|
@@ -41918,16 +42039,29 @@ function parseCliArgs(argv) {
|
|
|
41918
42039
|
source = { chainId, tokenSymbol };
|
|
41919
42040
|
} else if (flags.with !== void 0) {
|
|
41920
42041
|
source = resolvePreset(flags.with);
|
|
42042
|
+
} else if (flags.from !== void 0) {
|
|
42043
|
+
source = resolveFromPreset(flags.from);
|
|
41921
42044
|
} else {
|
|
41922
42045
|
source = null;
|
|
41923
42046
|
}
|
|
42047
|
+
let email = null;
|
|
42048
|
+
if (flags.email !== void 0) {
|
|
42049
|
+
email = normalizeContactEmail(String(flags.email));
|
|
42050
|
+
if (email === void 0) {
|
|
42051
|
+
throw new CliError(
|
|
42052
|
+
"INVALID_EMAIL",
|
|
42053
|
+
"--email must be a valid email address, for example name@example.com. It is optional: leave it out to continue without one."
|
|
42054
|
+
);
|
|
42055
|
+
}
|
|
42056
|
+
}
|
|
41924
42057
|
const timeout = flags.timeout === void 0 ? 900 : Number(flags.timeout);
|
|
41925
42058
|
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
41926
42059
|
throw new CliError("BAD_VALUE", "--timeout must be a non-negative number of seconds.");
|
|
41927
42060
|
}
|
|
41928
42061
|
return {
|
|
41929
42062
|
command: "pay",
|
|
41930
|
-
target,
|
|
42063
|
+
target: target ?? null,
|
|
42064
|
+
bitrefill,
|
|
41931
42065
|
json,
|
|
41932
42066
|
source,
|
|
41933
42067
|
send: flags.send === true,
|
|
@@ -41939,15 +42073,18 @@ function parseCliArgs(argv) {
|
|
|
41939
42073
|
payer: flags.payer,
|
|
41940
42074
|
fresh: flags.fresh === true,
|
|
41941
42075
|
keyfile: flags.keyfile,
|
|
41942
|
-
envFile: flags["env-file"]
|
|
42076
|
+
envFile: flags["env-file"],
|
|
42077
|
+
...utmSource ? { utmSource } : {},
|
|
42078
|
+
...email ? { email } : {}
|
|
41943
42079
|
};
|
|
41944
42080
|
}
|
|
41945
|
-
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link
|
|
41946
|
-
or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.
|
|
42081
|
+
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link or a Bitrefill invoice
|
|
42082
|
+
(USDC on Base) with BTC Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.
|
|
41947
42083
|
|
|
41948
42084
|
USAGE
|
|
41949
42085
|
npx @rozoai/checkout pay <coinbase-link> (pick a coin from a list)
|
|
41950
42086
|
npx @rozoai/checkout pay <coinbase-link> --with <coin>
|
|
42087
|
+
npx @rozoai/checkout pay --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --expires-at <ISO> --with <coin>
|
|
41951
42088
|
npx @rozoai/checkout quote <coinbase-link>
|
|
41952
42089
|
npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
|
|
41953
42090
|
|
|
@@ -41978,11 +42115,20 @@ OPTIONS
|
|
|
41978
42115
|
--json, -j machine-readable output
|
|
41979
42116
|
--no-watch stop after showing the deposit instructions
|
|
41980
42117
|
--timeout <s> how long to poll for settlement (default 900)
|
|
42118
|
+
--bitrefill-invoice <id> pay a Bitrefill invoice created with "USDC on Base"
|
|
42119
|
+
instead of a Coinbase link. Needs --to <0x\u2026 address Bitrefill
|
|
42120
|
+
shows>, --amount <exact USDC amount> and --expires-at <ISO>
|
|
42121
|
+
(required; refused under 5 minutes or over 30 minutes out).
|
|
42122
|
+
--from <coin> same as --with; also accepts chain-first, e.g. stellar-usdc
|
|
41981
42123
|
--payer <addr> optional. Check what this wallet holds and mark the coin
|
|
41982
42124
|
list accordingly. Display help only; it never changes what
|
|
41983
42125
|
gets signed.
|
|
41984
42126
|
--fresh ignore the remembered wallet address and coin for this run
|
|
41985
42127
|
--rpc <url> override the RPC endpoint for --send
|
|
42128
|
+
--utm-source <s> optional channel label for reporting; no identity, no
|
|
42129
|
+
privilege. Overrides ROZO_CHECKOUT_UTM_SOURCE.
|
|
42130
|
+
--email <addr> optional. A contact email stored with the order so ROZO can
|
|
42131
|
+
reach you if the payment needs attention. Never required.
|
|
41986
42132
|
--help, -h this text
|
|
41987
42133
|
--version, -v print the version
|
|
41988
42134
|
|
|
@@ -41990,10 +42136,15 @@ EXAMPLES
|
|
|
41990
42136
|
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01...
|
|
41991
42137
|
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01... --with usdt-solana
|
|
41992
42138
|
npx @rozoai/checkout pay pl_01... --with btc-lightning
|
|
42139
|
+
npx @rozoai/checkout pay pl_01... --with usdt-solana --email you@example.com
|
|
42140
|
+
npx @rozoai/checkout pay --bitrefill-invoice 8f3k2 --to 0xAbC\u2026123 --amount 7.90 --expires-at 2026-10-01T12:00:00Z --with usdc-stellar
|
|
41993
42141
|
npx @rozoai/checkout status 11111111-2222-4333-8444-555555555555
|
|
41994
42142
|
|
|
41995
42143
|
Creating an order moves no money; an unfunded order simply expires. Nothing is
|
|
41996
|
-
paid until you confirm, and --send is required before anything is signed
|
|
42144
|
+
paid until you confirm, and --send is required before anything is signed.
|
|
42145
|
+
|
|
42146
|
+
SUPPORT
|
|
42147
|
+
Email hi@rozo.ai, X https://x.com/ROZOai, Discord https://discord.gg/EfWejgTbuU`;
|
|
41997
42148
|
|
|
41998
42149
|
// scripts/src/lib/wallet-check.mjs
|
|
41999
42150
|
var INTENT_API_BASE = process.env.ROZO_CHECKOUT_INTENT_API || "https://intentapi.rozo.ai";
|
|
@@ -42241,12 +42392,23 @@ function readState(rozoPaymentId) {
|
|
|
42241
42392
|
function createOrderRecord(record) {
|
|
42242
42393
|
return withLock(() => createOrderRecordUnlocked(record));
|
|
42243
42394
|
}
|
|
42395
|
+
function mergeBitrefill(prev, next) {
|
|
42396
|
+
if (!next) return prev ?? null;
|
|
42397
|
+
if (!prev?.expiresAt || !next.expiresAt) return { ...next, expiresAt: next.expiresAt ?? prev?.expiresAt ?? null };
|
|
42398
|
+
const a = Date.parse(prev.expiresAt);
|
|
42399
|
+
const b = Date.parse(next.expiresAt);
|
|
42400
|
+
return { ...next, expiresAt: Number.isFinite(a) && (!Number.isFinite(b) || a < b) ? prev.expiresAt : next.expiresAt };
|
|
42401
|
+
}
|
|
42244
42402
|
function createOrderRecordUnlocked(record) {
|
|
42245
42403
|
const { rozoPaymentId } = record;
|
|
42246
42404
|
const existing = readState(rozoPaymentId);
|
|
42247
42405
|
const next = {
|
|
42248
42406
|
version: 1,
|
|
42249
42407
|
rozoPaymentId,
|
|
42408
|
+
// 'coinbase' (default, historical records) or 'bitrefill'. Drives which
|
|
42409
|
+
// payability checks presend and status run.
|
|
42410
|
+
provider: record.provider ?? existing?.provider ?? "coinbase",
|
|
42411
|
+
bitrefill: mergeBitrefill(existing?.bitrefill, record.bitrefill),
|
|
42250
42412
|
linkId: record.linkId,
|
|
42251
42413
|
paymentLink: record.paymentLink ?? null,
|
|
42252
42414
|
merchant: record.merchant ?? null,
|
|
@@ -54364,7 +54526,7 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
54364
54526
|
}
|
|
54365
54527
|
}
|
|
54366
54528
|
if (!res.ok) {
|
|
54367
|
-
const code = json?.code || json?.error?.code || (typeof json?.error === "string" ?
|
|
54529
|
+
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
54368
54530
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
54369
54531
|
throw new SkillError(code, redact(String(message)), {
|
|
54370
54532
|
httpStatus: res.status,
|
|
@@ -54410,18 +54572,42 @@ var PKG_VERSION = (() => {
|
|
|
54410
54572
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
54411
54573
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
54412
54574
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
54575
|
+
function buildAttribution({ utmSource } = {}) {
|
|
54576
|
+
const raw = utmSource !== void 0 ? utmSource : process.env.ROZO_CHECKOUT_UTM_SOURCE;
|
|
54577
|
+
const utm_source = normalizeUtmSource(raw);
|
|
54578
|
+
return { client: ATTRIBUTION_CLIENT, ...utm_source ? { utm_source } : {} };
|
|
54579
|
+
}
|
|
54413
54580
|
var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
|
|
54414
54581
|
async function quoteInvoice({ url, linkId }) {
|
|
54415
54582
|
const body = url ? { url } : { payment_id: linkId };
|
|
54416
54583
|
return postJson(`${MPP_BASE}/quote-invoice`, body);
|
|
54417
54584
|
}
|
|
54418
|
-
async function createInvoice({ url, linkId, source, quoteReceipt }) {
|
|
54585
|
+
async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email }) {
|
|
54419
54586
|
const body = {
|
|
54420
54587
|
...url ? { url } : { payment_id: linkId },
|
|
54421
54588
|
source: { chainId: String(source.chainId), tokenSymbol: source.tokenSymbol },
|
|
54422
54589
|
...quoteReceipt ? { quoteReceipt } : {},
|
|
54423
54590
|
client: CLIENT_LABEL,
|
|
54424
|
-
attribution: {
|
|
54591
|
+
attribution: buildAttribution({ utmSource }),
|
|
54592
|
+
// Optional payer contact email (validated by the caller). Omitted, never
|
|
54593
|
+
// sent empty, when the user gave none.
|
|
54594
|
+
...email ? { email } : {}
|
|
54595
|
+
};
|
|
54596
|
+
return postJson(`${MPP_BASE}/create-invoice`, body);
|
|
54597
|
+
}
|
|
54598
|
+
async function createBitrefillInvoice({ invoice, source, utmSource, email }) {
|
|
54599
|
+
const body = {
|
|
54600
|
+
provider: "bitrefill",
|
|
54601
|
+
bitrefill: {
|
|
54602
|
+
invoiceId: invoice.invoiceId,
|
|
54603
|
+
address: invoice.address,
|
|
54604
|
+
amount: invoice.amount,
|
|
54605
|
+
...invoice.expiresAt ? { expiresAt: invoice.expiresAt } : {}
|
|
54606
|
+
},
|
|
54607
|
+
source: { chainId: String(source.chainId), tokenSymbol: source.tokenSymbol },
|
|
54608
|
+
client: CLIENT_LABEL,
|
|
54609
|
+
attribution: buildAttribution({ utmSource }),
|
|
54610
|
+
...email ? { email } : {}
|
|
54425
54611
|
};
|
|
54426
54612
|
return postJson(`${MPP_BASE}/create-invoice`, body);
|
|
54427
54613
|
}
|
|
@@ -54734,7 +54920,15 @@ function checkPayable(statusResponse, now = Date.now()) {
|
|
|
54734
54920
|
}
|
|
54735
54921
|
return { ok: true, code: null, reason: null, derived };
|
|
54736
54922
|
}
|
|
54737
|
-
function classifyStatus({
|
|
54923
|
+
function classifyStatus({
|
|
54924
|
+
payment,
|
|
54925
|
+
routerState,
|
|
54926
|
+
coinbase,
|
|
54927
|
+
now = Date.now(),
|
|
54928
|
+
viewsFailed = false,
|
|
54929
|
+
provider = "coinbase"
|
|
54930
|
+
}) {
|
|
54931
|
+
const bitrefill = provider === "bitrefill";
|
|
54738
54932
|
const source = payment?.source || {};
|
|
54739
54933
|
const hasTx = Boolean(source.txHash);
|
|
54740
54934
|
const confirmed = Boolean(source.confirmedAt);
|
|
@@ -54767,7 +54961,7 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
|
|
|
54767
54961
|
{ escalate: true }
|
|
54768
54962
|
);
|
|
54769
54963
|
}
|
|
54770
|
-
if (routerStatus === "paid" || coinbase?.settled === true) {
|
|
54964
|
+
if (!bitrefill && (routerStatus === "paid" || coinbase?.settled === true)) {
|
|
54771
54965
|
return mk("settled", "Coinbase invoice settled by the funder wallet.", { terminal: true });
|
|
54772
54966
|
}
|
|
54773
54967
|
if (routerStatus === "failed_pay_invoice" || routerStatus === "failed_insufficient_balance") {
|
|
@@ -54789,6 +54983,17 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
|
|
|
54789
54983
|
escalate: true
|
|
54790
54984
|
});
|
|
54791
54985
|
}
|
|
54986
|
+
if (bitrefill) {
|
|
54987
|
+
const payoutTx = Boolean(payment?.destination?.txHash);
|
|
54988
|
+
if (payment?.status === "payment_payout_completed" || payment?.status === "payment_completed" && payoutTx) {
|
|
54989
|
+
return mk("settled", "USDC delivered to the Bitrefill invoice address on Base.", {
|
|
54990
|
+
terminal: true
|
|
54991
|
+
});
|
|
54992
|
+
}
|
|
54993
|
+
if (payment?.status === "payment_completed") {
|
|
54994
|
+
return mk("bridging", "Payment completed but no payout transaction is visible yet. Keep polling.");
|
|
54995
|
+
}
|
|
54996
|
+
}
|
|
54792
54997
|
switch (payment?.status) {
|
|
54793
54998
|
case "payment_unpaid": {
|
|
54794
54999
|
if (moneyDetected) {
|
|
@@ -54972,6 +55177,7 @@ async function main2(argv) {
|
|
|
54972
55177
|
}
|
|
54973
55178
|
const requested = { chainId, tokenSymbol };
|
|
54974
55179
|
const confirmed = Boolean(args.confirm);
|
|
55180
|
+
const email = contactEmailFromArgs(args);
|
|
54975
55181
|
let blacklist;
|
|
54976
55182
|
try {
|
|
54977
55183
|
blacklist = loadBlacklist();
|
|
@@ -54988,7 +55194,9 @@ async function main2(argv) {
|
|
|
54988
55194
|
const created = await createInvoice({
|
|
54989
55195
|
url: String(url),
|
|
54990
55196
|
source: requested,
|
|
54991
|
-
quoteReceipt
|
|
55197
|
+
quoteReceipt,
|
|
55198
|
+
utmSource: typeof args["utm-source"] === "string" ? args["utm-source"] : void 0,
|
|
55199
|
+
email
|
|
54992
55200
|
});
|
|
54993
55201
|
if (!created?.rozoPaymentId) {
|
|
54994
55202
|
throw new SkillError("CREATE_FAILED", "create-invoice returned no rozoPaymentId.", {
|
|
@@ -55145,6 +55353,13 @@ async function main2(argv) {
|
|
|
55145
55353
|
success: true,
|
|
55146
55354
|
step: "create-order",
|
|
55147
55355
|
confirmed,
|
|
55356
|
+
// True only when an email went out with a NEW order. A reused order keeps
|
|
55357
|
+
// whatever email it was first created with (the router never rebinds), so
|
|
55358
|
+
// reporting true there would be a false promise. The address itself is
|
|
55359
|
+
// never echoed. Note the --confirm run always sees the order as reused;
|
|
55360
|
+
// the CLI carries the first run's value forward.
|
|
55361
|
+
contactEmailProvided: contactEmailAttached(email, created.reused),
|
|
55362
|
+
support: SUPPORT,
|
|
55148
55363
|
reused: Boolean(created.reused),
|
|
55149
55364
|
reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
|
|
55150
55365
|
orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
|
|
@@ -55227,16 +55442,439 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
55227
55442
|
return main2(argv);
|
|
55228
55443
|
}
|
|
55229
55444
|
|
|
55445
|
+
// scripts/src/lib/bitrefill.mjs
|
|
55446
|
+
var PROVIDER_BITREFILL = "bitrefill";
|
|
55447
|
+
var BITREFILL_DESTINATION = Object.freeze({ chainId: "8453", tokenSymbol: "USDC" });
|
|
55448
|
+
var BITREFILL_MIN_EXPIRY_MS = 5 * 60 * 1e3;
|
|
55449
|
+
var BITREFILL_MIN_PAY_WINDOW_MS = 2 * 60 * 1e3;
|
|
55450
|
+
var BITREFILL_MAX_EXPIRY_AHEAD_MS = 30 * 60 * 1e3;
|
|
55451
|
+
var BITREFILL_ROUTER_HINTS = {
|
|
55452
|
+
BITREFILL_DISABLED: "Bitrefill payments are switched off on the router right now. Try again later.",
|
|
55453
|
+
BITREFILL_NOT_CONFIGURED: "The router is not configured for Bitrefill payments yet (503). Nothing was created; try again later.",
|
|
55454
|
+
RETRY_LATER: "The router is temporarily unable to create the order (503). Nothing was created; wait a minute and retry the same command.",
|
|
55455
|
+
INVALID_INPUT: "The router rejected the invoice details (400). Check the invoice id, address, amount and expiry against Bitrefill.",
|
|
55456
|
+
INVALID_ADDRESS: "The router rejected the receiving address. Copy it again from the Bitrefill invoice.",
|
|
55457
|
+
INVALID_AMOUNT: "The router rejected the amount. Use the exact USDC amount Bitrefill shows.",
|
|
55458
|
+
AMOUNT_OUT_OF_RANGE: "The amount is outside what the router accepts for Bitrefill invoices.",
|
|
55459
|
+
INVOICE_EXPIRING: "The invoice expires too soon (or too far out) for the router. Create a fresh Bitrefill invoice.",
|
|
55460
|
+
BLOCKED_ADDRESS: "The router refused this receiving address. Do not pay it; report it.",
|
|
55461
|
+
UNSUPPORTED_SOURCE: "The router does not accept this coin for Bitrefill invoices. Choose another coin."
|
|
55462
|
+
};
|
|
55463
|
+
function explainRouterError(err) {
|
|
55464
|
+
const hint = BITREFILL_ROUTER_HINTS[err?.code];
|
|
55465
|
+
if (!hint) return err;
|
|
55466
|
+
return new SkillError(err.code, `${hint}${err.message ? ` (router: ${err.message})` : ""}`, err.details);
|
|
55467
|
+
}
|
|
55468
|
+
function checkBitrefillExpiry(expiresAt, now = Date.now(), minMs = BITREFILL_MIN_PAY_WINDOW_MS) {
|
|
55469
|
+
const ms = parseDeadline(expiresAt);
|
|
55470
|
+
const base = { marginMs: minMs, deadlines: { invoiceMs: ms } };
|
|
55471
|
+
if (ms === null) {
|
|
55472
|
+
return { ...base, ok: false, code: "EXPIRY_UNPARSABLE", reason: "The Bitrefill invoice expiry is missing or unreadable.", effectiveDeadlineMs: null, msRemaining: null, msOfSlack: null };
|
|
55473
|
+
}
|
|
55474
|
+
const msRemaining = ms - now;
|
|
55475
|
+
const out2 = { ...base, effectiveDeadlineMs: ms, msRemaining, msOfSlack: msRemaining - minMs };
|
|
55476
|
+
if (msRemaining <= 0) return { ...out2, ok: false, code: "EXPIRED", reason: "The Bitrefill invoice has expired." };
|
|
55477
|
+
if (msRemaining < minMs) {
|
|
55478
|
+
return {
|
|
55479
|
+
...out2,
|
|
55480
|
+
ok: false,
|
|
55481
|
+
code: "INVOICE_EXPIRING",
|
|
55482
|
+
reason: `Only ${Math.floor(msRemaining / 1e3)}s left on the Bitrefill invoice; at least ${Math.round(minMs / 6e4)} min is required. Create a fresh invoice.`
|
|
55483
|
+
};
|
|
55484
|
+
}
|
|
55485
|
+
return { ...out2, ok: true, code: null, reason: null };
|
|
55486
|
+
}
|
|
55487
|
+
var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
55488
|
+
var AMOUNT_RE = /^(?:0|[1-9]\d*)(?:\.\d{1,6})?$/;
|
|
55489
|
+
var INVOICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{2,127}$/;
|
|
55490
|
+
function validateBitrefillInput({ invoiceId, address, amount, expiresAt }, now = Date.now(), minMs = BITREFILL_MIN_EXPIRY_MS) {
|
|
55491
|
+
const id = String(invoiceId ?? "").trim();
|
|
55492
|
+
if (!INVOICE_ID_RE.test(id)) {
|
|
55493
|
+
throw new SkillError(
|
|
55494
|
+
"BAD_BITREFILL_INVOICE",
|
|
55495
|
+
'A Bitrefill invoice id is required (letters, digits, "-" or "_").'
|
|
55496
|
+
);
|
|
55497
|
+
}
|
|
55498
|
+
const addr = String(address ?? "").trim();
|
|
55499
|
+
if (!EVM_ADDRESS_RE.test(addr)) {
|
|
55500
|
+
throw new SkillError(
|
|
55501
|
+
"INVALID_ADDRESS",
|
|
55502
|
+
"The Bitrefill receiving address must be a Base (EVM) address: 0x followed by 40 hex characters."
|
|
55503
|
+
);
|
|
55504
|
+
}
|
|
55505
|
+
const amt = String(amount ?? "").trim();
|
|
55506
|
+
if (!AMOUNT_RE.test(amt) || normalizeDecimal(amt) === "0") {
|
|
55507
|
+
throw new SkillError(
|
|
55508
|
+
"INVALID_AMOUNT",
|
|
55509
|
+
"The amount must be a positive USDC amount with at most 6 decimals, exactly as Bitrefill shows it."
|
|
55510
|
+
);
|
|
55511
|
+
}
|
|
55512
|
+
if (expiresAt === void 0 || expiresAt === null || expiresAt === "") {
|
|
55513
|
+
throw new SkillError("INVOICE_EXPIRY_REQUIRED", "The Bitrefill invoice expiry is required (--expires-at <ISO>), exactly as Bitrefill shows it.");
|
|
55514
|
+
}
|
|
55515
|
+
const ms = parseDeadline(expiresAt);
|
|
55516
|
+
if (ms === null) {
|
|
55517
|
+
throw new SkillError("INVOICE_EXPIRING", "The invoice expiry could not be read.");
|
|
55518
|
+
}
|
|
55519
|
+
if (ms - now < minMs) {
|
|
55520
|
+
throw new SkillError(
|
|
55521
|
+
"INVOICE_EXPIRING",
|
|
55522
|
+
`The Bitrefill invoice expires in less than ${Math.round(minMs / 6e4)} minutes. Create a fresh invoice on Bitrefill.`,
|
|
55523
|
+
{ expiresAt: new Date(ms).toISOString() }
|
|
55524
|
+
);
|
|
55525
|
+
}
|
|
55526
|
+
if (ms - now > BITREFILL_MAX_EXPIRY_AHEAD_MS) {
|
|
55527
|
+
throw new SkillError(
|
|
55528
|
+
"INVOICE_EXPIRING",
|
|
55529
|
+
"The invoice expiry is more than 30 minutes away, which a Bitrefill invoice never is. Check --expires-at.",
|
|
55530
|
+
{ expiresAt: new Date(ms).toISOString() }
|
|
55531
|
+
);
|
|
55532
|
+
}
|
|
55533
|
+
const expiresIso = new Date(ms).toISOString();
|
|
55534
|
+
return { invoiceId: id, address: addr, amount: normalizeDecimal(amt), expiresAt: expiresIso };
|
|
55535
|
+
}
|
|
55536
|
+
var sameAddress = (a, b) => typeof a === "string" && typeof b === "string" && a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
55537
|
+
function verifyBitrefillCreate({ requested, created }) {
|
|
55538
|
+
const drift = [];
|
|
55539
|
+
const dest = created?.destination || {};
|
|
55540
|
+
if (created?.provider !== PROVIDER_BITREFILL) {
|
|
55541
|
+
drift.push({ field: "provider", requested: PROVIDER_BITREFILL, created: created?.provider ?? null });
|
|
55542
|
+
}
|
|
55543
|
+
if (String(created?.invoiceId ?? "") !== requested.invoiceId) {
|
|
55544
|
+
drift.push({ field: "invoiceId", requested: requested.invoiceId, created: created?.invoiceId ?? null });
|
|
55545
|
+
}
|
|
55546
|
+
if (!sameAddress(dest.address, requested.address)) {
|
|
55547
|
+
drift.push({ field: "destination.address", requested: requested.address, created: dest.address ?? null });
|
|
55548
|
+
}
|
|
55549
|
+
if (normalizeDecimal(dest.amount) !== normalizeDecimal(requested.amount)) {
|
|
55550
|
+
drift.push({ field: "destination.amount", requested: requested.amount, created: dest.amount ?? null });
|
|
55551
|
+
}
|
|
55552
|
+
if (String(dest.chainId ?? "") !== BITREFILL_DESTINATION.chainId) {
|
|
55553
|
+
drift.push({ field: "destination.chainId", requested: "8453", created: dest.chainId ?? null });
|
|
55554
|
+
}
|
|
55555
|
+
if (String(dest.tokenSymbol ?? "").toUpperCase() !== BITREFILL_DESTINATION.tokenSymbol) {
|
|
55556
|
+
drift.push({ field: "destination.tokenSymbol", requested: "USDC", created: dest.tokenSymbol ?? null });
|
|
55557
|
+
}
|
|
55558
|
+
return drift.length ? {
|
|
55559
|
+
ok: false,
|
|
55560
|
+
code: "BITREFILL_ECHO_MISMATCH",
|
|
55561
|
+
reason: "The router created an order that does not match the Bitrefill invoice. Refusing to continue.",
|
|
55562
|
+
drift
|
|
55563
|
+
} : { ok: true, code: null, reason: null, drift };
|
|
55564
|
+
}
|
|
55565
|
+
function verifyBitrefillDestination({ requested, payment }) {
|
|
55566
|
+
const dest = payment?.destination || {};
|
|
55567
|
+
const drift = [];
|
|
55568
|
+
if (String(dest.chainId ?? "") !== BITREFILL_DESTINATION.chainId) {
|
|
55569
|
+
drift.push({ field: "destination.chainId", requested: "8453", live: dest.chainId ?? null });
|
|
55570
|
+
}
|
|
55571
|
+
if (String(dest.tokenSymbol ?? "").toUpperCase() !== BITREFILL_DESTINATION.tokenSymbol) {
|
|
55572
|
+
drift.push({ field: "destination.tokenSymbol", requested: "USDC", live: dest.tokenSymbol ?? null });
|
|
55573
|
+
}
|
|
55574
|
+
if (normalizeDecimal(dest.amount) !== normalizeDecimal(requested.amount)) {
|
|
55575
|
+
drift.push({ field: "destination.amount", requested: requested.amount, live: dest.amount ?? null });
|
|
55576
|
+
}
|
|
55577
|
+
const liveAddress = dest.receiverAddress ?? dest.address ?? null;
|
|
55578
|
+
if (!sameAddress(liveAddress, requested.address)) {
|
|
55579
|
+
drift.push({ field: "destination.address", requested: requested.address, live: liveAddress });
|
|
55580
|
+
}
|
|
55581
|
+
return drift.length ? {
|
|
55582
|
+
ok: false,
|
|
55583
|
+
code: "BITREFILL_ECHO_MISMATCH",
|
|
55584
|
+
reason: "The live order does not deliver to the Bitrefill invoice. Refusing to continue.",
|
|
55585
|
+
drift,
|
|
55586
|
+
addressVerified: false
|
|
55587
|
+
} : { ok: true, code: null, reason: null, drift, addressVerified: true };
|
|
55588
|
+
}
|
|
55589
|
+
function duplicateInvoicePaymentId(err) {
|
|
55590
|
+
if (err?.code !== "DUPLICATE_INVOICE") return null;
|
|
55591
|
+
const body = err?.details?.body;
|
|
55592
|
+
const id = body?.rozoPaymentId ?? body?.existing?.rozoPaymentId ?? null;
|
|
55593
|
+
return typeof id === "string" && id ? id : null;
|
|
55594
|
+
}
|
|
55595
|
+
function providerFromPayment(payment) {
|
|
55596
|
+
if (!payment) return null;
|
|
55597
|
+
if (String(payment.orderId ?? payment.order_id ?? "").startsWith("bitrefill_")) return PROVIDER_BITREFILL;
|
|
55598
|
+
const meta = payment.metadata?.provider;
|
|
55599
|
+
if (meta) return String(meta).toLowerCase();
|
|
55600
|
+
return null;
|
|
55601
|
+
}
|
|
55602
|
+
function earliestExpiry(...values) {
|
|
55603
|
+
const ms = values.map((v) => parseDeadline(v)).filter((v) => v !== null);
|
|
55604
|
+
return ms.length ? new Date(Math.min(...ms)).toISOString() : null;
|
|
55605
|
+
}
|
|
55606
|
+
function intentBitrefillExpiry(payment) {
|
|
55607
|
+
return payment?.metadata?.bitrefillExpiresAt ?? null;
|
|
55608
|
+
}
|
|
55609
|
+
|
|
55610
|
+
// scripts/src/create-bitrefill-order.mjs
|
|
55611
|
+
var bitrefillRecordKey = (invoiceId) => `bitrefill:${invoiceId}`;
|
|
55612
|
+
function confirmTier2(usdAmount) {
|
|
55613
|
+
const usd = Number(usdAmount);
|
|
55614
|
+
if (!Number.isFinite(usd)) return "explicit";
|
|
55615
|
+
if (usd <= 1) return "silent";
|
|
55616
|
+
if (usd <= 10) return "one-line";
|
|
55617
|
+
return "explicit";
|
|
55618
|
+
}
|
|
55619
|
+
function abort(step2, code, message, extra = {}, guidance = "Do NOT fund this order.") {
|
|
55620
|
+
emit(
|
|
55621
|
+
{ success: false, step: step2, provider: PROVIDER_BITREFILL, error: { code, message, ...extra.details ? { details: extra.details } : {} }, ...extra.fields, guidance },
|
|
55622
|
+
EXIT_ERROR
|
|
55623
|
+
);
|
|
55624
|
+
}
|
|
55625
|
+
async function main3(argv) {
|
|
55626
|
+
const args = parseArgs(argv);
|
|
55627
|
+
const chainId = String(args.chain ?? "").trim();
|
|
55628
|
+
const tokenSymbol = String(args.token ?? "").trim().toUpperCase();
|
|
55629
|
+
if (!chainId || !tokenSymbol) usage("Required: --chain <chainId> --token <SYMBOL>.");
|
|
55630
|
+
if (!isSupportedSource(chainId, tokenSymbol)) {
|
|
55631
|
+
throw new SkillError("UNSUPPORTED_SOURCE", `${tokenSymbol} on ${chainName(chainId)} is not a supported source.`, {
|
|
55632
|
+
supported: SUPPORTED_SOURCES
|
|
55633
|
+
});
|
|
55634
|
+
}
|
|
55635
|
+
const requested = { chainId, tokenSymbol };
|
|
55636
|
+
const confirmed = Boolean(args.confirm);
|
|
55637
|
+
const email = contactEmailFromArgs(args);
|
|
55638
|
+
const rawInvoice = {
|
|
55639
|
+
invoiceId: args["invoice-id"],
|
|
55640
|
+
address: args.to,
|
|
55641
|
+
amount: args.amount,
|
|
55642
|
+
expiresAt: args["expires-at"]
|
|
55643
|
+
};
|
|
55644
|
+
const invoice = validateBitrefillInput(rawInvoice, Date.now(), BITREFILL_MIN_PAY_WINDOW_MS);
|
|
55645
|
+
const priorRecord = findByLinkId(bitrefillRecordKey(invoice.invoiceId));
|
|
55646
|
+
if (!priorRecord) validateBitrefillInput(rawInvoice);
|
|
55647
|
+
let blacklist;
|
|
55648
|
+
try {
|
|
55649
|
+
blacklist = loadBlacklist();
|
|
55650
|
+
} catch (err) {
|
|
55651
|
+
throw new SkillError("BLACKLIST_UNAVAILABLE", `Compromised-address list unusable: ${err.message} Refusing to proceed.`);
|
|
55652
|
+
}
|
|
55653
|
+
assertNotBlacklisted([{ address: invoice.address, family: "evm", role: "Bitrefill receiving address" }], blacklist);
|
|
55654
|
+
let created = null;
|
|
55655
|
+
let rozoPaymentId;
|
|
55656
|
+
let resumed = false;
|
|
55657
|
+
try {
|
|
55658
|
+
created = await createBitrefillInvoice({
|
|
55659
|
+
invoice,
|
|
55660
|
+
source: requested,
|
|
55661
|
+
utmSource: typeof args["utm-source"] === "string" ? args["utm-source"] : void 0,
|
|
55662
|
+
email
|
|
55663
|
+
});
|
|
55664
|
+
} catch (err) {
|
|
55665
|
+
const existing = duplicateInvoicePaymentId(err);
|
|
55666
|
+
if (!existing) throw explainRouterError(err);
|
|
55667
|
+
rozoPaymentId = assertRozoPaymentId(existing);
|
|
55668
|
+
resumed = true;
|
|
55669
|
+
}
|
|
55670
|
+
if (created) {
|
|
55671
|
+
if (created.ok === false || !created.rozoPaymentId) {
|
|
55672
|
+
throw new SkillError(created?.error || "CREATE_FAILED", created?.message || "create-invoice returned no rozoPaymentId.");
|
|
55673
|
+
}
|
|
55674
|
+
rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
|
|
55675
|
+
const echo = verifyBitrefillCreate({ requested: invoice, created });
|
|
55676
|
+
if (!echo.ok) {
|
|
55677
|
+
abort("verify-create", echo.code, echo.reason, {
|
|
55678
|
+
details: { drift: echo.drift },
|
|
55679
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55680
|
+
}, "The order exists but was NOT validated. Do not fund it. Let it expire unfunded.");
|
|
55681
|
+
}
|
|
55682
|
+
}
|
|
55683
|
+
const local = readState(rozoPaymentId);
|
|
55684
|
+
if (local?.bitrefill) {
|
|
55685
|
+
const prior = verifyBitrefillCreate({
|
|
55686
|
+
requested: invoice,
|
|
55687
|
+
created: { provider: PROVIDER_BITREFILL, invoiceId: local.bitrefill.invoiceId, destination: { chainId: "8453", tokenSymbol: "USDC", address: local.bitrefill.address, amount: local.bitrefill.amount } }
|
|
55688
|
+
});
|
|
55689
|
+
if (!prior.ok) {
|
|
55690
|
+
abort("verify-local", prior.code, "This invoice id was recorded earlier with a different address or amount.", {
|
|
55691
|
+
details: { drift: prior.drift },
|
|
55692
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55693
|
+
});
|
|
55694
|
+
}
|
|
55695
|
+
}
|
|
55696
|
+
const payment = await getPayment(rozoPaymentId);
|
|
55697
|
+
const source = payment?.source || {};
|
|
55698
|
+
const dest = verifyBitrefillDestination({ requested: invoice, payment });
|
|
55699
|
+
if (!dest.ok) {
|
|
55700
|
+
abort("verify-destination", dest.code, dest.reason, {
|
|
55701
|
+
details: { drift: dest.drift },
|
|
55702
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55703
|
+
});
|
|
55704
|
+
}
|
|
55705
|
+
const guard = reuseGuard({ payment, requested, reused: resumed });
|
|
55706
|
+
if (!guard.ok) {
|
|
55707
|
+
emit(
|
|
55708
|
+
{
|
|
55709
|
+
success: false,
|
|
55710
|
+
step: "reuse-guard",
|
|
55711
|
+
provider: PROVIDER_BITREFILL,
|
|
55712
|
+
error: { code: guard.code, message: guard.reason, details: guard.evidence },
|
|
55713
|
+
invoiceId: invoice.invoiceId,
|
|
55714
|
+
rozoPaymentId,
|
|
55715
|
+
moneyDetected: guard.moneyDetected,
|
|
55716
|
+
guidance: guard.moneyDetected ? "MONEY DETECTED. Do not pay again. Preserve every id and tx hash above and escalate for manual reconciliation." : "Abort this run. Nothing was funded."
|
|
55717
|
+
},
|
|
55718
|
+
EXIT_ERROR
|
|
55719
|
+
);
|
|
55720
|
+
}
|
|
55721
|
+
const invoiceExpiry = earliestExpiry(
|
|
55722
|
+
invoice.expiresAt,
|
|
55723
|
+
local?.bitrefill?.expiresAt,
|
|
55724
|
+
priorRecord?.bitrefill?.expiresAt,
|
|
55725
|
+
intentBitrefillExpiry(payment)
|
|
55726
|
+
);
|
|
55727
|
+
const lightning = String(source.chainId) === "lightning";
|
|
55728
|
+
const expiry = checkBitrefillExpiry(invoiceExpiry, Date.now());
|
|
55729
|
+
if (!expiry.ok) {
|
|
55730
|
+
abort("expiry-guard", expiry.code, expiry.reason, {
|
|
55731
|
+
details: expiry,
|
|
55732
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55733
|
+
}, "Not enough time remains to fund and settle safely. Create a fresh Bitrefill invoice. Do not fund this order.");
|
|
55734
|
+
}
|
|
55735
|
+
try {
|
|
55736
|
+
assertNotBlacklisted([{ address: source.receiverAddress, family: chainFamily(source.chainId), role: "deposit address" }], blacklist);
|
|
55737
|
+
} catch (err) {
|
|
55738
|
+
abort("blacklist", err.code, err.message, { fields: { rozoPaymentId } }, "Do NOT send anything. Report this to the operator immediately.");
|
|
55739
|
+
}
|
|
55740
|
+
const bitrefill = { ...invoice, expiresAt: invoiceExpiry };
|
|
55741
|
+
createOrderRecord({
|
|
55742
|
+
rozoPaymentId,
|
|
55743
|
+
provider: PROVIDER_BITREFILL,
|
|
55744
|
+
bitrefill,
|
|
55745
|
+
linkId: bitrefillRecordKey(invoice.invoiceId),
|
|
55746
|
+
merchant: "Bitrefill",
|
|
55747
|
+
invoiceAmount: invoice.amount,
|
|
55748
|
+
source: { chainId: source.chainId, tokenSymbol: source.tokenSymbol },
|
|
55749
|
+
receiverAddress: source.receiverAddress,
|
|
55750
|
+
receiverMemo: source.receiverMemo ?? null,
|
|
55751
|
+
amount: source.amount,
|
|
55752
|
+
amountUnit: source.amountUnit ?? null,
|
|
55753
|
+
expiresAt: payment?.expiresAt ?? null
|
|
55754
|
+
});
|
|
55755
|
+
const tier = confirmTier2(invoice.amount);
|
|
55756
|
+
if (confirmed) recordConfirmation(rozoPaymentId, { source, invoiceAmount: invoice.amount, tier });
|
|
55757
|
+
const family = chainFamily(source.chainId);
|
|
55758
|
+
const depositInfo = guard.deposit;
|
|
55759
|
+
const bolt11 = source.lnInvoice ?? payment?.lnInvoice ?? null;
|
|
55760
|
+
const memoRequirement = lightning ? "Lightning invoices carry their own routing data; there is no separate memo." : source.receiverMemo ? "This deposit REQUIRES the memo/tag below. Sending without it will very likely lose the funds." : "This deposit does not use a memo. Leave the memo field empty.";
|
|
55761
|
+
emit({
|
|
55762
|
+
success: true,
|
|
55763
|
+
step: "create-order",
|
|
55764
|
+
provider: PROVIDER_BITREFILL,
|
|
55765
|
+
confirmed,
|
|
55766
|
+
// False on a resumed order: the router does not attach an email to it.
|
|
55767
|
+
contactEmailProvided: contactEmailAttached(email, resumed),
|
|
55768
|
+
support: SUPPORT,
|
|
55769
|
+
reused: resumed,
|
|
55770
|
+
reusedNote: resumed ? `An order for this Bitrefill invoice already existed (${rozoPaymentId}); it was resumed. Nothing new was created.` : null,
|
|
55771
|
+
orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
|
|
55772
|
+
invoiceId: invoice.invoiceId,
|
|
55773
|
+
rozoPaymentId,
|
|
55774
|
+
merchant: "Bitrefill",
|
|
55775
|
+
invoice: { amount: invoice.amount, currency: "USDC" },
|
|
55776
|
+
bitrefill: {
|
|
55777
|
+
invoiceId: invoice.invoiceId,
|
|
55778
|
+
addressMasked: maskAddress2(invoice.address),
|
|
55779
|
+
amount: invoice.amount,
|
|
55780
|
+
chain: "Base",
|
|
55781
|
+
token: "USDC",
|
|
55782
|
+
expiresAt: bitrefill.expiresAt,
|
|
55783
|
+
destinationAddressVerifiedOnIntent: dest.addressVerified
|
|
55784
|
+
},
|
|
55785
|
+
deposit: confirmed ? {
|
|
55786
|
+
chainId: source.chainId,
|
|
55787
|
+
chain: chainName(source.chainId),
|
|
55788
|
+
tokenSymbol: source.tokenSymbol,
|
|
55789
|
+
tokenAddress: source.tokenAddress || null,
|
|
55790
|
+
receiverAddress: lightning ? null : source.receiverAddress,
|
|
55791
|
+
receiverMemo: source.receiverMemo ?? null,
|
|
55792
|
+
receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
55793
|
+
amount: source.amount,
|
|
55794
|
+
amountUnit: source.amountUnit ?? null,
|
|
55795
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
55796
|
+
lnInvoice: bolt11 || null,
|
|
55797
|
+
payTo: depositInfo.payTo,
|
|
55798
|
+
expiresAt: payment?.expiresAt ?? null,
|
|
55799
|
+
expiresIn: formatRemaining(expiry.msRemaining)
|
|
55800
|
+
} : null,
|
|
55801
|
+
depositWithheld: !confirmed,
|
|
55802
|
+
display: {
|
|
55803
|
+
chain: chainName(source.chainId),
|
|
55804
|
+
token: source.tokenSymbol,
|
|
55805
|
+
amount: formatAmount(source),
|
|
55806
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
55807
|
+
payToMasked: maskAddress2(depositInfo.payTo),
|
|
55808
|
+
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
55809
|
+
hasMemo: Boolean(source.receiverMemo),
|
|
55810
|
+
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
55811
|
+
memoRequirement
|
|
55812
|
+
},
|
|
55813
|
+
expiry: {
|
|
55814
|
+
intentExpiresAt: payment?.expiresAt ?? null,
|
|
55815
|
+
invoiceExpiresAt: bitrefill.expiresAt,
|
|
55816
|
+
effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
|
|
55817
|
+
expiresIn: formatRemaining(expiry.msRemaining),
|
|
55818
|
+
msRemaining: expiry.msRemaining,
|
|
55819
|
+
marginMinutes: Math.round(expiry.marginMs / 6e4),
|
|
55820
|
+
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
|
|
55821
|
+
},
|
|
55822
|
+
confirmation: {
|
|
55823
|
+
required: tier,
|
|
55824
|
+
satisfied: confirmed,
|
|
55825
|
+
note: confirmed ? "Confirmation recorded. The send scripts will verify it against the live deposit data." : "BINDING CONFIRMATION POINT. Present the Bitrefill invoice id, the USDC amount Bitrefill receives, the coin and exact amount to send, the masked deposit address and the expiry, and get an explicit yes. Then re-run with --confirm to release the full deposit details.",
|
|
55826
|
+
warnings: [
|
|
55827
|
+
"Wrong token, wrong network, or wrong amount is usually unrecoverable.",
|
|
55828
|
+
memoRequirement,
|
|
55829
|
+
"Send exactly once. A second send to the same one-time address is not guaranteed to be credited.",
|
|
55830
|
+
"The deposit amount can exceed the invoice: it includes the bridge and network fees."
|
|
55831
|
+
]
|
|
55832
|
+
},
|
|
55833
|
+
blacklist: { checked: true, addressesInList: blacklist.entries.length, digest: blacklist.provenance.addressesSha256 },
|
|
55834
|
+
nextStep: confirmed ? {
|
|
55835
|
+
modeA: "Give the user the `deposit` block, then poll: rozo-checkout status <rozoPaymentId>",
|
|
55836
|
+
modeB: family === "evm" || family === "solana" ? "rozo-checkout pay \u2026 --send" : "not available for this chain \u2014 pay from a wallet (Mode A)"
|
|
55837
|
+
} : { confirm: "Re-run this exact command with --confirm once the user has said yes." }
|
|
55838
|
+
});
|
|
55839
|
+
}
|
|
55840
|
+
async function run3(argv = process.argv.slice(2)) {
|
|
55841
|
+
return main3(argv);
|
|
55842
|
+
}
|
|
55843
|
+
|
|
55230
55844
|
// scripts/src/status.mjs
|
|
55231
55845
|
var POLL_INTERVAL_MS = 1e4;
|
|
55232
55846
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
55233
|
-
|
|
55847
|
+
function resolveProvider(explicit, rozoPaymentId) {
|
|
55848
|
+
if (explicit) return String(explicit).toLowerCase();
|
|
55849
|
+
if (rozoPaymentId && isRozoPaymentId(rozoPaymentId)) {
|
|
55850
|
+
try {
|
|
55851
|
+
return readState(rozoPaymentId)?.provider ?? null;
|
|
55852
|
+
} catch {
|
|
55853
|
+
return null;
|
|
55854
|
+
}
|
|
55855
|
+
}
|
|
55856
|
+
return null;
|
|
55857
|
+
}
|
|
55858
|
+
async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
55859
|
+
let provider = resolveProvider(explicitProvider, rozoPaymentId);
|
|
55860
|
+
let prefetched = null;
|
|
55861
|
+
if (!provider && rozoPaymentId && isRozoPaymentId(rozoPaymentId)) {
|
|
55862
|
+
try {
|
|
55863
|
+
prefetched = await getPayment(rozoPaymentId);
|
|
55864
|
+
provider = providerFromPayment(prefetched);
|
|
55865
|
+
} catch {
|
|
55866
|
+
prefetched = null;
|
|
55867
|
+
}
|
|
55868
|
+
}
|
|
55869
|
+
provider = provider === "bitrefill" ? "bitrefill" : "coinbase";
|
|
55234
55870
|
let status = null;
|
|
55235
55871
|
let statusError = null;
|
|
55236
|
-
|
|
55237
|
-
|
|
55238
|
-
|
|
55239
|
-
|
|
55872
|
+
if (provider !== "bitrefill") {
|
|
55873
|
+
try {
|
|
55874
|
+
status = await invoiceStatus({ linkId, rozoPaymentId });
|
|
55875
|
+
} catch (err) {
|
|
55876
|
+
statusError = { code: err.code, message: err.message };
|
|
55877
|
+
}
|
|
55240
55878
|
}
|
|
55241
55879
|
let id = rozoPaymentId || status?.rozo_payment_id || null;
|
|
55242
55880
|
let idSource = rozoPaymentId ? "argument" : status?.rozo_payment_id ? "invoice-status" : null;
|
|
@@ -55249,33 +55887,61 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55249
55887
|
}
|
|
55250
55888
|
let payment = null;
|
|
55251
55889
|
let paymentError = null;
|
|
55252
|
-
if (
|
|
55890
|
+
if (prefetched && id === rozoPaymentId) {
|
|
55891
|
+
payment = prefetched;
|
|
55892
|
+
} else if (id && isRozoPaymentId(id)) {
|
|
55253
55893
|
try {
|
|
55254
55894
|
payment = await getPayment(id);
|
|
55255
55895
|
} catch (err) {
|
|
55256
55896
|
paymentError = { code: err.code, message: err.message };
|
|
55257
55897
|
}
|
|
55258
55898
|
}
|
|
55259
|
-
const viewsFailed = Boolean(statusError) && !payment;
|
|
55899
|
+
const viewsFailed = provider === "bitrefill" ? !payment : Boolean(statusError) && !payment;
|
|
55260
55900
|
const verdict = classifyStatus({
|
|
55261
55901
|
payment: payment || status?.rozoPayment || {},
|
|
55262
55902
|
routerState: status?.routerState,
|
|
55263
55903
|
coinbase: status?.coinbase,
|
|
55264
|
-
viewsFailed
|
|
55904
|
+
viewsFailed,
|
|
55905
|
+
provider
|
|
55265
55906
|
});
|
|
55266
55907
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
55908
|
+
let bitrefillExpiry = null;
|
|
55909
|
+
let state = verdict.state;
|
|
55910
|
+
let terminal = verdict.terminal;
|
|
55911
|
+
let detail = verdict.detail;
|
|
55912
|
+
if (provider === "bitrefill") {
|
|
55913
|
+
let local = null;
|
|
55914
|
+
try {
|
|
55915
|
+
local = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
55916
|
+
} catch {
|
|
55917
|
+
local = null;
|
|
55918
|
+
}
|
|
55919
|
+
bitrefillExpiry = earliestExpiry(local?.bitrefill?.expiresAt, intentBitrefillExpiry(payment));
|
|
55920
|
+
if (!verdict.moneyDetected && ["awaiting_deposit", "expired_unfunded"].includes(verdict.state)) {
|
|
55921
|
+
const ms = bitrefillExpiry ? Date.parse(bitrefillExpiry) : NaN;
|
|
55922
|
+
if (Number.isFinite(ms) && ms <= Date.now()) {
|
|
55923
|
+
state = "invoice_expired";
|
|
55924
|
+
terminal = true;
|
|
55925
|
+
detail = "The Bitrefill invoice has expired. Do NOT fund this order; create a fresh invoice.";
|
|
55926
|
+
} else if (!bitrefillExpiry && verdict.state === "awaiting_deposit") {
|
|
55927
|
+
state = "deadline_unknown";
|
|
55928
|
+
detail = "The Bitrefill invoice deadline is unknown on this machine, so this order is NOT shown as payable. Re-run pay with the invoice details, or create a fresh invoice.";
|
|
55929
|
+
}
|
|
55930
|
+
}
|
|
55931
|
+
}
|
|
55267
55932
|
return {
|
|
55933
|
+
provider,
|
|
55268
55934
|
rozoPaymentId: id,
|
|
55269
55935
|
rozoPaymentIdSource: idSource,
|
|
55270
55936
|
// Without the authoritative payment object we are reading a partial view.
|
|
55271
55937
|
authoritativeView: Boolean(payment),
|
|
55272
55938
|
linkId: linkId || status?.pl_id || null,
|
|
55273
|
-
state
|
|
55939
|
+
state,
|
|
55274
55940
|
unknown: verdict.unknown,
|
|
55275
55941
|
moneyDetected: verdict.moneyDetected,
|
|
55276
|
-
terminal
|
|
55942
|
+
terminal,
|
|
55277
55943
|
escalate: verdict.escalate,
|
|
55278
|
-
detail
|
|
55944
|
+
detail,
|
|
55279
55945
|
backend: {
|
|
55280
55946
|
paymentStatus: payment?.status ?? status?.rozoPayment?.status ?? null,
|
|
55281
55947
|
routerStatus: verdict.routerStatus,
|
|
@@ -55292,7 +55958,10 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55292
55958
|
chain: source.chainId ? chainName(source.chainId) : null
|
|
55293
55959
|
},
|
|
55294
55960
|
expiry: (() => {
|
|
55295
|
-
|
|
55961
|
+
if (provider === "bitrefill" && !bitrefillExpiry) {
|
|
55962
|
+
return { expiresAt: null, expiresIn: null, msRemaining: null, deadlineUnknown: true };
|
|
55963
|
+
}
|
|
55964
|
+
const iso = provider === "bitrefill" ? bitrefillExpiry : payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
|
|
55296
55965
|
if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
|
|
55297
55966
|
const ms = Date.parse(iso) - Date.now();
|
|
55298
55967
|
return {
|
|
@@ -55308,25 +55977,32 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55308
55977
|
errors: [statusError, paymentError].filter(Boolean)
|
|
55309
55978
|
};
|
|
55310
55979
|
}
|
|
55311
|
-
async function
|
|
55980
|
+
async function main4(argv) {
|
|
55312
55981
|
const args = parseArgs(argv);
|
|
55313
55982
|
const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
|
|
55314
55983
|
const linkId = args["link-id"] || (!rozoPaymentId ? args._[0] : null);
|
|
55984
|
+
const provider = args.provider ? String(args.provider) : null;
|
|
55985
|
+
if (provider && provider !== "bitrefill" && provider !== "coinbase") {
|
|
55986
|
+
usage("--provider must be coinbase or bitrefill");
|
|
55987
|
+
}
|
|
55988
|
+
if (provider === "bitrefill" && !rozoPaymentId) {
|
|
55989
|
+
usage("A Bitrefill order is tracked by --rozo-payment-id <uuid>.");
|
|
55990
|
+
}
|
|
55315
55991
|
if (!rozoPaymentId && !linkId) {
|
|
55316
55992
|
usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
|
|
55317
55993
|
}
|
|
55318
55994
|
const watch = Boolean(args.watch);
|
|
55319
55995
|
const timeoutMs = Math.max(0, Number(args.timeout ?? 600) * 1e3);
|
|
55320
55996
|
const deadline = Date.now() + timeoutMs;
|
|
55321
|
-
let result = await snapshot({ rozoPaymentId, linkId });
|
|
55997
|
+
let result = await snapshot({ rozoPaymentId, linkId, provider });
|
|
55322
55998
|
const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
|
|
55323
55999
|
while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
|
|
55324
56000
|
await sleep(POLL_INTERVAL_MS);
|
|
55325
|
-
const next = await snapshot({ rozoPaymentId: result.rozoPaymentId || rozoPaymentId, linkId });
|
|
56001
|
+
const next = await snapshot({ rozoPaymentId: result.rozoPaymentId || rozoPaymentId, linkId, provider });
|
|
55326
56002
|
if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
|
|
55327
56003
|
result = next;
|
|
55328
56004
|
}
|
|
55329
|
-
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.";
|
|
56005
|
+
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" || result.state === "invoice_expired") && result.provider === "bitrefill" ? "Nothing was funded, so nothing was lost. Create a fresh Bitrefill invoice and run rozo-checkout pay --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --with <coin>" : 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.";
|
|
55330
56006
|
const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
|
|
55331
56007
|
const failed = result.escalate || result.unknown || !result.authoritativeView;
|
|
55332
56008
|
emit(
|
|
@@ -55336,13 +56012,15 @@ async function main3(argv) {
|
|
|
55336
56012
|
...result,
|
|
55337
56013
|
history,
|
|
55338
56014
|
guidance,
|
|
55339
|
-
timedOut: unresolved
|
|
56015
|
+
timedOut: unresolved,
|
|
56016
|
+
// Anything short of a clean answer gets a human contact.
|
|
56017
|
+
...failed || unresolved ? { support: SUPPORT } : {}
|
|
55340
56018
|
},
|
|
55341
56019
|
failed ? EXIT_ERROR : unresolved ? EXIT_UNCONFIRMED : 0
|
|
55342
56020
|
);
|
|
55343
56021
|
}
|
|
55344
|
-
async function
|
|
55345
|
-
return
|
|
56022
|
+
async function run4(argv = process.argv.slice(2)) {
|
|
56023
|
+
return main4(argv);
|
|
55346
56024
|
}
|
|
55347
56025
|
|
|
55348
56026
|
// scripts/src/lib/passphrase.mjs
|
|
@@ -55392,6 +56070,16 @@ function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
|
55392
56070
|
}
|
|
55393
56071
|
|
|
55394
56072
|
// scripts/src/lib/presend.mjs
|
|
56073
|
+
function assertBitrefillLive(state, payment) {
|
|
56074
|
+
if (!state.bitrefill) {
|
|
56075
|
+
throw new SkillError("BITREFILL_ECHO_MISMATCH", "This Bitrefill order has no recorded invoice. Refusing to send.");
|
|
56076
|
+
}
|
|
56077
|
+
const dest = verifyBitrefillDestination({ requested: state.bitrefill, payment });
|
|
56078
|
+
if (!dest.ok) throw new SkillError(dest.code, dest.reason, { drift: dest.drift });
|
|
56079
|
+
const expiry = checkBitrefillExpiry(state.bitrefill.expiresAt, Date.now());
|
|
56080
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
56081
|
+
return expiry;
|
|
56082
|
+
}
|
|
55395
56083
|
async function preflight({
|
|
55396
56084
|
rozoPaymentId,
|
|
55397
56085
|
expectFamily,
|
|
@@ -55477,16 +56165,22 @@ async function preflight({
|
|
|
55477
56165
|
if ((source.receiverMemo ?? null) !== (state.receiverMemo ?? null)) {
|
|
55478
56166
|
throw new SkillError("DEPOSIT_CHANGED", "The live deposit memo differs from the recorded one.");
|
|
55479
56167
|
}
|
|
55480
|
-
|
|
55481
|
-
|
|
55482
|
-
|
|
55483
|
-
|
|
55484
|
-
|
|
55485
|
-
|
|
55486
|
-
|
|
55487
|
-
|
|
55488
|
-
|
|
55489
|
-
|
|
56168
|
+
let statusNow = null;
|
|
56169
|
+
let expiry;
|
|
56170
|
+
if (state.provider === "bitrefill") {
|
|
56171
|
+
expiry = assertBitrefillLive(state, payment);
|
|
56172
|
+
} else {
|
|
56173
|
+
statusNow = await invoiceStatus({ linkId: state.linkId });
|
|
56174
|
+
expiry = checkExpiry({
|
|
56175
|
+
now: Date.now(),
|
|
56176
|
+
chainId: source.chainId,
|
|
56177
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56178
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
56179
|
+
});
|
|
56180
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
56181
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
56182
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
56183
|
+
}
|
|
55490
56184
|
assertNotBlacklisted(
|
|
55491
56185
|
[
|
|
55492
56186
|
{ address: source.receiverAddress, family, role: "deposit address" },
|
|
@@ -55505,7 +56199,17 @@ async function preflight({
|
|
|
55505
56199
|
statusNow
|
|
55506
56200
|
};
|
|
55507
56201
|
}
|
|
55508
|
-
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt }) {
|
|
56202
|
+
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt, state = null, rozoPaymentId = null }) {
|
|
56203
|
+
if (state?.provider === "bitrefill") {
|
|
56204
|
+
const id = rozoPaymentId ?? state.rozoPaymentId;
|
|
56205
|
+
const payment = await getPayment(id);
|
|
56206
|
+
const guard = reuseGuard({ payment, requested: state.source });
|
|
56207
|
+
if (!guard.ok) {
|
|
56208
|
+
throw new SkillError(guard.code, guard.reason, { ...guard.evidence, moneyDetected: guard.moneyDetected });
|
|
56209
|
+
}
|
|
56210
|
+
const expiry2 = assertBitrefillLive(state, payment);
|
|
56211
|
+
return { statusNow: null, payable: { ok: true }, expiry: expiry2 };
|
|
56212
|
+
}
|
|
55509
56213
|
const statusNow = await invoiceStatus({ linkId });
|
|
55510
56214
|
const payable = checkPayable(statusNow, Date.now());
|
|
55511
56215
|
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
@@ -55751,7 +56455,7 @@ function chainDef(chainId, rpcUrl) {
|
|
|
55751
56455
|
rpcUrls: { default: { http: [rpcUrl] } }
|
|
55752
56456
|
});
|
|
55753
56457
|
}
|
|
55754
|
-
async function
|
|
56458
|
+
async function main5(argv) {
|
|
55755
56459
|
const args = parseArgs(argv);
|
|
55756
56460
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
55757
56461
|
assertNoTrackedDotEnv();
|
|
@@ -55869,7 +56573,9 @@ async function main4(argv) {
|
|
|
55869
56573
|
await finalPayabilityCheck({
|
|
55870
56574
|
linkId: state.linkId,
|
|
55871
56575
|
chainId: source.chainId,
|
|
55872
|
-
intentExpiresAt: payment?.expiresAt
|
|
56576
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56577
|
+
state,
|
|
56578
|
+
rozoPaymentId
|
|
55873
56579
|
});
|
|
55874
56580
|
claimSend(
|
|
55875
56581
|
rozoPaymentId,
|
|
@@ -55977,8 +56683,8 @@ async function main4(argv) {
|
|
|
55977
56683
|
outcome.exitCode
|
|
55978
56684
|
);
|
|
55979
56685
|
}
|
|
55980
|
-
async function
|
|
55981
|
-
return
|
|
56686
|
+
async function run5(argv = process.argv.slice(2)) {
|
|
56687
|
+
return main5(argv);
|
|
55982
56688
|
}
|
|
55983
56689
|
|
|
55984
56690
|
// scripts/src/send-sol.mjs
|
|
@@ -56048,7 +56754,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
|
|
|
56048
56754
|
var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
56049
56755
|
var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
|
|
56050
56756
|
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
56051
|
-
async function
|
|
56757
|
+
async function main6(argv) {
|
|
56052
56758
|
const args = parseArgs(argv);
|
|
56053
56759
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
56054
56760
|
assertNoTrackedDotEnv();
|
|
@@ -56197,7 +56903,9 @@ async function main5(argv) {
|
|
|
56197
56903
|
await finalPayabilityCheck({
|
|
56198
56904
|
linkId: state.linkId,
|
|
56199
56905
|
chainId: source.chainId,
|
|
56200
|
-
intentExpiresAt: payment?.expiresAt
|
|
56906
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56907
|
+
state,
|
|
56908
|
+
rozoPaymentId
|
|
56201
56909
|
});
|
|
56202
56910
|
claimSend(
|
|
56203
56911
|
rozoPaymentId,
|
|
@@ -56339,8 +57047,8 @@ function base58Encode(buf) {
|
|
|
56339
57047
|
}
|
|
56340
57048
|
return out2;
|
|
56341
57049
|
}
|
|
56342
|
-
async function
|
|
56343
|
-
return
|
|
57050
|
+
async function run6(argv = process.argv.slice(2)) {
|
|
57051
|
+
return main6(argv);
|
|
56344
57052
|
}
|
|
56345
57053
|
|
|
56346
57054
|
// scripts/src/cli.mjs
|
|
@@ -56376,6 +57084,9 @@ function printError(payload) {
|
|
|
56376
57084
|
if (e.message) out(` ${e.message}`);
|
|
56377
57085
|
if (payload?.guidance) out(` ${yellow(payload.guidance)}`);
|
|
56378
57086
|
}
|
|
57087
|
+
function printSupport() {
|
|
57088
|
+
out(` ${dim(SUPPORT_TEXT)}`);
|
|
57089
|
+
}
|
|
56379
57090
|
function printMoneyWarning(payload) {
|
|
56380
57091
|
if (!payload?.moneyDetected) return;
|
|
56381
57092
|
out();
|
|
@@ -56514,15 +57225,17 @@ async function cmdQuote(opts) {
|
|
|
56514
57225
|
}
|
|
56515
57226
|
async function cmdStatus(opts) {
|
|
56516
57227
|
const argv = [...targetToArgs(opts.target), "--timeout", String(opts.timeout ?? 600)];
|
|
57228
|
+
if (opts.provider) argv.push("--provider", opts.provider);
|
|
56517
57229
|
if (opts.watch) argv.push("--watch");
|
|
56518
57230
|
if (!opts.json) out(dim(" Checking payment status\u2026 (no money moves)"));
|
|
56519
|
-
const { payload, exitCode } = await step(
|
|
57231
|
+
const { payload, exitCode } = await step(run4, argv);
|
|
56520
57232
|
if (opts.json) {
|
|
56521
57233
|
printJson(payload);
|
|
56522
57234
|
return exitCode;
|
|
56523
57235
|
}
|
|
56524
57236
|
if (payload.error) {
|
|
56525
57237
|
printError(payload);
|
|
57238
|
+
printSupport();
|
|
56526
57239
|
return exitCode;
|
|
56527
57240
|
}
|
|
56528
57241
|
out();
|
|
@@ -56534,6 +57247,7 @@ async function cmdStatus(opts) {
|
|
|
56534
57247
|
if (payload.payin?.txHash) out(` Pay-in ${payload.payin.txHash}`);
|
|
56535
57248
|
if (payload.guidance) out(` ${payload.escalate ? red(payload.guidance) : dim(payload.guidance)}`);
|
|
56536
57249
|
printMoneyWarning(payload);
|
|
57250
|
+
if (payload.support) printSupport();
|
|
56537
57251
|
out();
|
|
56538
57252
|
return exitCode;
|
|
56539
57253
|
}
|
|
@@ -56546,7 +57260,7 @@ function presetFor(chainId, tokenSymbol) {
|
|
|
56546
57260
|
function stateLabel(state) {
|
|
56547
57261
|
if (state === "settled") return green(bold(state));
|
|
56548
57262
|
if (["underpaid", "stuck_after_payment", "unknown"].includes(state)) return red(bold(state));
|
|
56549
|
-
if (
|
|
57263
|
+
if (["expired_unfunded", "invoice_expired", "deadline_unknown"].includes(state)) return yellow(bold(state));
|
|
56550
57264
|
return bold(state);
|
|
56551
57265
|
}
|
|
56552
57266
|
async function cmdPay(opts) {
|
|
@@ -56555,7 +57269,9 @@ async function cmdPay(opts) {
|
|
|
56555
57269
|
let chosenPreset = null;
|
|
56556
57270
|
let payer = null;
|
|
56557
57271
|
let invoiceUsd = null;
|
|
56558
|
-
if (
|
|
57272
|
+
if (opts.bitrefill) {
|
|
57273
|
+
invoiceUsd = Number(opts.bitrefill.amount);
|
|
57274
|
+
} else if (!opts.source || opts.payer) {
|
|
56559
57275
|
if (!opts.json) out(dim(" Reading the payment link\u2026"));
|
|
56560
57276
|
const q = await step(run, ["--url", opts.target]);
|
|
56561
57277
|
if (!q.payload.success) {
|
|
@@ -56605,7 +57321,25 @@ async function cmdPay(opts) {
|
|
|
56605
57321
|
payer = { address: opts.payer, family };
|
|
56606
57322
|
}
|
|
56607
57323
|
const { chainId, tokenSymbol } = opts.source;
|
|
56608
|
-
const
|
|
57324
|
+
const createFlow = opts.bitrefill ? run3 : run2;
|
|
57325
|
+
const utmArgs = [
|
|
57326
|
+
...opts.utmSource ? ["--utm-source", opts.utmSource] : [],
|
|
57327
|
+
...opts.email ? ["--email", opts.email] : []
|
|
57328
|
+
];
|
|
57329
|
+
const baseArgs = opts.bitrefill ? [
|
|
57330
|
+
"--invoice-id",
|
|
57331
|
+
opts.bitrefill.invoiceId,
|
|
57332
|
+
"--to",
|
|
57333
|
+
opts.bitrefill.address,
|
|
57334
|
+
"--amount",
|
|
57335
|
+
opts.bitrefill.amount,
|
|
57336
|
+
...opts.bitrefill.expiresAt ? ["--expires-at", opts.bitrefill.expiresAt] : [],
|
|
57337
|
+
"--chain",
|
|
57338
|
+
chainId,
|
|
57339
|
+
"--token",
|
|
57340
|
+
tokenSymbol,
|
|
57341
|
+
...utmArgs
|
|
57342
|
+
] : ["--url", opts.target, "--chain", chainId, "--token", tokenSymbol, ...utmArgs];
|
|
56609
57343
|
if (opts.send) {
|
|
56610
57344
|
const family = chainFamily(chainId);
|
|
56611
57345
|
if (family === "evm" || family === "solana") {
|
|
@@ -56618,12 +57352,13 @@ async function cmdPay(opts) {
|
|
|
56618
57352
|
}
|
|
56619
57353
|
}
|
|
56620
57354
|
if (!opts.json) out(dim(" Creating a one-time order\u2026 (no money moves)"));
|
|
56621
|
-
const created = await step(
|
|
57355
|
+
const created = await step(createFlow, baseArgs);
|
|
56622
57356
|
if (!created.payload.success) {
|
|
56623
57357
|
if (opts.json) printJson(created.payload);
|
|
56624
57358
|
else {
|
|
56625
57359
|
printError(created.payload);
|
|
56626
57360
|
printMoneyWarning(created.payload);
|
|
57361
|
+
printSupport();
|
|
56627
57362
|
}
|
|
56628
57363
|
return created.exitCode;
|
|
56629
57364
|
}
|
|
@@ -56638,6 +57373,9 @@ async function cmdPay(opts) {
|
|
|
56638
57373
|
out();
|
|
56639
57374
|
out(` ${bold(p.merchant ?? "Unknown merchant")}`);
|
|
56640
57375
|
out(` Invoice ${bold(`${p.invoice?.amount} ${p.invoice?.currency ?? "USD"}`)}`);
|
|
57376
|
+
if (p.provider === "bitrefill") {
|
|
57377
|
+
out(` Bitrefill ${p.bitrefill?.invoiceId} ${dim(`\u2192 ${p.bitrefill?.amount} USDC on Base to ${p.bitrefill?.addressMasked}`)}`);
|
|
57378
|
+
}
|
|
56641
57379
|
out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}`);
|
|
56642
57380
|
out(` To ${p.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
|
|
56643
57381
|
if (p.display?.hasMemo) {
|
|
@@ -56650,6 +57388,12 @@ async function cmdPay(opts) {
|
|
|
56650
57388
|
p.reused ? ` ${dim(`Reusing existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}` : ` ${dim(`Order created: ${p.rozoPaymentId}`)}`
|
|
56651
57389
|
);
|
|
56652
57390
|
out(` ${dim("An order you never fund simply expires and costs nothing.")}`);
|
|
57391
|
+
if (opts.email) {
|
|
57392
|
+
out(
|
|
57393
|
+
p.reused ? ` ${dim(`Contact ${maskEmail(opts.email)} was not attached: this order already existed.`)}` : ` ${dim(`Contact ${maskEmail(opts.email)}, so ROZO can reach you if this payment needs attention.`)}`
|
|
57394
|
+
);
|
|
57395
|
+
}
|
|
57396
|
+
printSupport();
|
|
56653
57397
|
out();
|
|
56654
57398
|
out(dim(` The amount you send includes bridge and network fees, so it is`));
|
|
56655
57399
|
out(dim(` normally larger than the invoice.`));
|
|
@@ -56674,22 +57418,24 @@ async function cmdPay(opts) {
|
|
|
56674
57418
|
}
|
|
56675
57419
|
}
|
|
56676
57420
|
if (!opts.json) out(dim(" Confirming and releasing the deposit details\u2026 (no money moves yet)"));
|
|
56677
|
-
const confirmed = await step(
|
|
57421
|
+
const confirmed = await step(createFlow, [...baseArgs, "--confirm"]);
|
|
56678
57422
|
if (!confirmed.payload.success) {
|
|
56679
57423
|
if (opts.json) printJson(confirmed.payload);
|
|
56680
57424
|
else {
|
|
56681
57425
|
printError(confirmed.payload);
|
|
56682
57426
|
printMoneyWarning(confirmed.payload);
|
|
57427
|
+
printSupport();
|
|
56683
57428
|
}
|
|
56684
57429
|
return confirmed.exitCode;
|
|
56685
57430
|
}
|
|
56686
57431
|
const deposit = confirmed.payload.deposit;
|
|
57432
|
+
confirmed.payload.contactEmailProvided = Boolean(p.contactEmailProvided);
|
|
56687
57433
|
if (!opts.json) {
|
|
56688
57434
|
out(` ${dim(`Confirming order ${rozoPaymentId} \u2014 no second order was created.`)}`);
|
|
56689
57435
|
}
|
|
56690
57436
|
if (opts.send) {
|
|
56691
57437
|
const family = chainFamily(deposit.chainId);
|
|
56692
|
-
const sender = family === "evm" ?
|
|
57438
|
+
const sender = family === "evm" ? run5 : family === "solana" ? run6 : null;
|
|
56693
57439
|
if (!sender) {
|
|
56694
57440
|
const payload = formatFailure({
|
|
56695
57441
|
code: "SEND_UNSUPPORTED_CHAIN",
|
|
@@ -56719,6 +57465,7 @@ async function cmdPay(opts) {
|
|
|
56719
57465
|
} else if (!sent.payload.success) {
|
|
56720
57466
|
printError(sent.payload);
|
|
56721
57467
|
printMoneyWarning(sent.payload);
|
|
57468
|
+
printSupport();
|
|
56722
57469
|
return sent.exitCode;
|
|
56723
57470
|
} else if (opts.dryRun) {
|
|
56724
57471
|
if (sent.payload.keySource) out(` ${dim(`would sign with ${sent.payload.keySource}`)}`);
|
|
@@ -56744,7 +57491,7 @@ async function cmdPay(opts) {
|
|
|
56744
57491
|
return EXIT_OK;
|
|
56745
57492
|
}
|
|
56746
57493
|
if (!opts.json) out(dim(" Waiting for settlement\u2026 (Ctrl-C to stop; the payment continues)"));
|
|
56747
|
-
const watched = await step(
|
|
57494
|
+
const watched = await step(run4, [
|
|
56748
57495
|
"--rozo-payment-id",
|
|
56749
57496
|
rozoPaymentId,
|
|
56750
57497
|
"--watch",
|
|
@@ -56768,6 +57515,7 @@ async function cmdPay(opts) {
|
|
|
56768
57515
|
out(` ${watched.payload.escalate ? red(watched.payload.guidance) : dim(watched.payload.guidance)}`);
|
|
56769
57516
|
}
|
|
56770
57517
|
printMoneyWarning(watched.payload);
|
|
57518
|
+
if (watched.payload.state !== "settled") printSupport();
|
|
56771
57519
|
out();
|
|
56772
57520
|
return watched.exitCode;
|
|
56773
57521
|
}
|
|
@@ -56799,7 +57547,7 @@ function printDeposit(deposit, opts) {
|
|
|
56799
57547
|
out(dim(" Send it exactly once."));
|
|
56800
57548
|
out();
|
|
56801
57549
|
}
|
|
56802
|
-
async function
|
|
57550
|
+
async function main7() {
|
|
56803
57551
|
let opts;
|
|
56804
57552
|
try {
|
|
56805
57553
|
opts = parseCliArgs(process.argv.slice(2));
|
|
@@ -56848,9 +57596,12 @@ async function main6() {
|
|
|
56848
57596
|
return EXIT_USAGE;
|
|
56849
57597
|
}
|
|
56850
57598
|
}
|
|
56851
|
-
|
|
57599
|
+
main7().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
|
|
56852
57600
|
const payload = formatFailure(err);
|
|
56853
57601
|
if (process.argv.includes("--json")) printJson(payload);
|
|
56854
|
-
else
|
|
57602
|
+
else {
|
|
57603
|
+
printError(payload);
|
|
57604
|
+
printSupport();
|
|
57605
|
+
}
|
|
56855
57606
|
process.exit(EXIT_ERROR);
|
|
56856
57607
|
});
|