@rozoai/checkout 0.1.2 → 0.1.4
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 +30 -9
- package/docs/QUICKSTART.es.md +4 -2
- package/docs/QUICKSTART.ja.md +3 -2
- package/docs/QUICKSTART.md +7 -3
- package/docs/QUICKSTART.zh.md +3 -3
- package/docs/README.es.md +10 -2
- package/docs/README.ja.md +9 -2
- package/docs/README.zh.md +8 -2
- package/docs/review-history.md +77 -0
- package/llms.txt +7 -1
- package/package.json +1 -1
- package/scripts/dist/cli.js +2983 -2881
- package/scripts/dist/create-order.js +53 -2
- package/scripts/dist/quote.js +4 -0
- package/scripts/dist/send-evm.js +4 -0
- package/scripts/dist/send-sol.js +4 -0
- package/scripts/dist/status.js +39 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
|
|
5
9
|
// scripts/src/lib/output.mjs
|
|
6
10
|
var EXIT_OK = 0;
|
|
@@ -396,6 +400,7 @@ function formatAmount(source) {
|
|
|
396
400
|
function chainName(chainId) {
|
|
397
401
|
return CHAIN_NAMES[String(chainId)] || CHAIN_NAMES[chainId] || `chain ${chainId}`;
|
|
398
402
|
}
|
|
403
|
+
var STELLAR_MEMO_TYPE = "MEMO_TEXT";
|
|
399
404
|
function chainFamily(chainId) {
|
|
400
405
|
return CHAIN_FAMILY[String(chainId)] || CHAIN_FAMILY[chainId] || null;
|
|
401
406
|
}
|
|
@@ -418,6 +423,16 @@ var MARGINS_MS = {
|
|
|
418
423
|
};
|
|
419
424
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
420
425
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
426
|
+
function formatRemaining(ms) {
|
|
427
|
+
if (!Number.isFinite(ms)) return "unknown";
|
|
428
|
+
if (ms <= 0) return "expired";
|
|
429
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
430
|
+
if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
|
|
431
|
+
if (totalMinutes < 60) return `${totalMinutes}m`;
|
|
432
|
+
const h = Math.floor(totalMinutes / 60);
|
|
433
|
+
const m = totalMinutes % 60;
|
|
434
|
+
return m ? `${h}h ${m}m` : `${h}h`;
|
|
435
|
+
}
|
|
421
436
|
function marginFor(chainId) {
|
|
422
437
|
const key = String(chainId);
|
|
423
438
|
return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
|
|
@@ -1135,6 +1150,33 @@ async function main(argv) {
|
|
|
1135
1150
|
const rozoPaymentId = assertRozoPaymentId(created.rozoPaymentId);
|
|
1136
1151
|
const verify = verifyCreateAgainstQuote({ snapshot, created, requested });
|
|
1137
1152
|
if (!verify.ok) {
|
|
1153
|
+
const onlySourceDrift = verify.drift.length > 0 && verify.drift.every((d) => d.field.startsWith("source."));
|
|
1154
|
+
if (created.reused && onlySourceDrift) {
|
|
1155
|
+
const existing = created.source || {};
|
|
1156
|
+
const remainingMs = created.expiresAt ? Date.parse(created.expiresAt) - Date.now() : NaN;
|
|
1157
|
+
emit(
|
|
1158
|
+
{
|
|
1159
|
+
success: false,
|
|
1160
|
+
step: "reuse-source-mismatch",
|
|
1161
|
+
error: {
|
|
1162
|
+
code: "REUSED_SOURCE_MISMATCH",
|
|
1163
|
+
message: `This link already has an unpaid order, created for ${existing.tokenSymbol ?? "?"} on chain ${existing.chainId ?? "?"}. You asked for ${tokenSymbol} on chain ${chainId}. Only one order exists per link at a time, so nothing new was created.`
|
|
1164
|
+
},
|
|
1165
|
+
linkId: created.linkId ?? parsedLinkId,
|
|
1166
|
+
rozoPaymentId,
|
|
1167
|
+
paymentLink: created.paymentLink ?? null,
|
|
1168
|
+
existingOrder: {
|
|
1169
|
+
rozoPaymentId,
|
|
1170
|
+
chainId: existing.chainId ?? null,
|
|
1171
|
+
tokenSymbol: existing.tokenSymbol ?? null,
|
|
1172
|
+
expiresAt: created.expiresAt ?? null,
|
|
1173
|
+
expiresIn: Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : null
|
|
1174
|
+
},
|
|
1175
|
+
guidance: `Either pay the existing order with ${existing.tokenSymbol ?? "its own coin"} (re-run with --chain ${existing.chainId} --token ${existing.tokenSymbol}), or wait ${Number.isFinite(remainingMs) ? formatRemaining(remainingMs) : "for it to expire"} for it to expire and then create a new one. Unpaid orders cost nothing.`
|
|
1176
|
+
},
|
|
1177
|
+
EXIT_ERROR
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1138
1180
|
emit(
|
|
1139
1181
|
{
|
|
1140
1182
|
success: false,
|
|
@@ -1256,7 +1298,8 @@ async function main(argv) {
|
|
|
1256
1298
|
step: "create-order",
|
|
1257
1299
|
confirmed,
|
|
1258
1300
|
reused: Boolean(created.reused),
|
|
1259
|
-
reusedNote: created.reused ?
|
|
1301
|
+
reusedNote: created.reused ? `An existing unpaid order for this link was reused (${rozoPaymentId}), valid for another ${formatRemaining(expiry.msRemaining)}. Nothing new was created.` : null,
|
|
1302
|
+
orderCost: "Creating an order moves no money. An order you never fund simply expires and costs nothing.",
|
|
1260
1303
|
linkId: created.linkId ?? parsedLinkId,
|
|
1261
1304
|
rozoPaymentId,
|
|
1262
1305
|
paymentLink: created.paymentLink ?? null,
|
|
@@ -1274,12 +1317,16 @@ async function main(argv) {
|
|
|
1274
1317
|
// Lightning has no deposit address: the BOLT11 IS the instruction.
|
|
1275
1318
|
receiverAddress: lightning ? null : source.receiverAddress,
|
|
1276
1319
|
receiverMemo: source.receiverMemo ?? null,
|
|
1320
|
+
// Stellar memos are TEXT even when they look numeric. Sending one as
|
|
1321
|
+
// MEMO_ID produces a different memo and the payment will not match.
|
|
1322
|
+
receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
1277
1323
|
amount: source.amount,
|
|
1278
1324
|
amountUnit: source.amountUnit ?? null,
|
|
1279
1325
|
isSats: isSatsUnit(source.amountUnit),
|
|
1280
1326
|
lnInvoice: bolt11 || null,
|
|
1281
1327
|
payTo: depositInfo.payTo,
|
|
1282
|
-
expiresAt: payment?.expiresAt ?? null
|
|
1328
|
+
expiresAt: payment?.expiresAt ?? null,
|
|
1329
|
+
expiresIn: formatRemaining(expiry.msRemaining)
|
|
1283
1330
|
} : null,
|
|
1284
1331
|
depositWithheld: !confirmed,
|
|
1285
1332
|
// Safe for prose / chat.
|
|
@@ -1291,12 +1338,16 @@ async function main(argv) {
|
|
|
1291
1338
|
payToMasked: maskAddress(depositInfo.payTo),
|
|
1292
1339
|
receiverMemoMasked: maskMemo(source.receiverMemo),
|
|
1293
1340
|
hasMemo: Boolean(source.receiverMemo),
|
|
1341
|
+
memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
|
|
1294
1342
|
memoRequirement
|
|
1295
1343
|
},
|
|
1296
1344
|
expiry: {
|
|
1297
1345
|
intentExpiresAt: payment?.expiresAt ?? null,
|
|
1298
1346
|
coinbaseExpiry: snapshot?.coinbase?.preApprovalExpiry ?? null,
|
|
1299
1347
|
effectiveDeadlineIso: new Date(expiry.effectiveDeadlineMs).toISOString(),
|
|
1348
|
+
// A duration, not just a timestamp: this is what a payer actually needs.
|
|
1349
|
+
expiresIn: formatRemaining(expiry.msRemaining),
|
|
1350
|
+
msRemaining: expiry.msRemaining,
|
|
1300
1351
|
marginMinutes: Math.round(expiry.marginMs / 6e4),
|
|
1301
1352
|
minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4)
|
|
1302
1353
|
},
|
package/scripts/dist/quote.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
|
|
5
9
|
// scripts/src/lib/output.mjs
|
|
6
10
|
var EXIT_OK = 0;
|
package/scripts/dist/send-evm.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
var __defProp = Object.defineProperty;
|
|
5
9
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
10
|
var __esm = (fn, res) => function __init() {
|
package/scripts/dist/send-sol.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
var __create = Object.create;
|
|
5
9
|
var __defProp = Object.defineProperty;
|
|
6
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
package/scripts/dist/status.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __rozoCreateRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __rozoFileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __rozoDirname } from 'node:path';
|
|
3
5
|
const require = __rozoCreateRequire(import.meta.url);
|
|
6
|
+
const __filename = __rozoFileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __rozoDirname(__filename);
|
|
4
8
|
|
|
5
9
|
// scripts/src/lib/output.mjs
|
|
6
10
|
var EXIT_OK = 0;
|
|
@@ -428,6 +432,30 @@ function classifyStatus({ payment, routerState, coinbase, now = Date.now(), view
|
|
|
428
432
|
);
|
|
429
433
|
}
|
|
430
434
|
|
|
435
|
+
// scripts/src/lib/expiry.mjs
|
|
436
|
+
var MINUTE = 6e4;
|
|
437
|
+
var MARGINS_MS = {
|
|
438
|
+
1: 10 * MINUTE,
|
|
439
|
+
56: 10 * MINUTE,
|
|
440
|
+
137: 10 * MINUTE,
|
|
441
|
+
8453: 10 * MINUTE,
|
|
442
|
+
900: 5 * MINUTE,
|
|
443
|
+
1500: 10 * MINUTE,
|
|
444
|
+
lightning: 10 * MINUTE
|
|
445
|
+
};
|
|
446
|
+
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
447
|
+
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
448
|
+
function formatRemaining(ms) {
|
|
449
|
+
if (!Number.isFinite(ms)) return "unknown";
|
|
450
|
+
if (ms <= 0) return "expired";
|
|
451
|
+
const totalMinutes = Math.floor(ms / 6e4);
|
|
452
|
+
if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
|
|
453
|
+
if (totalMinutes < 60) return `${totalMinutes}m`;
|
|
454
|
+
const h = Math.floor(totalMinutes / 60);
|
|
455
|
+
const m = totalMinutes % 60;
|
|
456
|
+
return m ? `${h}h ${m}m` : `${h}h`;
|
|
457
|
+
}
|
|
458
|
+
|
|
431
459
|
// scripts/src/lib/state.mjs
|
|
432
460
|
import fs from "node:fs";
|
|
433
461
|
import path from "node:path";
|
|
@@ -519,6 +547,16 @@ async function snapshot({ rozoPaymentId, linkId }) {
|
|
|
519
547
|
senderAddressMasked: source.senderAddress ? maskAddress(source.senderAddress) : null,
|
|
520
548
|
chain: source.chainId ? chainName(source.chainId) : null
|
|
521
549
|
},
|
|
550
|
+
expiry: (() => {
|
|
551
|
+
const iso = payment?.expiresAt ?? status?.rozoPayment?.expiresAt ?? null;
|
|
552
|
+
if (!iso) return { expiresAt: null, expiresIn: null, msRemaining: null };
|
|
553
|
+
const ms = Date.parse(iso) - Date.now();
|
|
554
|
+
return {
|
|
555
|
+
expiresAt: iso,
|
|
556
|
+
expiresIn: formatRemaining(ms),
|
|
557
|
+
msRemaining: Number.isFinite(ms) ? ms : null
|
|
558
|
+
};
|
|
559
|
+
})(),
|
|
522
560
|
payout: {
|
|
523
561
|
txHash: payment?.destination?.txHash ?? status?.rozoPayment?.destination?.txHash ?? null,
|
|
524
562
|
confirmedAt: payment?.destination?.confirmedAt ?? status?.rozoPayment?.destination?.confirmedAt ?? null
|
|
@@ -544,7 +582,7 @@ async function main(argv) {
|
|
|
544
582
|
if (next.state !== result.state) history.push({ at: (/* @__PURE__ */ new Date()).toISOString(), state: next.state });
|
|
545
583
|
result = next;
|
|
546
584
|
}
|
|
547
|
-
const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded.
|
|
585
|
+
const guidance = result.escalate ? "MONEY DETECTED and the order is not on a healthy path. Do NOT pay again and do NOT create a new order for this link. Preserve linkId, rozoPaymentId and every tx hash, then escalate to the operator for manual reconciliation." : result.unknown ? "The order state could not be established. This is NOT evidence that nothing was paid \u2014 do not create a new order and do not send again on the strength of it. Retry, or pass --rozo-payment-id so the authoritative pay-in view can be read." : !result.authoritativeView ? "Only the fulfilment view was readable; the pay-in view is unavailable, so the money-detected rule cannot be enforced. Pass --rozo-payment-id for a complete answer." : result.state === "expired_unfunded" ? "Nothing was funded, so nothing was lost. Start a fresh order with: rozo-checkout pay <coinbase-link> --with <coin> (or create-order.js --url <link> --chain <id> --token <SYMBOL>)" : result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
|
|
548
586
|
const unresolved = watch && !result.terminal && !result.escalate && !result.unknown;
|
|
549
587
|
const failed = result.escalate || result.unknown || !result.authoritativeView;
|
|
550
588
|
emit(
|