@rozoai/checkout 0.1.19 → 0.1.21
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 +37 -9
- package/docs/QUICKSTART.es.md +14 -6
- package/docs/QUICKSTART.ja.md +8 -3
- package/docs/QUICKSTART.md +13 -5
- package/docs/QUICKSTART.zh.md +6 -4
- package/docs/README.es.md +19 -4
- package/docs/README.ja.md +18 -4
- package/docs/README.zh.md +18 -4
- package/llms.txt +126 -13
- package/package.json +7 -2
- package/scripts/dist/cli.js +1312 -563
- package/scripts/dist/create-order.js +209 -22
- package/scripts/dist/quote.js +107 -5
- package/scripts/dist/receipt.js +84 -13
- package/scripts/dist/resume.js +103 -18
- package/scripts/dist/send-evm.js +31852 -211
- package/scripts/dist/send-sol.js +468 -248
- package/scripts/dist/status.js +87 -13
package/scripts/dist/status.js
CHANGED
|
@@ -358,10 +358,20 @@ var CHAIN_NAMES = {
|
|
|
358
358
|
56: "BNB Chain",
|
|
359
359
|
137: "Polygon",
|
|
360
360
|
8453: "Base",
|
|
361
|
+
42161: "Arbitrum",
|
|
361
362
|
900: "Solana",
|
|
362
363
|
1500: "Stellar",
|
|
363
364
|
lightning: "Bitcoin Lightning"
|
|
364
365
|
};
|
|
366
|
+
var NATIVE_COINS = Object.freeze({
|
|
367
|
+
"1:ETH": { chainId: "1", symbol: "ETH", decimals: 18 },
|
|
368
|
+
"8453:ETH": { chainId: "8453", symbol: "ETH", decimals: 18 },
|
|
369
|
+
"42161:ETH": { chainId: "42161", symbol: "ETH", decimals: 18 },
|
|
370
|
+
"56:BNB": { chainId: "56", symbol: "BNB", decimals: 18 },
|
|
371
|
+
"137:POL": { chainId: "137", symbol: "POL", decimals: 18 },
|
|
372
|
+
"900:SOL": { chainId: "900", symbol: "SOL", decimals: 9 }
|
|
373
|
+
});
|
|
374
|
+
var NATIVE_MIN_USD_FALLBACK = Object.freeze({ "1:ETH": 1 });
|
|
365
375
|
var DECIMALS = {
|
|
366
376
|
"1:USDC": 6,
|
|
367
377
|
"1:USDT": 6,
|
|
@@ -371,10 +381,20 @@ var DECIMALS = {
|
|
|
371
381
|
"137:USDC": 6,
|
|
372
382
|
"137:USDT": 6,
|
|
373
383
|
"8453:USDC": 6,
|
|
384
|
+
// Arbitrum One: native USDC and USDT0 are both 6-decimals.
|
|
385
|
+
"42161:USDC": 6,
|
|
386
|
+
"42161:USDT": 6,
|
|
374
387
|
"900:USDC": 6,
|
|
375
388
|
"900:USDT": 6,
|
|
376
389
|
// Stellar classic assets carry 7 decimal places.
|
|
377
|
-
"1500:USDC": 7
|
|
390
|
+
"1500:USDC": 7,
|
|
391
|
+
// Native coins: wei (18) on every EVM chain, lamports (9) on Solana.
|
|
392
|
+
"1:ETH": 18,
|
|
393
|
+
"8453:ETH": 18,
|
|
394
|
+
"42161:ETH": 18,
|
|
395
|
+
"56:BNB": 18,
|
|
396
|
+
"137:POL": 18,
|
|
397
|
+
"900:SOL": 9
|
|
378
398
|
};
|
|
379
399
|
var AmountError = class extends Error {
|
|
380
400
|
constructor(code, message) {
|
|
@@ -467,8 +487,18 @@ function formatAmount(source) {
|
|
|
467
487
|
function chainName(chainId) {
|
|
468
488
|
return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
|
|
469
489
|
}
|
|
490
|
+
function isNativeSource(chainId, tokenSymbol) {
|
|
491
|
+
return Object.prototype.hasOwnProperty.call(NATIVE_COINS, decimalsKey(chainId, tokenSymbol));
|
|
492
|
+
}
|
|
470
493
|
|
|
471
494
|
// scripts/src/lib/guards.mjs
|
|
495
|
+
var NATIVE_PROGRESS_STATUSES = /* @__PURE__ */ new Set([
|
|
496
|
+
"payment_payin_completed",
|
|
497
|
+
"payment_bridging",
|
|
498
|
+
"payment_payout_started",
|
|
499
|
+
"payment_payout_completed",
|
|
500
|
+
"payment_completed"
|
|
501
|
+
]);
|
|
472
502
|
function receiptSignal(source) {
|
|
473
503
|
const raw = source?.amountReceived;
|
|
474
504
|
if (raw === null || raw === void 0 || raw === "") {
|
|
@@ -598,7 +628,8 @@ function classifyStatus({
|
|
|
598
628
|
if (!bitrefill && (routerStatus === "paid" || coinbase?.settled === true)) {
|
|
599
629
|
return mk("settled", "Coinbase invoice settled by the funder wallet.", { terminal: true });
|
|
600
630
|
}
|
|
601
|
-
|
|
631
|
+
const nativeAccepted = isNativeSource(source.chainId, source.tokenSymbol) && NATIVE_PROGRESS_STATUSES.has(payment?.status);
|
|
632
|
+
if (moneyDetected && receipt && receipt.state === "underpaid" && !nativeAccepted) {
|
|
602
633
|
return mk(
|
|
603
634
|
"underpaid",
|
|
604
635
|
"Less arrived than the order requires. Do NOT send a top-up to the same address \u2014 escalate.",
|
|
@@ -679,6 +710,7 @@ var MARGINS_MS = {
|
|
|
679
710
|
56: 10 * MINUTE,
|
|
680
711
|
137: 10 * MINUTE,
|
|
681
712
|
8453: 10 * MINUTE,
|
|
713
|
+
42161: 10 * MINUTE,
|
|
682
714
|
900: 5 * MINUTE,
|
|
683
715
|
1500: 10 * MINUTE,
|
|
684
716
|
lightning: 10 * MINUTE
|
|
@@ -726,13 +758,16 @@ function checkExpiry({
|
|
|
726
758
|
chainId,
|
|
727
759
|
intentExpiresAt,
|
|
728
760
|
coinbaseExpiry,
|
|
729
|
-
bolt11ExpiresAt = void 0
|
|
761
|
+
bolt11ExpiresAt = void 0,
|
|
762
|
+
quoteExpiresAt = void 0
|
|
730
763
|
}) {
|
|
731
764
|
const marginMs = marginFor(chainId);
|
|
732
765
|
const intentMs = parseDeadline(intentExpiresAt);
|
|
733
766
|
const coinbaseMs = parseDeadline(coinbaseExpiry);
|
|
734
767
|
const bolt11Ms = bolt11ExpiresAt === void 0 ? void 0 : parseDeadline(bolt11ExpiresAt);
|
|
735
|
-
const
|
|
768
|
+
const native = quoteExpiresAt !== void 0;
|
|
769
|
+
const quoteMs = native ? parseDeadline(quoteExpiresAt) : null;
|
|
770
|
+
const deadlines = { intentMs, coinbaseMs, bolt11Ms: bolt11Ms ?? null, quoteMs };
|
|
736
771
|
const base = {
|
|
737
772
|
marginMs,
|
|
738
773
|
effectiveDeadlineMs: null,
|
|
@@ -756,7 +791,16 @@ function checkExpiry({
|
|
|
756
791
|
reason: "Coinbase preApprovalExpiry is missing or unparsable."
|
|
757
792
|
};
|
|
758
793
|
}
|
|
759
|
-
|
|
794
|
+
if (native && quoteMs === null) {
|
|
795
|
+
return {
|
|
796
|
+
...base,
|
|
797
|
+
ok: false,
|
|
798
|
+
code: "EXPIRY_UNPARSABLE",
|
|
799
|
+
reason: "This native coin order carries no price lock (quoteExpiresAt), so the coin amount cannot be trusted to be honoured. Refusing to treat it as payable."
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const effective = native ? Math.min(intentMs, coinbaseMs, quoteMs) : Math.min(intentMs, coinbaseMs);
|
|
803
|
+
const lockBinds = native && quoteMs <= Math.min(intentMs, coinbaseMs);
|
|
760
804
|
const msRemaining = effective - now;
|
|
761
805
|
const msOfSlack = msRemaining - marginMs;
|
|
762
806
|
const withDeadline = {
|
|
@@ -769,16 +813,16 @@ function checkExpiry({
|
|
|
769
813
|
return {
|
|
770
814
|
...withDeadline,
|
|
771
815
|
ok: false,
|
|
772
|
-
code: "EXPIRED",
|
|
773
|
-
reason: "The order or the Coinbase link has already expired."
|
|
816
|
+
code: lockBinds ? "QUOTE_EXPIRED" : "EXPIRED",
|
|
817
|
+
reason: lockBinds ? "The coin price for this order was locked until quoteExpiresAt, and that has passed. A payment now would be valued at the price when it arrives and may fall short. Do not send it; let the order expire unfunded and start a new one." : "The order or the Coinbase link has already expired."
|
|
774
818
|
};
|
|
775
819
|
}
|
|
776
820
|
if (msOfSlack <= 0) {
|
|
777
821
|
return {
|
|
778
822
|
...withDeadline,
|
|
779
823
|
ok: false,
|
|
780
|
-
code: "EXPIRY_MARGIN",
|
|
781
|
-
reason: `Only ${Math.floor(msRemaining / 1e3)}s left before the earliest deadline; this chain needs a ${Math.floor(marginMs / 6e4)} min safety margin.`
|
|
824
|
+
code: lockBinds ? "QUOTE_EXPIRY_MARGIN" : "EXPIRY_MARGIN",
|
|
825
|
+
reason: `Only ${Math.floor(msRemaining / 1e3)}s left before the earliest deadline${lockBinds ? " (the coin price lock)" : ""}; this chain needs a ${Math.floor(marginMs / 6e4)} min safety margin.`
|
|
782
826
|
};
|
|
783
827
|
}
|
|
784
828
|
if (bolt11ExpiresAt !== void 0) {
|
|
@@ -802,6 +846,12 @@ function checkExpiry({
|
|
|
802
846
|
}
|
|
803
847
|
return { ...withDeadline, ok: true, code: null, reason: null };
|
|
804
848
|
}
|
|
849
|
+
function priceLockFor({ native, payment = null, others = [] }) {
|
|
850
|
+
if (!native) return void 0;
|
|
851
|
+
const candidates = [payment?.quoteExpiresAt, payment?.source?.quoteExpiresAt, ...others].map(parseDeadline).filter((ms) => ms !== null);
|
|
852
|
+
if (!candidates.length) return null;
|
|
853
|
+
return new Date(Math.min(...candidates)).toISOString();
|
|
854
|
+
}
|
|
805
855
|
|
|
806
856
|
// scripts/src/lib/bitrefill.mjs
|
|
807
857
|
var PROVIDER_BITREFILL = "bitrefill";
|
|
@@ -997,7 +1047,7 @@ function localSendFor(id, read = readState) {
|
|
|
997
1047
|
if (!send || !send.status) return null;
|
|
998
1048
|
return { status: send.status, txHash: publicHash(send.txHash ?? null), claimedAt: send.claimedAt ?? null };
|
|
999
1049
|
}
|
|
1000
|
-
function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, now = Date.now() }) {
|
|
1050
|
+
function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, quoteExpiresAt, now = Date.now() }) {
|
|
1001
1051
|
if (provider === "bitrefill") {
|
|
1002
1052
|
const deadline = bitrefillExpiry ?? null;
|
|
1003
1053
|
const expiry2 = checkExpiry({
|
|
@@ -1017,7 +1067,9 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
|
|
|
1017
1067
|
now,
|
|
1018
1068
|
chainId,
|
|
1019
1069
|
intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
|
|
1020
|
-
coinbaseExpiry: status?.coinbase?.preApprovalExpiry
|
|
1070
|
+
coinbaseExpiry: status?.coinbase?.preApprovalExpiry,
|
|
1071
|
+
// Native orders: the price lock is a deadline too (undefined otherwise).
|
|
1072
|
+
...quoteExpiresAt !== void 0 ? { quoteExpiresAt } : {}
|
|
1021
1073
|
});
|
|
1022
1074
|
return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4), deadlineMs: expiry.effectiveDeadlineMs } : { ok: false, code: expiry.code, reason: expiry.reason };
|
|
1023
1075
|
}
|
|
@@ -1071,6 +1123,18 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1071
1123
|
provider
|
|
1072
1124
|
});
|
|
1073
1125
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
1126
|
+
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
1127
|
+
let localState = null;
|
|
1128
|
+
try {
|
|
1129
|
+
localState = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
1130
|
+
} catch {
|
|
1131
|
+
localState = null;
|
|
1132
|
+
}
|
|
1133
|
+
const quoteExpiresAt = priceLockFor({
|
|
1134
|
+
native,
|
|
1135
|
+
payment: payment || status?.rozoPayment || null,
|
|
1136
|
+
others: [localState?.confirmation?.quoteExpiresAt, localState?.quoteExpiresAt]
|
|
1137
|
+
});
|
|
1074
1138
|
let bitrefillExpiry = null;
|
|
1075
1139
|
let state = verdict.state;
|
|
1076
1140
|
let terminal = verdict.terminal;
|
|
@@ -1130,10 +1194,17 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1130
1194
|
const iso = provider === "bitrefill" ? bitrefillExpiry : payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
|
|
1131
1195
|
if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
|
|
1132
1196
|
const ms = Date.parse(iso) - Date.now();
|
|
1197
|
+
const lockMs = quoteExpiresAt ? Date.parse(quoteExpiresAt) - Date.now() : NaN;
|
|
1133
1198
|
return {
|
|
1134
1199
|
expiresAt: iso,
|
|
1135
1200
|
expiresIn: formatRemaining(ms),
|
|
1136
|
-
msRemaining: Number.isFinite(ms) ? ms : null
|
|
1201
|
+
msRemaining: Number.isFinite(ms) ? ms : null,
|
|
1202
|
+
// Native orders: the order stays open after the price lock, but a
|
|
1203
|
+
// payment after it is valued at the arrival price. Pay before this.
|
|
1204
|
+
...native ? {
|
|
1205
|
+
quoteExpiresAt: quoteExpiresAt ?? null,
|
|
1206
|
+
priceLockExpiresIn: Number.isFinite(lockMs) ? formatRemaining(lockMs) : null
|
|
1207
|
+
} : {}
|
|
1137
1208
|
};
|
|
1138
1209
|
})(),
|
|
1139
1210
|
payout: {
|
|
@@ -1141,7 +1212,7 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1141
1212
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
1142
1213
|
},
|
|
1143
1214
|
localSend: localSendFor(id),
|
|
1144
|
-
sendWindow: state === "awaiting_deposit" ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry }) : null,
|
|
1215
|
+
sendWindow: state === "awaiting_deposit" ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry, quoteExpiresAt }) : null,
|
|
1145
1216
|
errors: [statusError, paymentError].filter(Boolean)
|
|
1146
1217
|
};
|
|
1147
1218
|
}
|
|
@@ -1170,6 +1241,9 @@ function watchTimeoutMs(timeoutArg, result, now = Date.now()) {
|
|
|
1170
1241
|
if (result?.payin?.chain === chainName("lightning")) {
|
|
1171
1242
|
return lightningWatchMs(result?.expiry?.expiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
|
|
1172
1243
|
}
|
|
1244
|
+
if (result?.expiry?.quoteExpiresAt) {
|
|
1245
|
+
return lightningWatchMs(result.expiry.quoteExpiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
|
|
1246
|
+
}
|
|
1173
1247
|
return DEFAULT_WATCH_MS;
|
|
1174
1248
|
}
|
|
1175
1249
|
async function main(argv) {
|