@rozoai/checkout 0.1.5 → 0.1.6

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/dist/cli.js +156 -126
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozoai/checkout",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "Pay an OpenRouter Coinbase Payment Link with BTC Lightning, or USDT/USDC on Solana, BNB Chain, Ethereum, Polygon, Base or Stellar.",
6
6
  "license": "MIT",
@@ -42588,6 +42588,143 @@ function maskMemo(memo) {
42588
42588
  return `${s.slice(0, 4)}...${s.slice(-3)}`;
42589
42589
  }
42590
42590
 
42591
+ // scripts/src/lib/expiry.mjs
42592
+ var MINUTE = 6e4;
42593
+ var MARGINS_MS = {
42594
+ 1: 10 * MINUTE,
42595
+ 56: 10 * MINUTE,
42596
+ 137: 10 * MINUTE,
42597
+ 8453: 10 * MINUTE,
42598
+ 900: 5 * MINUTE,
42599
+ 1500: 10 * MINUTE,
42600
+ lightning: 10 * MINUTE
42601
+ };
42602
+ var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
42603
+ var DEFAULT_MARGIN_MS = 10 * MINUTE;
42604
+ function formatRemaining(ms) {
42605
+ if (!Number.isFinite(ms)) return "unknown";
42606
+ if (ms <= 0) return "expired";
42607
+ const totalMinutes = Math.floor(ms / 6e4);
42608
+ if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
42609
+ if (totalMinutes < 60) return `${totalMinutes}m`;
42610
+ const h = Math.floor(totalMinutes / 60);
42611
+ const m = totalMinutes % 60;
42612
+ return m ? `${h}h ${m}m` : `${h}h`;
42613
+ }
42614
+ function formatDeadline(iso, { now = Date.now(), locale, timeZone } = {}) {
42615
+ if (!iso) return null;
42616
+ const at = new Date(iso);
42617
+ if (Number.isNaN(at.getTime())) return null;
42618
+ const msLeft = at.getTime() - now;
42619
+ if (msLeft <= 0) return "expired";
42620
+ const minutes = Math.round(msLeft / 6e4);
42621
+ const human = minutes < 1 ? "less than a minute" : minutes < 60 ? `about ${minutes} minute${minutes === 1 ? "" : "s"}` : `about ${Math.floor(minutes / 60)}h ${minutes % 60}m`;
42622
+ const clock = at.toLocaleTimeString(locale, {
42623
+ hour: "numeric",
42624
+ minute: "2-digit",
42625
+ ...timeZone ? { timeZone } : {}
42626
+ });
42627
+ return `in ${human}, at ${clock} local time`;
42628
+ }
42629
+ function marginFor(chainId) {
42630
+ const key = String(chainId);
42631
+ return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
42632
+ }
42633
+ function parseDeadline(value) {
42634
+ if (value === null || value === void 0 || value === "") return null;
42635
+ if (typeof value === "number" && Number.isFinite(value)) {
42636
+ return value < 1e12 ? Math.round(value * 1e3) : Math.round(value);
42637
+ }
42638
+ const s = String(value).trim();
42639
+ if (/^\d+$/.test(s)) {
42640
+ const n = Number(s);
42641
+ return n < 1e12 ? n * 1e3 : n;
42642
+ }
42643
+ const t = Date.parse(s);
42644
+ return Number.isFinite(t) ? t : null;
42645
+ }
42646
+ function checkExpiry({
42647
+ now,
42648
+ chainId,
42649
+ intentExpiresAt,
42650
+ coinbaseExpiry,
42651
+ bolt11ExpiresAt = void 0
42652
+ }) {
42653
+ const marginMs = marginFor(chainId);
42654
+ const intentMs = parseDeadline(intentExpiresAt);
42655
+ const coinbaseMs = parseDeadline(coinbaseExpiry);
42656
+ const bolt11Ms = bolt11ExpiresAt === void 0 ? void 0 : parseDeadline(bolt11ExpiresAt);
42657
+ const deadlines = { intentMs, coinbaseMs, bolt11Ms: bolt11Ms ?? null };
42658
+ const base = {
42659
+ marginMs,
42660
+ effectiveDeadlineMs: null,
42661
+ msRemaining: null,
42662
+ msOfSlack: null,
42663
+ deadlines
42664
+ };
42665
+ if (intentMs === null) {
42666
+ return {
42667
+ ...base,
42668
+ ok: false,
42669
+ code: "EXPIRY_UNPARSABLE",
42670
+ reason: "Intent expiresAt is missing or unparsable."
42671
+ };
42672
+ }
42673
+ if (coinbaseMs === null) {
42674
+ return {
42675
+ ...base,
42676
+ ok: false,
42677
+ code: "EXPIRY_UNPARSABLE",
42678
+ reason: "Coinbase preApprovalExpiry is missing or unparsable."
42679
+ };
42680
+ }
42681
+ const effective = Math.min(intentMs, coinbaseMs);
42682
+ const msRemaining = effective - now;
42683
+ const msOfSlack = msRemaining - marginMs;
42684
+ const withDeadline = {
42685
+ ...base,
42686
+ effectiveDeadlineMs: effective,
42687
+ msRemaining,
42688
+ msOfSlack
42689
+ };
42690
+ if (msRemaining <= 0) {
42691
+ return {
42692
+ ...withDeadline,
42693
+ ok: false,
42694
+ code: "EXPIRED",
42695
+ reason: "The order or the Coinbase link has already expired."
42696
+ };
42697
+ }
42698
+ if (msOfSlack <= 0) {
42699
+ return {
42700
+ ...withDeadline,
42701
+ ok: false,
42702
+ code: "EXPIRY_MARGIN",
42703
+ reason: `Only ${Math.floor(msRemaining / 1e3)}s left before the earliest deadline; this chain needs a ${Math.floor(marginMs / 6e4)} min safety margin.`
42704
+ };
42705
+ }
42706
+ if (bolt11ExpiresAt !== void 0) {
42707
+ if (bolt11Ms === null) {
42708
+ return {
42709
+ ...withDeadline,
42710
+ ok: false,
42711
+ code: "EXPIRY_UNPARSABLE",
42712
+ reason: "BOLT11 invoice expiry is missing or unparsable."
42713
+ };
42714
+ }
42715
+ const bolt11Remaining = bolt11Ms - now;
42716
+ if (bolt11Remaining < BOLT11_MIN_VALIDITY_MS) {
42717
+ return {
42718
+ ...withDeadline,
42719
+ ok: false,
42720
+ code: "BOLT11_TOO_SHORT",
42721
+ reason: `BOLT11 invoice has ${Math.max(0, Math.floor(bolt11Remaining / 1e3))}s of validity left; at least 10 min is required. Request a fresh invoice.`
42722
+ };
42723
+ }
42724
+ }
42725
+ return { ...withDeadline, ok: true, code: null, reason: null };
42726
+ }
42727
+
42591
42728
  // scripts/src/lib/key-source.mjs
