@rozoai/checkout 0.1.16 → 0.1.18
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 +51 -2
- package/llms.txt +5 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +655 -123
- package/scripts/dist/create-order.js +423 -217
- package/scripts/dist/quote.js +99 -33
- package/scripts/dist/receipt.js +149 -83
- package/scripts/dist/resume.js +1552 -0
- package/scripts/dist/send-evm.js +298 -232
- package/scripts/dist/send-sol.js +298 -232
- package/scripts/dist/status.js +168 -85
package/scripts/dist/cli.js
CHANGED
|
@@ -20544,7 +20544,7 @@ var require_dist = __commonJS({
|
|
|
20544
20544
|
}
|
|
20545
20545
|
}
|
|
20546
20546
|
}
|
|
20547
|
-
function*
|
|
20547
|
+
function* run9(value, struct2, options = {}) {
|
|
20548
20548
|
const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
|
|
20549
20549
|
const ctx = { path: path7, branch, mask: mask2 };
|
|
20550
20550
|
if (coerce2) {
|
|
@@ -20557,7 +20557,7 @@ var require_dist = __commonJS({
|
|
|
20557
20557
|
yield [failure, void 0];
|
|
20558
20558
|
}
|
|
20559
20559
|
for (let [k, v, s] of struct2.entries(value, ctx)) {
|
|
20560
|
-
const ts =
|
|
20560
|
+
const ts = run9(v, s, {
|
|
20561
20561
|
path: k === void 0 ? path7 : [...path7, k],
|
|
20562
20562
|
branch: k === void 0 ? branch : [...branch, v],
|
|
20563
20563
|
coerce: coerce2,
|
|
@@ -20685,7 +20685,7 @@ var require_dist = __commonJS({
|
|
|
20685
20685
|
return !result[0];
|
|
20686
20686
|
}
|
|
20687
20687
|
function validate7(value, struct2, options = {}) {
|
|
20688
|
-
const tuples =
|
|
20688
|
+
const tuples = run9(value, struct2, options);
|
|
20689
20689
|
const tuple2 = shiftIterator(tuples);
|
|
20690
20690
|
if (tuple2[0]) {
|
|
20691
20691
|
const error = new StructError(tuple2[0], function* () {
|
|
@@ -21098,7 +21098,7 @@ var require_dist = __commonJS({
|
|
|
21098
21098
|
validator(value, ctx) {
|
|
21099
21099
|
const failures = [];
|
|
21100
21100
|
for (const S of Structs) {
|
|
21101
|
-
const [...tuples] =
|
|
21101
|
+
const [...tuples] = run9(value, S, ctx);
|
|
21102
21102
|
const [first] = tuples;
|
|
21103
21103
|
if (!first[0]) {
|
|
21104
21104
|
return [];
|
|
@@ -41455,6 +41455,8 @@ var SUPPORT = Object.freeze({
|
|
|
41455
41455
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
41456
41456
|
});
|
|
41457
41457
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
41458
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
41459
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
41458
41460
|
var CONTACT_EMAIL_MAX_LENGTH = 254;
|
|
41459
41461
|
var EMAIL_RE = /^[a-z0-9][^\s@]*@[^\s@.]+(\.[^\s@.]+)+$/;
|
|
41460
41462
|
function normalizeContactEmail(raw) {
|
|
@@ -41526,6 +41528,22 @@ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
|
|
|
41526
41528
|
function publicHash(value) {
|
|
41527
41529
|
return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
|
|
41528
41530
|
}
|
|
41531
|
+
var PUBLIC_PAY_PAGE_HOSTS = /* @__PURE__ */ new Set(["invoice.rozo.ai", "checkout.rozo.ai"]);
|
|
41532
|
+
var PAY_PAGE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41533
|
+
function publicPayPage(value) {
|
|
41534
|
+
if (typeof value !== "string") return value;
|
|
41535
|
+
let u;
|
|
41536
|
+
try {
|
|
41537
|
+
u = new URL(value);
|
|
41538
|
+
} catch {
|
|
41539
|
+
return value;
|
|
41540
|
+
}
|
|
41541
|
+
if (u.protocol !== "https:" || u.username || u.password || u.port || !PUBLIC_PAY_PAGE_HOSTS.has(u.hostname)) return value;
|
|
41542
|
+
if (u.pathname !== "/checkout") return value;
|
|
41543
|
+
const id = u.searchParams.get("id");
|
|
41544
|
+
if (!id || !PAY_PAGE_ID.test(id)) return value;
|
|
41545
|
+
return new PublicHash(`https://${u.hostname}/checkout?id=${id.toLowerCase()}`);
|
|
41546
|
+
}
|
|
41529
41547
|
function redactDeep(value) {
|
|
41530
41548
|
if (value instanceof PublicHash) return value.value;
|
|
41531
41549
|
if (typeof value === "string") return redact(value);
|
|
@@ -41769,8 +41787,61 @@ function normalizeUtmSource(value) {
|
|
|
41769
41787
|
return s;
|
|
41770
41788
|
}
|
|
41771
41789
|
|
|
41790
|
+
// scripts/src/lib/ids.mjs
|
|
41791
|
+
var IdError = class extends Error {
|
|
41792
|
+
constructor(code, message) {
|
|
41793
|
+
super(message);
|
|
41794
|
+
this.code = code;
|
|
41795
|
+
}
|
|
41796
|
+
};
|
|
41797
|
+
var PL_RE = /(pl_[0-9a-zA-Z]+)/;
|
|
41798
|
+
var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
|
|
41799
|
+
var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
41800
|
+
function extractLinkId(urlOrId) {
|
|
41801
|
+
if (typeof urlOrId !== "string" || !urlOrId.trim()) {
|
|
41802
|
+
throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
|
|
41803
|
+
}
|
|
41804
|
+
const s = urlOrId.trim();
|
|
41805
|
+
const session = s.match(SESSION_RE);
|
|
41806
|
+
if (session) return { linkId: session[1], kind: "payment_session" };
|
|
41807
|
+
const link = s.match(PL_RE);
|
|
41808
|
+
if (link) return { linkId: link[1], kind: "payment_link" };
|
|
41809
|
+
if (/commerce\.coinbase\.com\/pay\//.test(s)) {
|
|
41810
|
+
throw new IdError(
|
|
41811
|
+
"LEGACY_COMMERCE_URL",
|
|
41812
|
+
"commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
|
|
41813
|
+
);
|
|
41814
|
+
}
|
|
41815
|
+
throw new IdError(
|
|
41816
|
+
"BAD_LINK",
|
|
41817
|
+
"Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
|
|
41818
|
+
);
|
|
41819
|
+
}
|
|
41820
|
+
function assertRozoPaymentId(value) {
|
|
41821
|
+
const s = String(value || "").trim();
|
|
41822
|
+
if (!UUID_RE.test(s)) {
|
|
41823
|
+
throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
|
|
41824
|
+
}
|
|
41825
|
+
return s;
|
|
41826
|
+
}
|
|
41827
|
+
function isRozoPaymentId(value) {
|
|
41828
|
+
return UUID_RE.test(String(value || "").trim());
|
|
41829
|
+
}
|
|
41830
|
+
function maskAddress2(address) {
|
|
41831
|
+
const s = String(address ?? "").trim();
|
|
41832
|
+
if (!s) return "(none)";
|
|
41833
|
+
if (s.length <= 12) return s;
|
|
41834
|
+
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
41835
|
+
}
|
|
41836
|
+
function maskMemo(memo) {
|
|
41837
|
+
const s = String(memo ?? "").trim();
|
|
41838
|
+
if (!s) return null;
|
|
41839
|
+
if (s.length <= 8) return s;
|
|
41840
|
+
return `${s.slice(0, 4)}...${s.slice(-3)}`;
|
|
41841
|
+
}
|
|
41842
|
+
|
|
41772
41843
|
// scripts/src/lib/cli-args.mjs
|
|
41773
|
-
var COMMANDS = ["pay", "quote", "status", "receipt", "help", "version"];
|
|
41844
|
+
var COMMANDS = ["pay", "quote", "status", "receipt", "resume", "help", "version"];
|
|
41774
41845
|
var CHAIN_ALIASES = {
|
|
41775
41846
|
ethereum: "1",
|
|
41776
41847
|
eth: "1",
|
|
@@ -41998,6 +42069,18 @@ function parseCliArgs(argv) {
|
|
|
41998
42069
|
}
|
|
41999
42070
|
return { command, target, json, provider: flags.provider };
|
|
42000
42071
|
}
|
|
42072
|
+
if (command === "resume") {
|
|
42073
|
+
if (!target) {
|
|
42074
|
+
throw new CliError("MISSING_TARGET", "Usage: rozo-checkout resume <rozoPaymentId>");
|
|
42075
|
+
}
|
|
42076
|
+
if (!isRozoPaymentId(target)) {
|
|
42077
|
+
throw new CliError(
|
|
42078
|
+
"BAD_VALUE",
|
|
42079
|
+
"resume takes the order id (rozoPaymentId, a UUID) that pay printed, not a payment link."
|
|
42080
|
+
);
|
|
42081
|
+
}
|
|
42082
|
+
return { command, target, json, yes: flags.yes === true };
|
|
42083
|
+
}
|
|
42001
42084
|
if (command === "status") {
|
|
42002
42085
|
if (!target) {
|
|
42003
42086
|
throw new CliError(
|
|
@@ -42014,7 +42097,9 @@ function parseCliArgs(argv) {
|
|
|
42014
42097
|
json,
|
|
42015
42098
|
provider: flags.provider,
|
|
42016
42099
|
watch: flags.watch === true,
|
|
42017
|
-
|
|
42100
|
+
// Left undefined when absent so status can size the window itself:
|
|
42101
|
+
// 600s, or a Lightning invoice's remaining validity (max 60 min).
|
|
42102
|
+
timeout: flags.timeout === void 0 ? void 0 : Number(flags.timeout)
|
|
42018
42103
|
};
|
|
42019
42104
|
}
|
|
42020
42105
|
const bitrefillFlags = ["bitrefill-invoice", "to", "amount", "expires-at"].filter(
|
|
@@ -42097,6 +42182,7 @@ function parseCliArgs(argv) {
|
|
|
42097
42182
|
dryRun: flags["dry-run"] === true,
|
|
42098
42183
|
watch: flags["no-watch"] !== true,
|
|
42099
42184
|
timeout,
|
|
42185
|
+
timeoutExplicit: flags.timeout !== void 0,
|
|
42100
42186
|
rpc: flags.rpc,
|
|
42101
42187
|
payer: flags.payer,
|
|
42102
42188
|
fresh: flags.fresh === true,
|
|
@@ -42116,6 +42202,7 @@ USAGE
|
|
|
42116
42202
|
npx @rozoai/checkout quote <coinbase-link>
|
|
42117
42203
|
npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
|
|
42118
42204
|
npx @rozoai/checkout receipt <rozoPaymentId | coinbase-link>
|
|
42205
|
+
npx @rozoai/checkout resume <rozoPaymentId>
|
|
42119
42206
|
|
|
42120
42207
|
COINS (--with)
|
|
42121
42208
|
usdt-solana usdc-solana usdt-bnb usdc-bnb
|
|
@@ -42143,7 +42230,8 @@ OPTIONS
|
|
|
42143
42230
|
--dry-run with --send, show what would be signed and sign nothing
|
|
42144
42231
|
--json, -j machine-readable output
|
|
42145
42232
|
--no-watch stop after showing the deposit instructions
|
|
42146
|
-
--timeout <s> how long to poll for settlement (default 900
|
|
42233
|
+
--timeout <s> how long to poll for settlement (default 900; for Lightning,
|
|
42234
|
+
the invoice's remaining validity, at most 60 minutes)
|
|
42147
42235
|
--bitrefill-invoice <id> pay a Bitrefill invoice created with "USDC on Base"
|
|
42148
42236
|
instead of a Coinbase link. Needs --to <0x\u2026 address Bitrefill
|
|
42149
42237
|
shows>, --amount <exact USDC amount> and --expires-at <ISO>
|
|
@@ -42180,6 +42268,20 @@ RECEIPT
|
|
|
42180
42268
|
nextAction. The older "success" field means the query worked, not that the
|
|
42181
42269
|
invoice is paid.
|
|
42182
42270
|
|
|
42271
|
+
RESUME
|
|
42272
|
+
resume picks up an order you created earlier and did not pay yet. It reads
|
|
42273
|
+
the order (nothing is created or sent), and if it is still unpaid and has
|
|
42274
|
+
enough time left it prints the hosted payment page and, after you confirm
|
|
42275
|
+
(or with --yes), the exact deposit instructions again. A paid order is
|
|
42276
|
+
reported as paid (exit 0); an expired one says so and how to start over.
|
|
42277
|
+
|
|
42278
|
+
PRIVACY
|
|
42279
|
+
Each order carries an anonymous install id: a random UUID created on first
|
|
42280
|
+
run and stored in ~/.rozo-checkout/prefs.json. If OPENROUTER_API_KEY is set,
|
|
42281
|
+
a one-way salted sha256 hash of it is sent too; the key itself is never
|
|
42282
|
+
sent, logged or stored. Delete prefs.json
|
|
42283
|
+
to reset the id; set ROZO_CHECKOUT_ANON_ID=off to send neither.
|
|
42284
|
+
|
|
42183
42285
|
Creating an order moves no money; an unfunded order simply expires. Nothing is
|
|
42184
42286
|
paid until you confirm, and --send is required before anything is signed.
|
|
42185
42287
|
|
|
@@ -42601,7 +42703,42 @@ function prefsPath() {
|
|
|
42601
42703
|
const root = process.env.ROZO_CHECKOUT_STATE_DIR ? path2.dirname(stateRoot()) : path2.join(os2.homedir(), ".rozo-checkout");
|
|
42602
42704
|
return path2.join(root, "prefs.json");
|
|
42603
42705
|
}
|
|
42604
|
-
var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "updatedAt"];
|
|
42706
|
+
var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "installId", "updatedAt"];
|
|
42707
|
+
var INSTALL_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
42708
|
+
var LOCK_WAIT_MS2 = 2e3;
|
|
42709
|
+
var LOCK_STALE_MS2 = 1e4;
|
|
42710
|
+
function sleepSync2(ms) {
|
|
42711
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
42712
|
+
}
|
|
42713
|
+
function withPrefsLock(fn) {
|
|
42714
|
+
const lock = `${prefsPath()}.lock`;
|
|
42715
|
+
fs2.mkdirSync(path2.dirname(lock), { recursive: true, mode: 448 });
|
|
42716
|
+
const deadline = Date.now() + LOCK_WAIT_MS2;
|
|
42717
|
+
for (; ; ) {
|
|
42718
|
+
try {
|
|
42719
|
+
fs2.closeSync(fs2.openSync(lock, "wx", 384));
|
|
42720
|
+
break;
|
|
42721
|
+
} catch (err) {
|
|
42722
|
+
if (err.code !== "EEXIST") throw err;
|
|
42723
|
+
}
|
|
42724
|
+
try {
|
|
42725
|
+
if (Date.now() - fs2.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
|
|
42726
|
+
fs2.rmSync(lock, { force: true });
|
|
42727
|
+
continue;
|
|
42728
|
+
}
|
|
42729
|
+
} catch {
|
|
42730
|
+
continue;
|
|
42731
|
+
}
|
|
42732
|
+
if (Date.now() > deadline) return void 0;
|
|
42733
|
+
sleepSync2(10);
|
|
42734
|
+
}
|
|
42735
|
+
try {
|
|
42736
|
+
return fn();
|
|
42737
|
+
} finally {
|
|
42738
|
+
fs2.rmSync(lock, { force: true });
|
|
42739
|
+
}
|
|
42740
|
+
}
|
|
42741
|
+
var USER_FIELDS = ["lastPayerAddress", "lastAddressFamily", "lastPreset"];
|
|
42605
42742
|
function readPrefs() {
|
|
42606
42743
|
let raw;
|
|
42607
42744
|
try {
|
|
@@ -42623,21 +42760,26 @@ function readPrefs() {
|
|
|
42623
42760
|
}
|
|
42624
42761
|
return Object.keys(out2).length ? out2 : null;
|
|
42625
42762
|
}
|
|
42626
|
-
function savePrefs(update) {
|
|
42627
|
-
const existing = readPrefs() || {};
|
|
42628
|
-
const next = { ...existing };
|
|
42629
|
-
for (const k of ALLOWED) {
|
|
42630
|
-
if (k === "updatedAt") continue;
|
|
42631
|
-
const v = update?.[k];
|
|
42632
|
-
if (typeof v === "string" && v.trim()) next[k] = v.trim();
|
|
42633
|
-
}
|
|
42634
|
-
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42763
|
+
function savePrefs(update, { installIdIfAbsent } = {}) {
|
|
42635
42764
|
try {
|
|
42636
|
-
|
|
42765
|
+
const saved = withPrefsLock(() => {
|
|
42766
|
+
const existing = readPrefs() || {};
|
|
42767
|
+
const next = { ...existing };
|
|
42768
|
+
for (const k of USER_FIELDS) {
|
|
42769
|
+
const v = update?.[k];
|
|
42770
|
+
if (typeof v === "string" && v.trim()) next[k] = v.trim();
|
|
42771
|
+
}
|
|
42772
|
+
if (!INSTALL_ID_RE.test(String(existing.installId ?? "")) && INSTALL_ID_RE.test(String(installIdIfAbsent ?? ""))) {
|
|
42773
|
+
next.installId = installIdIfAbsent;
|
|
42774
|
+
}
|
|
42775
|
+
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42776
|
+
writeAtomic(prefsPath(), next);
|
|
42777
|
+
return next;
|
|
42778
|
+
});
|
|
42779
|
+
return saved ?? null;
|
|
42637
42780
|
} catch {
|
|
42638
42781
|
return null;
|
|
42639
42782
|
}
|
|
42640
|
-
return next;
|
|
42641
42783
|
}
|
|
42642
42784
|
|
|
42643
42785
|
// scripts/src/lib/blacklist.mjs
|
|
@@ -42763,59 +42905,6 @@ function assertNotBlacklisted(targets, blacklist) {
|
|
|
42763
42905
|
return true;
|
|
42764
42906
|
}
|
|
42765
42907
|
|
|
42766
|
-
// scripts/src/lib/ids.mjs
|
|
42767
|
-
var IdError = class extends Error {
|
|
42768
|
-
constructor(code, message) {
|
|
42769
|
-
super(message);
|
|
42770
|
-
this.code = code;
|
|
42771
|
-
}
|
|
42772
|
-
};
|
|
42773
|
-
var PL_RE = /(pl_[0-9a-zA-Z]+)/;
|
|
42774
|
-
var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
|
|
42775
|
-
var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
42776
|
-
function extractLinkId(urlOrId) {
|
|
42777
|
-
if (typeof urlOrId !== "string" || !urlOrId.trim()) {
|
|
42778
|
-
throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
|
|
42779
|
-
}
|
|
42780
|
-
const s = urlOrId.trim();
|
|
42781
|
-
const session = s.match(SESSION_RE);
|
|
42782
|
-
if (session) return { linkId: session[1], kind: "payment_session" };
|
|
42783
|
-
const link = s.match(PL_RE);
|
|
42784
|
-
if (link) return { linkId: link[1], kind: "payment_link" };
|
|
42785
|
-
if (/commerce\.coinbase\.com\/pay\//.test(s)) {
|
|
42786
|
-
throw new IdError(
|
|
42787
|
-
"LEGACY_COMMERCE_URL",
|
|
42788
|
-
"commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
|
|
42789
|
-
);
|
|
42790
|
-
}
|
|
42791
|
-
throw new IdError(
|
|
42792
|
-
"BAD_LINK",
|
|
42793
|
-
"Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
|
|
42794
|
-
);
|
|
42795
|
-
}
|
|
42796
|
-
function assertRozoPaymentId(value) {
|
|
42797
|
-
const s = String(value || "").trim();
|
|
42798
|
-
if (!UUID_RE.test(s)) {
|
|
42799
|
-
throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
|
|
42800
|
-
}
|
|
42801
|
-
return s;
|
|
42802
|
-
}
|
|
42803
|
-
function isRozoPaymentId(value) {
|
|
42804
|
-
return UUID_RE.test(String(value || "").trim());
|
|
42805
|
-
}
|
|
42806
|
-
function maskAddress2(address) {
|
|
42807
|
-
const s = String(address ?? "").trim();
|
|
42808
|
-
if (!s) return "(none)";
|
|
42809
|
-
if (s.length <= 12) return s;
|
|
42810
|
-
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
42811
|
-
}
|
|
42812
|
-
function maskMemo(memo) {
|
|
42813
|
-
const s = String(memo ?? "").trim();
|
|
42814
|
-
if (!s) return null;
|
|
42815
|
-
if (s.length <= 8) return s;
|
|
42816
|
-
return `${s.slice(0, 4)}...${s.slice(-3)}`;
|
|
42817
|
-
}
|
|
42818
|
-
|
|
42819
42908
|
// scripts/src/lib/expiry.mjs
|
|
42820
42909
|
var MINUTE = 6e4;
|
|
42821
42910
|
var MARGINS_MS = {
|
|
@@ -42829,6 +42918,25 @@ var MARGINS_MS = {
|
|
|
42829
42918
|
};
|
|
42830
42919
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
42831
42920
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
42921
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
42922
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
42923
|
+
function lightningWatchMs(expiresAt, { now = Date.now(), fallbackMs = DEFAULT_WATCH_MS } = {}) {
|
|
42924
|
+
const at = parseDeadline(expiresAt);
|
|
42925
|
+
if (at === null) return fallbackMs;
|
|
42926
|
+
const remaining = at - now;
|
|
42927
|
+
if (!(remaining > 0)) return fallbackMs;
|
|
42928
|
+
return Math.min(remaining, LIGHTNING_WATCH_CAP_MS);
|
|
42929
|
+
}
|
|
42930
|
+
function settlementWatchSeconds(opts, deposit, now = Date.now()) {
|
|
42931
|
+
const fallbackMs = Number(opts.timeout ?? 900) * 1e3;
|
|
42932
|
+
if (opts.timeoutExplicit || !deposit?.lnInvoice) return Math.round(fallbackMs / 1e3);
|
|
42933
|
+
return Math.ceil(lightningWatchMs(deposit.expiresAt, { now, fallbackMs }) / 1e3);
|
|
42934
|
+
}
|
|
42935
|
+
function minutesRemaining(expiresAt, now = Date.now()) {
|
|
42936
|
+
const at = parseDeadline(expiresAt);
|
|
42937
|
+
if (at === null) return null;
|
|
42938
|
+
return Math.max(0, Math.floor((at - now) / MINUTE));
|
|
42939
|
+
}
|
|
42832
42940
|
function formatRemaining(ms) {
|
|
42833
42941
|
if (!Number.isFinite(ms)) return "unknown";
|
|
42834
42942
|
if (ms <= 0) return "expired";
|
|
@@ -54532,18 +54640,66 @@ function displayPath(p) {
|
|
|
54532
54640
|
return s.startsWith(home + path6.sep) ? `~${s.slice(home.length)}` : s;
|
|
54533
54641
|
}
|
|
54534
54642
|
|
|
54535
|
-
// scripts/src/lib/
|
|
54643
|
+
// scripts/src/lib/version.mjs
|
|
54536
54644
|
import { createRequire } from "node:module";
|
|
54645
|
+
var PKG_VERSION = (() => {
|
|
54646
|
+
const requireFrom = createRequire(import.meta.url);
|
|
54647
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
54648
|
+
try {
|
|
54649
|
+
const pkg = requireFrom(candidate);
|
|
54650
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
54651
|
+
} catch {
|
|
54652
|
+
}
|
|
54653
|
+
}
|
|
54654
|
+
return "0.0.0";
|
|
54655
|
+
})();
|
|
54537
54656
|
|
|
54538
54657
|
// scripts/src/lib/http.mjs
|
|
54539
54658
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
54540
|
-
var USER_AGENT =
|
|
54541
|
-
|
|
54659
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
54660
|
+
var MAX_RETRIES = 3;
|
|
54661
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
54662
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
54663
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
54664
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
54665
|
+
if (value === null || value === void 0) return null;
|
|
54666
|
+
const s = String(value).trim();
|
|
54667
|
+
if (!s) return null;
|
|
54668
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
54669
|
+
const at = Date.parse(s);
|
|
54670
|
+
if (!Number.isFinite(at)) return null;
|
|
54671
|
+
return Math.max(0, at - now);
|
|
54672
|
+
}
|
|
54673
|
+
function bodyRetryAfterMs(json) {
|
|
54674
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
54675
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
54676
|
+
const n = Number(raw);
|
|
54677
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
54678
|
+
}
|
|
54679
|
+
function rateLimitInfo(res) {
|
|
54680
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
54681
|
+
const info = {
|
|
54682
|
+
limit: pick("x-ratelimit-limit"),
|
|
54683
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
54684
|
+
tier: pick("x-ratelimit-tier"),
|
|
54685
|
+
scope: pick("x-ratelimit-scope")
|
|
54686
|
+
};
|
|
54687
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
54688
|
+
return Object.keys(present).length ? present : null;
|
|
54689
|
+
}
|
|
54690
|
+
function parseBody(text) {
|
|
54691
|
+
if (!text) return null;
|
|
54692
|
+
try {
|
|
54693
|
+
return JSON.parse(text);
|
|
54694
|
+
} catch {
|
|
54695
|
+
return null;
|
|
54696
|
+
}
|
|
54697
|
+
}
|
|
54698
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
54542
54699
|
const controller = new AbortController();
|
|
54543
54700
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
54544
|
-
let res;
|
|
54545
54701
|
try {
|
|
54546
|
-
res = await fetch(url, {
|
|
54702
|
+
const res = await fetch(url, {
|
|
54547
54703
|
method,
|
|
54548
54704
|
headers: {
|
|
54549
54705
|
accept: "application/json",
|
|
@@ -54553,6 +54709,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
54553
54709
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
54554
54710
|
signal: controller.signal
|
|
54555
54711
|
});
|
|
54712
|
+
const text = await res.text();
|
|
54713
|
+
return { res, text };
|
|
54556
54714
|
} catch (err) {
|
|
54557
54715
|
throw new SkillError(
|
|
54558
54716
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -54562,31 +54720,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
54562
54720
|
} finally {
|
|
54563
54721
|
clearTimeout(timer);
|
|
54564
54722
|
}
|
|
54565
|
-
|
|
54566
|
-
|
|
54567
|
-
|
|
54568
|
-
|
|
54569
|
-
|
|
54570
|
-
|
|
54571
|
-
|
|
54723
|
+
}
|
|
54724
|
+
async function request(method, url, {
|
|
54725
|
+
body,
|
|
54726
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
54727
|
+
sleep: sleep2 = defaultSleep,
|
|
54728
|
+
now = Date.now,
|
|
54729
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
54730
|
+
} = {}) {
|
|
54731
|
+
let waitedMs = 0;
|
|
54732
|
+
let retries = 0;
|
|
54733
|
+
for (; ; ) {
|
|
54734
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
54735
|
+
const json = parseBody(text);
|
|
54736
|
+
if (res.ok) {
|
|
54737
|
+
if (json === null) {
|
|
54738
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
54739
|
+
url: redactUrl(url),
|
|
54740
|
+
snippet: redact(text).slice(0, 400)
|
|
54741
|
+
});
|
|
54742
|
+
}
|
|
54743
|
+
return json;
|
|
54744
|
+
}
|
|
54745
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
54746
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
54747
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
54748
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
54749
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
54750
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
54751
|
+
retries += 1;
|
|
54752
|
+
waitedMs += waitMs;
|
|
54753
|
+
await sleep2(waitMs);
|
|
54754
|
+
continue;
|
|
54755
|
+
}
|
|
54572
54756
|
}
|
|
54573
|
-
}
|
|
54574
|
-
if (!res.ok) {
|
|
54575
54757
|
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}`;
|
|
54576
54758
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
54759
|
+
const rateLimit = rateLimitInfo(res);
|
|
54577
54760
|
throw new SkillError(code, redact(String(message)), {
|
|
54578
54761
|
httpStatus: res.status,
|
|
54579
54762
|
url: redactUrl(url),
|
|
54580
|
-
body: json ?? redact(text).slice(0, 800)
|
|
54581
|
-
|
|
54582
|
-
|
|
54583
|
-
|
|
54584
|
-
|
|
54585
|
-
|
|
54586
|
-
snippet: redact(text).slice(0, 400)
|
|
54763
|
+
body: json ?? redact(text).slice(0, 800),
|
|
54764
|
+
...retryable ? {
|
|
54765
|
+
retries,
|
|
54766
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
54767
|
+
} : {},
|
|
54768
|
+
...rateLimit ? { rateLimit } : {}
|
|
54587
54769
|
});
|
|
54588
54770
|
}
|
|
54589
|
-
return json;
|
|
54590
54771
|
}
|
|
54591
54772
|
function redactUrl(url) {
|
|
54592
54773
|
try {
|
|
@@ -54603,31 +54784,65 @@ function postJson(url, body, opts) {
|
|
|
54603
54784
|
return request("POST", url, { ...opts, body });
|
|
54604
54785
|
}
|
|
54605
54786
|
|
|
54606
|
-
// scripts/src/lib/
|
|
54607
|
-
|
|
54608
|
-
|
|
54609
|
-
|
|
54610
|
-
|
|
54611
|
-
|
|
54612
|
-
|
|
54613
|
-
|
|
54614
|
-
|
|
54787
|
+
// scripts/src/lib/identity.mjs
|
|
54788
|
+
import crypto7 from "node:crypto";
|
|
54789
|
+
var ACCOUNT_HASH_PREFIX = "rozo-acct-v1:";
|
|
54790
|
+
function isValidInstallId(value) {
|
|
54791
|
+
return typeof value === "string" && INSTALL_ID_RE.test(value);
|
|
54792
|
+
}
|
|
54793
|
+
function attributionDisabled(env = process.env) {
|
|
54794
|
+
const v = String(env.ROZO_CHECKOUT_ANON_ID ?? "").trim().toLowerCase();
|
|
54795
|
+
return ["off", "0", "false", "no"].includes(v);
|
|
54796
|
+
}
|
|
54797
|
+
function getOrCreateInstallId() {
|
|
54798
|
+
const existing = readPrefs()?.installId;
|
|
54799
|
+
if (isValidInstallId(existing)) return existing;
|
|
54800
|
+
const saved = savePrefs({}, { installIdIfAbsent: crypto7.randomUUID().toLowerCase() });
|
|
54801
|
+
if (isValidInstallId(saved?.installId)) return saved.installId;
|
|
54802
|
+
const onDisk = readPrefs()?.installId;
|
|
54803
|
+
return isValidInstallId(onDisk) ? onDisk : crypto7.randomUUID().toLowerCase();
|
|
54804
|
+
}
|
|
54805
|
+
function hashAccount(raw) {
|
|
54806
|
+
return crypto7.createHash("sha256").update(`${ACCOUNT_HASH_PREFIX}${raw}`, "utf8").digest("hex");
|
|
54807
|
+
}
|
|
54808
|
+
function rawApiKey(env) {
|
|
54809
|
+
const v = typeof env.OPENROUTER_API_KEY === "string" ? env.OPENROUTER_API_KEY.trim() : "";
|
|
54810
|
+
return v && v.length <= 1024 ? v : null;
|
|
54811
|
+
}
|
|
54812
|
+
function attributionIdentity(env = process.env) {
|
|
54813
|
+
if (attributionDisabled(env)) return {};
|
|
54814
|
+
const out2 = {};
|
|
54815
|
+
try {
|
|
54816
|
+
const id = getOrCreateInstallId();
|
|
54817
|
+
if (isValidInstallId(id)) out2.install_id = id;
|
|
54818
|
+
} catch {
|
|
54615
54819
|
}
|
|
54616
|
-
|
|
54617
|
-
|
|
54820
|
+
const raw = rawApiKey(env);
|
|
54821
|
+
if (raw) out2.account_hash = hashAccount(raw);
|
|
54822
|
+
return out2;
|
|
54823
|
+
}
|
|
54824
|
+
|
|
54825
|
+
// scripts/src/lib/api.mjs
|
|
54618
54826
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
54619
54827
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
54620
54828
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
54621
|
-
function buildAttribution({ utmSource } = {}) {
|
|
54829
|
+
function buildAttribution({ utmSource, identity } = {}) {
|
|
54622
54830
|
const raw = utmSource !== void 0 ? utmSource : process.env.ROZO_CHECKOUT_UTM_SOURCE;
|
|
54623
54831
|
const utm_source = normalizeUtmSource(raw);
|
|
54624
|
-
|
|
54832
|
+
const ids = identity ?? attributionIdentity();
|
|
54833
|
+
return {
|
|
54834
|
+
client: ATTRIBUTION_CLIENT,
|
|
54835
|
+
...utm_source ? { utm_source } : {},
|
|
54836
|
+
...ids.install_id ? { install_id: ids.install_id } : {},
|
|
54837
|
+
...ids.account_hash ? { account_hash: ids.account_hash } : {}
|
|
54838
|
+
};
|
|
54625
54839
|
}
|
|
54626
54840
|
var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
|
|
54627
54841
|
async function quoteInvoice({ url, linkId }) {
|
|
54628
54842
|
const body = url ? { url } : { payment_id: linkId };
|
|
54629
54843
|
return postJson(`${MPP_BASE}/quote-invoice`, body);
|
|
54630
54844
|
}
|
|
54845
|
+
var QUOTE_RECEIPT_RETRY_BUDGET_MS = 2e4;
|
|
54631
54846
|
async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email }) {
|
|
54632
54847
|
const body = {
|
|
54633
54848
|
...url ? { url } : { payment_id: linkId },
|
|
@@ -54639,7 +54854,11 @@ async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, ema
|
|
|
54639
54854
|
// sent empty, when the user gave none.
|
|
54640
54855
|
...email ? { email } : {}
|
|
54641
54856
|
};
|
|
54642
|
-
return postJson(
|
|
54857
|
+
return postJson(
|
|
54858
|
+
`${MPP_BASE}/create-invoice`,
|
|
54859
|
+
body,
|
|
54860
|
+
quoteReceipt ? { maxTotalWaitMs: QUOTE_RECEIPT_RETRY_BUDGET_MS } : void 0
|
|
54861
|
+
);
|
|
54643
54862
|
}
|
|
54644
54863
|
async function createBitrefillInvoice({ invoice, source, utmSource, email }) {
|
|
54645
54864
|
const body = {
|
|
@@ -56168,7 +56387,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
|
|
|
56168
56387
|
intentExpiresAt: payment?.expiresAt ?? deadline,
|
|
56169
56388
|
coinbaseExpiry: deadline
|
|
56170
56389
|
});
|
|
56171
|
-
return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4) } : { ok: false, code: expiry2.code, reason: expiry2.reason };
|
|
56390
|
+
return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4), deadlineMs: expiry2.effectiveDeadlineMs } : { ok: false, code: expiry2.code, reason: expiry2.reason };
|
|
56172
56391
|
}
|
|
56173
56392
|
if (!status) {
|
|
56174
56393
|
return { ok: false, code: "LINK_PAYABILITY_UNKNOWN", reason: "The Coinbase link state could not be read." };
|
|
@@ -56181,7 +56400,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
|
|
|
56181
56400
|
intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
|
|
56182
56401
|
coinbaseExpiry: status?.coinbase?.preApprovalExpiry
|
|
56183
56402
|
});
|
|
56184
|
-
return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4) } : { ok: false, code: expiry.code, reason: expiry.reason };
|
|
56403
|
+
return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4), deadlineMs: expiry.effectiveDeadlineMs } : { ok: false, code: expiry.code, reason: expiry.reason };
|
|
56185
56404
|
}
|
|
56186
56405
|
async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
56187
56406
|
let provider = resolveProvider(explicitProvider, rozoPaymentId);
|
|
@@ -56324,6 +56543,16 @@ function statusGuidance(result) {
|
|
|
56324
56543
|
if (result.state === "expired_unfunded") return EXPIRED_UNFUNDED_GUIDANCE;
|
|
56325
56544
|
return result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
|
|
56326
56545
|
}
|
|
56546
|
+
function watchTimeoutMs(timeoutArg, result, now = Date.now()) {
|
|
56547
|
+
if (timeoutArg !== void 0 && timeoutArg !== null && timeoutArg !== true) {
|
|
56548
|
+
const n = Number(timeoutArg);
|
|
56549
|
+
if (Number.isFinite(n)) return Math.max(0, n * 1e3);
|
|
56550
|
+
}
|
|
56551
|
+
if (result?.payin?.chain === chainName("lightning")) {
|
|
56552
|
+
return lightningWatchMs(result?.expiry?.expiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
|
|
56553
|
+
}
|
|
56554
|
+
return DEFAULT_WATCH_MS;
|
|
56555
|
+
}
|
|
56327
56556
|
async function main4(argv) {
|
|
56328
56557
|
const args = parseArgs(argv);
|
|
56329
56558
|
const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
|
|
@@ -56339,9 +56568,9 @@ async function main4(argv) {
|
|
|
56339
56568
|
usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
|
|
56340
56569
|
}
|
|
56341
56570
|
const watch = Boolean(args.watch);
|
|
56342
|
-
const
|
|
56343
|
-
const deadline = Date.now() + timeoutMs;
|
|
56571
|
+
const started = Date.now();
|
|
56344
56572
|
let result = await snapshot({ rozoPaymentId, linkId, provider });
|
|
56573
|
+
const deadline = started + watchTimeoutMs(args.timeout, result, started);
|
|
56345
56574
|
const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
|
|
56346
56575
|
while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
|
|
56347
56576
|
await sleep(POLL_INTERVAL_MS);
|
|
@@ -56411,6 +56640,241 @@ async function run5(argv = process.argv.slice(2)) {
|
|
|
56411
56640
|
return main5(argv);
|
|
56412
56641
|
}
|
|
56413
56642
|
|
|
56643
|
+
// scripts/src/resume.mjs
|
|
56644
|
+
var PAID_STATES = /* @__PURE__ */ new Set([
|
|
56645
|
+
"payin_detected",
|
|
56646
|
+
"payin_confirmed",
|
|
56647
|
+
"bridging",
|
|
56648
|
+
"paying_coinbase",
|
|
56649
|
+
"settled"
|
|
56650
|
+
]);
|
|
56651
|
+
var EXPIRED_STATES = /* @__PURE__ */ new Set(["expired_unfunded", "invoice_expired"]);
|
|
56652
|
+
function localPaymentLink(id) {
|
|
56653
|
+
try {
|
|
56654
|
+
return readState(id)?.paymentLink ?? null;
|
|
56655
|
+
} catch {
|
|
56656
|
+
return null;
|
|
56657
|
+
}
|
|
56658
|
+
}
|
|
56659
|
+
function resumeVerdict(snap) {
|
|
56660
|
+
const base = {
|
|
56661
|
+
rozoPaymentId: snap.rozoPaymentId,
|
|
56662
|
+
linkId: snap.linkId ?? null,
|
|
56663
|
+
provider: snap.provider,
|
|
56664
|
+
state: snap.state,
|
|
56665
|
+
moneyDetected: Boolean(snap.moneyDetected)
|
|
56666
|
+
};
|
|
56667
|
+
if (snap.escalate) {
|
|
56668
|
+
return { ...base, resumable: false, outcome: "needs_attention", code: "ORDER_NEEDS_ATTENTION", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
|
|
56669
|
+
}
|
|
56670
|
+
if (snap.unknown || !snap.authoritativeView) {
|
|
56671
|
+
return { ...base, resumable: false, outcome: "unknown", code: "ORDER_STATE_UNKNOWN", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
|
|
56672
|
+
}
|
|
56673
|
+
if (snap.moneyDetected || PAID_STATES.has(snap.state)) {
|
|
56674
|
+
const done = snap.state === "settled";
|
|
56675
|
+
return {
|
|
56676
|
+
...base,
|
|
56677
|
+
resumable: false,
|
|
56678
|
+
outcome: done ? "paid" : "paid_in_progress",
|
|
56679
|
+
code: "ORDER_ALREADY_PAID",
|
|
56680
|
+
exitCode: EXIT_OK,
|
|
56681
|
+
message: done ? "This order is already paid and settled. Nothing to resume. Do not pay again." : `A payment for this order was already detected (state: ${snap.state}). Do NOT pay again. Follow it with: rozo-checkout status ${snap.rozoPaymentId} --watch`
|
|
56682
|
+
};
|
|
56683
|
+
}
|
|
56684
|
+
if (EXPIRED_STATES.has(snap.state)) {
|
|
56685
|
+
return {
|
|
56686
|
+
...base,
|
|
56687
|
+
resumable: false,
|
|
56688
|
+
outcome: "expired",
|
|
56689
|
+
code: "ORDER_EXPIRED",
|
|
56690
|
+
exitCode: EXIT_ERROR,
|
|
56691
|
+
message: snap.provider === "bitrefill" ? "This order expired before any funds arrived. Nothing was lost. Create a fresh Bitrefill invoice and run pay again." : EXPIRED_UNFUNDED_GUIDANCE
|
|
56692
|
+
};
|
|
56693
|
+
}
|
|
56694
|
+
if (snap.localSend) {
|
|
56695
|
+
return {
|
|
56696
|
+
...base,
|
|
56697
|
+
resumable: false,
|
|
56698
|
+
outcome: "sent_locally",
|
|
56699
|
+
code: "ALREADY_SENT",
|
|
56700
|
+
exitCode: EXIT_ERROR,
|
|
56701
|
+
message: `This machine already recorded a send for this order (${snap.localSend.status}). Do NOT pay again. Check it with: rozo-checkout status ${snap.rozoPaymentId}`
|
|
56702
|
+
};
|
|
56703
|
+
}
|
|
56704
|
+
if (snap.state !== "awaiting_deposit") {
|
|
56705
|
+
return {
|
|
56706
|
+
...base,
|
|
56707
|
+
resumable: false,
|
|
56708
|
+
outcome: "not_payable",
|
|
56709
|
+
code: "ORDER_NOT_RESUMABLE",
|
|
56710
|
+
exitCode: EXIT_ERROR,
|
|
56711
|
+
message: `This order is in state "${snap.state}" and cannot be paid now. ${statusGuidance(snap)}`
|
|
56712
|
+
};
|
|
56713
|
+
}
|
|
56714
|
+
if (!snap.sendWindow?.ok) {
|
|
56715
|
+
return {
|
|
56716
|
+
...base,
|
|
56717
|
+
resumable: false,
|
|
56718
|
+
outcome: "expired",
|
|
56719
|
+
code: snap.sendWindow?.code ?? "SEND_WINDOW_CLOSED",
|
|
56720
|
+
exitCode: EXIT_ERROR,
|
|
56721
|
+
message: `${snap.sendWindow?.reason ?? "Not enough time is left to pay this order safely."} Do not fund it. Let it expire unfunded, then create a new order for the same link.`
|
|
56722
|
+
};
|
|
56723
|
+
}
|
|
56724
|
+
return { ...base, resumable: true, outcome: "awaiting_deposit", code: null, exitCode: EXIT_OK, message: null };
|
|
56725
|
+
}
|
|
56726
|
+
async function main6(argv) {
|
|
56727
|
+
const args = parseArgs(argv);
|
|
56728
|
+
const rozoPaymentId = args["rozo-payment-id"] || args._[0];
|
|
56729
|
+
if (!rozoPaymentId || rozoPaymentId === true) usage("Required: <rozoPaymentId> (the order id printed by pay)");
|
|
56730
|
+
if (!isRozoPaymentId(String(rozoPaymentId))) {
|
|
56731
|
+
usage("resume takes the order id (rozoPaymentId, a UUID) printed by pay, not a payment link.");
|
|
56732
|
+
}
|
|
56733
|
+
const id = String(rozoPaymentId);
|
|
56734
|
+
const confirmed = Boolean(args.confirm);
|
|
56735
|
+
let blacklist;
|
|
56736
|
+
try {
|
|
56737
|
+
blacklist = loadBlacklist();
|
|
56738
|
+
} catch (err) {
|
|
56739
|
+
throw new SkillError("BLACKLIST_UNAVAILABLE", `Compromised-address list unusable: ${err.message} Refusing to proceed.`);
|
|
56740
|
+
}
|
|
56741
|
+
const snap = await snapshot({ rozoPaymentId: id });
|
|
56742
|
+
const verdict = resumeVerdict(snap);
|
|
56743
|
+
if (!verdict.resumable) {
|
|
56744
|
+
emit(
|
|
56745
|
+
{
|
|
56746
|
+
success: verdict.exitCode === EXIT_OK,
|
|
56747
|
+
step: "resume",
|
|
56748
|
+
resumable: false,
|
|
56749
|
+
outcome: verdict.outcome,
|
|
56750
|
+
rozoPaymentId: id,
|
|
56751
|
+
linkId: verdict.linkId,
|
|
56752
|
+
state: verdict.state,
|
|
56753
|
+
moneyDetected: verdict.moneyDetected,
|
|
56754
|
+
...verdict.exitCode === EXIT_OK ? { message: verdict.message } : { error: { code: verdict.code, message: verdict.message } },
|
|
56755
|
+
payin: snap.payin,
|
|
56756
|
+
...verdict.exitCode === EXIT_OK ? {} : { support: SUPPORT }
|
|
56757
|
+
},
|
|
56758
|
+
verdict.exitCode
|
|
56759
|
+
);
|
|
56760
|
+
}
|
|
56761
|
+
const payment = await getPayment(id);
|
|
56762
|
+
const source = payment?.source || {};
|
|
56763
|
+
const guard = reuseGuard({ payment, requested: { chainId: source.chainId, tokenSymbol: source.tokenSymbol }, reused: true });
|
|
56764
|
+
if (!guard.ok) {
|
|
56765
|
+
emit(
|
|
56766
|
+
{
|
|
56767
|
+
success: false,
|
|
56768
|
+
step: "resume",
|
|
56769
|
+
resumable: false,
|
|
56770
|
+
outcome: guard.moneyDetected ? "paid_in_progress" : "not_payable",
|
|
56771
|
+
rozoPaymentId: id,
|
|
56772
|
+
moneyDetected: guard.moneyDetected,
|
|
56773
|
+
error: { code: guard.code, message: guard.reason },
|
|
56774
|
+
guidance: guard.moneyDetected ? "A payment for this order was detected. Do NOT pay again." : "This order cannot be paid as it stands. Do not fund it.",
|
|
56775
|
+
support: SUPPORT
|
|
56776
|
+
},
|
|
56777
|
+
EXIT_ERROR
|
|
56778
|
+
);
|
|
56779
|
+
}
|
|
56780
|
+
const finalWindow = checkExpiry({
|
|
56781
|
+
now: Date.now(),
|
|
56782
|
+
chainId: source.chainId,
|
|
56783
|
+
intentExpiresAt: payment?.expiresAt ?? snap.sendWindow?.deadlineMs,
|
|
56784
|
+
coinbaseExpiry: snap.sendWindow?.deadlineMs
|
|
56785
|
+
});
|
|
56786
|
+
if (!finalWindow.ok) {
|
|
56787
|
+
emit(
|
|
56788
|
+
{
|
|
56789
|
+
success: false,
|
|
56790
|
+
step: "resume",
|
|
56791
|
+
resumable: false,
|
|
56792
|
+
outcome: "expired",
|
|
56793
|
+
rozoPaymentId: id,
|
|
56794
|
+
moneyDetected: false,
|
|
56795
|
+
error: { code: finalWindow.code, message: finalWindow.reason },
|
|
56796
|
+
guidance: "Not enough time is left to pay this order safely. Do not fund it; let it expire unfunded and create a new order.",
|
|
56797
|
+
support: SUPPORT
|
|
56798
|
+
},
|
|
56799
|
+
EXIT_ERROR
|
|
56800
|
+
);
|
|
56801
|
+
}
|
|
56802
|
+
const lightning = String(source.chainId) === "lightning";
|
|
56803
|
+
if (!lightning) {
|
|
56804
|
+
try {
|
|
56805
|
+
assertNotBlacklisted(
|
|
56806
|
+
[{ address: source.receiverAddress, family: chainFamily(source.chainId), role: "deposit address" }],
|
|
56807
|
+
blacklist
|
|
56808
|
+
);
|
|
56809
|
+
} catch (err) {
|
|
56810
|
+
emit(
|
|
56811
|
+
{
|
|
56812
|
+
success: false,
|
|
56813
|
+
step: "resume",
|
|
56814
|
+
resumable: false,
|
|
56815
|
+
rozoPaymentId: id,
|
|
56816
|
+
error: { code: err.code, message: err.message },
|
|
56817
|
+
guidance: "Do NOT send anything. Report this to the operator immediately.",
|
|
56818
|
+
support: SUPPORT
|
|
56819
|
+
},
|
|
56820
|
+
EXIT_ERROR
|
|
56821
|
+
);
|
|
56822
|
+
}
|
|
56823
|
+
}
|
|
56824
|
+
const expiresAt = payment?.expiresAt ?? snap.expiry?.expiresAt ?? null;
|
|
56825
|
+
const msRemaining = expiresAt ? Date.parse(expiresAt) - Date.now() : NaN;
|
|
56826
|
+
const expiresIn = Number.isFinite(msRemaining) ? formatRemaining(msRemaining) : null;
|
|
56827
|
+
const paymentLink = payment?.paymentLink ?? localPaymentLink(id);
|
|
56828
|
+
const bolt11 = source.lnInvoice ?? payment?.lnInvoice ?? null;
|
|
56829
|
+
emit({
|
|
56830
|
+
success: true,
|
|
56831
|
+
step: "resume",
|
|
56832
|
+
resumable: true,
|
|
56833
|
+
outcome: "awaiting_deposit",
|
|
56834
|
+
confirmed,
|
|
56835
|
+
rozoPaymentId: id,
|
|
56836
|
+
linkId: snap.linkId ?? payment?.orderId ?? null,
|
|
56837
|
+
provider: snap.provider,
|
|
56838
|
+
state: snap.state,
|
|
56839
|
+
// Hosted pay page for this order: a human can open it and pay from there.
|
|
56840
|
+
// Shown in full only for Rozo's own pay-page hosts; anything else is
|
|
56841
|
+
// reduced to its host by the normal output redaction.
|
|
56842
|
+
paymentLink: publicPayPage(paymentLink),
|
|
56843
|
+
deposit: confirmed ? {
|
|
56844
|
+
chainId: source.chainId,
|
|
56845
|
+
chain: chainName(source.chainId),
|
|
56846
|
+
tokenSymbol: source.tokenSymbol,
|
|
56847
|
+
tokenAddress: source.tokenAddress || null,
|
|
56848
|
+
receiverAddress: lightning ? null : source.receiverAddress,
|
|
56849
|
+
receiverMemo: source.receiverMemo ?? null,
|
|
56850
|
+
receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
56851
|
+
amount: source.amount,
|
|
56852
|
+
amountUnit: source.amountUnit ?? null,
|
|
56853
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
56854
|
+
lnInvoice: bolt11 || null,
|
|
56855
|
+
payTo: guard.deposit.payTo,
|
|
56856
|
+
expiresAt,
|
|
56857
|
+
expiresIn
|
|
56858
|
+
} : null,
|
|
56859
|
+
depositWithheld: !confirmed,
|
|
56860
|
+
display: {
|
|
56861
|
+
chain: chainName(source.chainId),
|
|
56862
|
+
token: source.tokenSymbol,
|
|
56863
|
+
amount: formatAmount(source),
|
|
56864
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
56865
|
+
payToMasked: maskAddress2(guard.deposit.payTo),
|
|
56866
|
+
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
56867
|
+
hasMemo: Boolean(source.receiverMemo),
|
|
56868
|
+
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null
|
|
56869
|
+
},
|
|
56870
|
+
expiry: { expiresAt, expiresIn, minutesOfSlack: snap.sendWindow?.minutesOfSlack ?? null },
|
|
56871
|
+
note: confirmed ? "Send exactly once, exactly this amount, on this chain. Then: rozo-checkout status <id> --watch" : "Deposit address withheld. Confirm the chain, token and amount with the payer, then re-run with --confirm, or open paymentLink."
|
|
56872
|
+
});
|
|
56873
|
+
}
|
|
56874
|
+
async function run6(argv = process.argv.slice(2)) {
|
|
56875
|
+
return main6(argv);
|
|
56876
|
+
}
|
|
56877
|
+
|
|
56414
56878
|
// scripts/src/lib/passphrase.mjs
|
|
56415
56879
|
import readline from "node:readline";
|
|
56416
56880
|
function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
@@ -57127,7 +57591,7 @@ function chainDef(chainId, rpcUrl) {
|
|
|
57127
57591
|
rpcUrls: { default: { http: [rpcUrl] } }
|
|
57128
57592
|
});
|
|
57129
57593
|
}
|
|
57130
|
-
async function
|
|
57594
|
+
async function main7(argv) {
|
|
57131
57595
|
const args = parseArgs(argv);
|
|
57132
57596
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
57133
57597
|
assertNoTrackedDotEnv();
|
|
@@ -57370,8 +57834,8 @@ async function main6(argv) {
|
|
|
57370
57834
|
outcome.exitCode
|
|
57371
57835
|
);
|
|
57372
57836
|
}
|
|
57373
|
-
async function
|
|
57374
|
-
return
|
|
57837
|
+
async function run7(argv = process.argv.slice(2)) {
|
|
57838
|
+
return main7(argv);
|
|
57375
57839
|
}
|
|
57376
57840
|
|
|
57377
57841
|
// scripts/src/send-sol.mjs
|
|
@@ -57441,7 +57905,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
|
|
|
57441
57905
|
var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
57442
57906
|
var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
|
|
57443
57907
|
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
57444
|
-
async function
|
|
57908
|
+
async function main8(argv) {
|
|
57445
57909
|
const args = parseArgs(argv);
|
|
57446
57910
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
57447
57911
|
assertNoTrackedDotEnv();
|
|
@@ -57740,8 +58204,8 @@ function base58Encode(buf) {
|
|
|
57740
58204
|
}
|
|
57741
58205
|
return out2;
|
|
57742
58206
|
}
|
|
57743
|
-
async function
|
|
57744
|
-
return
|
|
58207
|
+
async function run8(argv = process.argv.slice(2)) {
|
|
58208
|
+
return main8(argv);
|
|
57745
58209
|
}
|
|
57746
58210
|
|
|
57747
58211
|
// scripts/src/cli.mjs
|
|
@@ -57780,6 +58244,9 @@ function printError(payload) {
|
|
|
57780
58244
|
function printSupport() {
|
|
57781
58245
|
out(` ${dim(SUPPORT_TEXT)}`);
|
|
57782
58246
|
}
|
|
58247
|
+
function printBulkHint() {
|
|
58248
|
+
out(` ${dim(BULK_HINT_TEXT)}`);
|
|
58249
|
+
}
|
|
57783
58250
|
function printMoneyWarning(payload) {
|
|
57784
58251
|
if (!payload?.moneyDetected) return;
|
|
57785
58252
|
out();
|
|
@@ -57917,7 +58384,8 @@ async function cmdQuote(opts) {
|
|
|
57917
58384
|
return exitCode;
|
|
57918
58385
|
}
|
|
57919
58386
|
async function cmdStatus(opts) {
|
|
57920
|
-
const argv = [...targetToArgs(opts.target)
|
|
58387
|
+
const argv = [...targetToArgs(opts.target)];
|
|
58388
|
+
if (opts.timeout !== void 0) argv.push("--timeout", String(opts.timeout));
|
|
57921
58389
|
if (opts.provider) argv.push("--provider", opts.provider);
|
|
57922
58390
|
if (opts.watch) argv.push("--watch");
|
|
57923
58391
|
if (!opts.json) out(dim(" Checking payment status\u2026 (no money moves)"));
|
|
@@ -57978,9 +58446,66 @@ async function cmdReceipt(opts) {
|
|
|
57978
58446
|
out();
|
|
57979
58447
|
out(` Next: ${r.nextAction.type === "contact_support" ? red(r.nextAction.message) : r.nextAction.message}`);
|
|
57980
58448
|
if (payload.support) printSupport();
|
|
58449
|
+
if (r.paymentOutcome === "settled") printBulkHint();
|
|
57981
58450
|
out();
|
|
57982
58451
|
return exitCode;
|
|
57983
58452
|
}
|
|
58453
|
+
async function cmdResume(opts) {
|
|
58454
|
+
if (!opts.json) out(dim(" Reading the order\u2026 (no money moves)"));
|
|
58455
|
+
const first = await step(run6, [opts.target]);
|
|
58456
|
+
let payload = first.payload;
|
|
58457
|
+
let exitCode = first.exitCode;
|
|
58458
|
+
if (payload.resumable && !opts.json) {
|
|
58459
|
+
out();
|
|
58460
|
+
out(` Order ${payload.rozoPaymentId} ${dim("(unpaid)")}`);
|
|
58461
|
+
out(` You send ${bold(payload.display?.amount)} on ${bold(payload.display?.chain)}`);
|
|
58462
|
+
out(` To ${payload.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
|
|
58463
|
+
if (payload.display?.hasMemo) {
|
|
58464
|
+
out(` Memo ${payload.display.receiverMemoMasked} ${dim(`(${payload.display.memoType})`)}`);
|
|
58465
|
+
}
|
|
58466
|
+
if (payload.expiry?.expiresIn) out(` Expires in ${bold(payload.expiry.expiresIn)}`);
|
|
58467
|
+
if (payload.paymentLink) out(` Pay page ${payload.paymentLink}`);
|
|
58468
|
+
out();
|
|
58469
|
+
}
|
|
58470
|
+
if (payload.resumable) {
|
|
58471
|
+
let go = opts.yes;
|
|
58472
|
+
if (!go && !opts.json && process.stdin.isTTY) {
|
|
58473
|
+
go = await askYesNo(` ${bold("Show the deposit details to pay this now?")} [y/N] `);
|
|
58474
|
+
}
|
|
58475
|
+
if (go) {
|
|
58476
|
+
const second = await step(run6, [opts.target, "--confirm"]);
|
|
58477
|
+
payload = second.payload;
|
|
58478
|
+
exitCode = second.exitCode;
|
|
58479
|
+
}
|
|
58480
|
+
}
|
|
58481
|
+
if (opts.json) {
|
|
58482
|
+
printJson(payload);
|
|
58483
|
+
return exitCode;
|
|
58484
|
+
}
|
|
58485
|
+
if (payload.error) {
|
|
58486
|
+
printError(payload);
|
|
58487
|
+
printMoneyWarning(payload);
|
|
58488
|
+
printSupport();
|
|
58489
|
+
out();
|
|
58490
|
+
return exitCode;
|
|
58491
|
+
}
|
|
58492
|
+
if (!payload.resumable) {
|
|
58493
|
+
out();
|
|
58494
|
+
out(` State ${stateLabel(payload.state)}`);
|
|
58495
|
+
out(` ${payload.message}`);
|
|
58496
|
+
out();
|
|
58497
|
+
return exitCode;
|
|
58498
|
+
}
|
|
58499
|
+
if (payload.deposit) {
|
|
58500
|
+
printDeposit(payload.deposit, opts);
|
|
58501
|
+
out(dim(` Then: rozo-checkout status ${payload.rozoPaymentId} --watch`));
|
|
58502
|
+
out();
|
|
58503
|
+
} else {
|
|
58504
|
+
out(dim(" Deposit details withheld. Open the pay page above, or re-run with --yes."));
|
|
58505
|
+
out();
|
|
58506
|
+
}
|
|
58507
|
+
return exitCode;
|
|
58508
|
+
}
|
|
57984
58509
|
function outcomeLabel(outcome) {
|
|
57985
58510
|
if (outcome === "settled") return green(bold(outcome));
|
|
57986
58511
|
if (outcome === "needs_attention" || outcome === "unknown") return red(bold(outcome));
|
|
@@ -58171,7 +58696,7 @@ async function cmdPay(opts) {
|
|
|
58171
58696
|
}
|
|
58172
58697
|
if (opts.send) {
|
|
58173
58698
|
const family = chainFamily(deposit.chainId);
|
|
58174
|
-
const sender = family === "evm" ?
|
|
58699
|
+
const sender = family === "evm" ? run7 : family === "solana" ? run8 : null;
|
|
58175
58700
|
if (!sender) {
|
|
58176
58701
|
const payload = formatFailure({
|
|
58177
58702
|
code: "SEND_UNSUPPORTED_CHAIN",
|
|
@@ -58232,7 +58757,7 @@ async function cmdPay(opts) {
|
|
|
58232
58757
|
rozoPaymentId,
|
|
58233
58758
|
"--watch",
|
|
58234
58759
|
"--timeout",
|
|
58235
|
-
String(opts
|
|
58760
|
+
String(settlementWatchSeconds(opts, deposit))
|
|
58236
58761
|
]);
|
|
58237
58762
|
if (opts.json) {
|
|
58238
58763
|
printJson({
|
|
@@ -58252,6 +58777,7 @@ async function cmdPay(opts) {
|
|
|
58252
58777
|
}
|
|
58253
58778
|
printMoneyWarning(watched.payload);
|
|
58254
58779
|
if (watched.payload.state !== "settled") printSupport();
|
|
58780
|
+
else printBulkHint();
|
|
58255
58781
|
out();
|
|
58256
58782
|
return watched.exitCode;
|
|
58257
58783
|
}
|
|
@@ -58262,6 +58788,10 @@ function printDeposit(deposit, opts) {
|
|
|
58262
58788
|
if (deposit.lnInvoice) {
|
|
58263
58789
|
out(` Amount ${bold(`${deposit.amount} ${isSatsUnit(deposit.amountUnit) ? "sats" : deposit.tokenSymbol}`)}`);
|
|
58264
58790
|
out(` BOLT11 ${deposit.lnInvoice}`);
|
|
58791
|
+
const validMin = minutesRemaining(deposit.expiresAt);
|
|
58792
|
+
if (validMin !== null) {
|
|
58793
|
+
out(` Valid ${bold(validMin < 1 ? "under 1 more minute" : `${validMin} more minutes`)}`);
|
|
58794
|
+
}
|
|
58265
58795
|
} else {
|
|
58266
58796
|
out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)}`);
|
|
58267
58797
|
out(` Chain ${bold(deposit.chain)}`);
|
|
@@ -58283,7 +58813,7 @@ function printDeposit(deposit, opts) {
|
|
|
58283
58813
|
out(dim(" Send it exactly once."));
|
|
58284
58814
|
out();
|
|
58285
58815
|
}
|
|
58286
|
-
async function
|
|
58816
|
+
async function main9() {
|
|
58287
58817
|
let opts;
|
|
58288
58818
|
try {
|
|
58289
58819
|
opts = parseCliArgs(process.argv.slice(2));
|
|
@@ -58314,6 +58844,8 @@ async function main8() {
|
|
|
58314
58844
|
return cmdStatus(opts);
|
|
58315
58845
|
case "receipt":
|
|
58316
58846
|
return cmdReceipt(opts);
|
|
58847
|
+
case "resume":
|
|
58848
|
+
return cmdResume(opts);
|
|
58317
58849
|
case "pay":
|
|
58318
58850
|
try {
|
|
58319
58851
|
return await cmdPay(opts);
|
|
@@ -58334,7 +58866,7 @@ async function main8() {
|
|
|
58334
58866
|
return EXIT_USAGE;
|
|
58335
58867
|
}
|
|
58336
58868
|
}
|
|
58337
|
-
|
|
58869
|
+
main9().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
|
|
58338
58870
|
const payload = formatFailure(err);
|
|
58339
58871
|
if (process.argv.includes("--json")) printJson(payload);
|
|
58340
58872
|
else {
|