@rozoai/checkout 0.1.23 → 0.1.24
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 +6 -1
- package/llms.txt +9 -1
- package/package.json +1 -1
- package/scripts/dist/cli.js +429 -54
- package/scripts/dist/create-order.js +303 -28
- package/scripts/dist/quote.js +102 -6
- package/scripts/dist/receipt.js +56 -9
- package/scripts/dist/resume.js +176 -20
- package/scripts/dist/send-evm.js +132 -7
- package/scripts/dist/send-sol.js +132 -7
- package/scripts/dist/status.js +56 -9
package/scripts/dist/receipt.js
CHANGED
|
@@ -360,7 +360,8 @@ var CHAIN_NAMES = {
|
|
|
360
360
|
42161: "Arbitrum",
|
|
361
361
|
900: "Solana",
|
|
362
362
|
1500: "Stellar",
|
|
363
|
-
lightning: "Bitcoin Lightning"
|
|
363
|
+
lightning: "Bitcoin Lightning",
|
|
364
|
+
9133: "Zcash"
|
|
364
365
|
};
|
|
365
366
|
var NATIVE_COINS = Object.freeze({
|
|
366
367
|
"1:ETH": { chainId: "1", symbol: "ETH", decimals: 18 },
|
|
@@ -393,7 +394,9 @@ var DECIMALS = {
|
|
|
393
394
|
"42161:ETH": 18,
|
|
394
395
|
"56:BNB": 18,
|
|
395
396
|
"137:POL": 18,
|
|
396
|
-
"900:SOL": 9
|
|
397
|
+
"900:SOL": 9,
|
|
398
|
+
// Zcash: zatoshi.
|
|
399
|
+
"9133:ZEC": 8
|
|
397
400
|
};
|
|
398
401
|
var AmountError = class extends Error {
|
|
399
402
|
constructor(code, message) {
|
|
@@ -490,6 +493,32 @@ function isNativeSource(chainId, tokenSymbol) {
|
|
|
490
493
|
return Object.prototype.hasOwnProperty.call(NATIVE_COINS, decimalsKey(chainId, tokenSymbol));
|
|
491
494
|
}
|
|
492
495
|
|
|
496
|
+
// scripts/src/lib/zec.mjs
|
|
497
|
+
var ZEC_CHAIN_ID = "9133";
|
|
498
|
+
var ZEC_SYMBOL = "ZEC";
|
|
499
|
+
var ZEC_PAYIN_STATES = /* @__PURE__ */ new Set(["awaiting", "seen", "confirming", "eligible", "expired", "reorged"]);
|
|
500
|
+
function isZecSource(chainId, tokenSymbol) {
|
|
501
|
+
return String(chainId ?? "").trim() === ZEC_CHAIN_ID && String(tokenSymbol ?? "").trim().toUpperCase() === ZEC_SYMBOL;
|
|
502
|
+
}
|
|
503
|
+
function zecStatusOf(source) {
|
|
504
|
+
const z = source?.zec;
|
|
505
|
+
if (!z || typeof z !== "object") return null;
|
|
506
|
+
const receive = z.receive === "shielded" || z.receive === "transparent" ? z.receive : null;
|
|
507
|
+
const payinState = ZEC_PAYIN_STATES.has(z.payinState) ? z.payinState : null;
|
|
508
|
+
const confirmations = Number.isInteger(z.confirmations) && z.confirmations >= 0 ? z.confirmations : null;
|
|
509
|
+
const requiredConfirmations = z.requiredConfirmations === 1 || z.requiredConfirmations === 2 ? z.requiredConfirmations : null;
|
|
510
|
+
const quoteExpiresAt = typeof z.quoteExpiresAt === "string" && z.quoteExpiresAt ? z.quoteExpiresAt : null;
|
|
511
|
+
const receivedZat = typeof z.receivedZat === "string" && /^\d+$/.test(z.receivedZat) ? z.receivedZat : null;
|
|
512
|
+
return { receive, payinState, confirmations, requiredConfirmations, quoteExpiresAt, receivedZat };
|
|
513
|
+
}
|
|
514
|
+
function zecMoneySeen(zec) {
|
|
515
|
+
if (!zec) return false;
|
|
516
|
+
if (zec.payinState === "seen" || zec.payinState === "confirming" || zec.payinState === "eligible") return true;
|
|
517
|
+
if (zec.receivedZat !== null && zec.receivedZat !== "0") return true;
|
|
518
|
+
if (zec.payinState === null) return true;
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
493
522
|
// scripts/src/lib/guards.mjs
|
|
494
523
|
var NATIVE_PROGRESS_STATUSES = /* @__PURE__ */ new Set([
|
|
495
524
|
"payment_payin_completed",
|
|
@@ -712,7 +741,9 @@ var MARGINS_MS = {
|
|
|
712
741
|
42161: 10 * MINUTE,
|
|
713
742
|
900: 5 * MINUTE,
|
|
714
743
|
1500: 10 * MINUTE,
|
|
715
|
-
lightning: 10 * MINUTE
|
|
744
|
+
lightning: 10 * MINUTE,
|
|
745
|
+
// Zcash: 75 s blocks, 1 to 2 confirmations, then the hub advances the merchant.
|
|
746
|
+
9133: 10 * MINUTE
|
|
716
747
|
};
|
|
717
748
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
718
749
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
@@ -1202,13 +1233,15 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1202
1233
|
});
|
|
1203
1234
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
1204
1235
|
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
1236
|
+
const isZec = isZecSource(source.chainId, source.tokenSymbol);
|
|
1237
|
+
const zec = isZec ? zecStatusOf(source) : null;
|
|
1205
1238
|
let localState = null;
|
|
1206
1239
|
try {
|
|
1207
1240
|
localState = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
1208
1241
|
} catch {
|
|
1209
1242
|
localState = null;
|
|
1210
1243
|
}
|
|
1211
|
-
const quoteExpiresAt = priceLockFor({
|
|
1244
|
+
const quoteExpiresAt = isZec ? zec?.quoteExpiresAt ?? null : priceLockFor({
|
|
1212
1245
|
native,
|
|
1213
1246
|
payment: payment || status?.rozoPayment || null,
|
|
1214
1247
|
others: [localState?.confirmation?.quoteExpiresAt, localState?.quoteExpiresAt]
|
|
@@ -1217,6 +1250,17 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1217
1250
|
let state = verdict.state;
|
|
1218
1251
|
let terminal = verdict.terminal;
|
|
1219
1252
|
let detail = verdict.detail;
|
|
1253
|
+
const zecMoney = Boolean(zec && zecMoneySeen(zec));
|
|
1254
|
+
let escalate = verdict.escalate;
|
|
1255
|
+
if (zecMoney && state === "expired_unfunded") {
|
|
1256
|
+
state = "stuck_after_payment";
|
|
1257
|
+
terminal = false;
|
|
1258
|
+
escalate = true;
|
|
1259
|
+
detail = "The order expired but the custody rail reports a ZEC pay-in. Do not pay again; escalate for manual reconciliation.";
|
|
1260
|
+
} else if (zecMoney && state === "awaiting_deposit") {
|
|
1261
|
+
state = "payin_detected";
|
|
1262
|
+
detail = zec.payinState === "seen" || zec.payinState === "confirming" ? `Shielded pay-in ${zec.payinState}: ${zec.confirmations ?? 0} of ${zec.requiredConfirmations ?? "?"} confirmations.` : `The custody rail reports a ZEC pay-in (${zec.payinState ?? "state unreadable"}). Do not pay again.`;
|
|
1263
|
+
}
|
|
1220
1264
|
if (provider === "bitrefill") {
|
|
1221
1265
|
let local = null;
|
|
1222
1266
|
try {
|
|
@@ -1246,9 +1290,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1246
1290
|
linkId: linkId || status?.pl_id || null,
|
|
1247
1291
|
state,
|
|
1248
1292
|
unknown: verdict.unknown,
|
|
1249
|
-
moneyDetected: verdict.moneyDetected,
|
|
1293
|
+
moneyDetected: verdict.moneyDetected || zecMoney,
|
|
1250
1294
|
terminal,
|
|
1251
|
-
escalate
|
|
1295
|
+
escalate,
|
|
1252
1296
|
detail,
|
|
1253
1297
|
backend: {
|
|
1254
1298
|
paymentStatus: payment?.status ?? status?.rozoPayment?.status ?? null,
|
|
@@ -1263,7 +1307,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1263
1307
|
txHash: publicHash(source.txHash ?? null),
|
|
1264
1308
|
confirmedAt: source.confirmedAt ?? null,
|
|
1265
1309
|
senderAddressMasked: source.senderAddress ? maskAddress(source.senderAddress) : null,
|
|
1266
|
-
chain: source.chainId ? chainName(source.chainId) : null
|
|
1310
|
+
chain: source.chainId ? chainName(source.chainId) : null,
|
|
1311
|
+
// Shielded ZEC only: the custody rail's own view of the pay-in.
|
|
1312
|
+
...zec ? { zec } : {}
|
|
1267
1313
|
},
|
|
1268
1314
|
expiry: (() => {
|
|
1269
1315
|
if (provider === "bitrefill" && !bitrefillExpiry) {
|
|
@@ -1279,7 +1325,7 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1279
1325
|
msRemaining: Number.isFinite(ms) ? ms : null,
|
|
1280
1326
|
// Native orders: the order stays open after the price lock, but a
|
|
1281
1327
|
// payment after it is valued at the arrival price. Pay before this.
|
|
1282
|
-
...native ? {
|
|
1328
|
+
...native || isZec ? {
|
|
1283
1329
|
quoteExpiresAt: quoteExpiresAt ?? null,
|
|
1284
1330
|
priceLockExpiresIn: Number.isFinite(lockMs) ? formatRemaining(lockMs) : null
|
|
1285
1331
|
} : {}
|
|
@@ -1290,7 +1336,8 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1290
1336
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
1291
1337
|
},
|
|
1292
1338
|
localSend: localSendFor(id),
|
|
1293
|
-
|
|
1339
|
+
// Never a send window once the custody rail has seen a ZEC pay-in.
|
|
1340
|
+
sendWindow: state === "awaiting_deposit" && !zecMoney ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry, quoteExpiresAt }) : null,
|
|
1294
1341
|
errors: [statusError, paymentError].filter(Boolean)
|
|
1295
1342
|
};
|
|
1296
1343
|
}
|
package/scripts/dist/resume.js
CHANGED
|
@@ -382,7 +382,8 @@ var CHAIN_NAMES = {
|
|
|
382
382
|
42161: "Arbitrum",
|
|
383
383
|
900: "Solana",
|
|
384
384
|
1500: "Stellar",
|
|
385
|
-
lightning: "Bitcoin Lightning"
|
|
385
|
+
lightning: "Bitcoin Lightning",
|
|
386
|
+
9133: "Zcash"
|
|
386
387
|
};
|
|
387
388
|
var CHAIN_FAMILY = {
|
|
388
389
|
1: "evm",
|
|
@@ -392,7 +393,9 @@ var CHAIN_FAMILY = {
|
|
|
392
393
|
42161: "evm",
|
|
393
394
|
900: "solana",
|
|
394
395
|
1500: "stellar",
|
|
395
|
-
lightning: "lightning"
|
|
396
|
+
lightning: "lightning",
|
|
397
|
+
// Zcash: the CLI creates shielded orders only (pool Unified Address); it never signs.
|
|
398
|
+
9133: "zcash"
|
|
396
399
|
};
|
|
397
400
|
var NATIVE_COINS = Object.freeze({
|
|
398
401
|
"1:ETH": { chainId: "1", symbol: "ETH", decimals: 18 },
|
|
@@ -425,7 +428,9 @@ var DECIMALS = {
|
|
|
425
428
|
"42161:ETH": 18,
|
|
426
429
|
"56:BNB": 18,
|
|
427
430
|
"137:POL": 18,
|
|
428
|
-
"900:SOL": 9
|
|
431
|
+
"900:SOL": 9,
|
|
432
|
+
// Zcash: zatoshi.
|
|
433
|
+
"9133:ZEC": 8
|
|
429
434
|
};
|
|
430
435
|
var AmountError = class extends Error {
|
|
431
436
|
constructor(code, message) {
|
|
@@ -526,6 +531,96 @@ function isNativeSource(chainId, tokenSymbol) {
|
|
|
526
531
|
return Object.prototype.hasOwnProperty.call(NATIVE_COINS, decimalsKey(chainId, tokenSymbol));
|
|
527
532
|
}
|
|
528
533
|
|
|
534
|
+
// scripts/src/lib/zec.mjs
|
|
535
|
+
var ZEC_CHAIN_ID = "9133";
|
|
536
|
+
var ZEC_SYMBOL = "ZEC";
|
|
537
|
+
var ZEC_PAYIN_STATES = /* @__PURE__ */ new Set(["awaiting", "seen", "confirming", "eligible", "expired", "reorged"]);
|
|
538
|
+
var UA_RE = /^u1[02-9ac-hj-np-z]{100,400}$/;
|
|
539
|
+
function isZecSource(chainId, tokenSymbol) {
|
|
540
|
+
return String(chainId ?? "").trim() === ZEC_CHAIN_ID && String(tokenSymbol ?? "").trim().toUpperCase() === ZEC_SYMBOL;
|
|
541
|
+
}
|
|
542
|
+
function isUnifiedAddress(value) {
|
|
543
|
+
return typeof value === "string" && UA_RE.test(value.trim());
|
|
544
|
+
}
|
|
545
|
+
function zecStatusOf(source) {
|
|
546
|
+
const z = source?.zec;
|
|
547
|
+
if (!z || typeof z !== "object") return null;
|
|
548
|
+
const receive = z.receive === "shielded" || z.receive === "transparent" ? z.receive : null;
|
|
549
|
+
const payinState = ZEC_PAYIN_STATES.has(z.payinState) ? z.payinState : null;
|
|
550
|
+
const confirmations = Number.isInteger(z.confirmations) && z.confirmations >= 0 ? z.confirmations : null;
|
|
551
|
+
const requiredConfirmations = z.requiredConfirmations === 1 || z.requiredConfirmations === 2 ? z.requiredConfirmations : null;
|
|
552
|
+
const quoteExpiresAt = typeof z.quoteExpiresAt === "string" && z.quoteExpiresAt ? z.quoteExpiresAt : null;
|
|
553
|
+
const receivedZat = typeof z.receivedZat === "string" && /^\d+$/.test(z.receivedZat) ? z.receivedZat : null;
|
|
554
|
+
return { receive, payinState, confirmations, requiredConfirmations, quoteExpiresAt, receivedZat };
|
|
555
|
+
}
|
|
556
|
+
function zecMoneySeen(zec) {
|
|
557
|
+
if (!zec) return false;
|
|
558
|
+
if (zec.payinState === "seen" || zec.payinState === "confirming" || zec.payinState === "eligible") return true;
|
|
559
|
+
if (zec.receivedZat !== null && zec.receivedZat !== "0") return true;
|
|
560
|
+
if (zec.payinState === null) return true;
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
function parsePaymentUri(uri) {
|
|
564
|
+
if (typeof uri !== "string") return null;
|
|
565
|
+
const m = /^zcash:([0-9a-z]+)\?([^#]*)$/i.exec(uri.trim());
|
|
566
|
+
if (!m) return null;
|
|
567
|
+
const address = m[1];
|
|
568
|
+
const out = { address, amount: null, memo: null };
|
|
569
|
+
const seen = /* @__PURE__ */ new Set();
|
|
570
|
+
for (const part of m[2].split("&")) {
|
|
571
|
+
if (!part) return null;
|
|
572
|
+
const eq = part.indexOf("=");
|
|
573
|
+
if (eq <= 0) return null;
|
|
574
|
+
const key = part.slice(0, eq);
|
|
575
|
+
const value = part.slice(eq + 1);
|
|
576
|
+
if (seen.has(key)) return null;
|
|
577
|
+
seen.add(key);
|
|
578
|
+
if (key === "amount") {
|
|
579
|
+
if (!/^\d+(\.\d{1,8})?$/.test(value)) return null;
|
|
580
|
+
out.amount = value;
|
|
581
|
+
} else if (key === "memo") {
|
|
582
|
+
if (!/^[A-Za-z0-9_-]{1,684}$/.test(value)) return null;
|
|
583
|
+
out.memo = value;
|
|
584
|
+
} else {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (out.amount === null) return null;
|
|
589
|
+
return out;
|
|
590
|
+
}
|
|
591
|
+
function sameDecimal(a, b) {
|
|
592
|
+
const norm = (v) => {
|
|
593
|
+
const s = String(v ?? "").trim();
|
|
594
|
+
if (!/^\d+(\.\d+)?$/.test(s)) return null;
|
|
595
|
+
const [w, f = ""] = s.split(".");
|
|
596
|
+
const frac = f.replace(/0+$/, "");
|
|
597
|
+
return `${w.replace(/^0+(?=\d)/, "")}${frac ? `.${frac}` : ""}`;
|
|
598
|
+
};
|
|
599
|
+
const na = norm(a);
|
|
600
|
+
return na !== null && na === norm(b);
|
|
601
|
+
}
|
|
602
|
+
function validateShieldedDeposit(source) {
|
|
603
|
+
const address = typeof source?.receiverAddress === "string" ? source.receiverAddress.trim() : "";
|
|
604
|
+
if (!isUnifiedAddress(address)) {
|
|
605
|
+
return { ok: false, code: "ZEC_ADDRESS_INVALID", reason: "The order did not return a mainnet Unified Address (u1...). Refusing to show it as payable." };
|
|
606
|
+
}
|
|
607
|
+
const uri = typeof source?.paymentUri === "string" ? source.paymentUri.trim() : "";
|
|
608
|
+
if (!uri) {
|
|
609
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISSING", reason: "The order carries no payment URI yet. Nothing is payable until it appears." };
|
|
610
|
+
}
|
|
611
|
+
const parsed = parsePaymentUri(uri);
|
|
612
|
+
if (!parsed) {
|
|
613
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_INVALID", reason: "The payment URI is not a single-payment ZIP-321 URI. Refusing to show it." };
|
|
614
|
+
}
|
|
615
|
+
if (parsed.address !== address) {
|
|
616
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI names a different address than the order. Refusing to show it." };
|
|
617
|
+
}
|
|
618
|
+
if (!sameDecimal(parsed.amount, source?.amount)) {
|
|
619
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI amount differs from the order amount. Refusing to show it." };
|
|
620
|
+
}
|
|
621
|
+
return { ok: true, code: null, reason: null, address, paymentUri: uri, memo: parsed.memo };
|
|
622
|
+
}
|
|
623
|
+
|
|
529
624
|
// scripts/src/lib/guards.mjs
|
|
530
625
|
var UNPAID_STATUS = "payment_unpaid";
|
|
531
626
|
var NATIVE_PROGRESS_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -586,6 +681,11 @@ function validateDepositInstructions(source) {
|
|
|
586
681
|
reason: "The live payments response returned no deposit address."
|
|
587
682
|
};
|
|
588
683
|
}
|
|
684
|
+
if (family === "zcash" && zecStatusOf(source)?.receive === "shielded") {
|
|
685
|
+
const zec = validateShieldedDeposit(source);
|
|
686
|
+
if (!zec.ok) return { ok: false, code: zec.code, reason: zec.reason };
|
|
687
|
+
return { ok: true, code: null, reason: null, family, amountAtomic, payTo: zec.address, memo: null, paymentUri: zec.paymentUri };
|
|
688
|
+
}
|
|
589
689
|
if (family === "stellar" && !memo) {
|
|
590
690
|
return {
|
|
591
691
|
ok: false,
|
|
@@ -618,13 +718,14 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
618
718
|
const hasTx = source.txHash !== null && source.txHash !== void 0 && source.txHash !== "";
|
|
619
719
|
const signal = receiptSignal(source);
|
|
620
720
|
const hasConfirm = source.confirmedAt !== null && source.confirmedAt !== void 0 && source.confirmedAt !== "";
|
|
621
|
-
|
|
721
|
+
const zecSeen = isZecSource(source.chainId, source.tokenSymbol) && zecMoneySeen(zecStatusOf(source));
|
|
722
|
+
if (hasTx || signal.money || hasConfirm || zecSeen) {
|
|
622
723
|
return {
|
|
623
724
|
ok: false,
|
|
624
725
|
code: "ORDER_ALREADY_FUNDED",
|
|
625
|
-
reason: signal.unparsable ? "This order reports an amountReceived that cannot be read. It is treated as funded until a human confirms otherwise \u2014 do NOT pay again." : "This Coinbase link already has a funded Rozo order (it may have been paid elsewhere). Do NOT pay again \u2014 escalate for manual reconciliation.",
|
|
726
|
+
reason: signal.unparsable ? "This order reports an amountReceived that cannot be read. It is treated as funded until a human confirms otherwise \u2014 do NOT pay again." : zecSeen && !hasTx && !signal.money && !hasConfirm ? "A shielded ZEC payment to this order has already been seen on the network. Do NOT pay again; watch it with status instead." : "This Coinbase link already has a funded Rozo order (it may have been paid elsewhere). Do NOT pay again \u2014 escalate for manual reconciliation.",
|
|
626
727
|
moneyDetected: true,
|
|
627
|
-
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable }
|
|
728
|
+
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable, ...zecSeen ? { zec: zecStatusOf(source) } : {} }
|
|
628
729
|
};
|
|
629
730
|
}
|
|
630
731
|
if (payment?.status !== UNPAID_STATUS) {
|
|
@@ -652,6 +753,28 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
652
753
|
evidence
|
|
653
754
|
};
|
|
654
755
|
}
|
|
756
|
+
if (isZecSource(gotChain, gotToken)) {
|
|
757
|
+
const zec = zecStatusOf(source);
|
|
758
|
+
const wantReceive = requested?.shielded === true ? "shielded" : "transparent";
|
|
759
|
+
if (!zec || zec.receive !== wantReceive) {
|
|
760
|
+
return {
|
|
761
|
+
ok: false,
|
|
762
|
+
code: "ZEC_RECEIVE_MODE_MISMATCH",
|
|
763
|
+
reason: `This link already has a ZEC order that is paid ${zec?.receive ?? "in an unknown way"}, but you asked for ${wantReceive}. Nothing new was created. Pay that order the way it was made, or wait for it to expire unfunded.`,
|
|
764
|
+
moneyDetected: false,
|
|
765
|
+
evidence: { ...evidence, zec }
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
if (zec.payinState === "expired" || zec.payinState === "reorged") {
|
|
769
|
+
return {
|
|
770
|
+
ok: false,
|
|
771
|
+
code: "ZEC_QUOTE_EXPIRED",
|
|
772
|
+
reason: `The ZEC price lock on this order has ${zec.payinState === "reorged" ? "been disturbed by a reorg" : "expired"} with nothing received. Do not pay it; re-run pay to get a fresh order.`,
|
|
773
|
+
moneyDetected: false,
|
|
774
|
+
evidence: { ...evidence, zec }
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
655
778
|
const deposit = validateDepositInstructions(source);
|
|
656
779
|
if (!deposit.ok) {
|
|
657
780
|
return {
|
|
@@ -989,7 +1112,9 @@ var MARGINS_MS = {
|
|
|
989
1112
|
42161: 10 * MINUTE,
|
|
990
1113
|
900: 5 * MINUTE,
|
|
991
1114
|
1500: 10 * MINUTE,
|
|
992
|
-
lightning: 10 * MINUTE
|
|
1115
|
+
lightning: 10 * MINUTE,
|
|
1116
|
+
// Zcash: 75 s blocks, 1 to 2 confirmations, then the hub advances the merchant.
|
|
1117
|
+
9133: 10 * MINUTE
|
|
993
1118
|
};
|
|
994
1119
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
995
1120
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
@@ -1276,13 +1401,15 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1276
1401
|
});
|
|
1277
1402
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
1278
1403
|
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
1404
|
+
const isZec = isZecSource(source.chainId, source.tokenSymbol);
|
|
1405
|
+
const zec = isZec ? zecStatusOf(source) : null;
|
|
1279
1406
|
let localState = null;
|
|
1280
1407
|
try {
|
|
1281
1408
|
localState = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
1282
1409
|
} catch {
|
|
1283
1410
|
localState = null;
|
|
1284
1411
|
}
|
|
1285
|
-
const quoteExpiresAt = priceLockFor({
|
|
1412
|
+
const quoteExpiresAt = isZec ? zec?.quoteExpiresAt ?? null : priceLockFor({
|
|
1286
1413
|
native,
|
|
1287
1414
|
payment: payment || status?.rozoPayment || null,
|
|
1288
1415
|
others: [localState?.confirmation?.quoteExpiresAt, localState?.quoteExpiresAt]
|
|
@@ -1291,6 +1418,17 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1291
1418
|
let state = verdict.state;
|
|
1292
1419
|
let terminal = verdict.terminal;
|
|
1293
1420
|
let detail = verdict.detail;
|
|
1421
|
+
const zecMoney = Boolean(zec && zecMoneySeen(zec));
|
|
1422
|
+
let escalate = verdict.escalate;
|
|
1423
|
+
if (zecMoney && state === "expired_unfunded") {
|
|
1424
|
+
state = "stuck_after_payment";
|
|
1425
|
+
terminal = false;
|
|
1426
|
+
escalate = true;
|
|
1427
|
+
detail = "The order expired but the custody rail reports a ZEC pay-in. Do not pay again; escalate for manual reconciliation.";
|
|
1428
|
+
} else if (zecMoney && state === "awaiting_deposit") {
|
|
1429
|
+
state = "payin_detected";
|
|
1430
|
+
detail = zec.payinState === "seen" || zec.payinState === "confirming" ? `Shielded pay-in ${zec.payinState}: ${zec.confirmations ?? 0} of ${zec.requiredConfirmations ?? "?"} confirmations.` : `The custody rail reports a ZEC pay-in (${zec.payinState ?? "state unreadable"}). Do not pay again.`;
|
|
1431
|
+
}
|
|
1294
1432
|
if (provider === "bitrefill") {
|
|
1295
1433
|
let local = null;
|
|
1296
1434
|
try {
|
|
@@ -1320,9 +1458,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1320
1458
|
linkId: linkId || status?.pl_id || null,
|
|
1321
1459
|
state,
|
|
1322
1460
|
unknown: verdict.unknown,
|
|
1323
|
-
moneyDetected: verdict.moneyDetected,
|
|
1461
|
+
moneyDetected: verdict.moneyDetected || zecMoney,
|
|
1324
1462
|
terminal,
|
|
1325
|
-
escalate
|
|
1463
|
+
escalate,
|
|
1326
1464
|
detail,
|
|
1327
1465
|
backend: {
|
|
1328
1466
|
paymentStatus: payment?.status ?? status?.rozoPayment?.status ?? null,
|
|
@@ -1337,7 +1475,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1337
1475
|
txHash: publicHash(source.txHash ?? null),
|
|
1338
1476
|
confirmedAt: source.confirmedAt ?? null,
|
|
1339
1477
|
senderAddressMasked: source.senderAddress ? maskAddress(source.senderAddress) : null,
|
|
1340
|
-
chain: source.chainId ? chainName(source.chainId) : null
|
|
1478
|
+
chain: source.chainId ? chainName(source.chainId) : null,
|
|
1479
|
+
// Shielded ZEC only: the custody rail's own view of the pay-in.
|
|
1480
|
+
...zec ? { zec } : {}
|
|
1341
1481
|
},
|
|
1342
1482
|
expiry: (() => {
|
|
1343
1483
|
if (provider === "bitrefill" && !bitrefillExpiry) {
|
|
@@ -1353,7 +1493,7 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1353
1493
|
msRemaining: Number.isFinite(ms) ? ms : null,
|
|
1354
1494
|
// Native orders: the order stays open after the price lock, but a
|
|
1355
1495
|
// payment after it is valued at the arrival price. Pay before this.
|
|
1356
|
-
...native ? {
|
|
1496
|
+
...native || isZec ? {
|
|
1357
1497
|
quoteExpiresAt: quoteExpiresAt ?? null,
|
|
1358
1498
|
priceLockExpiresIn: Number.isFinite(lockMs) ? formatRemaining(lockMs) : null
|
|
1359
1499
|
} : {}
|
|
@@ -1364,7 +1504,8 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
1364
1504
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
1365
1505
|
},
|
|
1366
1506
|
localSend: localSendFor(id),
|
|
1367
|
-
|
|
1507
|
+
// Never a send window once the custody rail has seen a ZEC pay-in.
|
|
1508
|
+
sendWindow: state === "awaiting_deposit" && !zecMoney ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry, quoteExpiresAt }) : null,
|
|
1368
1509
|
errors: [statusError, paymentError].filter(Boolean)
|
|
1369
1510
|
};
|
|
1370
1511
|
}
|
|
@@ -1506,7 +1647,12 @@ async function main(argv) {
|
|
|
1506
1647
|
}
|
|
1507
1648
|
const payment = await getPayment(id);
|
|
1508
1649
|
const source = payment?.source || {};
|
|
1509
|
-
const
|
|
1650
|
+
const resumedShielded = isZecSource(source.chainId, source.tokenSymbol) && zecStatusOf(source)?.receive === "shielded";
|
|
1651
|
+
const guard = reuseGuard({
|
|
1652
|
+
payment,
|
|
1653
|
+
requested: { chainId: source.chainId, tokenSymbol: source.tokenSymbol, ...resumedShielded ? { shielded: true } : {} },
|
|
1654
|
+
reused: true
|
|
1655
|
+
});
|
|
1510
1656
|
if (!guard.ok) {
|
|
1511
1657
|
emit(
|
|
1512
1658
|
{
|
|
@@ -1524,13 +1670,16 @@ async function main(argv) {
|
|
|
1524
1670
|
);
|
|
1525
1671
|
}
|
|
1526
1672
|
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
1527
|
-
const
|
|
1673
|
+
const isZec = isZecSource(source.chainId, source.tokenSymbol);
|
|
1674
|
+
const zecStatus = isZec ? zecStatusOf(source) : null;
|
|
1675
|
+
const zec = Boolean(zecStatus && zecStatus.receive === "shielded");
|
|
1676
|
+
const quoteExpiresAt = isZec ? zecStatus?.quoteExpiresAt ?? null : priceLockFor({ native, payment, others: [snap.expiry?.quoteExpiresAt] });
|
|
1528
1677
|
const finalWindow = checkExpiry({
|
|
1529
1678
|
now: Date.now(),
|
|
1530
1679
|
chainId: source.chainId,
|
|
1531
1680
|
intentExpiresAt: payment?.expiresAt ?? snap.sendWindow?.deadlineMs,
|
|
1532
1681
|
coinbaseExpiry: snap.sendWindow?.deadlineMs,
|
|
1533
|
-
...native ? { quoteExpiresAt } : {}
|
|
1682
|
+
...native || isZec ? { quoteExpiresAt } : {}
|
|
1534
1683
|
});
|
|
1535
1684
|
if (!finalWindow.ok) {
|
|
1536
1685
|
emit(
|
|
@@ -1605,8 +1754,14 @@ async function main(argv) {
|
|
|
1605
1754
|
expiresAt,
|
|
1606
1755
|
expiresIn,
|
|
1607
1756
|
native,
|
|
1608
|
-
exactOut: native || lightning,
|
|
1609
|
-
quoteExpiresAt: native ? quoteExpiresAt : null
|
|
1757
|
+
exactOut: native || lightning || zec,
|
|
1758
|
+
quoteExpiresAt: native || zec ? quoteExpiresAt : null,
|
|
1759
|
+
...zec ? {
|
|
1760
|
+
shielded: true,
|
|
1761
|
+
paymentUri: guard.deposit.paymentUri,
|
|
1762
|
+
requiredConfirmations: zecStatus.requiredConfirmations,
|
|
1763
|
+
payinState: zecStatus.payinState
|
|
1764
|
+
} : {}
|
|
1610
1765
|
} : null,
|
|
1611
1766
|
depositWithheld: !confirmed,
|
|
1612
1767
|
display: {
|
|
@@ -1619,12 +1774,13 @@ async function main(argv) {
|
|
|
1619
1774
|
hasMemo: Boolean(source.receiverMemo),
|
|
1620
1775
|
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
1621
1776
|
native,
|
|
1622
|
-
priceLockedUntil: native ? quoteExpiresAt : null
|
|
1777
|
+
priceLockedUntil: native || zec ? quoteExpiresAt : null,
|
|
1778
|
+
...zec ? { shielded: true } : {}
|
|
1623
1779
|
},
|
|
1624
1780
|
expiry: {
|
|
1625
1781
|
expiresAt,
|
|
1626
1782
|
expiresIn,
|
|
1627
|
-
...native ? { quoteExpiresAt } : {},
|
|
1783
|
+
...native || zec ? { quoteExpiresAt } : {},
|
|
1628
1784
|
minutesOfSlack: snap.sendWindow?.minutesOfSlack ?? null
|
|
1629
1785
|
},
|
|
1630
1786
|
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."
|
package/scripts/dist/send-evm.js
CHANGED
|
@@ -41579,7 +41579,8 @@ var CHAIN_NAMES = {
|
|
|
41579
41579
|
42161: "Arbitrum",
|
|
41580
41580
|
900: "Solana",
|
|
41581
41581
|
1500: "Stellar",
|
|
41582
|
-
lightning: "Bitcoin Lightning"
|
|
41582
|
+
lightning: "Bitcoin Lightning",
|
|
41583
|
+
9133: "Zcash"
|
|
41583
41584
|
};
|
|
41584
41585
|
var CHAIN_FAMILY = {
|
|
41585
41586
|
1: "evm",
|
|
@@ -41589,7 +41590,9 @@ var CHAIN_FAMILY = {
|
|
|
41589
41590
|
42161: "evm",
|
|
41590
41591
|
900: "solana",
|
|
41591
41592
|
1500: "stellar",
|
|
41592
|
-
lightning: "lightning"
|
|
41593
|
+
lightning: "lightning",
|
|
41594
|
+
// Zcash: the CLI creates shielded orders only (pool Unified Address); it never signs.
|
|
41595
|
+
9133: "zcash"
|
|
41593
41596
|
};
|
|
41594
41597
|
var NATIVE_COINS = Object.freeze({
|
|
41595
41598
|
"1:ETH": { chainId: "1", symbol: "ETH", decimals: 18 },
|
|
@@ -41622,7 +41625,9 @@ var DECIMALS = {
|
|
|
41622
41625
|
"42161:ETH": 18,
|
|
41623
41626
|
"56:BNB": 18,
|
|
41624
41627
|
"137:POL": 18,
|
|
41625
|
-
"900:SOL": 9
|
|
41628
|
+
"900:SOL": 9,
|
|
41629
|
+
// Zcash: zatoshi.
|
|
41630
|
+
"9133:ZEC": 8
|
|
41626
41631
|
};
|
|
41627
41632
|
var AmountError = class extends Error {
|
|
41628
41633
|
constructor(code, message) {
|
|
@@ -53693,6 +53698,96 @@ async function getPayment(rozoPaymentId) {
|
|
|
53693
53698
|
return getJson(`${INTENTS_BASE}/payments/${encodeURIComponent(rozoPaymentId)}`);
|
|
53694
53699
|
}
|
|
53695
53700
|
|
|
53701
|
+
// scripts/src/lib/zec.mjs
|
|
53702
|
+
var ZEC_CHAIN_ID = "9133";
|
|
53703
|
+
var ZEC_SYMBOL = "ZEC";
|
|
53704
|
+
var ZEC_PAYIN_STATES = /* @__PURE__ */ new Set(["awaiting", "seen", "confirming", "eligible", "expired", "reorged"]);
|
|
53705
|
+
var UA_RE = /^u1[02-9ac-hj-np-z]{100,400}$/;
|
|
53706
|
+
function isZecSource(chainId, tokenSymbol) {
|
|
53707
|
+
return String(chainId ?? "").trim() === ZEC_CHAIN_ID && String(tokenSymbol ?? "").trim().toUpperCase() === ZEC_SYMBOL;
|
|
53708
|
+
}
|
|
53709
|
+
function isUnifiedAddress(value) {
|
|
53710
|
+
return typeof value === "string" && UA_RE.test(value.trim());
|
|
53711
|
+
}
|
|
53712
|
+
function zecStatusOf(source) {
|
|
53713
|
+
const z = source?.zec;
|
|
53714
|
+
if (!z || typeof z !== "object") return null;
|
|
53715
|
+
const receive = z.receive === "shielded" || z.receive === "transparent" ? z.receive : null;
|
|
53716
|
+
const payinState = ZEC_PAYIN_STATES.has(z.payinState) ? z.payinState : null;
|
|
53717
|
+
const confirmations = Number.isInteger(z.confirmations) && z.confirmations >= 0 ? z.confirmations : null;
|
|
53718
|
+
const requiredConfirmations = z.requiredConfirmations === 1 || z.requiredConfirmations === 2 ? z.requiredConfirmations : null;
|
|
53719
|
+
const quoteExpiresAt = typeof z.quoteExpiresAt === "string" && z.quoteExpiresAt ? z.quoteExpiresAt : null;
|
|
53720
|
+
const receivedZat = typeof z.receivedZat === "string" && /^\d+$/.test(z.receivedZat) ? z.receivedZat : null;
|
|
53721
|
+
return { receive, payinState, confirmations, requiredConfirmations, quoteExpiresAt, receivedZat };
|
|
53722
|
+
}
|
|
53723
|
+
function zecMoneySeen(zec) {
|
|
53724
|
+
if (!zec) return false;
|
|
53725
|
+
if (zec.payinState === "seen" || zec.payinState === "confirming" || zec.payinState === "eligible") return true;
|
|
53726
|
+
if (zec.receivedZat !== null && zec.receivedZat !== "0") return true;
|
|
53727
|
+
if (zec.payinState === null) return true;
|
|
53728
|
+
return false;
|
|
53729
|
+
}
|
|
53730
|
+
function parsePaymentUri(uri) {
|
|
53731
|
+
if (typeof uri !== "string") return null;
|
|
53732
|
+
const m = /^zcash:([0-9a-z]+)\?([^#]*)$/i.exec(uri.trim());
|
|
53733
|
+
if (!m) return null;
|
|
53734
|
+
const address = m[1];
|
|
53735
|
+
const out = { address, amount: null, memo: null };
|
|
53736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53737
|
+
for (const part of m[2].split("&")) {
|
|
53738
|
+
if (!part) return null;
|
|
53739
|
+
const eq = part.indexOf("=");
|
|
53740
|
+
if (eq <= 0) return null;
|
|
53741
|
+
const key = part.slice(0, eq);
|
|
53742
|
+
const value = part.slice(eq + 1);
|
|
53743
|
+
if (seen.has(key)) return null;
|
|
53744
|
+
seen.add(key);
|
|
53745
|
+
if (key === "amount") {
|
|
53746
|
+
if (!/^\d+(\.\d{1,8})?$/.test(value)) return null;
|
|
53747
|
+
out.amount = value;
|
|
53748
|
+
} else if (key === "memo") {
|
|
53749
|
+
if (!/^[A-Za-z0-9_-]{1,684}$/.test(value)) return null;
|
|
53750
|
+
out.memo = value;
|
|
53751
|
+
} else {
|
|
53752
|
+
return null;
|
|
53753
|
+
}
|
|
53754
|
+
}
|
|
53755
|
+
if (out.amount === null) return null;
|
|
53756
|
+
return out;
|
|
53757
|
+
}
|
|
53758
|
+
function sameDecimal(a, b) {
|
|
53759
|
+
const norm = (v) => {
|
|
53760
|
+
const s = String(v ?? "").trim();
|
|
53761
|
+
if (!/^\d+(\.\d+)?$/.test(s)) return null;
|
|
53762
|
+
const [w, f = ""] = s.split(".");
|
|
53763
|
+
const frac = f.replace(/0+$/, "");
|
|
53764
|
+
return `${w.replace(/^0+(?=\d)/, "")}${frac ? `.${frac}` : ""}`;
|
|
53765
|
+
};
|
|
53766
|
+
const na = norm(a);
|
|
53767
|
+
return na !== null && na === norm(b);
|
|
53768
|
+
}
|
|
53769
|
+
function validateShieldedDeposit(source) {
|
|
53770
|
+
const address = typeof source?.receiverAddress === "string" ? source.receiverAddress.trim() : "";
|
|
53771
|
+
if (!isUnifiedAddress(address)) {
|
|
53772
|
+
return { ok: false, code: "ZEC_ADDRESS_INVALID", reason: "The order did not return a mainnet Unified Address (u1...). Refusing to show it as payable." };
|
|
53773
|
+
}
|
|
53774
|
+
const uri = typeof source?.paymentUri === "string" ? source.paymentUri.trim() : "";
|
|
53775
|
+
if (!uri) {
|
|
53776
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISSING", reason: "The order carries no payment URI yet. Nothing is payable until it appears." };
|
|
53777
|
+
}
|
|
53778
|
+
const parsed = parsePaymentUri(uri);
|
|
53779
|
+
if (!parsed) {
|
|
53780
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_INVALID", reason: "The payment URI is not a single-payment ZIP-321 URI. Refusing to show it." };
|
|
53781
|
+
}
|
|
53782
|
+
if (parsed.address !== address) {
|
|
53783
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI names a different address than the order. Refusing to show it." };
|
|
53784
|
+
}
|
|
53785
|
+
if (!sameDecimal(parsed.amount, source?.amount)) {
|
|
53786
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI amount differs from the order amount. Refusing to show it." };
|
|
53787
|
+
}
|
|
53788
|
+
return { ok: true, code: null, reason: null, address, paymentUri: uri, memo: parsed.memo };
|
|
53789
|
+
}
|
|
53790
|
+
|
|
53696
53791
|
// scripts/src/lib/guards.mjs
|
|
53697
53792
|
var UNPAID_STATUS = "payment_unpaid";
|
|
53698
53793
|
function receiptSignal(source) {
|
|
@@ -53746,6 +53841,11 @@ function validateDepositInstructions(source) {
|
|
|
53746
53841
|
reason: "The live payments response returned no deposit address."
|
|
53747
53842
|
};
|
|
53748
53843
|
}
|
|
53844
|
+
if (family === "zcash" && zecStatusOf(source)?.receive === "shielded") {
|
|
53845
|
+
const zec = validateShieldedDeposit(source);
|
|
53846
|
+
if (!zec.ok) return { ok: false, code: zec.code, reason: zec.reason };
|
|
53847
|
+
return { ok: true, code: null, reason: null, family, amountAtomic, payTo: zec.address, memo: null, paymentUri: zec.paymentUri };
|
|
53848
|
+
}
|
|
53749
53849
|
if (family === "stellar" && !memo) {
|
|
53750
53850
|
return {
|
|
53751
53851
|
ok: false,
|
|
@@ -53778,13 +53878,14 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
53778
53878
|
const hasTx = source.txHash !== null && source.txHash !== void 0 && source.txHash !== "";
|
|
53779
53879
|
const signal = receiptSignal(source);
|
|
53780
53880
|
const hasConfirm = source.confirmedAt !== null && source.confirmedAt !== void 0 && source.confirmedAt !== "";
|
|
53781
|
-
|
|
53881
|
+
const zecSeen = isZecSource(source.chainId, source.tokenSymbol) && zecMoneySeen(zecStatusOf(source));
|
|
53882
|
+
if (hasTx || signal.money || hasConfirm || zecSeen) {
|
|
53782
53883
|
return {
|
|
53783
53884
|
ok: false,
|
|
53784
53885
|
code: "ORDER_ALREADY_FUNDED",
|
|
53785
|
-
reason: signal.unparsable ? "This order reports an amountReceived that cannot be read. It is treated as funded until a human confirms otherwise \u2014 do NOT pay again." : "This Coinbase link already has a funded Rozo order (it may have been paid elsewhere). Do NOT pay again \u2014 escalate for manual reconciliation.",
|
|
53886
|
+
reason: signal.unparsable ? "This order reports an amountReceived that cannot be read. It is treated as funded until a human confirms otherwise \u2014 do NOT pay again." : zecSeen && !hasTx && !signal.money && !hasConfirm ? "A shielded ZEC payment to this order has already been seen on the network. Do NOT pay again; watch it with status instead." : "This Coinbase link already has a funded Rozo order (it may have been paid elsewhere). Do NOT pay again \u2014 escalate for manual reconciliation.",
|
|
53786
53887
|
moneyDetected: true,
|
|
53787
|
-
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable }
|
|
53888
|
+
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable, ...zecSeen ? { zec: zecStatusOf(source) } : {} }
|
|
53788
53889
|
};
|
|
53789
53890
|
}
|
|
53790
53891
|
if (payment?.status !== UNPAID_STATUS) {
|
|
@@ -53812,6 +53913,28 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
53812
53913
|
evidence
|
|
53813
53914
|
};
|
|
53814
53915
|
}
|
|
53916
|
+
if (isZecSource(gotChain, gotToken)) {
|
|
53917
|
+
const zec = zecStatusOf(source);
|
|
53918
|
+
const wantReceive = requested?.shielded === true ? "shielded" : "transparent";
|
|
53919
|
+
if (!zec || zec.receive !== wantReceive) {
|
|
53920
|
+
return {
|
|
53921
|
+
ok: false,
|
|
53922
|
+
code: "ZEC_RECEIVE_MODE_MISMATCH",
|
|
53923
|
+
reason: `This link already has a ZEC order that is paid ${zec?.receive ?? "in an unknown way"}, but you asked for ${wantReceive}. Nothing new was created. Pay that order the way it was made, or wait for it to expire unfunded.`,
|
|
53924
|
+
moneyDetected: false,
|
|
53925
|
+
evidence: { ...evidence, zec }
|
|
53926
|
+
};
|
|
53927
|
+
}
|
|
53928
|
+
if (zec.payinState === "expired" || zec.payinState === "reorged") {
|
|
53929
|
+
return {
|
|
53930
|
+
ok: false,
|
|
53931
|
+
code: "ZEC_QUOTE_EXPIRED",
|
|
53932
|
+
reason: `The ZEC price lock on this order has ${zec.payinState === "reorged" ? "been disturbed by a reorg" : "expired"} with nothing received. Do not pay it; re-run pay to get a fresh order.`,
|
|
53933
|
+
moneyDetected: false,
|
|
53934
|
+
evidence: { ...evidence, zec }
|
|
53935
|
+
};
|
|
53936
|
+
}
|
|
53937
|
+
}
|
|
53815
53938
|
const deposit = validateDepositInstructions(source);
|
|
53816
53939
|
if (!deposit.ok) {
|
|
53817
53940
|
return {
|
|
@@ -53910,7 +54033,9 @@ var MARGINS_MS = {
|
|
|
53910
54033
|
42161: 10 * MINUTE,
|
|
53911
54034
|
900: 5 * MINUTE,
|
|
53912
54035
|
1500: 10 * MINUTE,
|
|
53913
|
-
lightning: 10 * MINUTE
|
|
54036
|
+
lightning: 10 * MINUTE,
|
|
54037
|
+
// Zcash: 75 s blocks, 1 to 2 confirmations, then the hub advances the merchant.
|
|
54038
|
+
9133: 10 * MINUTE
|
|
53914
54039
|
};
|
|
53915
54040
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
53916
54041
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|