42592
42729
  import fs5 from "node:fs";
42593
42730
  import path5 from "node:path";
@@ -54155,128 +54292,6 @@ function snapshotFromQuote(quote) {
54155
54292
  };
54156
54293
  }
54157
54294
 
54158
- // scripts/src/lib/expiry.mjs
54159
- var MINUTE = 6e4;
54160
- var MARGINS_MS = {
54161
- 1: 10 * MINUTE,
54162
- 56: 10 * MINUTE,
54163
- 137: 10 * MINUTE,
54164
- 8453: 10 * MINUTE,
54165
- 900: 5 * MINUTE,
54166
- 1500: 10 * MINUTE,
54167
- lightning: 10 * MINUTE
54168
- };
54169
- var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
54170
- var DEFAULT_MARGIN_MS = 10 * MINUTE;
54171
- function formatRemaining(ms) {
54172
- if (!Number.isFinite(ms)) return "unknown";
54173
- if (ms <= 0) return "expired";
54174
- const totalMinutes = Math.floor(ms / 6e4);
54175
- if (totalMinutes < 1) return `${Math.floor(ms / 1e3)}s`;
54176
- if (totalMinutes < 60) return `${totalMinutes}m`;
54177
- const h = Math.floor(totalMinutes / 60);
54178
- const m = totalMinutes % 60;
54179
- return m ? `${h}h ${m}m` : `${h}h`;
54180
- }
54181
- function marginFor(chainId) {
54182
- const key = String(chainId);
54183
- return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
54184
- }
54185
- function parseDeadline(value) {
54186
- if (value === null || value === void 0 || value === "") return null;
54187
- if (typeof value === "number" && Number.isFinite(value)) {
54188
- return value < 1e12 ? Math.round(value * 1e3) : Math.round(value);
54189
- }
54190
- const s = String(value).trim();
54191
- if (/^\d+$/.test(s)) {
54192
- const n = Number(s);
54193
- return n < 1e12 ? n * 1e3 : n;
54194
- }
54195
- const t = Date.parse(s);
54196
- return Number.isFinite(t) ? t : null;
54197
- }
54198
- function checkExpiry({
54199
- now,
54200
- chainId,
54201
- intentExpiresAt,
54202
- coinbaseExpiry,
54203
- bolt11ExpiresAt = void 0
54204
- }) {
54205
- const marginMs = marginFor(chainId);
54206
- const intentMs = parseDeadline(intentExpiresAt);
54207
- const coinbaseMs = parseDeadline(coinbaseExpiry);
54208
- const bolt11Ms = bolt11ExpiresAt === void 0 ? void 0 : parseDeadline(bolt11ExpiresAt);
54209
- const deadlines = { intentMs, coinbaseMs, bolt11Ms: bolt11Ms ?? null };
54210
- const base = {
54211
- marginMs,
54212
- effectiveDeadlineMs: null,
54213
- msRemaining: null,
54214
- msOfSlack: null,
54215
- deadlines
54216
- };
54217
- if (intentMs === null) {
54218
- return {
54219
- ...base,
54220
- ok: false,
54221
- code: "EXPIRY_UNPARSABLE",
54222
- reason: "Intent expiresAt is missing or unparsable."
54223
- };
54224
- }
54225
- if (coinbaseMs === null) {
54226
- return {
54227
- ...base,
54228
- ok: false,
54229
- code: "EXPIRY_UNPARSABLE",
54230
- reason: "Coinbase preApprovalExpiry is missing or unparsable."
54231
- };
54232
- }
54233
- const effective = Math.min(intentMs, coinbaseMs);
54234
- const msRemaining = effective - now;
54235
- const msOfSlack = msRemaining - marginMs;
54236
- const withDeadline = {
54237
- ...base,
54238
- effectiveDeadlineMs: effective,
54239
- msRemaining,
54240
- msOfSlack
54241
- };
54242
- if (msRemaining <= 0) {
54243
- return {
54244
- ...withDeadline,
54245
- ok: false,
54246
- code: "EXPIRED",
54247
- reason: "The order or the Coinbase link has already expired."
54248
- };
54249
- }
54250
- if (msOfSlack <= 0) {
54251
- return {
54252
- ...withDeadline,
54253
- ok: false,
54254
- code: "EXPIRY_MARGIN",
54255
- reason: `Only ${Math.floor(msRemaining / 1e3)}s left before the earliest deadline; this chain needs a ${Math.floor(marginMs / 6e4)} min safety margin.`
54256
- };
54257
- }
54258
- if (bolt11ExpiresAt !== void 0) {
54259
- if (bolt11Ms === null) {
54260
- return {
54261
- ...withDeadline,
54262
- ok: false,
54263
- code: "EXPIRY_UNPARSABLE",
54264
- reason: "BOLT11 invoice expiry is missing or unparsable."
54265
- };
54266
- }
54267
- const bolt11Remaining = bolt11Ms - now;
54268
- if (bolt11Remaining < BOLT11_MIN_VALIDITY_MS) {
54269
- return {
54270
- ...withDeadline,
54271
- ok: false,
54272
- code: "BOLT11_TOO_SHORT",
54273
- reason: `BOLT11 invoice has ${Math.max(0, Math.floor(bolt11Remaining / 1e3))}s of validity left; at least 10 min is required. Request a fresh invoice.`
54274
- };
54275
- }
54276
- }
54277
- return { ...withDeadline, ok: true, code: null, reason: null };
54278
- }
54279
-
54280
54295
  // scripts/src/lib/guards.mjs
