@rozoai/checkout 0.1.22 → 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 +7 -2
- package/docs/QUICKSTART.es.md +1 -1
- package/docs/QUICKSTART.ja.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/docs/QUICKSTART.zh.md +1 -1
- package/docs/README.es.md +1 -1
- package/docs/README.ja.md +1 -1
- package/docs/README.zh.md +1 -1
- package/llms.txt +10 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +432 -57
- package/scripts/dist/create-order.js +304 -29
- package/scripts/dist/quote.js +104 -8
- 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/cli.js
CHANGED
|
@@ -41646,7 +41646,8 @@ var CHAIN_NAMES = {
|
|
|
41646
41646
|
42161: "Arbitrum",
|
|
41647
41647
|
900: "Solana",
|
|
41648
41648
|
1500: "Stellar",
|
|
41649
|
-
lightning: "Bitcoin Lightning"
|
|
41649
|
+
lightning: "Bitcoin Lightning",
|
|
41650
|
+
9133: "Zcash"
|
|
41650
41651
|
};
|
|
41651
41652
|
var CHAIN_FAMILY = {
|
|
41652
41653
|
1: "evm",
|
|
@@ -41656,7 +41657,9 @@ var CHAIN_FAMILY = {
|
|
|
41656
41657
|
42161: "evm",
|
|
41657
41658
|
900: "solana",
|
|
41658
41659
|
1500: "stellar",
|
|
41659
|
-
lightning: "lightning"
|
|
41660
|
+
lightning: "lightning",
|
|
41661
|
+
// Zcash: the CLI creates shielded orders only (pool Unified Address); it never signs.
|
|
41662
|
+
9133: "zcash"
|
|
41660
41663
|
};
|
|
41661
41664
|
var SUPPORTED_SOURCES = [
|
|
41662
41665
|
{ chainId: "1", chain: "Ethereum", tokens: ["USDC", "USDT", "ETH"] },
|
|
@@ -41666,7 +41669,9 @@ var SUPPORTED_SOURCES = [
|
|
|
41666
41669
|
{ chainId: "8453", chain: "Base", tokens: ["USDC", "ETH"] },
|
|
41667
41670
|
{ chainId: "42161", chain: "Arbitrum", tokens: ["USDC", "USDT", "ETH"] },
|
|
41668
41671
|
{ chainId: "1500", chain: "Stellar", tokens: ["USDC"] },
|
|
41669
|
-
{ chainId: "lightning", chain: "Bitcoin Lightning", tokens: ["BTC"] }
|
|
41672
|
+
{ chainId: "lightning", chain: "Bitcoin Lightning", tokens: ["BTC"] },
|
|
41673
|
+
// ZEC (Beta): shielded orders only, offered per invoice by the router (see lib/zec.mjs).
|
|
41674
|
+
{ chainId: "9133", chain: "Zcash", tokens: ["ZEC"] }
|
|
41670
41675
|
];
|
|
41671
41676
|
var NATIVE_COINS = Object.freeze({
|
|
41672
41677
|
"1:ETH": { chainId: "1", symbol: "ETH", decimals: 18 },
|
|
@@ -41676,7 +41681,7 @@ var NATIVE_COINS = Object.freeze({
|
|
|
41676
41681
|
"137:POL": { chainId: "137", symbol: "POL", decimals: 18 },
|
|
41677
41682
|
"900:SOL": { chainId: "900", symbol: "SOL", decimals: 9 }
|
|
41678
41683
|
});
|
|
41679
|
-
var NATIVE_MAX_USD =
|
|
41684
|
+
var NATIVE_MAX_USD = 3500;
|
|
41680
41685
|
var NATIVE_MIN_USD_FALLBACK = Object.freeze({ "1:ETH": 1 });
|
|
41681
41686
|
var DECIMALS = {
|
|
41682
41687
|
"1:USDC": 6,
|
|
@@ -41700,7 +41705,9 @@ var DECIMALS = {
|
|
|
41700
41705
|
"42161:ETH": 18,
|
|
41701
41706
|
"56:BNB": 18,
|
|
41702
41707
|
"137:POL": 18,
|
|
41703
|
-
"900:SOL": 9
|
|
41708
|
+
"900:SOL": 9,
|
|
41709
|
+
// Zcash: zatoshi.
|
|
41710
|
+
"9133:ZEC": 8
|
|
41704
41711
|
};
|
|
41705
41712
|
var AmountError = class extends Error {
|
|
41706
41713
|
constructor(code, message) {
|
|
@@ -41871,6 +41878,161 @@ function nativeOfferFor({ chainId, tokenSymbol, supportedSources, nativeMinUsd,
|
|
|
41871
41878
|
return { ok: true, code: null, reason: null, minUsd };
|
|
41872
41879
|
}
|
|
41873
41880
|
|
|
41881
|
+
// scripts/src/lib/zec.mjs
|
|
41882
|
+
var ZEC_CHAIN_ID = "9133";
|
|
41883
|
+
var ZEC_SYMBOL = "ZEC";
|
|
41884
|
+
var ZEC_SOURCE_META_KEY = "ZEC@9133";
|
|
41885
|
+
var ZEC_BETA_PARAM = "zec,zec_shielded";
|
|
41886
|
+
var ZEC_SHIELDED_MAX_USD = 1200;
|
|
41887
|
+
var ZEC_MIN_USD = 5;
|
|
41888
|
+
var ZEC_TWO_CONF_FROM_USD = 110;
|
|
41889
|
+
var ZEC_PAYIN_STATES = /* @__PURE__ */ new Set(["awaiting", "seen", "confirming", "eligible", "expired", "reorged"]);
|
|
41890
|
+
var UA_RE = /^u1[02-9ac-hj-np-z]{100,400}$/;
|
|
41891
|
+
function isZecSource(chainId, tokenSymbol) {
|
|
41892
|
+
return String(chainId ?? "").trim() === ZEC_CHAIN_ID && String(tokenSymbol ?? "").trim().toUpperCase() === ZEC_SYMBOL;
|
|
41893
|
+
}
|
|
41894
|
+
function isUnifiedAddress(value) {
|
|
41895
|
+
return typeof value === "string" && UA_RE.test(value.trim());
|
|
41896
|
+
}
|
|
41897
|
+
function zecRequiredConfirmations(usd) {
|
|
41898
|
+
const n = Number(usd);
|
|
41899
|
+
return Number.isFinite(n) && n >= ZEC_TWO_CONF_FROM_USD ? 2 : 1;
|
|
41900
|
+
}
|
|
41901
|
+
function zecShieldedOfferFor({ supportedSources, sourceMeta, callerPaysUsd }) {
|
|
41902
|
+
const list = supportedSources && typeof supportedSources === "object" ? supportedSources[ZEC_CHAIN_ID] : null;
|
|
41903
|
+
if (!Array.isArray(list) || !list.map((t) => String(t).toUpperCase()).includes(ZEC_SYMBOL)) {
|
|
41904
|
+
return {
|
|
41905
|
+
ok: false,
|
|
41906
|
+
code: "ZEC_NOT_OFFERED",
|
|
41907
|
+
reason: "ZEC is not offered for this invoice. Pay with USDC or USDT instead.",
|
|
41908
|
+
requiredConfirmations: null
|
|
41909
|
+
};
|
|
41910
|
+
}
|
|
41911
|
+
const meta = sourceMeta && typeof sourceMeta === "object" ? sourceMeta[ZEC_SOURCE_META_KEY] : null;
|
|
41912
|
+
const available = meta && typeof meta === "object" && (meta.shieldedAvailable === true || meta.shielded_available === true);
|
|
41913
|
+
if (!available) {
|
|
41914
|
+
return {
|
|
41915
|
+
ok: false,
|
|
41916
|
+
code: "ZEC_SHIELDED_NOT_AVAILABLE",
|
|
41917
|
+
reason: "Paying from a shielded balance is not available for this invoice right now.",
|
|
41918
|
+
requiredConfirmations: null
|
|
41919
|
+
};
|
|
41920
|
+
}
|
|
41921
|
+
const usd = Number(callerPaysUsd);
|
|
41922
|
+
if (!Number.isFinite(usd) || usd <= 0) {
|
|
41923
|
+
return {
|
|
41924
|
+
ok: false,
|
|
41925
|
+
code: "ZEC_AMOUNT_UNKNOWN",
|
|
41926
|
+
reason: "The quote carries no USD amount, so the shielded ZEC limits cannot be checked. Pay with USDC or USDT instead.",
|
|
41927
|
+
requiredConfirmations: null
|
|
41928
|
+
};
|
|
41929
|
+
}
|
|
41930
|
+
if (usd > ZEC_SHIELDED_MAX_USD) {
|
|
41931
|
+
return {
|
|
41932
|
+
ok: false,
|
|
41933
|
+
code: "ZEC_AMOUNT_TOO_HIGH",
|
|
41934
|
+
reason: `Shielded ZEC is limited to $${ZEC_SHIELDED_MAX_USD} per payment during the beta; this invoice is $${usd}. Pay with USDC or USDT instead.`,
|
|
41935
|
+
requiredConfirmations: null
|
|
41936
|
+
};
|
|
41937
|
+
}
|
|
41938
|
+
if (usd < ZEC_MIN_USD) {
|
|
41939
|
+
return {
|
|
41940
|
+
ok: false,
|
|
41941
|
+
code: "ZEC_AMOUNT_TOO_LOW",
|
|
41942
|
+
reason: `ZEC payments need an invoice of at least $${ZEC_MIN_USD}; this one is $${usd}. Pay with another coin.`,
|
|
41943
|
+
requiredConfirmations: null
|
|
41944
|
+
};
|
|
41945
|
+
}
|
|
41946
|
+
return { ok: true, code: null, reason: null, requiredConfirmations: zecRequiredConfirmations(usd) };
|
|
41947
|
+
}
|
|
41948
|
+
function zecStatusOf(source) {
|
|
41949
|
+
const z = source?.zec;
|
|
41950
|
+
if (!z || typeof z !== "object") return null;
|
|
41951
|
+
const receive = z.receive === "shielded" || z.receive === "transparent" ? z.receive : null;
|
|
41952
|
+
const payinState = ZEC_PAYIN_STATES.has(z.payinState) ? z.payinState : null;
|
|
41953
|
+
const confirmations = Number.isInteger(z.confirmations) && z.confirmations >= 0 ? z.confirmations : null;
|
|
41954
|
+
const requiredConfirmations = z.requiredConfirmations === 1 || z.requiredConfirmations === 2 ? z.requiredConfirmations : null;
|
|
41955
|
+
const quoteExpiresAt = typeof z.quoteExpiresAt === "string" && z.quoteExpiresAt ? z.quoteExpiresAt : null;
|
|
41956
|
+
const receivedZat = typeof z.receivedZat === "string" && /^\d+$/.test(z.receivedZat) ? z.receivedZat : null;
|
|
41957
|
+
return { receive, payinState, confirmations, requiredConfirmations, quoteExpiresAt, receivedZat };
|
|
41958
|
+
}
|
|
41959
|
+
function zecMoneySeen(zec) {
|
|
41960
|
+
if (!zec) return false;
|
|
41961
|
+
if (zec.payinState === "seen" || zec.payinState === "confirming" || zec.payinState === "eligible") return true;
|
|
41962
|
+
if (zec.receivedZat !== null && zec.receivedZat !== "0") return true;
|
|
41963
|
+
if (zec.payinState === null) return true;
|
|
41964
|
+
return false;
|
|
41965
|
+
}
|
|
41966
|
+
function parsePaymentUri(uri) {
|
|
41967
|
+
if (typeof uri !== "string") return null;
|
|
41968
|
+
const m = /^zcash:([0-9a-z]+)\?([^#]*)$/i.exec(uri.trim());
|
|
41969
|
+
if (!m) return null;
|
|
41970
|
+
const address = m[1];
|
|
41971
|
+
const out2 = { address, amount: null, memo: null };
|
|
41972
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41973
|
+
for (const part of m[2].split("&")) {
|
|
41974
|
+
if (!part) return null;
|
|
41975
|
+
const eq = part.indexOf("=");
|
|
41976
|
+
if (eq <= 0) return null;
|
|
41977
|
+
const key = part.slice(0, eq);
|
|
41978
|
+
const value = part.slice(eq + 1);
|
|
41979
|
+
if (seen.has(key)) return null;
|
|
41980
|
+
seen.add(key);
|
|
41981
|
+
if (key === "amount") {
|
|
41982
|
+
if (!/^\d+(\.\d{1,8})?$/.test(value)) return null;
|
|
41983
|
+
out2.amount = value;
|
|
41984
|
+
} else if (key === "memo") {
|
|
41985
|
+
if (!/^[A-Za-z0-9_-]{1,684}$/.test(value)) return null;
|
|
41986
|
+
out2.memo = value;
|
|
41987
|
+
} else {
|
|
41988
|
+
return null;
|
|
41989
|
+
}
|
|
41990
|
+
}
|
|
41991
|
+
if (out2.amount === null) return null;
|
|
41992
|
+
return out2;
|
|
41993
|
+
}
|
|
41994
|
+
function sameDecimal(a, b) {
|
|
41995
|
+
const norm = (v) => {
|
|
41996
|
+
const s = String(v ?? "").trim();
|
|
41997
|
+
if (!/^\d+(\.\d+)?$/.test(s)) return null;
|
|
41998
|
+
const [w, f = ""] = s.split(".");
|
|
41999
|
+
const frac = f.replace(/0+$/, "");
|
|
42000
|
+
return `${w.replace(/^0+(?=\d)/, "")}${frac ? `.${frac}` : ""}`;
|
|
42001
|
+
};
|
|
42002
|
+
const na = norm(a);
|
|
42003
|
+
return na !== null && na === norm(b);
|
|
42004
|
+
}
|
|
42005
|
+
function validateShieldedDeposit(source) {
|
|
42006
|
+
const address = typeof source?.receiverAddress === "string" ? source.receiverAddress.trim() : "";
|
|
42007
|
+
if (!isUnifiedAddress(address)) {
|
|
42008
|
+
return { ok: false, code: "ZEC_ADDRESS_INVALID", reason: "The order did not return a mainnet Unified Address (u1...). Refusing to show it as payable." };
|
|
42009
|
+
}
|
|
42010
|
+
const uri = typeof source?.paymentUri === "string" ? source.paymentUri.trim() : "";
|
|
42011
|
+
if (!uri) {
|
|
42012
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISSING", reason: "The order carries no payment URI yet. Nothing is payable until it appears." };
|
|
42013
|
+
}
|
|
42014
|
+
const parsed = parsePaymentUri(uri);
|
|
42015
|
+
if (!parsed) {
|
|
42016
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_INVALID", reason: "The payment URI is not a single-payment ZIP-321 URI. Refusing to show it." };
|
|
42017
|
+
}
|
|
42018
|
+
if (parsed.address !== address) {
|
|
42019
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI names a different address than the order. Refusing to show it." };
|
|
42020
|
+
}
|
|
42021
|
+
if (!sameDecimal(parsed.amount, source?.amount)) {
|
|
42022
|
+
return { ok: false, code: "ZEC_PAYMENT_URI_MISMATCH", reason: "The payment URI amount differs from the order amount. Refusing to show it." };
|
|
42023
|
+
}
|
|
42024
|
+
return { ok: true, code: null, reason: null, address, paymentUri: uri, memo: parsed.memo };
|
|
42025
|
+
}
|
|
42026
|
+
function zecDepositWarnings({ requiredConfirmations }) {
|
|
42027
|
+
return [
|
|
42028
|
+
"Pay from a shielded balance, in ONE transaction, exactly the amount shown. The price is locked only until the time shown; after that, do not send, start a new order.",
|
|
42029
|
+
"Underpayments are not refunded automatically: a short payment is held for support, not credited.",
|
|
42030
|
+
`Scan the payment URI (QR content) with a shielded wallet such as Zashi; it carries the address, the amount and the order memo. Do not edit any of them.`,
|
|
42031
|
+
`The merchant is paid after ${requiredConfirmations ?? 1} confirmation${requiredConfirmations === 2 ? "s" : ""} (about 75 seconds each).`,
|
|
42032
|
+
`Beta: up to $${ZEC_SHIELDED_MAX_USD} per payment.`
|
|
42033
|
+
];
|
|
42034
|
+
}
|
|
42035
|
+
|
|
41874
42036
|
// scripts/src/lib/utm.mjs
|
|
41875
42037
|
function normalizeUtmSource(value) {
|
|
41876
42038
|
if (typeof value !== "string") return null;
|
|
@@ -41954,7 +42116,9 @@ var CHAIN_ALIASES = {
|
|
|
41954
42116
|
lightning: "lightning",
|
|
41955
42117
|
ln: "lightning",
|
|
41956
42118
|
bitcoin: "lightning",
|
|
41957
|
-
btc: "lightning"
|
|
42119
|
+
btc: "lightning",
|
|
42120
|
+
zcash: "9133",
|
|
42121
|
+
zec: "9133"
|
|
41958
42122
|
};
|
|
41959
42123
|
var PRESET_CHAIN = {
|
|
41960
42124
|
"900": "solana",
|
|
@@ -41964,12 +42128,14 @@ var PRESET_CHAIN = {
|
|
|
41964
42128
|
"8453": "base",
|
|
41965
42129
|
"42161": "arbitrum",
|
|
41966
42130
|
"1500": "stellar",
|
|
41967
|
-
lightning: "lightning"
|
|
42131
|
+
lightning: "lightning",
|
|
42132
|
+
"9133": "zcash"
|
|
41968
42133
|
};
|
|
41969
42134
|
var PRESET_SHORTCUTS = Object.freeze({
|
|
41970
42135
|
bnb: "bnb-bnb",
|
|
41971
42136
|
sol: "sol-solana",
|
|
41972
|
-
pol: "pol-polygon"
|
|
42137
|
+
pol: "pol-polygon",
|
|
42138
|
+
zec: "zec-zcash"
|
|
41973
42139
|
});
|
|
41974
42140
|
var PICKER_ROWS = [
|
|
41975
42141
|
{ group: "USDT", token: "USDT", chain: "Solana", chainId: "900" },
|
|
@@ -41990,14 +42156,17 @@ var PICKER_ROWS = [
|
|
|
41990
42156
|
{ group: "Native coins", token: "ETH", chain: "Arbitrum", chainId: "42161" },
|
|
41991
42157
|
{ group: "Native coins", token: "BNB", chain: "BNB Chain", chainId: "56" },
|
|
41992
42158
|
{ group: "Native coins", token: "SOL", chain: "Solana", chainId: "900" },
|
|
41993
|
-
{ group: "Native coins", token: "POL", chain: "Polygon", chainId: "137" }
|
|
42159
|
+
{ group: "Native coins", token: "POL", chain: "Polygon", chainId: "137" },
|
|
42160
|
+
// Shielded only; shown when the router offers shielded ZEC for this invoice.
|
|
42161
|
+
{ group: "Zcash (Beta)", token: "ZEC", chain: "Zcash", chainId: "9133" }
|
|
41994
42162
|
].map((o) => ({
|
|
41995
42163
|
...o,
|
|
41996
42164
|
tokenSymbol: o.token,
|
|
41997
42165
|
native: isNativeSource(o.chainId, o.token),
|
|
42166
|
+
zec: isZecSource(o.chainId, o.token),
|
|
41998
42167
|
// What a row says after its number. Native rows name the coin, since the
|
|
41999
42168
|
// group heading does not.
|
|
42000
|
-
label: isNativeSource(o.chainId, o.token) ? `${o.token} on ${o.chain}` : o.chain,
|
|
42169
|
+
label: isZecSource(o.chainId, o.token) ? "ZEC from a shielded balance" : isNativeSource(o.chainId, o.token) ? `${o.token} on ${o.chain}` : o.chain,
|
|
42001
42170
|
// The flag a user could have typed instead, echoed back so they learn it.
|
|
42002
42171
|
preset: `${o.token.toLowerCase()}-${PRESET_CHAIN[o.chainId]}`
|
|
42003
42172
|
}));
|
|
@@ -42007,7 +42176,7 @@ function numbered(rows) {
|
|
|
42007
42176
|
var PICKER_OPTIONS = numbered(PICKER_ROWS);
|
|
42008
42177
|
function pickerOptionsFor(isOffered = null) {
|
|
42009
42178
|
return numbered(
|
|
42010
|
-
PICKER_ROWS.filter((o) => !o.native || typeof isOffered === "function" && isOffered(o.chainId, o.tokenSymbol))
|
|
42179
|
+
PICKER_ROWS.filter((o) => !o.native && !o.zec || typeof isOffered === "function" && isOffered(o.chainId, o.tokenSymbol))
|
|
42011
42180
|
);
|
|
42012
42181
|
}
|
|
42013
42182
|
function resolvePickerChoice(input, options = PICKER_OPTIONS) {
|
|
@@ -42082,6 +42251,7 @@ function resolvePreset(preset) {
|
|
|
42082
42251
|
return { chainId, tokenSymbol };
|
|
42083
42252
|
}
|
|
42084
42253
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
42254
|
+
"shielded",
|
|
42085
42255
|
"send",
|
|
42086
42256
|
"yes",
|
|
42087
42257
|
"json",
|
|
@@ -42294,6 +42464,20 @@ function parseCliArgs(argv) {
|
|
|
42294
42464
|
} else {
|
|
42295
42465
|
source = null;
|
|
42296
42466
|
}
|
|
42467
|
+
const shielded = flags.shielded === true;
|
|
42468
|
+
if (source && isZecSource(source.chainId, source.tokenSymbol)) {
|
|
42469
|
+
if (!shielded && !bitrefill) {
|
|
42470
|
+
throw new CliError(
|
|
42471
|
+
"ZEC_SHIELDED_REQUIRED",
|
|
42472
|
+
"ZEC orders from this CLI pay from a shielded balance: add --shielded (rozo-checkout pay <link> --with zec --shielded). For transparent ZEC use https://checkout.rozo.ai/zec."
|
|
42473
|
+
);
|
|
42474
|
+
}
|
|
42475
|
+
if (flags.send === true) {
|
|
42476
|
+
throw new CliError("CONFLICTING_FLAGS", "--send is not available for ZEC: the payer pays from their own shielded wallet.");
|
|
42477
|
+
}
|
|
42478
|
+
} else if (shielded) {
|
|
42479
|
+
throw new CliError("CONFLICTING_FLAGS", "--shielded only applies to ZEC (--with zec).");
|
|
42480
|
+
}
|
|
42297
42481
|
let email = null;
|
|
42298
42482
|
if (flags.email !== void 0) {
|
|
42299
42483
|
email = normalizeContactEmail(String(flags.email));
|
|
@@ -42313,7 +42497,8 @@ function parseCliArgs(argv) {
|
|
|
42313
42497
|
target: target ?? null,
|
|
42314
42498
|
bitrefill,
|
|
42315
42499
|
json,
|
|
42316
|
-
source,
|
|
42500
|
+
source: source && shielded ? { ...source, shielded: true } : source,
|
|
42501
|
+
shielded,
|
|
42317
42502
|
send: flags.send === true,
|
|
42318
42503
|
yes: flags.yes === true,
|
|
42319
42504
|
dryRun: flags["dry-run"] === true,
|
|
@@ -42331,7 +42516,8 @@ function parseCliArgs(argv) {
|
|
|
42331
42516
|
}
|
|
42332
42517
|
var HELP = `rozo-checkout \u2014 pay a Coinbase Payment Link or a Bitrefill invoice
|
|
42333
42518
|
(USDC on Base) with BTC Lightning, USDT/USDC on Solana, BNB Chain, Ethereum, Polygon,
|
|
42334
|
-
Arbitrum, Base or Stellar,
|
|
42519
|
+
Arbitrum, Base or Stellar, a native coin: ETH (Ethereum, Base, Arbitrum), BNB, SOL, POL,
|
|
42520
|
+
or ZEC from a shielded balance.
|
|
42335
42521
|
|
|
42336
42522
|
USAGE
|
|
42337
42523
|
npx @rozoai/checkout pay <coinbase-link> (pick a coin from a list)
|
|
@@ -42350,11 +42536,13 @@ COINS (--with)
|
|
|
42350
42536
|
btc-lightning
|
|
42351
42537
|
native coins: eth-ethereum eth-base eth-arbitrum bnb-bnb (or bnb)
|
|
42352
42538
|
sol-solana (or sol) pol-polygon (or pol)
|
|
42539
|
+
shielded ZEC: --with zec --shielded (prints the address, amount and payment
|
|
42540
|
+
URI to scan with a shielded wallet; never sends; limits apply)
|
|
42353
42541
|
or give them raw: --chain 900 --token USDT
|
|
42354
42542
|
|
|
42355
42543
|
NATIVE COINS
|
|
42356
42544
|
Offered only when the invoice's quote lists the coin, and only for invoices
|
|
42357
|
-
up to $
|
|
42545
|
+
up to $3,500 (native ETH on Ethereum: $1 minimum). The order is exact-out:
|
|
42358
42546
|
send exactly the coin amount shown. Its price is locked only until the time
|
|
42359
42547
|
shown (about an hour); after that do not send, start a new order. The coin
|
|
42360
42548
|
amount includes an FX spread on top of the service fee, and your wallet also
|
|
@@ -43379,7 +43567,9 @@ var MARGINS_MS = {
|
|
|
43379
43567
|
42161: 10 * MINUTE,
|
|
43380
43568
|
900: 5 * MINUTE,
|
|
43381
43569
|
1500: 10 * MINUTE,
|
|
43382
|
-
lightning: 10 * MINUTE
|
|
43570
|
+
lightning: 10 * MINUTE,
|
|
43571
|
+
// Zcash: 75 s blocks, 1 to 2 confirmations, then the hub advances the merchant.
|
|
43572
|
+
9133: 10 * MINUTE
|
|
43383
43573
|
};
|
|
43384
43574
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
43385
43575
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
@@ -55323,15 +55513,27 @@ function buildAttribution({ utmSource, identity } = {}) {
|
|
|
55323
55513
|
};
|
|
55324
55514
|
}
|
|
55325
55515
|
var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
|
|
55326
|
-
async function quoteInvoice({ url, linkId }) {
|
|
55516
|
+
async function quoteInvoice({ url, linkId, beta }) {
|
|
55327
55517
|
const body = url ? { url } : { payment_id: linkId };
|
|
55328
|
-
return postJson(`${MPP_BASE}/quote-invoice`, body);
|
|
55518
|
+
return postJson(withBeta(`${MPP_BASE}/quote-invoice`, beta), body);
|
|
55519
|
+
}
|
|
55520
|
+
function withBeta(url, beta) {
|
|
55521
|
+
if (!beta) return url;
|
|
55522
|
+
const u = new URL(url);
|
|
55523
|
+
u.searchParams.set("beta", beta);
|
|
55524
|
+
return u.toString();
|
|
55329
55525
|
}
|
|
55330
55526
|
var QUOTE_RECEIPT_RETRY_BUDGET_MS = 2e4;
|
|
55331
|
-
async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email }) {
|
|
55527
|
+
async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email, beta }) {
|
|
55332
55528
|
const body = {
|
|
55333
55529
|
...url ? { url } : { payment_id: linkId },
|
|
55334
|
-
source: {
|
|
55530
|
+
source: {
|
|
55531
|
+
chainId: String(source.chainId),
|
|
55532
|
+
tokenSymbol: source.tokenSymbol,
|
|
55533
|
+
// Shielded ZEC: the router passes this to intents, which hands out a
|
|
55534
|
+
// pool Unified Address instead of a transparent one. Never sent otherwise.
|
|
55535
|
+
...source.shielded === true ? { shielded: true } : {}
|
|
55536
|
+
},
|
|
55335
55537
|
...quoteReceipt ? { quoteReceipt } : {},
|
|
55336
55538
|
client: CLIENT_LABEL,
|
|
55337
55539
|
attribution: buildAttribution({ utmSource }),
|
|
@@ -55340,7 +55542,7 @@ async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, ema
|
|
|
55340
55542
|
...email ? { email } : {}
|
|
55341
55543
|
};
|
|
55342
55544
|
return postJson(
|
|
55343
|
-
`${MPP_BASE}/create-invoice`,
|
|
55545
|
+
withBeta(`${MPP_BASE}/create-invoice`, beta),
|
|
55344
55546
|
body,
|
|
55345
55547
|
quoteReceipt ? { maxTotalWaitMs: QUOTE_RECEIPT_RETRY_BUDGET_MS } : void 0
|
|
55346
55548
|
);
|
|
@@ -55452,6 +55654,11 @@ function validateDepositInstructions(source) {
|
|
|
55452
55654
|
reason: "The live payments response returned no deposit address."
|
|
55453
55655
|
};
|
|
55454
55656
|
}
|
|
55657
|
+
if (family === "zcash" && zecStatusOf(source)?.receive === "shielded") {
|
|
55658
|
+
const zec = validateShieldedDeposit(source);
|
|
55659
|
+
if (!zec.ok) return { ok: false, code: zec.code, reason: zec.reason };
|
|
55660
|
+
return { ok: true, code: null, reason: null, family, amountAtomic, payTo: zec.address, memo: null, paymentUri: zec.paymentUri };
|
|
55661
|
+
}
|
|
55455
55662
|
if (family === "stellar" && !memo) {
|
|
55456
55663
|
return {
|
|
55457
55664
|
ok: false,
|
|
@@ -55484,13 +55691,14 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
55484
55691
|
const hasTx = source.txHash !== null && source.txHash !== void 0 && source.txHash !== "";
|
|
55485
55692
|
const signal = receiptSignal(source);
|
|
55486
55693
|
const hasConfirm = source.confirmedAt !== null && source.confirmedAt !== void 0 && source.confirmedAt !== "";
|
|
55487
|
-
|
|
55694
|
+
const zecSeen = isZecSource(source.chainId, source.tokenSymbol) && zecMoneySeen(zecStatusOf(source));
|
|
55695
|
+
if (hasTx || signal.money || hasConfirm || zecSeen) {
|
|
55488
55696
|
return {
|
|
55489
55697
|
ok: false,
|
|
55490
55698
|
code: "ORDER_ALREADY_FUNDED",
|
|
55491
|
-
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.",
|
|
55699
|
+
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.",
|
|
55492
55700
|
moneyDetected: true,
|
|
55493
|
-
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable }
|
|
55701
|
+
evidence: { ...evidence, receipt: signal.receipt, receiptUnparsable: signal.unparsable, ...zecSeen ? { zec: zecStatusOf(source) } : {} }
|
|
55494
55702
|
};
|
|
55495
55703
|
}
|
|
55496
55704
|
if (payment?.status !== UNPAID_STATUS) {
|
|
@@ -55518,6 +55726,28 @@ function reuseGuard({ payment, requested, reused = false }) {
|
|
|
55518
55726
|
evidence
|
|
55519
55727
|
};
|
|
55520
55728
|
}
|
|
55729
|
+
if (isZecSource(gotChain, gotToken)) {
|
|
55730
|
+
const zec = zecStatusOf(source);
|
|
55731
|
+
const wantReceive = requested?.shielded === true ? "shielded" : "transparent";
|
|
55732
|
+
if (!zec || zec.receive !== wantReceive) {
|
|
55733
|
+
return {
|
|
55734
|
+
ok: false,
|
|
55735
|
+
code: "ZEC_RECEIVE_MODE_MISMATCH",
|
|
55736
|
+
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.`,
|
|
55737
|
+
moneyDetected: false,
|
|
55738
|
+
evidence: { ...evidence, zec }
|
|
55739
|
+
};
|
|
55740
|
+
}
|
|
55741
|
+
if (zec.payinState === "expired" || zec.payinState === "reorged") {
|
|
55742
|
+
return {
|
|
55743
|
+
ok: false,
|
|
55744
|
+
code: "ZEC_QUOTE_EXPIRED",
|
|
55745
|
+
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.`,
|
|
55746
|
+
moneyDetected: false,
|
|
55747
|
+
evidence: { ...evidence, zec }
|
|
55748
|
+
};
|
|
55749
|
+
}
|
|
55750
|
+
}
|
|
55521
55751
|
const deposit = validateDepositInstructions(source);
|
|
55522
55752
|
if (!deposit.ok) {
|
|
55523
55753
|
return {
|
|
@@ -55867,7 +56097,7 @@ async function main(argv) {
|
|
|
55867
56097
|
chosenSource = { chainId, tokenSymbol };
|
|
55868
56098
|
}
|
|
55869
56099
|
const now = Date.now();
|
|
55870
|
-
const quote = await quoteInvoice({ url: String(url) });
|
|
56100
|
+
const quote = await quoteInvoice({ url: String(url), beta: ZEC_BETA_PARAM });
|
|
55871
56101
|
const snapshot2 = snapshotFromQuote(quote);
|
|
55872
56102
|
const payability = derivePayable(snapshot2, now);
|
|
55873
56103
|
const nativeOffered = Object.values(NATIVE_COINS).map((c2) => ({
|
|
@@ -55882,6 +56112,20 @@ async function main(argv) {
|
|
|
55882
56112
|
callerPaysUsd: snapshot2.callerPays ?? snapshot2.original
|
|
55883
56113
|
})
|
|
55884
56114
|
})).filter((c2) => c2.offer.ok).map((c2) => ({ chainId: c2.chainId, tokenSymbol: c2.tokenSymbol, chain: c2.chain, minUsd: c2.offer.minUsd }));
|
|
56115
|
+
const zecOffer = zecShieldedOfferFor({
|
|
56116
|
+
supportedSources: quote?.supportedSources,
|
|
56117
|
+
sourceMeta: quote?.sourceMeta,
|
|
56118
|
+
callerPaysUsd: snapshot2.callerPays ?? snapshot2.original
|
|
56119
|
+
});
|
|
56120
|
+
const zecShielded = {
|
|
56121
|
+
offered: zecOffer.ok,
|
|
56122
|
+
code: zecOffer.code,
|
|
56123
|
+
reason: zecOffer.reason,
|
|
56124
|
+
requiredConfirmations: zecOffer.requiredConfirmations,
|
|
56125
|
+
maxUsd: ZEC_SHIELDED_MAX_USD,
|
|
56126
|
+
note: "ZEC (Beta) is paid from a shielded wallet, exact amount in one transaction; the CLI shows the address, amount and payment URI and never sends. Underpayments are not refunded automatically."
|
|
56127
|
+
};
|
|
56128
|
+
const zecRequested = chosenSource && isZecSource(chosenSource.chainId, chosenSource.tokenSymbol) ? zecOffer : null;
|
|
55885
56129
|
const nativeRequested = chosenSource && isNativeSource(chosenSource.chainId, chosenSource.tokenSymbol) ? nativeOfferFor({
|
|
55886
56130
|
...chosenSource,
|
|
55887
56131
|
supportedSources: quote?.supportedSources,
|
|
@@ -55910,7 +56154,8 @@ async function main(argv) {
|
|
|
55910
56154
|
// and the native coins it makes payable for this invoice.
|
|
55911
56155
|
routerSupportedSources: quote?.supportedSources && typeof quote.supportedSources === "object" ? quote.supportedSources : null,
|
|
55912
56156
|
nativeOffered,
|
|
55913
|
-
|
|
56157
|
+
zecShielded,
|
|
56158
|
+
nativeNote: "Native coins are exact-out: the coin amount is fixed at order creation and its price is locked only until quoteExpiresAt. Up to $3,500 per invoice; native ETH on Ethereum needs $1.",
|
|
55914
56159
|
quoteReceipt: quote?.quoteReceipt ?? null,
|
|
55915
56160
|
quoteReceiptTtlSeconds: 60,
|
|
55916
56161
|
quoteReceiptNote: "Short-lived (~60s). create-order.js takes its own fresh quote; do not carry this over.",
|
|
@@ -55927,6 +56172,13 @@ async function main(argv) {
|
|
|
55927
56172
|
payload.error = { code: nativeRequested.code, message: nativeRequested.reason };
|
|
55928
56173
|
emit(payload, EXIT_ERROR);
|
|
55929
56174
|
}
|
|
56175
|
+
if (zecRequested && !zecRequested.ok) {
|
|
56176
|
+
payload.success = false;
|
|
56177
|
+
payload.nextStep = null;
|
|
56178
|
+
payload.error = { code: zecRequested.code, message: zecRequested.reason };
|
|
56179
|
+
emit(payload, EXIT_ERROR);
|
|
56180
|
+
}
|
|
56181
|
+
if (zecRequested) payload.nextStep = "create-order.js --url <url> --chain 9133 --token ZEC --shielded";
|
|
55930
56182
|
if (snapshot2.callerPays && snapshot2.original && normalizeDecimal(snapshot2.callerPays) !== normalizeDecimal(snapshot2.original)) {
|
|
55931
56183
|
payload.warnings = [
|
|
55932
56184
|
`callerPays (${snapshot2.callerPays}) differs from the invoice amount (${snapshot2.original}). This flow is supposed to charge the full invoice \u2014 do not proceed until that is explained.`
|
|
@@ -55962,7 +56214,19 @@ async function main2(argv) {
|
|
|
55962
56214
|
{ supported: SUPPORTED_SOURCES }
|
|
55963
56215
|
);
|
|
55964
56216
|
}
|
|
55965
|
-
const
|
|
56217
|
+
const zec = isZecSource(chainId, tokenSymbol);
|
|
56218
|
+
const shielded = Boolean(args.shielded);
|
|
56219
|
+
if (zec && !shielded) {
|
|
56220
|
+
throw new SkillError(
|
|
56221
|
+
"ZEC_SHIELDED_REQUIRED",
|
|
56222
|
+
"ZEC orders from this CLI pay from a shielded balance: add --shielded. For transparent ZEC use https://checkout.rozo.ai/zec."
|
|
56223
|
+
);
|
|
56224
|
+
}
|
|
56225
|
+
if (shielded && !zec) {
|
|
56226
|
+
throw new SkillError("CONFLICTING_FLAGS", "--shielded only applies to ZEC (--chain 9133 --token ZEC).");
|
|
56227
|
+
}
|
|
56228
|
+
const requested = zec ? { chainId, tokenSymbol, shielded: true } : { chainId, tokenSymbol };
|
|
56229
|
+
const beta = zec ? ZEC_BETA_PARAM : void 0;
|
|
55966
56230
|
const confirmed = Boolean(args.confirm);
|
|
55967
56231
|
const email = contactEmailFromArgs(args);
|
|
55968
56232
|
let blacklist;
|
|
@@ -55975,7 +56239,7 @@ async function main2(argv) {
|
|
|
55975
56239
|
);
|
|
55976
56240
|
}
|
|
55977
56241
|
const { linkId: parsedLinkId } = extractLinkId(String(url));
|
|
55978
|
-
const quote = await quoteInvoice({ url: String(url) });
|
|
56242
|
+
const quote = await quoteInvoice({ url: String(url), beta });
|
|
55979
56243
|
const snapshot2 = snapshotFromQuote(quote);
|
|
55980
56244
|
const quoteReceipt = quote?.quoteReceipt ?? null;
|
|
55981
56245
|
const native = isNativeSource(chainId, tokenSymbol);
|
|
@@ -56000,10 +56264,31 @@ async function main2(argv) {
|
|
|
56000
56264
|
);
|
|
56001
56265
|
}
|
|
56002
56266
|
}
|
|
56267
|
+
let zecOffer = null;
|
|
56268
|
+
if (zec) {
|
|
56269
|
+
zecOffer = zecShieldedOfferFor({
|
|
56270
|
+
supportedSources: quote?.supportedSources,
|
|
56271
|
+
sourceMeta: quote?.sourceMeta,
|
|
56272
|
+
callerPaysUsd: snapshot2.callerPays ?? snapshot2.original
|
|
56273
|
+
});
|
|
56274
|
+
if (!zecOffer.ok) {
|
|
56275
|
+
emit(
|
|
56276
|
+
{
|
|
56277
|
+
success: false,
|
|
56278
|
+
step: "zec-offer",
|
|
56279
|
+
error: { code: zecOffer.code, message: zecOffer.reason },
|
|
56280
|
+
linkId: snapshot2.linkId ?? parsedLinkId,
|
|
56281
|
+
guidance: "Nothing was created. Choose USDC or USDT, or another coin the quote lists."
|
|
56282
|
+
},
|
|
56283
|
+
EXIT_ERROR
|
|
56284
|
+
);
|
|
56285
|
+
}
|
|
56286
|
+
}
|
|
56003
56287
|
const created = await createInvoice({
|
|
56004
56288
|
url: String(url),
|
|
56005
56289
|
source: requested,
|
|
56006
56290
|
quoteReceipt,
|
|
56291
|
+
beta,
|
|
56007
56292
|
utmSource: typeof args["utm-source"] === "string" ? args["utm-source"] : void 0,
|
|
56008
56293
|
email
|
|
56009
56294
|
});
|
|
@@ -56013,6 +56298,33 @@ async function main2(argv) {
|
|
|
56013
56298
|
});
|
|
56014
56299
|
}
|
|
56015
56300
|
const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
|
|
56301
|
+
if (zec && (created.sourceMismatch === true || created.sourceMismatchCode === "ZEC_RECEIVE_MODE_MISMATCH")) {
|
|
56302
|
+
const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
|
|
56303
|
+
emit(
|
|
56304
|
+
{
|
|
56305
|
+
success: false,
|
|
56306
|
+
step: "reuse-source-mismatch",
|
|
56307
|
+
error: {
|
|
56308
|
+
code: "ZEC_RECEIVE_MODE_MISMATCH",
|
|
56309
|
+
message: "This link already has an unpaid ZEC order of the other kind (transparent or shielded). Only one order exists per link at a time, so nothing new was created."
|
|
56310
|
+
},
|
|
56311
|
+
linkId: created.linkId ?? parsedLinkId,
|
|
56312
|
+
rozoPaymentId,
|
|
56313
|
+
paymentLink: created.paymentLink ?? null,
|
|
56314
|
+
existingOrder: {
|
|
56315
|
+
rozoPaymentId,
|
|
56316
|
+
chainId: created.source?.chainId ?? null,
|
|
56317
|
+
tokenSymbol: created.source?.tokenSymbol ?? null,
|
|
56318
|
+
receive: created.source?.zec?.receive ?? null,
|
|
56319
|
+
expiresAt: created.expiresAt ?? null,
|
|
56320
|
+
expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
|
|
56321
|
+
},
|
|
56322
|
+
nextAction: { type: "choose_method", canRetryQuery: true, canCreateOrder: false, canSend: false, switchPossibleAfter: created.expiresAt ?? null },
|
|
56323
|
+
guidance: "Pay the existing order on https://checkout.rozo.ai/zec with the mode it was made for, or wait for its price lock to expire unfunded (about 30 minutes) and run pay again: an expired, unfunded ZEC order is replaced."
|
|
56324
|
+
},
|
|
56325
|
+
EXIT_ERROR
|
|
56326
|
+
);
|
|
56327
|
+
}
|
|
56016
56328
|
const verify = verifyCreateAgainstQuote({ snapshot: snapshot2, created, requested });
|
|
56017
56329
|
if (!verify.ok) {
|
|
56018
56330
|
const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
|
|
@@ -56092,7 +56404,8 @@ async function main2(argv) {
|
|
|
56092
56404
|
} catch {
|
|
56093
56405
|
prior = null;
|
|
56094
56406
|
}
|
|
56095
|
-
const
|
|
56407
|
+
const zecStatus = zec ? zecStatusOf(source) : null;
|
|
56408
|
+
const quoteExpiresAt = zec ? zecStatus?.quoteExpiresAt ?? null : priceLockFor({
|
|
56096
56409
|
native,
|
|
56097
56410
|
payment,
|
|
56098
56411
|
others: [created?.quoteExpiresAt, prior?.quoteExpiresAt, prior?.confirmation?.quoteExpiresAt]
|
|
@@ -56106,7 +56419,7 @@ async function main2(argv) {
|
|
|
56106
56419
|
// separate expiry if the backend exposes one; otherwise the intent expiry
|
|
56107
56420
|
// governs and we still require the 10-minute floor via the margin.
|
|
56108
56421
|
...lightning ? { bolt11ExpiresAt: payment?.expiresAt } : {},
|
|
56109
|
-
...native ? { quoteExpiresAt } : {}
|
|
56422
|
+
...native || zec ? { quoteExpiresAt } : {}
|
|
56110
56423
|
});
|
|
56111
56424
|
if (!expiry.ok) {
|
|
56112
56425
|
emit(
|
|
@@ -56166,13 +56479,13 @@ async function main2(argv) {
|
|
|
56166
56479
|
paymentLink: created.paymentLink ?? null,
|
|
56167
56480
|
merchant: created.merchant ?? snapshot2.merchant,
|
|
56168
56481
|
invoiceAmount: created.original ?? snapshot2.original,
|
|
56169
|
-
source: { chainId: source.chainId, tokenSymbol: source.tokenSymbol },
|
|
56482
|
+
source: { chainId: source.chainId, tokenSymbol: source.tokenSymbol, ...zec ? { receive: "shielded" } : {} },
|
|
56170
56483
|
receiverAddress: source.receiverAddress,
|
|
56171
56484
|
receiverMemo: source.receiverMemo ?? null,
|
|
56172
56485
|
amount: source.amount,
|
|
56173
56486
|
amountUnit: source.amountUnit ?? null,
|
|
56174
56487
|
expiresAt: payment?.expiresAt ?? null,
|
|
56175
|
-
...native ? { quoteExpiresAt } : {}
|
|
56488
|
+
...native || zec ? { quoteExpiresAt } : {}
|
|
56176
56489
|
});
|
|
56177
56490
|
const usd = created.original ?? snapshot2.original;
|
|
56178
56491
|
const family = chainFamily(source.chainId);
|
|
@@ -56183,10 +56496,10 @@ async function main2(argv) {
|
|
|
56183
56496
|
source,
|
|
56184
56497
|
invoiceAmount: usd,
|
|
56185
56498
|
tier,
|
|
56186
|
-
...native ? { quoteExpiresAt } : {}
|
|
56499
|
+
...native || zec ? { quoteExpiresAt } : {}
|
|
56187
56500
|
});
|
|
56188
56501
|
}
|
|
56189
|
-
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." : family === "stellar" ? "A Stellar deposit always requires a memo." : "This deposit does not use a memo. Leave the memo field empty.";
|
|
56502
|
+
const memoRequirement = lightning ? "Lightning invoices carry their own routing data; there is no separate memo." : zec ? "The order memo is inside the payment URI; scanning it fills the memo for you. Do not type one." : source.receiverMemo ? "This deposit REQUIRES the memo/tag below. Sending without it will very likely lose the funds." : family === "stellar" ? "A Stellar deposit always requires a memo." : "This deposit does not use a memo. Leave the memo field empty.";
|
|
56190
56503
|
emit({
|
|
56191
56504
|
success: true,
|
|
56192
56505
|
step: "create-order",
|
|
@@ -56231,8 +56544,16 @@ async function main2(argv) {
|
|
|
56231
56544
|
// Native coins are exact-out like Lightning: this exact coin amount,
|
|
56232
56545
|
// at a price locked only until quoteExpiresAt.
|
|
56233
56546
|
native,
|
|
56234
|
-
exactOut: native || lightning,
|
|
56235
|
-
quoteExpiresAt: native ? quoteExpiresAt : null
|
|
56547
|
+
exactOut: native || lightning || zec,
|
|
56548
|
+
quoteExpiresAt: native || zec ? quoteExpiresAt : null,
|
|
56549
|
+
// Shielded ZEC: what the payer scans. The QR content is the URI,
|
|
56550
|
+
// shown verbatim; it carries the address, the amount and the memo.
|
|
56551
|
+
...zec ? {
|
|
56552
|
+
shielded: true,
|
|
56553
|
+
paymentUri: depositInfo.paymentUri,
|
|
56554
|
+
requiredConfirmations: zecStatus?.requiredConfirmations ?? zecOffer?.requiredConfirmations ?? null,
|
|
56555
|
+
payinState: zecStatus?.payinState ?? null
|
|
56556
|
+
} : {}
|
|
56236
56557
|
} : null,
|
|
56237
56558
|
depositWithheld: !confirmed,
|
|
56238
56559
|
// Safe for prose / chat.
|
|
@@ -56247,14 +56568,15 @@ async function main2(argv) {
|
|
|
56247
56568
|
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
56248
56569
|
memoRequirement,
|
|
56249
56570
|
native,
|
|
56250
|
-
priceLockedUntil: native ? quoteExpiresAt : null,
|
|
56571
|
+
priceLockedUntil: native || zec ? quoteExpiresAt : null,
|
|
56251
56572
|
// The bare decimal amount (not secret), for balance comparisons.
|
|
56252
|
-
coinAmount: isSatsUnit(source.amountUnit) ? null : String(source.amount ?? "")
|
|
56573
|
+
coinAmount: isSatsUnit(source.amountUnit) ? null : String(source.amount ?? ""),
|
|
56574
|
+
...zec ? { shielded: true, requiredConfirmations: zecStatus?.requiredConfirmations ?? zecOffer?.requiredConfirmations ?? null } : {}
|
|
56253
56575
|
},
|
|
56254
56576
|
expiry: {
|
|
56255
56577
|
intentExpiresAt: payment?.expiresAt ?? null,
|
|
56256
56578
|
coinbaseExpiry: snapshot2?.coinbase?.preApprovalExpiry ?? null,
|
|
56257
|
-
quoteExpiresAt: native ? quoteExpiresAt : null,
|
|
56579
|
+
quoteExpiresAt: native || zec ? quoteExpiresAt : null,
|
|
56258
56580
|
effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
|
|
56259
56581
|
// A duration, not just a timestamp: this is what a payer actually needs.
|
|
56260
56582
|
expiresIn: formatRemaining(expiry.msRemaining),
|
|
@@ -56271,7 +56593,8 @@ async function main2(argv) {
|
|
|
56271
56593
|
memoRequirement,
|
|
56272
56594
|
"Send exactly once. A second send to the same one-time address is not guaranteed to be credited.",
|
|
56273
56595
|
"The deposit amount can exceed the invoice: it includes the bridge and network fees.",
|
|
56274
|
-
...native ? nativeDepositWarnings({ tokenSymbol: source.tokenSymbol, chainId: source.chainId }) : []
|
|
56596
|
+
...native ? nativeDepositWarnings({ tokenSymbol: source.tokenSymbol, chainId: source.chainId }) : [],
|
|
56597
|
+
...zec ? zecDepositWarnings({ requiredConfirmations: zecStatus?.requiredConfirmations ?? zecOffer?.requiredConfirmations ?? null }) : []
|
|
56275
56598
|
]
|
|
56276
56599
|
},
|
|
56277
56600
|
blacklist: {
|
|
@@ -56281,7 +56604,7 @@ async function main2(argv) {
|
|
|
56281
56604
|
},
|
|
56282
56605
|
nextStep: confirmed ? {
|
|
56283
56606
|
modeA: "Give the user the `deposit` block, then poll: status.js --rozo-payment-id <id>",
|
|
56284
|
-
modeB: family === "evm" ? `send-evm.js --rozo-payment-id ${rozoPaymentId} --send (requires ROZO_CHECKOUT_EVM_KEY)` : family === "solana" ? `send-sol.js --rozo-payment-id ${rozoPaymentId} --send (requires ROZO_CHECKOUT_SOL_KEY)` : "not available for this chain \u2014 pay from a wallet (Mode A)"
|
|
56607
|
+
modeB: family === "evm" ? `send-evm.js --rozo-payment-id ${rozoPaymentId} --send (requires ROZO_CHECKOUT_EVM_KEY)` : family === "solana" ? `send-sol.js --rozo-payment-id ${rozoPaymentId} --send (requires ROZO_CHECKOUT_SOL_KEY)` : zec ? "not available: scan deposit.paymentUri with a shielded Zcash wallet (Mode A)" : "not available for this chain \u2014 pay from a wallet (Mode A)"
|
|
56285
56608
|
} : {
|
|
56286
56609
|
confirm: "Re-run this exact command with --confirm once the user has said yes."
|
|
56287
56610
|
}
|
|
@@ -57034,13 +57357,15 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57034
57357
|
});
|
|
57035
57358
|
const source = payment?.source || status?.rozoPayment?.source || {};
|
|
57036
57359
|
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
57360
|
+
const isZec = isZecSource(source.chainId, source.tokenSymbol);
|
|
57361
|
+
const zec = isZec ? zecStatusOf(source) : null;
|
|
57037
57362
|
let localState = null;
|
|
57038
57363
|
try {
|
|
57039
57364
|
localState = id && isRozoPaymentId(id) ? readState(id) : null;
|
|
57040
57365
|
} catch {
|
|
57041
57366
|
localState = null;
|
|
57042
57367
|
}
|
|
57043
|
-
const quoteExpiresAt = priceLockFor({
|
|
57368
|
+
const quoteExpiresAt = isZec ? zec?.quoteExpiresAt ?? null : priceLockFor({
|
|
57044
57369
|
native,
|
|
57045
57370
|
payment: payment || status?.rozoPayment || null,
|
|
57046
57371
|
others: [localState?.confirmation?.quoteExpiresAt, localState?.quoteExpiresAt]
|
|
@@ -57049,6 +57374,17 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57049
57374
|
let state = verdict.state;
|
|
57050
57375
|
let terminal = verdict.terminal;
|
|
57051
57376
|
let detail = verdict.detail;
|
|
57377
|
+
const zecMoney = Boolean(zec && zecMoneySeen(zec));
|
|
57378
|
+
let escalate = verdict.escalate;
|
|
57379
|
+
if (zecMoney && state === "expired_unfunded") {
|
|
57380
|
+
state = "stuck_after_payment";
|
|
57381
|
+
terminal = false;
|
|
57382
|
+
escalate = true;
|
|
57383
|
+
detail = "The order expired but the custody rail reports a ZEC pay-in. Do not pay again; escalate for manual reconciliation.";
|
|
57384
|
+
} else if (zecMoney && state === "awaiting_deposit") {
|
|
57385
|
+
state = "payin_detected";
|
|
57386
|
+
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.`;
|
|
57387
|
+
}
|
|
57052
57388
|
if (provider === "bitrefill") {
|
|
57053
57389
|
let local = null;
|
|
57054
57390
|
try {
|
|
@@ -57078,9 +57414,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57078
57414
|
linkId: linkId || status?.pl_id || null,
|
|
57079
57415
|
state,
|
|
57080
57416
|
unknown: verdict.unknown,
|
|
57081
|
-
moneyDetected: verdict.moneyDetected,
|
|
57417
|
+
moneyDetected: verdict.moneyDetected || zecMoney,
|
|
57082
57418
|
terminal,
|
|
57083
|
-
escalate
|
|
57419
|
+
escalate,
|
|
57084
57420
|
detail,
|
|
57085
57421
|
backend: {
|
|
57086
57422
|
paymentStatus: payment?.status ?? status?.rozoPayment?.status ?? null,
|
|
@@ -57095,7 +57431,9 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57095
57431
|
txHash: publicHash(source.txHash ?? null),
|
|
57096
57432
|
confirmedAt: source.confirmedAt ?? null,
|
|
57097
57433
|
senderAddressMasked: source.senderAddress ? maskAddress2(source.senderAddress) : null,
|
|
57098
|
-
chain: source.chainId ? chainName(source.chainId) : null
|
|
57434
|
+
chain: source.chainId ? chainName(source.chainId) : null,
|
|
57435
|
+
// Shielded ZEC only: the custody rail's own view of the pay-in.
|
|
57436
|
+
...zec ? { zec } : {}
|
|
57099
57437
|
},
|
|
57100
57438
|
expiry: (() => {
|
|
57101
57439
|
if (provider === "bitrefill" && !bitrefillExpiry) {
|
|
@@ -57111,7 +57449,7 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57111
57449
|
msRemaining: Number.isFinite(ms) ? ms : null,
|
|
57112
57450
|
// Native orders: the order stays open after the price lock, but a
|
|
57113
57451
|
// payment after it is valued at the arrival price. Pay before this.
|
|
57114
|
-
...native ? {
|
|
57452
|
+
...native || isZec ? {
|
|
57115
57453
|
quoteExpiresAt: quoteExpiresAt ?? null,
|
|
57116
57454
|
priceLockExpiresIn: Number.isFinite(lockMs) ? formatRemaining(lockMs) : null
|
|
57117
57455
|
} : {}
|
|
@@ -57122,7 +57460,8 @@ async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
|
57122
57460
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
57123
57461
|
},
|
|
57124
57462
|
localSend: localSendFor(id),
|
|
57125
|
-
|
|
57463
|
+
// Never a send window once the custody rail has seen a ZEC pay-in.
|
|
57464
|
+
sendWindow: state === "awaiting_deposit" && !zecMoney ? sendWindowFor({ provider, chainId: source.chainId, payment, status, bitrefillExpiry, quoteExpiresAt }) : null,
|
|
57126
57465
|
errors: [statusError, paymentError].filter(Boolean)
|
|
57127
57466
|
};
|
|
57128
57467
|
}
|
|
@@ -57363,7 +57702,12 @@ async function main6(argv) {
|
|
|
57363
57702
|
}
|
|
57364
57703
|
const payment = await getPayment(id);
|
|
57365
57704
|
const source = payment?.source || {};
|
|
57366
|
-
const
|
|
57705
|
+
const resumedShielded = isZecSource(source.chainId, source.tokenSymbol) && zecStatusOf(source)?.receive === "shielded";
|
|
57706
|
+
const guard = reuseGuard({
|
|
57707
|
+
payment,
|
|
57708
|
+
requested: { chainId: source.chainId, tokenSymbol: source.tokenSymbol, ...resumedShielded ? { shielded: true } : {} },
|
|
57709
|
+
reused: true
|
|
57710
|
+
});
|
|
57367
57711
|
if (!guard.ok) {
|
|
57368
57712
|
emit(
|
|
57369
57713
|
{
|
|
@@ -57381,13 +57725,16 @@ async function main6(argv) {
|
|
|
57381
57725
|
);
|
|
57382
57726
|
}
|
|
57383
57727
|
const native = isNativeSource(source.chainId, source.tokenSymbol);
|
|
57384
|
-
const
|
|
57728
|
+
const isZec = isZecSource(source.chainId, source.tokenSymbol);
|
|
57729
|
+
const zecStatus = isZec ? zecStatusOf(source) : null;
|
|
57730
|
+
const zec = Boolean(zecStatus && zecStatus.receive === "shielded");
|
|
57731
|
+
const quoteExpiresAt = isZec ? zecStatus?.quoteExpiresAt ?? null : priceLockFor({ native, payment, others: [snap.expiry?.quoteExpiresAt] });
|
|
57385
57732
|
const finalWindow = checkExpiry({
|
|
57386
57733
|
now: Date.now(),
|
|
57387
57734
|
chainId: source.chainId,
|
|
57388
57735
|
intentExpiresAt: payment?.expiresAt ?? snap.sendWindow?.deadlineMs,
|
|
57389
57736
|
coinbaseExpiry: snap.sendWindow?.deadlineMs,
|
|
57390
|
-
...native ? { quoteExpiresAt } : {}
|
|
57737
|
+
...native || isZec ? { quoteExpiresAt } : {}
|
|
57391
57738
|
});
|
|
57392
57739
|
if (!finalWindow.ok) {
|
|
57393
57740
|
emit(
|
|
@@ -57462,8 +57809,14 @@ async function main6(argv) {
|
|
|
57462
57809
|
expiresAt,
|
|
57463
57810
|
expiresIn,
|
|
57464
57811
|
native,
|
|
57465
|
-
exactOut: native || lightning,
|
|
57466
|
-
quoteExpiresAt: native ? quoteExpiresAt : null
|
|
57812
|
+
exactOut: native || lightning || zec,
|
|
57813
|
+
quoteExpiresAt: native || zec ? quoteExpiresAt : null,
|
|
57814
|
+
...zec ? {
|
|
57815
|
+
shielded: true,
|
|
57816
|
+
paymentUri: guard.deposit.paymentUri,
|
|
57817
|
+
requiredConfirmations: zecStatus.requiredConfirmations,
|
|
57818
|
+
payinState: zecStatus.payinState
|
|
57819
|
+
} : {}
|
|
57467
57820
|
} : null,
|
|
57468
57821
|
depositWithheld: !confirmed,
|
|
57469
57822
|
display: {
|
|
@@ -57476,12 +57829,13 @@ async function main6(argv) {
|
|
|
57476
57829
|
hasMemo: Boolean(source.receiverMemo),
|
|
57477
57830
|
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
57478
57831
|
native,
|
|
57479
|
-
priceLockedUntil: native ? quoteExpiresAt : null
|
|
57832
|
+
priceLockedUntil: native || zec ? quoteExpiresAt : null,
|
|
57833
|
+
...zec ? { shielded: true } : {}
|
|
57480
57834
|
},
|
|
57481
57835
|
expiry: {
|
|
57482
57836
|
expiresAt,
|
|
57483
57837
|
expiresIn,
|
|
57484
|
-
...native ? { quoteExpiresAt } : {},
|
|
57838
|
+
...native || zec ? { quoteExpiresAt } : {},
|
|
57485
57839
|
minutesOfSlack: snap.sendWindow?.minutesOfSlack ?? null
|
|
57486
57840
|
},
|
|
57487
57841
|
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."
|
|
@@ -59993,6 +60347,7 @@ async function cmdPay(opts) {
|
|
|
59993
60347
|
}
|
|
59994
60348
|
invoiceUsd = Number(q.payload.invoice?.amount);
|
|
59995
60349
|
const offered = new Set((q.payload.nativeOffered ?? []).map((n) => `${n.chainId}:${n.tokenSymbol}`));
|
|
60350
|
+
if (q.payload.zecShielded?.offered === true) offered.add("9133:ZEC");
|
|
59996
60351
|
isOffered = (chainId2, tokenSymbol2) => offered.has(`${chainId2}:${String(tokenSymbol2).toUpperCase()}`);
|
|
59997
60352
|
}
|
|
59998
60353
|
if (!opts.source) {
|
|
@@ -60003,7 +60358,9 @@ async function cmdPay(opts) {
|
|
|
60003
60358
|
);
|
|
60004
60359
|
}
|
|
60005
60360
|
const picked = await pickSource({ invoiceUsd, saved, fresh: opts.fresh, isOffered });
|
|
60006
|
-
|
|
60361
|
+
const pickedZec = String(picked.chainId) === "9133";
|
|
60362
|
+
opts.source = { chainId: picked.chainId, tokenSymbol: picked.tokenSymbol, ...pickedZec ? { shielded: true } : {} };
|
|
60363
|
+
if (pickedZec) opts.shielded = true;
|
|
60007
60364
|
chosenPreset = picked.preset;
|
|
60008
60365
|
payer = picked.payer;
|
|
60009
60366
|
} else if (opts.payer) {
|
|
@@ -60068,7 +60425,7 @@ async function cmdPay(opts) {
|
|
|
60068
60425
|
"--token",
|
|
60069
60426
|
tokenSymbol,
|
|
60070
60427
|
...utmArgs
|
|
60071
|
-
] : ["--url", opts.target, "--chain", chainId, "--token", tokenSymbol, ...utmArgs];
|
|
60428
|
+
] : ["--url", opts.target, "--chain", chainId, "--token", tokenSymbol, ...opts.shielded ? ["--shielded"] : [], ...utmArgs];
|
|
60072
60429
|
if (opts.send) {
|
|
60073
60430
|
const family = chainFamily(chainId);
|
|
60074
60431
|
if (family === "evm" || family === "solana") {
|
|
@@ -60105,13 +60462,17 @@ async function cmdPay(opts) {
|
|
|
60105
60462
|
if (p.provider === "bitrefill") {
|
|
60106
60463
|
out(` Bitrefill ${p.bitrefill?.invoiceId} ${dim(`\u2192 ${p.bitrefill?.amount} USDC on Base to ${p.bitrefill?.addressMasked}`)}`);
|
|
60107
60464
|
}
|
|
60108
|
-
out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}`);
|
|
60465
|
+
out(` You send ${bold(p.display?.amount)} on ${bold(p.display?.chain)}${p.display?.shielded ? dim(" (from a shielded balance, Beta)") : ""}`);
|
|
60109
60466
|
out(` To ${p.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
|
|
60110
60467
|
if (p.display?.hasMemo) {
|
|
60111
60468
|
out(` Memo ${p.display.receiverMemoMasked} ${dim(`(${p.display.memoType})`)}`);
|
|
60112
60469
|
}
|
|
60113
60470
|
const deadline = formatDeadline(p.expiry?.effectiveDeadlineIso);
|
|
60114
|
-
if (p.display?.
|
|
60471
|
+
if (p.display?.shielded) {
|
|
60472
|
+
out(` Price ${bold(`locked ${deadline ?? `for ${p.expiry?.expiresIn ?? "?"}`}`)}`);
|
|
60473
|
+
out(` ${dim("Pay the exact amount in one transaction before then. Underpayments are not refunded automatically.")}`);
|
|
60474
|
+
out(` ${dim(`Merchant is paid after ${p.display.requiredConfirmations ?? 1} confirmation${p.display.requiredConfirmations === 2 ? "s" : ""} (about 75 s each).`)}`);
|
|
60475
|
+
} else if (p.display?.native) {
|
|
60115
60476
|
out(` Price ${bold(`locked ${deadline ?? `for ${p.expiry?.expiresIn ?? "?"}`}`)}`);
|
|
60116
60477
|
out(` ${dim("Send exactly this amount before then. After that, do not send it: start a new order.")}`);
|
|
60117
60478
|
out(` ${dim("The coin amount includes an FX spread on top of the service fee.")}`);
|
|
@@ -60299,6 +60660,16 @@ function printDeposit(deposit, opts) {
|
|
|
60299
60660
|
if (validMin !== null) {
|
|
60300
60661
|
out(` Valid ${bold(validMin < 1 ? "under 1 more minute" : `${validMin} more minutes`)}`);
|
|
60301
60662
|
}
|
|
60663
|
+
} else if (deposit.shielded) {
|
|
60664
|
+
out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)} ${dim("(exact, one transaction)")}`);
|
|
60665
|
+
out(` Chain ${bold(`${deposit.chain}, shielded (Beta)`)}`);
|
|
60666
|
+
out(` Address ${bold(deposit.receiverAddress)}`);
|
|
60667
|
+
out(` URI ${deposit.paymentUri}`);
|
|
60668
|
+
out(` ${dim("(scan or paste this in Zashi or another shielded wallet; it carries the memo)")}`);
|
|
60669
|
+
if (deposit.expiresIn) out(` Price locked for ${bold(deposit.expiresIn)} more`);
|
|
60670
|
+
const lock = formatDeadline(deposit.quoteExpiresAt);
|
|
60671
|
+
if (lock) out(` ${dim(`(${lock})`)}`);
|
|
60672
|
+
out(` Settles after ${bold(`${deposit.requiredConfirmations ?? 1} confirmation${deposit.requiredConfirmations === 2 ? "s" : ""}`)} ${dim("(about 75 s each)")}`);
|
|
60302
60673
|
} else {
|
|
60303
60674
|
out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)}`);
|
|
60304
60675
|
out(` Chain ${bold(deposit.chain)}`);
|
|
@@ -60322,6 +60693,10 @@ function printDeposit(deposit, opts) {
|
|
|
60322
60693
|
out(
|
|
60323
60694
|
` Make sure the wallet you pay from holds at least ${bold(`${deposit.amount} ${unit}`)} ${where}.`
|
|
60324
60695
|
);
|
|
60696
|
+
if (deposit.shielded) {
|
|
60697
|
+
out(dim(" Pay from a shielded balance, exactly this amount, in one transaction. Underpayments are"));
|
|
60698
|
+
out(dim(" not refunded automatically. After the price lock ends, do not send: start a new order."));
|
|
60699
|
+
}
|
|
60325
60700
|
if (deposit.native) {
|
|
60326
60701
|
out(dim(` Plus a little ${deposit.tokenSymbol} for the network fee. The amount that arrives must be`));
|
|
60327
60702
|
out(dim(" exactly the amount above: from an exchange, check its withdrawal fee is not taken from it."));
|