@rozoai/checkout 0.1.0
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/LICENSE +21 -0
- package/QUICKSTART.es.md +192 -0
- package/QUICKSTART.ja.md +187 -0
- package/QUICKSTART.md +188 -0
- package/QUICKSTART.zh.md +178 -0
- package/README.es.md +287 -0
- package/README.ja.md +265 -0
- package/README.md +270 -0
- package/README.zh.md +187 -0
- package/llms.txt +95 -0
- package/package.json +60 -0
- package/scripts/dist/blacklist.json +141 -0
- package/scripts/dist/cli.js +57390 -0
- package/scripts/dist/create-order.js +1332 -0
- package/scripts/dist/quote.js +396 -0
- package/scripts/dist/send-evm.js +22557 -0
- package/scripts/dist/send-sol.js +34695 -0
- package/scripts/dist/status.js +567 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
const require = __rozoCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// scripts/src/lib/output.mjs
|
|
6
|
+
var EXIT_OK = 0;
|
|
7
|
+
var EXIT_ERROR = 1;
|
|
8
|
+
var EXIT_USAGE = 2;
|
|
9
|
+
function redact(text) {
|
|
10
|
+
if (text === null || text === void 0) return text;
|
|
11
|
+
let s = typeof text === "string" ? text : String(text);
|
|
12
|
+
s = s.replace(/0x[0-9a-fA-F]{64}/g, "0x<redacted>");
|
|
13
|
+
s = s.replace(/\b[0-9a-fA-F]{64}\b/g, "<redacted>");
|
|
14
|
+
s = s.replace(/\b[1-9A-HJ-NP-Za-km-z]{80,90}\b/g, "<redacted>");
|
|
15
|
+
s = s.replace(/\[(?:\s*\d{1,3}\s*,){40,}\s*\d{1,3}\s*\]/g, "[<redacted>]");
|
|
16
|
+
s = s.replace(/\b([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^\s"'<>]+)/g, (_m, scheme, rest) => {
|
|
17
|
+
const withoutUserinfo = rest.includes("@") ? rest.slice(rest.indexOf("@") + 1) : rest;
|
|
18
|
+
const host = withoutUserinfo.split(/[/?#]/)[0];
|
|
19
|
+
const hadMore = withoutUserinfo.length > host.length;
|
|
20
|
+
return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
|
|
21
|
+
});
|
|
22
|
+
s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
|
|
23
|
+
s = s.replace(
|
|
24
|
+
/\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
|
|
25
|
+
"$1$2<redacted>"
|
|
26
|
+
);
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
function redactDeep(value) {
|
|
30
|
+
if (typeof value === "string") return redact(value);
|
|
31
|
+
if (Array.isArray(value)) return value.map(redactDeep);
|
|
32
|
+
if (value && typeof value === "object") {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [k, v] of Object.entries(value)) {
|
|
35
|
+
if (/priv(ate)?[-_]?key|secret|mnemonic|seed/i.test(k)) {
|
|
36
|
+
out[k] = "<redacted>";
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
out[k] = redactDeep(v);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
var capturing = false;
|
|
46
|
+
var EmitSignal = class extends Error {
|
|
47
|
+
constructor(payload, exitCode) {
|
|
48
|
+
super("emit");
|
|
49
|
+
this.payload = payload;
|
|
50
|
+
this.exitCode = exitCode;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function formatFailure(err, fallbackCode = "RUNTIME_ERROR") {
|
|
54
|
+
const code = err && err.code || fallbackCode;
|
|
55
|
+
const message = redact(err && err.message || String(err));
|
|
56
|
+
const payload = { success: false, error: { code, message } };
|
|
57
|
+
if (err && err.details) payload.error.details = redactDeep(err.details);
|
|
58
|
+
return payload;
|
|
59
|
+
}
|
|
60
|
+
function emit(payload, exitCode = EXIT_OK) {
|
|
61
|
+
const redacted = redactDeep(payload);
|
|
62
|
+
if (capturing) throw new EmitSignal(redacted, exitCode);
|
|
63
|
+
process.stdout.write(JSON.stringify(redacted, null, 2) + "\n");
|
|
64
|
+
process.exit(exitCode);
|
|
65
|
+
}
|
|
66
|
+
function fail(err, fallbackCode = "RUNTIME_ERROR", exitCode = EXIT_ERROR) {
|
|
67
|
+
const payload = formatFailure(err, fallbackCode);
|
|
68
|
+
if (capturing) throw new EmitSignal(payload, exitCode);
|
|
69
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
70
|
+
process.exit(exitCode);
|
|
71
|
+
}
|
|
72
|
+
function usage(message) {
|
|
73
|
+
fail({ code: "USAGE", message }, "USAGE", EXIT_USAGE);
|
|
74
|
+
}
|
|
75
|
+
function parseArgs(argv) {
|
|
76
|
+
const out = { _: [] };
|
|
77
|
+
for (let i = 0; i < argv.length; i++) {
|
|
78
|
+
const a = argv[i];
|
|
79
|
+
if (a.startsWith("--")) {
|
|
80
|
+
const key = a.slice(2);
|
|
81
|
+
const next = argv[i + 1];
|
|
82
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
83
|
+
out[key] = true;
|
|
84
|
+
} else {
|
|
85
|
+
out[key] = next;
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
out._.push(a);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
var SkillError = class extends Error {
|
|
95
|
+
constructor(code, message, details) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.code = code;
|
|
98
|
+
if (details) this.details = details;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// scripts/src/lib/ids.mjs
|
|
103
|
+
var IdError = class extends Error {
|
|
104
|
+
constructor(code, message) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.code = code;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var PL_RE = /(pl_[0-9a-zA-Z]+)/;
|
|
110
|
+
var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
|
|
111
|
+
function extractLinkId(urlOrId) {
|
|
112
|
+
if (typeof urlOrId !== "string" || !urlOrId.trim()) {
|
|
113
|
+
throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
|
|
114
|
+
}
|
|
115
|
+
const s = urlOrId.trim();
|
|
116
|
+
const session = s.match(SESSION_RE);
|
|
117
|
+
if (session) return { linkId: session[1], kind: "payment_session" };
|
|
118
|
+
const link = s.match(PL_RE);
|
|
119
|
+
if (link) return { linkId: link[1], kind: "payment_link" };
|
|
120
|
+
if (/commerce\.coinbase\.com\/pay\//.test(s)) {
|
|
121
|
+
throw new IdError(
|
|
122
|
+
"LEGACY_COMMERCE_URL",
|
|
123
|
+
"commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
throw new IdError(
|
|
127
|
+
"BAD_LINK",
|
|
128
|
+
"Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// scripts/src/lib/http.mjs
|
|
133
|
+
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
134
|
+
var USER_AGENT = "rozo-checkout-skill/1.0";
|
|
135
|
+
async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
138
|
+
let res;
|
|
139
|
+
try {
|
|
140
|
+
res = await fetch(url, {
|
|
141
|
+
method,
|
|
142
|
+
headers: {
|
|
143
|
+
accept: "application/json",
|
|
144
|
+
"user-agent": USER_AGENT,
|
|
145
|
+
...body !== void 0 ? { "content-type": "application/json" } : {}
|
|
146
|
+
},
|
|
147
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
148
|
+
signal: controller.signal
|
|
149
|
+
});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
throw new SkillError(
|
|
152
|
+
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
153
|
+
`${method} request failed: ${redact(err?.message || String(err))}`,
|
|
154
|
+
{ url: redactUrl(url) }
|
|
155
|
+
);
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
}
|
|
159
|
+
const text = await res.text();
|
|
160
|
+
let json = null;
|
|
161
|
+
if (text) {
|
|
162
|
+
try {
|
|
163
|
+
json = JSON.parse(text);
|
|
164
|
+
} catch {
|
|
165
|
+
json = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
const code = json?.code || json?.error?.code || (typeof json?.error === "string" ? null : null) || `HTTP_${res.status}`;
|
|
170
|
+
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
171
|
+
throw new SkillError(code, redact(String(message)), {
|
|
172
|
+
httpStatus: res.status,
|
|
173
|
+
url: redactUrl(url),
|
|
174
|
+
body: json ?? redact(text).slice(0, 800)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (json === null) {
|
|
178
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
179
|
+
url: redactUrl(url),
|
|
180
|
+
snippet: redact(text).slice(0, 400)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return json;
|
|
184
|
+
}
|
|
185
|
+
function redactUrl(url) {
|
|
186
|
+
try {
|
|
187
|
+
const u = new URL(url);
|
|
188
|
+
return `${u.origin}${u.pathname}`;
|
|
189
|
+
} catch {
|
|
190
|
+
return String(url);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function postJson(url, body, opts) {
|
|
194
|
+
return request("POST", url, { ...opts, body });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// scripts/src/lib/api.mjs
|
|
198
|
+
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
199
|
+
var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
|
|
200
|
+
async function quoteInvoice({ url, linkId }) {
|
|
201
|
+
const body = url ? { url } : { payment_id: linkId };
|
|
202
|
+
return postJson(`${MPP_BASE}/quote-invoice`, body);
|
|
203
|
+
}
|
|
204
|
+
function snapshotFromQuote(quote) {
|
|
205
|
+
const cb = quote?.coinbasePayment || quote?.paymentLink || null;
|
|
206
|
+
return {
|
|
207
|
+
linkId: quote?.linkId ?? quote?.paymentId ?? null,
|
|
208
|
+
protocolVersion: quote?.protocolVersion ?? null,
|
|
209
|
+
merchant: quote?.merchant ?? null,
|
|
210
|
+
original: quote?.invoice?.amount ?? null,
|
|
211
|
+
callerPays: quote?.quote?.callerPays ?? null,
|
|
212
|
+
fiat: quote?.invoice?.fiat ?? null,
|
|
213
|
+
coinbase: cb ? {
|
|
214
|
+
id: cb.id ?? null,
|
|
215
|
+
status: cb.status ?? null,
|
|
216
|
+
usageCount: cb.usageCount ?? null,
|
|
217
|
+
maxUsage: cb.maxUsage ?? null,
|
|
218
|
+
preApprovalExpiry: cb.preApprovalExpiry ?? cb.expiresAt ?? null
|
|
219
|
+
} : null
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// scripts/src/lib/amounts.mjs
|
|
224
|
+
var CHAIN_NAMES = {
|
|
225
|
+
1: "Ethereum",
|
|
226
|
+
56: "BNB Chain",
|
|
227
|
+
137: "Polygon",
|
|
228
|
+
8453: "Base",
|
|
229
|
+
900: "Solana",
|
|
230
|
+
1500: "Stellar",
|
|
231
|
+
lightning: "Bitcoin Lightning"
|
|
232
|
+
};
|
|
233
|
+
var SUPPORTED_SOURCES = [
|
|
234
|
+
{ chainId: "1", chain: "Ethereum", tokens: ["USDC", "USDT"] },
|
|
235
|
+
{ chainId: "56", chain: "BNB Chain", tokens: ["USDC", "USDT"] },
|
|
236
|
+
{ chainId: "137", chain: "Polygon", tokens: ["USDC", "USDT"] },
|
|
237
|
+
{ chainId: "900", chain: "Solana", tokens: ["USDC", "USDT"] },
|
|
238
|
+
{ chainId: "8453", chain: "Base", tokens: ["USDC"] },
|
|
239
|
+
{ chainId: "1500", chain: "Stellar", tokens: ["USDC"] },
|
|
240
|
+
{ chainId: "lightning", chain: "Bitcoin Lightning", tokens: ["BTC"] }
|
|
241
|
+
];
|
|
242
|
+
function chainName(chainId) {
|
|
243
|
+
return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
|
|
244
|
+
}
|
|
245
|
+
function isSupportedSource(chainId, tokenSymbol) {
|
|
246
|
+
const cid = String(chainId).trim();
|
|
247
|
+
const sym = String(tokenSymbol || "").trim().toUpperCase();
|
|
248
|
+
return SUPPORTED_SOURCES.some((s) => s.chainId === cid && s.tokens.includes(sym));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// scripts/src/lib/expiry.mjs
|
|
252
|
+
var MINUTE = 6e4;
|
|
253
|
+
var MARGINS_MS = {
|
|
254
|
+
1: 10 * MINUTE,
|
|
255
|
+
56: 10 * MINUTE,
|
|
256
|
+
137: 10 * MINUTE,
|
|
257
|
+
8453: 10 * MINUTE,
|
|
258
|
+
900: 5 * MINUTE,
|
|
259
|
+
1500: 10 * MINUTE,
|
|
260
|
+
lightning: 10 * MINUTE
|
|
261
|
+
};
|
|
262
|
+
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
263
|
+
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
264
|
+
function parseDeadline(value) {
|
|
265
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
266
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
267
|
+
return value < 1e12 ? Math.round(value * 1e3) : Math.round(value);
|
|
268
|
+
}
|
|
269
|
+
const s = String(value).trim();
|
|
270
|
+
if (/^\d+$/.test(s)) {
|
|
271
|
+
const n = Number(s);
|
|
272
|
+
return n < 1e12 ? n * 1e3 : n;
|
|
273
|
+
}
|
|
274
|
+
const t = Date.parse(s);
|
|
275
|
+
return Number.isFinite(t) ? t : null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// scripts/src/lib/guards.mjs
|
|
279
|
+
function normalizeDecimal(v) {
|
|
280
|
+
if (v === null || v === void 0) return null;
|
|
281
|
+
const s = String(v).trim();
|
|
282
|
+
if (!/^\d+(\.\d+)?$/.test(s)) return s;
|
|
283
|
+
const [w, f = ""] = s.split(".");
|
|
284
|
+
const frac = f.replace(/0+$/, "");
|
|
285
|
+
const whole = w.replace(/^0+(?=\d)/, "");
|
|
286
|
+
return frac ? `${whole}.${frac}` : whole;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// scripts/src/quote.mjs
|
|
290
|
+
function derivePayable(snapshot, now) {
|
|
291
|
+
const cb = snapshot.coinbase;
|
|
292
|
+
if (!cb) {
|
|
293
|
+
return { payable: false, code: "LINK_NO_LONGER_PAYABLE", reason: "No Coinbase state in the quote." };
|
|
294
|
+
}
|
|
295
|
+
if (snapshot.protocolVersion === "v3" && cb.status && cb.status !== "PAYMENT_SESSION_STATUS_CREATED") {
|
|
296
|
+
return {
|
|
297
|
+
payable: false,
|
|
298
|
+
code: "LINK_NO_LONGER_PAYABLE",
|
|
299
|
+
reason: `Payment Session status is ${cb.status}; only PAYMENT_SESSION_STATUS_CREATED is payable.`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (cb.usageCount !== null && cb.usageCount !== void 0) {
|
|
303
|
+
const max = cb.maxUsage ?? 1;
|
|
304
|
+
if (Number(cb.usageCount) >= Number(max)) {
|
|
305
|
+
return {
|
|
306
|
+
payable: false,
|
|
307
|
+
code: "LINK_NO_LONGER_PAYABLE",
|
|
308
|
+
reason: `This payment link has already been used (${cb.usageCount}/${max}).`
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const exp = parseDeadline(cb.preApprovalExpiry);
|
|
313
|
+
if (exp === null) {
|
|
314
|
+
return {
|
|
315
|
+
payable: false,
|
|
316
|
+
code: "EXPIRY_UNPARSABLE",
|
|
317
|
+
reason: "The Coinbase expiry is missing or unparsable; refusing to treat it as payable."
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (exp <= now) {
|
|
321
|
+
return {
|
|
322
|
+
payable: false,
|
|
323
|
+
code: "LINK_NO_LONGER_PAYABLE",
|
|
324
|
+
reason: `This payment link expired at ${new Date(exp).toISOString()}. Ask for a fresh one.`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return { payable: true, code: null, reason: null, expiryMs: exp };
|
|
328
|
+
}
|
|
329
|
+
async function main(argv) {
|
|
330
|
+
const args = parseArgs(argv);
|
|
331
|
+
const url = args.url || args._[0];
|
|
332
|
+
if (!url || url === true) {
|
|
333
|
+
usage('Required: --url "<coinbase payment link or paymentSession url/id>"');
|
|
334
|
+
}
|
|
335
|
+
const { linkId, kind } = extractLinkId(String(url));
|
|
336
|
+
let chosenSource = null;
|
|
337
|
+
if (args.chain || args.token) {
|
|
338
|
+
const chainId = String(args.chain ?? "").trim();
|
|
339
|
+
const tokenSymbol = String(args.token ?? "").trim().toUpperCase();
|
|
340
|
+
if (!chainId || !tokenSymbol) {
|
|
341
|
+
usage("--chain and --token must be given together, e.g. --chain 900 --token USDT");
|
|
342
|
+
}
|
|
343
|
+
if (!isSupportedSource(chainId, tokenSymbol)) {
|
|
344
|
+
throw new SkillError(
|
|
345
|
+
"UNSUPPORTED_SOURCE",
|
|
346
|
+
`${tokenSymbol} on ${chainName(chainId)} is not a supported source.`,
|
|
347
|
+
{ supported: SUPPORTED_SOURCES }
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
chosenSource = { chainId, tokenSymbol };
|
|
351
|
+
}
|
|
352
|
+
const now = Date.now();
|
|
353
|
+
const quote = await quoteInvoice({ url: String(url) });
|
|
354
|
+
const snapshot = snapshotFromQuote(quote);
|
|
355
|
+
const payability = derivePayable(snapshot, now);
|
|
356
|
+
const payload = {
|
|
357
|
+
success: payability.payable,
|
|
358
|
+
step: "quote",
|
|
359
|
+
linkId: snapshot.linkId ?? linkId,
|
|
360
|
+
linkKind: kind,
|
|
361
|
+
protocolVersion: snapshot.protocolVersion,
|
|
362
|
+
merchant: snapshot.merchant,
|
|
363
|
+
invoice: {
|
|
364
|
+
amount: snapshot.original,
|
|
365
|
+
fiat: snapshot.fiat
|
|
366
|
+
},
|
|
367
|
+
// No discount on this line: the caller pays the full invoice amount.
|
|
368
|
+
callerPays: snapshot.callerPays,
|
|
369
|
+
discountPolicy: "none \u2014 callerPays equals the invoice amount",
|
|
370
|
+
coinbase: snapshot.coinbase,
|
|
371
|
+
coinbaseExpiryIso: payability.expiryMs ? new Date(payability.expiryMs).toISOString() : null,
|
|
372
|
+
chosenSource,
|
|
373
|
+
supportedSources: SUPPORTED_SOURCES,
|
|
374
|
+
quoteReceipt: quote?.quoteReceipt ?? null,
|
|
375
|
+
quoteReceiptTtlSeconds: 60,
|
|
376
|
+
quoteReceiptNote: "Short-lived (~60s). create-order.js takes its own fresh quote; do not carry this over.",
|
|
377
|
+
snapshot,
|
|
378
|
+
nextStep: payability.payable ? "create-order.js --url <url> --chain <chainId> --token <SYMBOL>" : null
|
|
379
|
+
};
|
|
380
|
+
if (!payability.payable) {
|
|
381
|
+
payload.error = { code: payability.code, message: payability.reason };
|
|
382
|
+
emit(payload, EXIT_ERROR);
|
|
383
|
+
}
|
|
384
|
+
if (snapshot.callerPays && snapshot.original && normalizeDecimal(snapshot.callerPays) !== normalizeDecimal(snapshot.original)) {
|
|
385
|
+
payload.warnings = [
|
|
386
|
+
`callerPays (${snapshot.callerPays}) differs from the invoice amount (${snapshot.original}). This flow is supposed to charge the full invoice \u2014 do not proceed until that is explained.`
|
|
387
|
+
];
|
|
388
|
+
}
|
|
389
|
+
emit(payload);
|
|
390
|
+
}
|
|
391
|
+
async function run(argv = process.argv.slice(2)) {
|
|
392
|
+
return main(argv);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// scripts/src/bin/quote.mjs
|
|
396
|
+
run().catch((err) => fail(err));
|