@rozoai/checkout 0.1.11 → 0.1.12
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 +12 -0
- package/llms.txt +16 -0
- package/package.json +1 -1
- package/scripts/dist/cli.js +683 -69
- package/scripts/dist/create-order.js +12 -1
- package/scripts/dist/quote.js +1 -1
- package/scripts/dist/send-evm.js +100 -13
- package/scripts/dist/send-sol.js +100 -13
- package/scripts/dist/status.js +157 -18
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);
|
|
@@ -41819,7 +41819,37 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
41819
41819
|
"watch",
|
|
41820
41820
|
"fresh"
|
|
41821
41821
|
]);
|
|
41822
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
41822
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
41823
|
+
"with",
|
|
41824
|
+
"from",
|
|
41825
|
+
"chain",
|
|
41826
|
+
"token",
|
|
41827
|
+
"rpc",
|
|
41828
|
+
"timeout",
|
|
41829
|
+
"payer",
|
|
41830
|
+
"keyfile",
|
|
41831
|
+
"env-file",
|
|
41832
|
+
// Bitrefill invoice (pay) and provider override (status)
|
|
41833
|
+
"bitrefill-invoice",
|
|
41834
|
+
"to",
|
|
41835
|
+
"amount",
|
|
41836
|
+
"expires-at",
|
|
41837
|
+
"provider"
|
|
41838
|
+
]);
|
|
41839
|
+
function resolveFromPreset(value) {
|
|
41840
|
+
try {
|
|
41841
|
+
return resolvePreset(value);
|
|
41842
|
+
} catch (err) {
|
|
41843
|
+
const raw = String(value ?? "").trim();
|
|
41844
|
+
const idx = raw.indexOf("-");
|
|
41845
|
+
if (idx <= 0) throw err;
|
|
41846
|
+
try {
|
|
41847
|
+
return resolvePreset(`${raw.slice(idx + 1)}-${raw.slice(0, idx)}`);
|
|
41848
|
+
} catch {
|
|
41849
|
+
throw err;
|
|
41850
|
+
}
|
|
41851
|
+
}
|
|
41852
|
+
}
|
|
41823
41853
|
function parseCliArgs(argv) {
|
|
41824
41854
|
const positional = [];
|
|
41825
41855
|
const flags = {};
|
|
@@ -41884,15 +41914,43 @@ function parseCliArgs(argv) {
|
|
|
41884
41914
|
"Usage: rozo-checkout status <rozoPaymentId | coinbase-link>"
|
|
41885
41915
|
);
|
|
41886
41916
|
}
|
|
41917
|
+
if (flags.provider !== void 0 && !["coinbase", "bitrefill"].includes(String(flags.provider))) {
|
|
41918
|
+
throw new CliError("BAD_VALUE", "--provider must be coinbase or bitrefill.");
|
|
41919
|
+
}
|
|
41887
41920
|
return {
|
|
41888
41921
|
command,
|
|
41889
41922
|
target,
|
|
41890
41923
|
json,
|
|
41924
|
+
provider: flags.provider,
|
|
41891
41925
|
watch: flags.watch === true,
|
|
41892
41926
|
timeout: flags.timeout === void 0 ? 600 : Number(flags.timeout)
|
|
41893
41927
|
};
|
|
41894
41928
|
}
|
|
41895
|
-
|
|
41929
|
+
const bitrefillFlags = ["bitrefill-invoice", "to", "amount", "expires-at"].filter(
|
|
41930
|
+
(k) => flags[k] !== void 0
|
|
41931
|
+
);
|
|
41932
|
+
let bitrefill = null;
|
|
41933
|
+
if (bitrefillFlags.length) {
|
|
41934
|
+
if (flags["bitrefill-invoice"] === void 0 || flags.to === void 0 || flags.amount === void 0 || flags["expires-at"] === void 0) {
|
|
41935
|
+
throw new CliError(
|
|
41936
|
+
"MISSING_VALUE",
|
|
41937
|
+
"A Bitrefill invoice needs --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --expires-at <ISO>."
|
|
41938
|
+
);
|
|
41939
|
+
}
|
|
41940
|
+
if (target) {
|
|
41941
|
+
throw new CliError("CONFLICTING_FLAGS", "Pass either a Coinbase link or --bitrefill-invoice, not both.");
|
|
41942
|
+
}
|
|
41943
|
+
bitrefill = {
|
|
41944
|
+
invoiceId: String(flags["bitrefill-invoice"]).trim(),
|
|
41945
|
+
address: String(flags.to).trim(),
|
|
41946
|
+
amount: String(flags.amount).trim(),
|
|
41947
|
+
expiresAt: String(flags["expires-at"]).trim()
|
|
41948
|
+
};
|
|
41949
|
+
}
|
|
41950
|
+
if (flags.from !== void 0 && flags.with !== void 0) {
|
|
41951
|
+
throw new CliError("CONFLICTING_FLAGS", "Use either --with or --from, not both.");
|
|
41952
|
+
}
|
|
41953
|
+
if (!target && !bitrefill) {
|
|
41896
41954
|
throw new CliError(
|
|
41897
41955
|
"MISSING_TARGET",
|
|
41898
41956
|
"Usage: rozo-checkout pay <coinbase-link> --with usdt-solana"
|
|
@@ -41903,7 +41961,7 @@ function parseCliArgs(argv) {
|
|
|
41903
41961
|
if (flags.chain === void 0 || flags.token === void 0) {
|
|
41904
41962
|
throw new CliError("MISSING_VALUE", "--chain and --token must be given together.");
|
|
41905
41963
|
}
|
|
41906
|
-
if (flags.with !== void 0) {
|
|
41964
|
+
if (flags.with !== void 0 || flags.from !== void 0) {
|
|
41907
41965
|
throw new CliError("CONFLICTING_FLAGS", "Use either --with, or --chain plus --token.");
|
|
41908
41966
|
}
|
|
41909
41967
|
const chainId = String(flags.chain).trim();
|
|
@@ -41918,6 +41976,8 @@ function parseCliArgs(argv) {
|
|
|
41918
41976
|
source = { chainId, tokenSymbol };
|
|
41919
41977
|
} else if (flags.with !== void 0) {
|
|
41920
41978
|
source = resolvePreset(flags.with);
|
|
41979
|
+
} else if (flags.from !== void 0) {
|
|
41980
|
+
source = resolveFromPreset(flags.from);
|
|
41921
41981
|
} else {
|
|
41922
41982
|
source = null;
|
|
41923
41983
|
}
|
|
@@ -41927,7 +41987,8 @@ function parseCliArgs(argv) {
|
|
|
41927
41987
|
}
|
|
41928
41988
|
return {
|
|
41929
41989
|
command: "pay",
|
|
41930
|
-
target,
|
|
41990
|
+
target: target ?? null,
|
|
41991
|
+
bitrefill,
|
|
41931
41992
|
json,
|
|
41932
41993
|
source,
|
|
41933
41994
|
send: flags.send === true,
|
|
@@ -41942,12 +42003,13 @@ function parseCliArgs(argv) {
|
|
|
41942
42003
|
envFile: flags["env-file"]
|
|
41943
42004
|
};
|
|
41944
42005
|
}
|
|
41945
|
-
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link
|
|
41946
|
-
or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.
|
|
42006
|
+
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link or a Bitrefill invoice
|
|
42007
|
+
(USDC on Base) with BTC Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.
|
|
41947
42008
|
|
|
41948
42009
|
USAGE
|
|
41949
42010
|
npx @rozoai/checkout pay <coinbase-link> (pick a coin from a list)
|
|
41950
42011
|
npx @rozoai/checkout pay <coinbase-link> --with <coin>
|
|
42012
|
+
npx @rozoai/checkout pay --bitrefill-invoice <id> --to <0x\u2026> --amount <USDC> --expires-at <ISO> --with <coin>
|
|
41951
42013
|
npx @rozoai/checkout quote <coinbase-link>
|
|
41952
42014
|
npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
|
|
41953
42015
|
|
|
@@ -41978,6 +42040,11 @@ OPTIONS
|
|
|
41978
42040
|
--json, -j machine-readable output
|
|
41979
42041
|
--no-watch stop after showing the deposit instructions
|
|
41980
42042
|
--timeout <s> how long to poll for settlement (default 900)
|
|
42043
|
+
--bitrefill-invoice <id> pay a Bitrefill invoice created with "USDC on Base"
|
|
42044
|
+
instead of a Coinbase link. Needs --to <0x\u2026 address Bitrefill
|
|
42045
|
+
shows>, --amount <exact USDC amount> and --expires-at <ISO>
|
|
42046
|
+
(required; refused under 5 minutes or over 30 minutes out).
|
|
42047
|
+
--from <coin> same as --with; also accepts chain-first, e.g. stellar-usdc
|
|
41981
42048
|
--payer <addr> optional. Check what this wallet holds and mark the coin
|
|
41982
42049
|
list accordingly. Display help only; it never changes what
|
|
41983
42050
|
gets signed.
|
|
@@ -41990,6 +42057,7 @@ EXAMPLES
|
|
|
41990
42057
|
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01...
|
|
41991
42058
|
npx @rozoai/checkout pay https://payments.coinbase.com/payment-links/pl_01... --with usdt-solana
|
|
41992
42059
|
npx @rozoai/checkout pay pl_01... --with btc-lightning
|
|
42060
|
+
npx @rozoai/checkout pay --bitrefill-invoice 8f3k2 --to 0xAbC\u2026123 --amount 7.90 --expires-at 2026-10-01T12:00:00Z --with usdc-stellar
|
|
41993
42061
|
npx @rozoai/checkout status 11111111-2222-4333-8444-555555555555
|
|
41994
42062
|
|
|
41995
42063
|
Creating an order moves no money; an unfunded order simply expires. Nothing is
|
|
@@ -42241,12 +42309,23 @@ function readState(rozoPaymentId) {
|
|
|
42241
42309
|
function createOrderRecord(record) {
|
|
42242
42310
|
return withLock(() => createOrderRecordUnlocked(record));
|
|
42243
42311
|
}
|
|
42312
|
+
function mergeBitrefill(prev, next) {
|
|
42313
|
+
if (!next) return prev ?? null;
|
|
42314
|
+
if (!prev?.expiresAt || !next.expiresAt) return { ...next, expiresAt: next.expiresAt ?? prev?.expiresAt ?? null };
|
|
42315
|
+
const a = Date.parse(prev.expiresAt);
|
|
42316
|
+
const b = Date.parse(next.expiresAt);
|
|
42317
|
+
return { ...next, expiresAt: Number.isFinite(a) && (!Number.isFinite(b) || a < b) ? prev.expiresAt : next.expiresAt };
|
|
42318
|
+
}
|
|
42244
42319
|
function createOrderRecordUnlocked(record) {
|
|
42245
42320
|
const { rozoPaymentId } = record;
|
|
42246
42321
|
const existing = readState(rozoPaymentId);
|
|
42247
42322
|
const next = {
|
|
42248
42323
|
version: 1,
|
|
42249
42324
|
rozoPaymentId,
|
|
42325
|
+
// 'coinbase' (default, historical records) or 'bitrefill'. Drives which
|
|
42326
|
+
// payability checks presend and status run.
|
|
42327
|
+
provider: record.provider ?? existing?.provider ?? "coinbase",
|
|
42328
|
+
bitrefill: mergeBitrefill(existing?.bitrefill, record.bitrefill),
|
|
42250
42329
|
linkId: record.linkId,
|
|
42251
42330
|
paymentLink: record.paymentLink ?? null,
|
|
42252
42331
|
merchant: record.merchant ?? null,
|
|
@@ -54364,7 +54443,7 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
54364
54443
|
}
|
|
54365
54444
|
}
|
|
54366
54445
|
if (!res.ok) {
|
|
54367
|
-
const code = json?.code || json?.error?.code || (typeof json?.error === "string" ?
|
|
54446
|
+
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
54447
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
54369
54448
|
throw new SkillError(code, redact(String(message)), {
|
|
54370
54449
|
httpStatus: res.status,
|
|
@@ -54425,6 +54504,21 @@ async function createInvoice({ url, linkId, source, quoteReceipt }) {
|
|
|
54425
54504
|
};
|
|
54426
54505
|
return postJson(`${MPP_BASE}/create-invoice`, body);
|
|
54427
54506
|
}
|
|
54507
|
+
async function createBitrefillInvoice({ invoice, source }) {
|
|
54508
|
+
const body = {
|
|
54509
|
+
provider: "bitrefill",
|
|
54510
|
+
bitrefill: {
|
|
54511
|
+
invoiceId: invoice.invoiceId,
|
|
54512
|
+
address: invoice.address,
|
|
54513
|
+
amount: invoice.amount,
|
|
54514
|
+
...invoice.expiresAt ? { expiresAt: invoice.expiresAt } : {}
|
|
54515
|
+
},
|
|
54516
|
+
source: { chainId: String(source.chainId), tokenSymbol: source.tokenSymbol },
|
|
54517
|
+
client: CLIENT_LABEL,
|
|
54518
|
+
attribution: { client: ATTRIBUTION_CLIENT }
|
|
54519
|
+
};
|
|
54520
|
+
return postJson(`${MPP_BASE}/create-invoice`, body);
|
|
54521
|
+
}
|
|
54428
54522
|
async function invoiceStatus({ linkId, rozoPaymentId }) {
|
|
54429
54523
|
const qs = new URLSearchParams();
|
|
54430
54524
|
if (linkId) qs.set("payment_id", linkId);
|
|
@@ -54734,7 +54828,15 @@ function checkPayable(statusResponse, now = Date.now()) {
|
|
|
54734
54828
|
}
|
|
54735
54829
|
return { ok: true, code: null, reason: null, derived };
|
|
54736
54830
|
}
|
|
54737
|
-
function classifyStatus({
|
|
54831
|
+
function classifyStatus({
|
|
54832
|
+
payment,
|
|
54833
|
+
routerState,
|
|
54834
|
+
coinbase,
|
|
54835
|
+
now = Date.now(),
|
|
54836
|
+
viewsFailed = false,
|
|
54837
|
+
provider = "coinbase"
|
|
54838
|
+
}) {
|
|
54839
|
+
const bitrefill = provider === "bitrefill";
|
|
54738
54840
|
const source = payment?.source || {};
|
|
54739
54841
|
const hasTx = Boolean(source.txHash);
|
|
54740
54842
|
const confirmed = Boolean(source.confirmedAt);
|
|
@@ -54767,7 +54869,7 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
|
|
|
54767
54869
|
{ escalate: true }
|
|
54768
54870
|
);
|
|
54769
54871
|
}
|
|
54770
|
-
if (routerStatus === "paid" || coinbase?.settled === true) {
|
|
54872
|
+
if (!bitrefill && (routerStatus === "paid" || coinbase?.settled === true)) {
|
|
54771
54873
|
return mk("settled", "Coinbase invoice settled by the funder wallet.", { terminal: true });
|
|
54772
54874
|
}
|
|
54773
54875
|
if (routerStatus === "failed_pay_invoice" || routerStatus === "failed_insufficient_balance") {
|
|
@@ -54789,6 +54891,17 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
|
|
|
54789
54891
|
escalate: true
|
|
54790
54892
|
});
|
|
54791
54893
|
}
|
|
54894
|
+
if (bitrefill) {
|
|
54895
|
+
const payoutTx = Boolean(payment?.destination?.txHash);
|
|
54896
|
+
if (payment?.status === "payment_payout_completed" || payment?.status === "payment_completed" && payoutTx) {
|
|
54897
|
+
return mk("settled", "USDC delivered to the Bitrefill invoice address on Base.", {
|
|
54898
|
+
terminal: true
|
|
54899
|
+
});
|
|
54900
|
+
}
|
|
54901
|
+
if (payment?.status === "payment_completed") {
|
|
54902
|
+
return mk("bridging", "Payment completed but no payout transaction is visible yet. Keep polling.");
|
|
54903
|
+
}
|
|
54904
|
+
}
|
|
54792
54905
|
switch (payment?.status) {
|
|
54793
54906
|
case "payment_unpaid": {
|
|
54794
54907
|
if (moneyDetected) {
|
|
@@ -55227,16 +55340,430 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
55227
55340
|
return main2(argv);
|
|
55228
55341
|
}
|
|
55229
55342
|
|
|
55343
|
+
// scripts/src/lib/bitrefill.mjs
|
|
55344
|
+
var PROVIDER_BITREFILL = "bitrefill";
|
|
55345
|
+
var BITREFILL_DESTINATION = Object.freeze({ chainId: "8453", tokenSymbol: "USDC" });
|
|
55346
|
+
var BITREFILL_MIN_EXPIRY_MS = 5 * 60 * 1e3;
|
|
55347
|
+
var BITREFILL_MIN_PAY_WINDOW_MS = 2 * 60 * 1e3;
|
|
55348
|
+
var BITREFILL_MAX_EXPIRY_AHEAD_MS = 30 * 60 * 1e3;
|
|
55349
|
+
var BITREFILL_ROUTER_HINTS = {
|
|
55350
|
+
BITREFILL_DISABLED: "Bitrefill payments are switched off on the router right now. Try again later.",
|
|
55351
|
+
BITREFILL_NOT_CONFIGURED: "The router is not configured for Bitrefill payments yet (503). Nothing was created; try again later.",
|
|
55352
|
+
RETRY_LATER: "The router is temporarily unable to create the order (503). Nothing was created; wait a minute and retry the same command.",
|
|
55353
|
+
INVALID_INPUT: "The router rejected the invoice details (400). Check the invoice id, address, amount and expiry against Bitrefill.",
|
|
55354
|
+
INVALID_ADDRESS: "The router rejected the receiving address. Copy it again from the Bitrefill invoice.",
|
|
55355
|
+
INVALID_AMOUNT: "The router rejected the amount. Use the exact USDC amount Bitrefill shows.",
|
|
55356
|
+
AMOUNT_OUT_OF_RANGE: "The amount is outside what the router accepts for Bitrefill invoices.",
|
|
55357
|
+
INVOICE_EXPIRING: "The invoice expires too soon (or too far out) for the router. Create a fresh Bitrefill invoice.",
|
|
55358
|
+
BLOCKED_ADDRESS: "The router refused this receiving address. Do not pay it; report it.",
|
|
55359
|
+
UNSUPPORTED_SOURCE: "The router does not accept this coin for Bitrefill invoices. Choose another coin."
|
|
55360
|
+
};
|
|
55361
|
+
function explainRouterError(err) {
|
|
55362
|
+
const hint = BITREFILL_ROUTER_HINTS[err?.code];
|
|
55363
|
+
if (!hint) return err;
|
|
55364
|
+
return new SkillError(err.code, `${hint}${err.message ? ` (router: ${err.message})` : ""}`, err.details);
|
|
55365
|
+
}
|
|
55366
|
+
function checkBitrefillExpiry(expiresAt, now = Date.now(), minMs = BITREFILL_MIN_PAY_WINDOW_MS) {
|
|
55367
|
+
const ms = parseDeadline(expiresAt);
|
|
55368
|
+
const base = { marginMs: minMs, deadlines: { invoiceMs: ms } };
|
|
55369
|
+
if (ms === null) {
|
|
55370
|
+
return { ...base, ok: false, code: "EXPIRY_UNPARSABLE", reason: "The Bitrefill invoice expiry is missing or unreadable.", effectiveDeadlineMs: null, msRemaining: null, msOfSlack: null };
|
|
55371
|
+
}
|
|
55372
|
+
const msRemaining = ms - now;
|
|
55373
|
+
const out2 = { ...base, effectiveDeadlineMs: ms, msRemaining, msOfSlack: msRemaining - minMs };
|
|
55374
|
+
if (msRemaining <= 0) return { ...out2, ok: false, code: "EXPIRED", reason: "The Bitrefill invoice has expired." };
|
|
55375
|
+
if (msRemaining < minMs) {
|
|
55376
|
+
return {
|
|
55377
|
+
...out2,
|
|
55378
|
+
ok: false,
|
|
55379
|
+
code: "INVOICE_EXPIRING",
|
|
55380
|
+
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.`
|
|
55381
|
+
};
|
|
55382
|
+
}
|
|
55383
|
+
return { ...out2, ok: true, code: null, reason: null };
|
|
55384
|
+
}
|
|
55385
|
+
var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
55386
|
+
var AMOUNT_RE = /^(?:0|[1-9]\d*)(?:\.\d{1,6})?$/;
|
|
55387
|
+
var INVOICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{2,127}$/;
|
|
55388
|
+
function validateBitrefillInput({ invoiceId, address, amount, expiresAt }, now = Date.now(), minMs = BITREFILL_MIN_EXPIRY_MS) {
|
|
55389
|
+
const id = String(invoiceId ?? "").trim();
|
|
55390
|
+
if (!INVOICE_ID_RE.test(id)) {
|
|
55391
|
+
throw new SkillError(
|
|
55392
|
+
"BAD_BITREFILL_INVOICE",
|
|
55393
|
+
'A Bitrefill invoice id is required (letters, digits, "-" or "_").'
|
|
55394
|
+
);
|
|
55395
|
+
}
|
|
55396
|
+
const addr = String(address ?? "").trim();
|
|
55397
|
+
if (!EVM_ADDRESS_RE.test(addr)) {
|
|
55398
|
+
throw new SkillError(
|
|
55399
|
+
"INVALID_ADDRESS",
|
|
55400
|
+
"The Bitrefill receiving address must be a Base (EVM) address: 0x followed by 40 hex characters."
|
|
55401
|
+
);
|
|
55402
|
+
}
|
|
55403
|
+
const amt = String(amount ?? "").trim();
|
|
55404
|
+
if (!AMOUNT_RE.test(amt) || normalizeDecimal(amt) === "0") {
|
|
55405
|
+
throw new SkillError(
|
|
55406
|
+
"INVALID_AMOUNT",
|
|
55407
|
+
"The amount must be a positive USDC amount with at most 6 decimals, exactly as Bitrefill shows it."
|
|
55408
|
+
);
|
|
55409
|
+
}
|
|
55410
|
+
if (expiresAt === void 0 || expiresAt === null || expiresAt === "") {
|
|
55411
|
+
throw new SkillError("INVOICE_EXPIRY_REQUIRED", "The Bitrefill invoice expiry is required (--expires-at <ISO>), exactly as Bitrefill shows it.");
|
|
55412
|
+
}
|
|
55413
|
+
const ms = parseDeadline(expiresAt);
|
|
55414
|
+
if (ms === null) {
|
|
55415
|
+
throw new SkillError("INVOICE_EXPIRING", "The invoice expiry could not be read.");
|
|
55416
|
+
}
|
|
55417
|
+
if (ms - now < minMs) {
|
|
55418
|
+
throw new SkillError(
|
|
55419
|
+
"INVOICE_EXPIRING",
|
|
55420
|
+
`The Bitrefill invoice expires in less than ${Math.round(minMs / 6e4)} minutes. Create a fresh invoice on Bitrefill.`,
|
|
55421
|
+
{ expiresAt: new Date(ms).toISOString() }
|
|
55422
|
+
);
|
|
55423
|
+
}
|
|
55424
|
+
if (ms - now > BITREFILL_MAX_EXPIRY_AHEAD_MS) {
|
|
55425
|
+
throw new SkillError(
|
|
55426
|
+
"INVOICE_EXPIRING",
|
|
55427
|
+
"The invoice expiry is more than 30 minutes away, which a Bitrefill invoice never is. Check --expires-at.",
|
|
55428
|
+
{ expiresAt: new Date(ms).toISOString() }
|
|
55429
|
+
);
|
|
55430
|
+
}
|
|
55431
|
+
const expiresIso = new Date(ms).toISOString();
|
|
55432
|
+
return { invoiceId: id, address: addr, amount: normalizeDecimal(amt), expiresAt: expiresIso };
|
|
55433
|
+
}
|
|
55434
|
+
var sameAddress = (a, b) => typeof a === "string" && typeof b === "string" && a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
55435
|
+
function verifyBitrefillCreate({ requested, created }) {
|
|
55436
|
+
const drift = [];
|
|
55437
|
+
const dest = created?.destination || {};
|
|
55438
|
+
if (created?.provider !== PROVIDER_BITREFILL) {
|
|
55439
|
+
drift.push({ field: "provider", requested: PROVIDER_BITREFILL, created: created?.provider ?? null });
|
|
55440
|
+
}
|
|
55441
|
+
if (String(created?.invoiceId ?? "") !== requested.invoiceId) {
|
|
55442
|
+
drift.push({ field: "invoiceId", requested: requested.invoiceId, created: created?.invoiceId ?? null });
|
|
55443
|
+
}
|
|
55444
|
+
if (!sameAddress(dest.address, requested.address)) {
|
|
55445
|
+
drift.push({ field: "destination.address", requested: requested.address, created: dest.address ?? null });
|
|
55446
|
+
}
|
|
55447
|
+
if (normalizeDecimal(dest.amount) !== normalizeDecimal(requested.amount)) {
|
|
55448
|
+
drift.push({ field: "destination.amount", requested: requested.amount, created: dest.amount ?? null });
|
|
55449
|
+
}
|
|
55450
|
+
if (String(dest.chainId ?? "") !== BITREFILL_DESTINATION.chainId) {
|
|
55451
|
+
drift.push({ field: "destination.chainId", requested: "8453", created: dest.chainId ?? null });
|
|
55452
|
+
}
|
|
55453
|
+
if (String(dest.tokenSymbol ?? "").toUpperCase() !== BITREFILL_DESTINATION.tokenSymbol) {
|
|
55454
|
+
drift.push({ field: "destination.tokenSymbol", requested: "USDC", created: dest.tokenSymbol ?? null });
|
|
55455
|
+
}
|
|
55456
|
+
return drift.length ? {
|
|
55457
|
+
ok: false,
|
|
55458
|
+
code: "BITREFILL_ECHO_MISMATCH",
|
|
55459
|
+
reason: "The router created an order that does not match the Bitrefill invoice. Refusing to continue.",
|
|
55460
|
+
drift
|
|
55461
|
+
} : { ok: true, code: null, reason: null, drift };
|
|
55462
|
+
}
|
|
55463
|
+
function verifyBitrefillDestination({ requested, payment }) {
|
|
55464
|
+
const dest = payment?.destination || {};
|
|
55465
|
+
const drift = [];
|
|
55466
|
+
if (String(dest.chainId ?? "") !== BITREFILL_DESTINATION.chainId) {
|
|
55467
|
+
drift.push({ field: "destination.chainId", requested: "8453", live: dest.chainId ?? null });
|
|
55468
|
+
}
|
|
55469
|
+
if (String(dest.tokenSymbol ?? "").toUpperCase() !== BITREFILL_DESTINATION.tokenSymbol) {
|
|
55470
|
+
drift.push({ field: "destination.tokenSymbol", requested: "USDC", live: dest.tokenSymbol ?? null });
|
|
55471
|
+
}
|
|
55472
|
+
if (normalizeDecimal(dest.amount) !== normalizeDecimal(requested.amount)) {
|
|
55473
|
+
drift.push({ field: "destination.amount", requested: requested.amount, live: dest.amount ?? null });
|
|
55474
|
+
}
|
|
55475
|
+
const liveAddress = dest.receiverAddress ?? dest.address ?? null;
|
|
55476
|
+
if (!sameAddress(liveAddress, requested.address)) {
|
|
55477
|
+
drift.push({ field: "destination.address", requested: requested.address, live: liveAddress });
|
|
55478
|
+
}
|
|
55479
|
+
return drift.length ? {
|
|
55480
|
+
ok: false,
|
|
55481
|
+
code: "BITREFILL_ECHO_MISMATCH",
|
|
55482
|
+
reason: "The live order does not deliver to the Bitrefill invoice. Refusing to continue.",
|
|
55483
|
+
drift,
|
|
55484
|
+
addressVerified: false
|
|
55485
|
+
} : { ok: true, code: null, reason: null, drift, addressVerified: true };
|
|
55486
|
+
}
|
|
55487
|
+
function duplicateInvoicePaymentId(err) {
|
|
55488
|
+
if (err?.code !== "DUPLICATE_INVOICE") return null;
|
|
55489
|
+
const body = err?.details?.body;
|
|
55490
|
+
const id = body?.rozoPaymentId ?? body?.existing?.rozoPaymentId ?? null;
|
|
55491
|
+
return typeof id === "string" && id ? id : null;
|
|
55492
|
+
}
|
|
55493
|
+
function providerFromPayment(payment) {
|
|
55494
|
+
if (!payment) return null;
|
|
55495
|
+
if (String(payment.orderId ?? payment.order_id ?? "").startsWith("bitrefill_")) return PROVIDER_BITREFILL;
|
|
55496
|
+
const meta = payment.metadata?.provider;
|
|
55497
|
+
if (meta) return String(meta).toLowerCase();
|
|
55498
|
+
return null;
|
|
55499
|
+
}
|
|
55500
|
+
function earliestExpiry(...values) {
|
|
55501
|
+
const ms = values.map((v) => parseDeadline(v)).filter((v) => v !== null);
|
|
55502
|
+
return ms.length ? new Date(Math.min(...ms)).toISOString() : null;
|
|
55503
|
+
}
|
|
55504
|
+
function intentBitrefillExpiry(payment) {
|
|
55505
|
+
return payment?.metadata?.bitrefillExpiresAt ?? null;
|
|
55506
|
+
}
|
|
55507
|
+
|
|
55508
|
+
// scripts/src/create-bitrefill-order.mjs
|
|
55509
|
+
var bitrefillRecordKey = (invoiceId) => `bitrefill:${invoiceId}`;
|
|
55510
|
+
function confirmTier2(usdAmount) {
|
|
55511
|
+
const usd = Number(usdAmount);
|
|
55512
|
+
if (!Number.isFinite(usd)) return "explicit";
|
|
55513
|
+
if (usd <= 1) return "silent";
|
|
55514
|
+
if (usd <= 10) return "one-line";
|
|
55515
|
+
return "explicit";
|
|
55516
|
+
}
|
|
55517
|
+
function abort(step2, code, message, extra = {}, guidance = "Do NOT fund this order.") {
|
|
55518
|
+
emit(
|
|
55519
|
+
{ success: false, step: step2, provider: PROVIDER_BITREFILL, error: { code, message, ...extra.details ? { details: extra.details } : {} }, ...extra.fields, guidance },
|
|
55520
|
+
EXIT_ERROR
|
|
55521
|
+
);
|
|
55522
|
+
}
|
|
55523
|
+
async function main3(argv) {
|
|
55524
|
+
const args = parseArgs(argv);
|
|
55525
|
+
const chainId = String(args.chain ?? "").trim();
|
|
55526
|
+
const tokenSymbol = String(args.token ?? "").trim().toUpperCase();
|
|
55527
|
+
if (!chainId || !tokenSymbol) usage("Required: --chain <chainId> --token <SYMBOL>.");
|
|
55528
|
+
if (!isSupportedSource(chainId, tokenSymbol)) {
|
|
55529
|
+
throw new SkillError("UNSUPPORTED_SOURCE", `${tokenSymbol} on ${chainName(chainId)} is not a supported source.`, {
|
|
55530
|
+
supported: SUPPORTED_SOURCES
|
|
55531
|
+
});
|
|
55532
|
+
}
|
|
55533
|
+
const requested = { chainId, tokenSymbol };
|
|
55534
|
+
const confirmed = Boolean(args.confirm);
|
|
55535
|
+
const rawInvoice = {
|
|
55536
|
+
invoiceId: args["invoice-id"],
|
|
55537
|
+
address: args.to,
|
|
55538
|
+
amount: args.amount,
|
|
55539
|
+
expiresAt: args["expires-at"]
|
|
55540
|
+
};
|
|
55541
|
+
const invoice = validateBitrefillInput(rawInvoice, Date.now(), BITREFILL_MIN_PAY_WINDOW_MS);
|
|
55542
|
+
const priorRecord = findByLinkId(bitrefillRecordKey(invoice.invoiceId));
|
|
55543
|
+
if (!priorRecord) validateBitrefillInput(rawInvoice);
|
|
55544
|
+
let blacklist;
|
|
55545
|
+
try {
|
|
55546
|
+
blacklist = loadBlacklist();
|
|
55547
|
+
} catch (err) {
|
|
55548
|
+
throw new SkillError("BLACKLIST_UNAVAILABLE", `Compromised-address list unusable: ${err.message} Refusing to proceed.`);
|
|
55549
|
+
}
|
|
55550
|
+
assertNotBlacklisted([{ address: invoice.address, family: "evm", role: "Bitrefill receiving address" }], blacklist);
|
|
55551
|
+
let created = null;
|
|
55552
|
+
let rozoPaymentId;
|
|
55553
|
+
let resumed = false;
|
|
55554
|
+
try {
|
|
55555
|
+
created = await createBitrefillInvoice({ invoice, source: requested });
|
|
55556
|
+
} catch (err) {
|
|
55557
|
+
const existing = duplicateInvoicePaymentId(err);
|
|
55558
|
+
if (!existing) throw explainRouterError(err);
|
|
55559
|
+
rozoPaymentId = assertRozoPaymentId(existing);
|
|
55560
|
+
resumed = true;
|
|
55561
|
+
}
|
|
55562
|
+
if (created) {
|
|
55563
|
+
if (created.ok === false || !created.rozoPaymentId) {
|
|
55564
|
+
throw new SkillError(created?.error || "CREATE_FAILED", created?.message || "create-invoice returned no rozoPaymentId.");
|
|
55565
|
+
}
|
|
55566
|
+
rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
|
|
55567
|
+
const echo = verifyBitrefillCreate({ requested: invoice, created });
|
|
55568
|
+
if (!echo.ok) {
|
|
55569
|
+
abort("verify-create", echo.code, echo.reason, {
|
|
55570
|
+
details: { drift: echo.drift },
|
|
55571
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55572
|
+
}, "The order exists but was NOT validated. Do not fund it. Let it expire unfunded.");
|
|
55573
|
+
}
|
|
55574
|
+
}
|
|
55575
|
+
const local = readState(rozoPaymentId);
|
|
55576
|
+
if (local?.bitrefill) {
|
|
55577
|
+
const prior = verifyBitrefillCreate({
|
|
55578
|
+
requested: invoice,
|
|
55579
|
+
created: { provider: PROVIDER_BITREFILL, invoiceId: local.bitrefill.invoiceId, destination: { chainId: "8453", tokenSymbol: "USDC", address: local.bitrefill.address, amount: local.bitrefill.amount } }
|
|
55580
|
+
});
|
|
55581
|
+
if (!prior.ok) {
|
|
55582
|
+
abort("verify-local", prior.code, "This invoice id was recorded earlier with a different address or amount.", {
|
|
55583
|
+
details: { drift: prior.drift },
|
|
55584
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55585
|
+
});
|
|
55586
|
+
}
|
|
55587
|
+
}
|
|
55588
|
+
const payment = await getPayment(rozoPaymentId);
|
|
55589
|
+
const source = payment?.source || {};
|
|
55590
|
+
const dest = verifyBitrefillDestination({ requested: invoice, payment });
|
|
55591
|
+
if (!dest.ok) {
|
|
55592
|
+
abort("verify-destination", dest.code, dest.reason, {
|
|
55593
|
+
details: { drift: dest.drift },
|
|
55594
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55595
|
+
});
|
|
55596
|
+
}
|
|
55597
|
+
const guard = reuseGuard({ payment, requested, reused: resumed });
|
|
55598
|
+
if (!guard.ok) {
|
|
55599
|
+
emit(
|
|
55600
|
+
{
|
|
55601
|
+
success: false,
|
|
55602
|
+
step: "reuse-guard",
|
|
55603
|
+
provider: PROVIDER_BITREFILL,
|
|
55604
|
+
error: { code: guard.code, message: guard.reason, details: guard.evidence },
|
|
55605
|
+
invoiceId: invoice.invoiceId,
|
|
55606
|
+
rozoPaymentId,
|
|
55607
|
+
moneyDetected: guard.moneyDetected,
|
|
55608
|
+
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."
|
|
55609
|
+
},
|
|
55610
|
+
EXIT_ERROR
|
|
55611
|
+
);
|
|
55612
|
+
}
|
|
55613
|
+
const invoiceExpiry = earliestExpiry(
|
|
55614
|
+
invoice.expiresAt,
|
|
55615
|
+
local?.bitrefill?.expiresAt,
|
|
55616
|
+
priorRecord?.bitrefill?.expiresAt,
|
|
55617
|
+
intentBitrefillExpiry(payment)
|
|
55618
|
+
);
|
|
55619
|
+
const lightning = String(source.chainId) === "lightning";
|
|
55620
|
+
const expiry = checkBitrefillExpiry(invoiceExpiry, Date.now());
|
|
55621
|
+
if (!expiry.ok) {
|
|
55622
|
+
abort("expiry-guard", expiry.code, expiry.reason, {
|
|
55623
|
+
details: expiry,
|
|
55624
|
+
fields: { rozoPaymentId, invoiceId: invoice.invoiceId }
|
|
55625
|
+
}, "Not enough time remains to fund and settle safely. Create a fresh Bitrefill invoice. Do not fund this order.");
|
|
55626
|
+
}
|
|
55627
|
+
try {
|
|
55628
|
+
assertNotBlacklisted([{ address: source.receiverAddress, family: chainFamily(source.chainId), role: "deposit address" }], blacklist);
|
|
55629
|
+
} catch (err) {
|
|
55630
|
+
abort("blacklist", err.code, err.message, { fields: { rozoPaymentId } }, "Do NOT send anything. Report this to the operator immediately.");
|
|
55631
|
+
}
|
|
55632
|
+
const bitrefill = { ...invoice, expiresAt: invoiceExpiry };
|
|
55633
|
+
createOrderRecord({
|
|
55634
|
+
rozoPaymentId,
|
|
55635
|
+
provider: PROVIDER_BITREFILL,
|
|
55636
|
+
bitrefill,
|
|
55637
|
+
linkId: bitrefillRecordKey(invoice.invoiceId),
|
|
55638
|
+
merchant: "Bitrefill",
|
|
55639
|
+
invoiceAmount: invoice.amount,
|
|
55640
|
+
source: { chainId: source.chainId, tokenSymbol: source.tokenSymbol },
|
|
55641
|
+
receiverAddress: source.receiverAddress,
|
|
55642
|
+
receiverMemo: source.receiverMemo ?? null,
|
|
55643
|
+
amount: source.amount,
|
|
55644
|
+
amountUnit: source.amountUnit ?? null,
|
|
55645
|
+
expiresAt: payment?.expiresAt ?? null
|
|
55646
|
+
});
|
|
55647
|
+
const tier = confirmTier2(invoice.amount);
|
|
55648
|
+
if (confirmed) recordConfirmation(rozoPaymentId, { source, invoiceAmount: invoice.amount, tier });
|
|
55649
|
+
const family = chainFamily(source.chainId);
|
|
55650
|
+
const depositInfo = guard.deposit;
|
|
55651
|
+
const bolt11 = source.lnInvoice ?? payment?.lnInvoice ?? null;
|
|
55652
|
+
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.";
|
|
55653
|
+
emit({
|
|
55654
|
+
success: true,
|
|
55655
|
+
step: "create-order",
|
|
55656
|
+
provider: PROVIDER_BITREFILL,
|
|
55657
|
+
confirmed,
|
|
55658
|
+
reused: resumed,
|
|
55659
|
+
reusedNote: resumed ? `An order for this Bitrefill invoice already existed (${rozoPaymentId}); it was resumed. Nothing new was created.` : null,
|
|
55660
|
+
orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
|
|
55661
|
+
invoiceId: invoice.invoiceId,
|
|
55662
|
+
rozoPaymentId,
|
|
55663
|
+
merchant: "Bitrefill",
|
|
55664
|
+
invoice: { amount: invoice.amount, currency: "USDC" },
|
|
55665
|
+
bitrefill: {
|
|
55666
|
+
invoiceId: invoice.invoiceId,
|
|
55667
|
+
addressMasked: maskAddress2(invoice.address),
|
|
55668
|
+
amount: invoice.amount,
|
|
55669
|
+
chain: "Base",
|
|
55670
|
+
token: "USDC",
|
|
55671
|
+
expiresAt: bitrefill.expiresAt,
|
|
55672
|
+
destinationAddressVerifiedOnIntent: dest.addressVerified
|
|
55673
|
+
},
|
|
55674
|
+
deposit: confirmed ? {
|
|
55675
|
+
chainId: source.chainId,
|
|
55676
|
+
chain: chainName(source.chainId),
|
|
55677
|
+
tokenSymbol: source.tokenSymbol,
|
|
55678
|
+
tokenAddress: source.tokenAddress || null,
|
|
55679
|
+
receiverAddress: lightning ? null : source.receiverAddress,
|
|
55680
|
+
receiverMemo: source.receiverMemo ?? null,
|
|
55681
|
+
receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
55682
|
+
amount: source.amount,
|
|
55683
|
+
amountUnit: source.amountUnit ?? null,
|
|
55684
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
55685
|
+
lnInvoice: bolt11 || null,
|
|
55686
|
+
payTo: depositInfo.payTo,
|
|
55687
|
+
expiresAt: payment?.expiresAt ?? null,
|
|
55688
|
+
expiresIn: formatRemaining(expiry.msRemaining)
|
|
55689
|
+
} : null,
|
|
55690
|
+
depositWithheld: !confirmed,
|
|
55691
|
+
display: {
|
|
55692
|
+
chain: chainName(source.chainId),
|
|
55693
|
+
token: source.tokenSymbol,
|
|
55694
|
+
amount: formatAmount(source),
|
|
55695
|
+
isSats: isSatsUnit(source.amountUnit),
|
|
55696
|
+
payToMasked: maskAddress2(depositInfo.payTo),
|
|
55697
|
+
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
55698
|
+
hasMemo: Boolean(source.receiverMemo),
|
|
55699
|
+
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
55700
|
+
memoRequirement
|
|
55701
|
+
},
|
|
55702
|
+
expiry: {
|
|
55703
|
+
intentExpiresAt: payment?.expiresAt ?? null,
|
|
55704
|
+
invoiceExpiresAt: bitrefill.expiresAt,
|
|
55705
|
+
effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
|
|
55706
|
+
expiresIn: formatRemaining(expiry.msRemaining),
|
|
55707
|
+
msRemaining: expiry.msRemaining,
|
|
55708
|
+
marginMinutes: Math.round(expiry.marginMs / 6e4),
|
|
55709
|
+
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
|
|
55710
|
+
},
|
|
55711
|
+
confirmation: {
|
|
55712
|
+
required: tier,
|
|
55713
|
+
satisfied: confirmed,
|
|
55714
|
+
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.",
|
|
55715
|
+
warnings: [
|
|
55716
|
+
"Wrong token, wrong network, or wrong amount is usually unrecoverable.",
|
|
55717
|
+
memoRequirement,
|
|
55718
|
+
"Send exactly once. A second send to the same one-time address is not guaranteed to be credited.",
|
|
55719
|
+
"The deposit amount can exceed the invoice: it includes the bridge and network fees."
|
|
55720
|
+
]
|
|
55721
|
+
},
|
|
55722
|
+
blacklist: { checked: true, addressesInList: blacklist.entries.length, digest: blacklist.provenance.addressesSha256 },
|
|
55723
|
+
nextStep: confirmed ? {
|
|
55724
|
+
modeA: "Give the user the `deposit` block, then poll: rozo-checkout status <rozoPaymentId>",
|
|
55725
|
+
modeB: family === "evm" || family === "solana" ? "rozo-checkout pay \u2026 --send" : "not available for this chain \u2014 pay from a wallet (Mode A)"
|
|
55726
|
+
} : { confirm: "Re-run this exact command with --confirm once the user has said yes." }
|
|
55727
|
+
});
|
|
55728
|
+
}
|
|
55729
|
+
async function run3(argv = process.argv.slice(2)) {
|
|
55730
|
+
return main3(argv);
|
|
55731
|
+
}
|
|
55732
|
+
|
|
55230
55733
|
// scripts/src/status.mjs
|
|
55231
55734
|
var POLL_INTERVAL_MS = 1e4;
|
|
55232
55735
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
55233
|
-
|
|
55736
|
+
function resolveProvider(explicit, rozoPaymentId) {
|
|
55737
|
+
if (explicit) return String(explicit).toLowerCase();
|
|
55738
|
+
if (rozoPaymentId && isRozoPaymentId(rozoPaymentId)) {
|
|
55739
|
+
try {
|
|
55740
|
+
return readState(rozoPaymentId)?.provider ?? null;
|
|
55741
|
+
} catch {
|
|
55742
|
+
return null;
|
|
55743
|
+
}
|
|
55744
|
+
}
|
|
55745
|
+
return null;
|
|
55746
|
+
}
|
|
55747
|
+
async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
55748
|
+
let provider = resolveProvider(explicitProvider, rozoPaymentId);
|
|
55749
|
+
let prefetched = null;
|
|
55750
|
+
if (!provider && rozoPaymentId && isRozoPaymentId(rozoPaymentId)) {
|
|
55751
|
+
try {
|
|
55752
|
+
prefetched = await getPayment(rozoPaymentId);
|
|
55753
|
+
provider = providerFromPayment(prefetched);
|
|
55754
|
+
} catch {
|
|
55755
|
+
prefetched = null;
|
|
55756
|
+
}
|
|
55757
|
+
}
|
|
55758
|
+
provider = provider === "bitrefill" ? "bitrefill" : "coinbase";
|
|
55234
55759
|
let status = null;
|
|
55235
55760
|
let statusError = null;
|
|
55236
|
-
|
|
55237
|
-
|
|
55238
|
-
|
|
55239
|
-
|
|
55761
|
+
if (provider !== "bitrefill") {
|
|
55762
|
+
try {
|
|
55763
|
+
status = await invoiceStatus({ linkId, rozoPaymentId });
|
|
55764
|
+
} catch (err) {
|
|
55765
|
+
statusError = { code: err.code, message: err.message };
|
|
55766
|
+
}
|
|
55240
55767
|
}
|
|
55241
55768
|
let id = rozoPaymentId || status?.rozo_payment_id || null;
|
|
55242
55769
|
let idSource = rozoPaymentId ? "argument" : status?.rozo_payment_id ? "invoice-status" : null;
|
|
@@ -55249,33 +55776,61 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55249
55776
|
}
|
|
55250
55777
|
let payment = null;
|
|
55251
55778
|
let paymentError = null;
|
|
55252
|
-
if (
|
|
55779
|
+
if (prefetched && id === rozoPaymentId) {
|
|
55780
|
+
payment = prefetched;
|
|
55781
|
+
} else if (id && isRozoPaymentId(id)) {
|
|
55253
55782
|
try {
|
|
55254
55783
|
payment = await getPayment(id);
|
|
55255
55784
|
} catch (err) {
|
|
55256
55785
|
paymentError = { code: err.code, message: err.message };
|
|
55257
55786
|
}
|
|
55258
55787
|
}
|
|
55259
|
-
const viewsFailed = Boolean(statusError) && !payment;
|
|
55788
|
+
const viewsFailed = provider === "bitrefill" ? !payment : Boolean(statusError) && !payment;
|
|
55260
55789
|
const verdict = classifyStatus({
|
|
55261
55790
|
payment: payment || status?.rozoPayment || {},
|
|
55262
55791
|
routerState: status?.routerState,
|
|
55263
55792
|
coinbase: status?.coinbase,
|
|
55264
|
-
viewsFailed
|
|
55793
|
+
viewsFailed,
|
|
55794
|
+
provider
|
|
55265
55795
|
});
|
|
55266
55796
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
55797
|
+
let bitrefillExpiry = null;
|
|
55798
|
+
let state = verdict.state;
|
|
55799
|
+
let terminal = verdict.terminal;
|
|
55800
|
+
let detail = verdict.detail;
|
|
55801
|
+
if (provider === "bitrefill") {
|
|
55802
|
+
let local = null;
|
|
55803
|
+
try {
|
|
55804
|
+
local = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
55805
|
+
} catch {
|
|
55806
|
+
local = null;
|
|
55807
|
+
}
|
|
55808
|
+
bitrefillExpiry = earliestExpiry(local?.bitrefill?.expiresAt, intentBitrefillExpiry(payment));
|
|
55809
|
+
if (!verdict.moneyDetected && ["awaiting_deposit", "expired_unfunded"].includes(verdict.state)) {
|
|
55810
|
+
const ms = bitrefillExpiry ? Date.parse(bitrefillExpiry) : NaN;
|
|
55811
|
+
if (Number.isFinite(ms) && ms <= Date.now()) {
|
|
55812
|
+
state = "invoice_expired";
|
|
55813
|
+
terminal = true;
|
|
55814
|
+
detail = "The Bitrefill invoice has expired. Do NOT fund this order; create a fresh invoice.";
|
|
55815
|
+
} else if (!bitrefillExpiry && verdict.state === "awaiting_deposit") {
|
|
55816
|
+
state = "deadline_unknown";
|
|
55817
|
+
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.";
|
|
55818
|
+
}
|
|
55819
|
+
}
|
|
55820
|
+
}
|
|
55267
55821
|
return {
|
|
55822
|
+
provider,
|
|
55268
55823
|
rozoPaymentId: id,
|
|
55269
55824
|
rozoPaymentIdSource: idSource,
|
|
55270
55825
|
// Without the authoritative payment object we are reading a partial view.
|
|
55271
55826
|
authoritativeView: Boolean(payment),
|
|
55272
55827
|
linkId: linkId || status?.pl_id || null,
|
|
55273
|
-
state
|
|
55828
|
+
state,
|
|
55274
55829
|
unknown: verdict.unknown,
|
|
55275
55830
|
moneyDetected: verdict.moneyDetected,
|
|
55276
|
-
terminal
|
|
55831
|
+
terminal,
|
|
55277
55832
|
escalate: verdict.escalate,
|
|
55278
|
-
detail
|
|
55833
|
+
detail,
|
|
55279
55834
|
backend: {
|
|
55280
55835
|
paymentStatus: payment?.status ?? status?.rozoPayment?.status ?? null,
|
|
55281
55836
|
routerStatus: verdict.routerStatus,
|
|
@@ -55292,7 +55847,10 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55292
55847
|
chain: source.chainId ? chainName(source.chainId) : null
|
|
55293
55848
|
},
|
|
55294
55849
|
expiry: (() => {
|
|
55295
|
-
|
|
55850
|
+
if (provider === "bitrefill" && !bitrefillExpiry) {
|
|
55851
|
+
return { expiresAt: null, expiresIn: null, msRemaining: null, deadlineUnknown: true };
|
|
55852
|
+
}
|
|
55853
|
+
const iso = provider === "bitrefill" ? bitrefillExpiry : payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
|
|
55296
55854
|
if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
|
|
55297
55855
|
const ms = Date.parse(iso) - Date.now();
|
|
55298
55856
|
return {
|
|
@@ -55308,25 +55866,32 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
55308
55866
|
errors: [statusError, paymentError].filter(Boolean)
|
|
55309
55867
|
};
|
|
55310
55868
|
}
|
|
55311
|
-
async function
|
|
55869
|
+
async function main4(argv) {
|
|
55312
55870
|
const args = parseArgs(argv);
|
|
55313
55871
|
const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
|
|
55314
55872
|
const linkId = args["link-id"] || (!rozoPaymentId ? args._[0] : null);
|
|
55873
|
+
const provider = args.provider ? String(args.provider) : null;
|
|
55874
|
+
if (provider && provider !== "bitrefill" && provider !== "coinbase") {
|
|
55875
|
+
usage("--provider must be coinbase or bitrefill");
|
|
55876
|
+
}
|
|
55877
|
+
if (provider === "bitrefill" && !rozoPaymentId) {
|
|
55878
|
+
usage("A Bitrefill order is tracked by --rozo-payment-id <uuid>.");
|
|
55879
|
+
}
|
|
55315
55880
|
if (!rozoPaymentId && !linkId) {
|
|
55316
55881
|
usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
|
|
55317
55882
|
}
|
|
55318
55883
|
const watch = Boolean(args.watch);
|
|
55319
55884
|
const timeoutMs = Math.max(0, Number(args.timeout ?? 600) * 1e3);
|
|
55320
55885
|
const deadline = Date.now() + timeoutMs;
|
|
55321
|
-
let result = await snapshot({ rozoPaymentId, linkId });
|
|
55886
|
+
let result = await snapshot({ rozoPaymentId, linkId, provider });
|
|
55322
55887
|
const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
|
|
55323
55888
|
while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
|
|
55324
55889
|
await sleep(POLL_INTERVAL_MS);
|
|
55325
|
-
const next = await snapshot({ rozoPaymentId: result.rozoPaymentId || rozoPaymentId, linkId });
|
|
55890
|
+
const next = await snapshot({ rozoPaymentId: result.rozoPaymentId || rozoPaymentId, linkId, provider });
|
|
55326
55891
|
if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
|
|
55327
55892
|
result = next;
|
|
55328
55893
|
}
|
|
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.";
|
|
55894
|
+
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
55895
|
const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
|
|
55331
55896
|
const failed = result.escalate || result.unknown || !result.authoritativeView;
|
|
55332
55897
|
emit(
|
|
@@ -55341,8 +55906,8 @@ async function main3(argv) {
|
|
|
55341
55906
|
failed ? EXIT_ERROR : unresolved ? EXIT_UNCONFIRMED : 0
|
|
55342
55907
|
);
|
|
55343
55908
|
}
|
|
55344
|
-
async function
|
|
55345
|
-
return
|
|
55909
|
+
async function run4(argv = process.argv.slice(2)) {
|
|
55910
|
+
return main4(argv);
|
|
55346
55911
|
}
|
|
55347
55912
|
|
|
55348
55913
|
// scripts/src/lib/passphrase.mjs
|
|
@@ -55392,6 +55957,16 @@ function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
|
55392
55957
|
}
|
|
55393
55958
|
|
|
55394
55959
|
// scripts/src/lib/presend.mjs
|
|
55960
|
+
function assertBitrefillLive(state, payment) {
|
|
55961
|
+
if (!state.bitrefill) {
|
|
55962
|
+
throw new SkillError("BITREFILL_ECHO_MISMATCH", "This Bitrefill order has no recorded invoice. Refusing to send.");
|
|
55963
|
+
}
|
|
55964
|
+
const dest = verifyBitrefillDestination({ requested: state.bitrefill, payment });
|
|
55965
|
+
if (!dest.ok) throw new SkillError(dest.code, dest.reason, { drift: dest.drift });
|
|
55966
|
+
const expiry = checkBitrefillExpiry(state.bitrefill.expiresAt, Date.now());
|
|
55967
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
55968
|
+
return expiry;
|
|
55969
|
+
}
|
|
55395
55970
|
async function preflight({
|
|
55396
55971
|
rozoPaymentId,
|
|
55397
55972
|
expectFamily,
|
|
@@ -55477,16 +56052,22 @@ async function preflight({
|
|
|
55477
56052
|
if ((source.receiverMemo ?? null) !== (state.receiverMemo ?? null)) {
|
|
55478
56053
|
throw new SkillError("DEPOSIT_CHANGED", "The live deposit memo differs from the recorded one.");
|
|
55479
56054
|
}
|
|
55480
|
-
|
|
55481
|
-
|
|
55482
|
-
|
|
55483
|
-
|
|
55484
|
-
|
|
55485
|
-
|
|
55486
|
-
|
|
55487
|
-
|
|
55488
|
-
|
|
55489
|
-
|
|
56055
|
+
let statusNow = null;
|
|
56056
|
+
let expiry;
|
|
56057
|
+
if (state.provider === "bitrefill") {
|
|
56058
|
+
expiry = assertBitrefillLive(state, payment);
|
|
56059
|
+
} else {
|
|
56060
|
+
statusNow = await invoiceStatus({ linkId: state.linkId });
|
|
56061
|
+
expiry = checkExpiry({
|
|
56062
|
+
now: Date.now(),
|
|
56063
|
+
chainId: source.chainId,
|
|
56064
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56065
|
+
coinbaseExpiry: statusNow?.coinbase?.preApprovalExpiry
|
|
56066
|
+
});
|
|
56067
|
+
if (!expiry.ok) throw new SkillError(expiry.code, expiry.reason, expiry);
|
|
56068
|
+
const payable = checkPayable(statusNow, Date.now());
|
|
56069
|
+
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
56070
|
+
}
|
|
55490
56071
|
assertNotBlacklisted(
|
|
55491
56072
|
[
|
|
55492
56073
|
{ address: source.receiverAddress, family, role: "deposit address" },
|
|
@@ -55505,7 +56086,17 @@ async function preflight({
|
|
|
55505
56086
|
statusNow
|
|
55506
56087
|
};
|
|
55507
56088
|
}
|
|
55508
|
-
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt }) {
|
|
56089
|
+
async function finalPayabilityCheck({ linkId, chainId, intentExpiresAt, state = null, rozoPaymentId = null }) {
|
|
56090
|
+
if (state?.provider === "bitrefill") {
|
|
56091
|
+
const id = rozoPaymentId ?? state.rozoPaymentId;
|
|
56092
|
+
const payment = await getPayment(id);
|
|
56093
|
+
const guard = reuseGuard({ payment, requested: state.source });
|
|
56094
|
+
if (!guard.ok) {
|
|
56095
|
+
throw new SkillError(guard.code, guard.reason, { ...guard.evidence, moneyDetected: guard.moneyDetected });
|
|
56096
|
+
}
|
|
56097
|
+
const expiry2 = assertBitrefillLive(state, payment);
|
|
56098
|
+
return { statusNow: null, payable: { ok: true }, expiry: expiry2 };
|
|
56099
|
+
}
|
|
55509
56100
|
const statusNow = await invoiceStatus({ linkId });
|
|
55510
56101
|
const payable = checkPayable(statusNow, Date.now());
|
|
55511
56102
|
if (!payable.ok) throw new SkillError(payable.code, payable.reason, payable.derived);
|
|
@@ -55751,7 +56342,7 @@ function chainDef(chainId, rpcUrl) {
|
|
|
55751
56342
|
rpcUrls: { default: { http: [rpcUrl] } }
|
|
55752
56343
|
});
|
|
55753
56344
|
}
|
|
55754
|
-
async function
|
|
56345
|
+
async function main5(argv) {
|
|
55755
56346
|
const args = parseArgs(argv);
|
|
55756
56347
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
55757
56348
|
assertNoTrackedDotEnv();
|
|
@@ -55869,7 +56460,9 @@ async function main4(argv) {
|
|
|
55869
56460
|
await finalPayabilityCheck({
|
|
55870
56461
|
linkId: state.linkId,
|
|
55871
56462
|
chainId: source.chainId,
|
|
55872
|
-
intentExpiresAt: payment?.expiresAt
|
|
56463
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56464
|
+
state,
|
|
56465
|
+
rozoPaymentId
|
|
55873
56466
|
});
|
|
55874
56467
|
claimSend(
|
|
55875
56468
|
rozoPaymentId,
|
|
@@ -55977,8 +56570,8 @@ async function main4(argv) {
|
|
|
55977
56570
|
outcome.exitCode
|
|
55978
56571
|
);
|
|
55979
56572
|
}
|
|
55980
|
-
async function
|
|
55981
|
-
return
|
|
56573
|
+
async function run5(argv = process.argv.slice(2)) {
|
|
56574
|
+
return main5(argv);
|
|
55982
56575
|
}
|
|
55983
56576
|
|
|
55984
56577
|
// scripts/src/send-sol.mjs
|
|
@@ -56048,7 +56641,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
|
|
|
56048
56641
|
var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
56049
56642
|
var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
|
|
56050
56643
|
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
56051
|
-
async function
|
|
56644
|
+
async function main6(argv) {
|
|
56052
56645
|
const args = parseArgs(argv);
|
|
56053
56646
|
const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
|
|
56054
56647
|
assertNoTrackedDotEnv();
|
|
@@ -56197,7 +56790,9 @@ async function main5(argv) {
|
|
|
56197
56790
|
await finalPayabilityCheck({
|
|
56198
56791
|
linkId: state.linkId,
|
|
56199
56792
|
chainId: source.chainId,
|
|
56200
|
-
intentExpiresAt: payment?.expiresAt
|
|
56793
|
+
intentExpiresAt: payment?.expiresAt,
|
|
56794
|
+
state,
|
|
56795
|
+
rozoPaymentId
|
|
56201
56796
|
});
|
|
56202
56797
|
claimSend(
|
|
56203
56798
|
rozoPaymentId,
|
|
@@ -56339,8 +56934,8 @@ function base58Encode(buf) {
|
|
|
56339
56934
|
}
|
|
56340
56935
|
return out2;
|
|
56341
56936
|
}
|
|
56342
|
-
async function
|
|
56343
|
-
return
|
|
56937
|
+
async function run6(argv = process.argv.slice(2)) {
|
|
56938
|
+
return main6(argv);
|
|
56344
56939
|
}
|
|
56345
56940
|
|
|
56346
56941
|
// scripts/src/cli.mjs
|
|
@@ -56514,9 +57109,10 @@ async function cmdQuote(opts) {
|
|
|
56514
57109
|
}
|
|
56515
57110
|
async function cmdStatus(opts) {
|
|
56516
57111
|
const argv = [...targetToArgs(opts.target), "--timeout", String(opts.timeout ?? 600)];
|
|
57112
|
+
if (opts.provider) argv.push("--provider", opts.provider);
|
|
56517
57113
|
if (opts.watch) argv.push("--watch");
|
|
56518
57114
|
if (!opts.json) out(dim(" Checking payment status\u2026 (no money moves)"));
|
|
56519
|
-
const { payload, exitCode } = await step(
|
|
57115
|
+
const { payload, exitCode } = await step(run4, argv);
|
|
56520
57116
|
if (opts.json) {
|
|
56521
57117
|
printJson(payload);
|
|
56522
57118
|
return exitCode;
|
|
@@ -56546,7 +57142,7 @@ function presetFor(chainId, tokenSymbol) {
|
|
|
56546
57142
|
function stateLabel(state) {
|
|
56547
57143
|
if (state === "settled") return green(bold(state));
|
|
56548
57144
|
if (["underpaid", "stuck_after_payment", "unknown"].includes(state)) return red(bold(state));
|
|
56549
|
-
if (
|
|
57145
|
+
if (["expired_unfunded", "invoice_expired", "deadline_unknown"].includes(state)) return yellow(bold(state));
|
|
56550
57146
|
return bold(state);
|
|
56551
57147
|
}
|
|
56552
57148
|
async function cmdPay(opts) {
|
|
@@ -56555,7 +57151,9 @@ async function cmdPay(opts) {
|
|
|
56555
57151
|
let chosenPreset = null;
|
|
56556
57152
|
let payer = null;
|
|
56557
57153
|
let invoiceUsd = null;
|
|
56558
|
-
if (
|
|
57154
|
+
if (opts.bitrefill) {
|
|
57155
|
+
invoiceUsd = Number(opts.bitrefill.amount);
|
|
57156
|
+
} else if (!opts.source || opts.payer) {
|
|
56559
57157
|
if (!opts.json) out(dim(" Reading the payment link\u2026"));
|
|
56560
57158
|
const q = await step(run, ["--url", opts.target]);
|
|
56561
57159
|
if (!q.payload.success) {
|
|
@@ -56605,7 +57203,20 @@ async function cmdPay(opts) {
|
|
|
56605
57203
|
payer = { address: opts.payer, family };
|
|
56606
57204
|
}
|
|
56607
57205
|
const { chainId, tokenSymbol } = opts.source;
|
|
56608
|
-
const
|
|
57206
|
+
const createFlow = opts.bitrefill ? run3 : run2;
|
|
57207
|
+
const baseArgs = opts.bitrefill ? [
|
|
57208
|
+
"--invoice-id",
|
|
57209
|
+
opts.bitrefill.invoiceId,
|
|
57210
|
+
"--to",
|
|
57211
|
+
opts.bitrefill.address,
|
|
57212
|
+
"--amount",
|
|
57213
|
+
opts.bitrefill.amount,
|
|
57214
|
+
...opts.bitrefill.expiresAt ? ["--expires-at", opts.bitrefill.expiresAt] : [],
|
|
57215
|
+
"--chain",
|
|
57216
|
+
chainId,
|
|
57217
|
+
"--token",
|
|
57218
|
+
tokenSymbol
|
|
57219
|
+
] : ["--url", opts.target, "--chain", chainId, "--token", tokenSymbol];
|
|
56609
57220
|
if (opts.send) {
|
|
56610
57221
|
const family = chainFamily(chainId);
|
|
56611
57222
|
if (family === "evm" || family === "solana") {
|
|
@@ -56618,7 +57229,7 @@ async function cmdPay(opts) {
|
|
|
56618
57229
|
}
|
|
56619
57230
|
}
|
|
56620
57231
|
if (!opts.json) out(dim(" Creating a one-time order\u2026 (no money moves)"));
|
|
56621
|
-
const created = await step(
|
|
57232
|
+
const created = await step(createFlow, baseArgs);
|
|
56622
57233
|
if (!created.payload.success) {
|
|
56623
57234
|
if (opts.json) printJson(created.payload);
|
|
56624
57235
|
else {
|
|
@@ -56638,6 +57249,9 @@ async function cmdPay(opts) {
|
|
|
56638
57249
|
out();
|
|
56639
57250
|
out(` ${bold(p.merchant ?? "Unknown merchant")}`);
|
|
56640
57251
|
out(` Invoice ${bold(`${p.invoice?.amount} ${p.invoice?.currency ?? "USD"}`)}`);
|
|
57252
|
+
if (p.provider === "bitrefill") {
|
|
57253
|
+
out(` Bitrefill ${p.bitrefill?.invoiceId} ${dim(`\u2192 ${p.bitrefill?.amount} USDC on Base to ${p.bitrefill?.addressMasked}`)}`);
|
|
57254
|
+
}
|
|
56641
57255
|
out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}`);
|
|
56642
57256
|
out(` To ${p.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
|
|
56643
57257
|
if (p.display?.hasMemo) {
|
|
@@ -56674,7 +57288,7 @@ async function cmdPay(opts) {
|
|
|
56674
57288
|
}
|
|
56675
57289
|
}
|
|
56676
57290
|
if (!opts.json) out(dim(" Confirming and releasing the deposit details\u2026 (no money moves yet)"));
|
|
56677
|
-
const confirmed = await step(
|
|
57291
|
+
const confirmed = await step(createFlow, [...baseArgs, "--confirm"]);
|
|
56678
57292
|
if (!confirmed.payload.success) {
|
|
56679
57293
|
if (opts.json) printJson(confirmed.payload);
|
|
56680
57294
|
else {
|
|
@@ -56689,7 +57303,7 @@ async function cmdPay(opts) {
|
|
|
56689
57303
|
}
|
|
56690
57304
|
if (opts.send) {
|
|
56691
57305
|
const family = chainFamily(deposit.chainId);
|
|
56692
|
-
const sender = family === "evm" ?
|
|
57306
|
+
const sender = family === "evm" ? run5 : family === "solana" ? run6 : null;
|
|
56693
57307
|
if (!sender) {
|
|
56694
57308
|
const payload = formatFailure({
|
|
56695
57309
|
code: "SEND_UNSUPPORTED_CHAIN",
|
|
@@ -56744,7 +57358,7 @@ async function cmdPay(opts) {
|
|
|
56744
57358
|
return EXIT_OK;
|
|
56745
57359
|
}
|
|
56746
57360
|
if (!opts.json) out(dim(" Waiting for settlement\u2026 (Ctrl-C to stop; the payment continues)"));
|
|
56747
|
-
const watched = await step(
|
|
57361
|
+
const watched = await step(run4, [
|
|
56748
57362
|
"--rozo-payment-id",
|
|
56749
57363
|
rozoPaymentId,
|
|
56750
57364
|
"--watch",
|
|
@@ -56799,7 +57413,7 @@ function printDeposit(deposit, opts) {
|
|
|
56799
57413
|
out(dim(" Send it exactly once."));
|
|
56800
57414
|
out();
|
|
56801
57415
|
}
|
|
56802
|
-
async function
|
|
57416
|
+
async function main7() {
|
|
56803
57417
|
let opts;
|
|
56804
57418
|
try {
|
|
56805
57419
|
opts = parseCliArgs(process.argv.slice(2));
|
|
@@ -56848,7 +57462,7 @@ async function main6() {
|
|
|
56848
57462
|
return EXIT_USAGE;
|
|
56849
57463
|
}
|
|
56850
57464
|
}
|
|
56851
|
-
|
|
57465
|
+
main7().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
|
|
56852
57466
|
const payload = formatFailure(err);
|
|
56853
57467
|
if (process.argv.includes("--json")) printJson(payload);
|
|
56854
57468
|
else printError(payload);
|