54281
54296
  var UNPAID_STATUS = "payment_unpaid";
54282
54297
  function receiptSignal(source) {
@@ -58057,6 +58072,16 @@ async function cmdPay(opts) {
58057
58072
  }
58058
58073
  if (!result.ok && !opts.json) {
58059
58074
  out(` ${yellow(`Could not check balances (${result.reason}). Continuing.`)}`);
58075
+ } else if (result.ok && !opts.json) {
58076
+ const hit = (result.options ?? []).find(
58077
+ (o) => o.chainId === String(opts.source.chainId) && o.tokenSymbol === String(opts.source.tokenSymbol).toUpperCase()
58078
+ );
58079
+ const short = `${opts.payer.slice(0, 6)}\u2026${opts.payer.slice(-4)}`;
58080
+ if (hit?.balanceUsd != null) {
58081
+ out(` ${dim(`Payer ${short} holds about $${hit.balanceUsd} of ${opts.source.tokenSymbol} \u2014 enough for this invoice.`)}`);
58082
+ } else {
58083
+ out(` ${dim(`Payer ${short}: balance not reported for this coin, so it was not verified.`)}`);
58084
+ }
58060
58085
  }
58061
58086
  payer = { address: opts.payer, family };
58062
58087
  }
@@ -58099,10 +58124,12 @@ async function cmdPay(opts) {
58099
58124
  if (p.display?.hasMemo) {
58100
58125
  out(` Memo ${p.display.receiverMemoMasked} ${dim(`(${p.display.memoType})`)}`);
58101
58126
  }
58102
- out(` Expires in ${bold(p.expiry?.expiresIn ?? "?")} ${dim(`(${p.expiry?.effectiveDeadlineIso})`)}`);
58103
- if (p.reused) {
58104
- out(` ${dim(`Reusing the existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}`);
58105
- }
58127
+ const deadline = formatDeadline(p.expiry?.effectiveDeadlineIso);
58128
+ out(` Expires ${bold(deadline ?? `in ${p.expiry?.expiresIn ?? "?"}`)}`);
58129
+ out(` ${dim("After that this order cannot be used and a new one must be created.")}`);
58130
+ out(
58131
+ p.reused ? ` ${dim(`Reusing existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}` : ` ${dim(`Order created: ${p.rozoPaymentId}`)}`
58132
+ );
58106
58133
  out(` ${dim("An order you never fund simply expires and costs nothing.")}`);
58107
58134
  out();
58108
58135
  out(dim(` The amount you send includes bridge and network fees, so it is`));
@@ -58138,6 +58165,9 @@ async function cmdPay(opts) {
58138
58165
  return confirmed.exitCode;
58139
58166
  }
58140
58167
  const deposit = confirmed.payload.deposit;
58168
+ if (!opts.json) {
58169
+ out(` ${dim(`Confirming order ${rozoPaymentId} \u2014 no second order was created.`)}`);
58170
+ }
58141
58171
  if (opts.send) {
58142
58172
  const family = chainFamily(deposit.chainId);
58143
58173
  const sender = family === "evm" ? run4 : family === "solana" ? run5 : null;