@rozoai/checkout 0.1.16 → 0.1.18

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.
@@ -20544,7 +20544,7 @@ var require_dist = __commonJS({
20544
20544
  }
20545
20545
  }
20546
20546
  }
20547
- function* run8(value, struct2, options = {}) {
20547
+ function* run9(value, struct2, options = {}) {
20548
20548
  const { path: path7 = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
20549
20549
  const ctx = { path: path7, branch, mask: mask2 };
20550
20550
  if (coerce2) {
@@ -20557,7 +20557,7 @@ var require_dist = __commonJS({
20557
20557
  yield [failure, void 0];
20558
20558
  }
20559
20559
  for (let [k, v, s] of struct2.entries(value, ctx)) {
20560
- const ts = run8(v, s, {
20560
+ const ts = run9(v, s, {
20561
20561
  path: k === void 0 ? path7 : [...path7, k],
20562
20562
  branch: k === void 0 ? branch : [...branch, v],
20563
20563
  coerce: coerce2,
@@ -20685,7 +20685,7 @@ var require_dist = __commonJS({
20685
20685
  return !result[0];
20686
20686
  }
20687
20687
  function validate7(value, struct2, options = {}) {
20688
- const tuples = run8(value, struct2, options);
20688
+ const tuples = run9(value, struct2, options);
20689
20689
  const tuple2 = shiftIterator(tuples);
20690
20690
  if (tuple2[0]) {
20691
20691
  const error = new StructError(tuple2[0], function* () {
@@ -21098,7 +21098,7 @@ var require_dist = __commonJS({
21098
21098
  validator(value, ctx) {
21099
21099
  const failures = [];
21100
21100
  for (const S of Structs) {
21101
- const [...tuples] = run8(value, S, ctx);
21101
+ const [...tuples] = run9(value, S, ctx);
21102
21102
  const [first] = tuples;
21103
21103
  if (!first[0]) {
21104
21104
  return [];
@@ -41455,6 +41455,8 @@ var SUPPORT = Object.freeze({
41455
41455
  discord: "https://discord.gg/EfWejgTbuU"
41456
41456
  });
41457
41457
  var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
41458
+ var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
41459
+ var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
41458
41460
  var CONTACT_EMAIL_MAX_LENGTH = 254;
41459
41461
  var EMAIL_RE = /^[a-z0-9][^\s@]*@[^\s@.]+(\.[^\s@.]+)+$/;
41460
41462
  function normalizeContactEmail(raw) {
@@ -41526,6 +41528,22 @@ var TX_HASH_VALUE = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{86,88})$/;
41526
41528
  function publicHash(value) {
41527
41529
  return typeof value === "string" && TX_HASH_VALUE.test(value) ? new PublicHash(value) : value;
41528
41530
  }
41531
+ var PUBLIC_PAY_PAGE_HOSTS = /* @__PURE__ */ new Set(["invoice.rozo.ai", "checkout.rozo.ai"]);
41532
+ var PAY_PAGE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41533
+ function publicPayPage(value) {
41534
+ if (typeof value !== "string") return value;
41535
+ let u;
41536
+ try {
41537
+ u = new URL(value);
41538
+ } catch {
41539
+ return value;
41540
+ }
41541
+ if (u.protocol !== "https:" || u.username || u.password || u.port || !PUBLIC_PAY_PAGE_HOSTS.has(u.hostname)) return value;
41542
+ if (u.pathname !== "/checkout") return value;
41543
+ const id = u.searchParams.get("id");
41544
+ if (!id || !PAY_PAGE_ID.test(id)) return value;
41545
+ return new PublicHash(`https://${u.hostname}/checkout?id=${id.toLowerCase()}`);
41546
+ }
41529
41547
  function redactDeep(value) {
41530
41548
  if (value instanceof PublicHash) return value.value;
41531
41549
  if (typeof value === "string") return redact(value);
@@ -41769,8 +41787,61 @@ function normalizeUtmSource(value) {
41769
41787
  return s;
41770
41788
  }
41771
41789
 
41790
+ // scripts/src/lib/ids.mjs
41791
+ var IdError = class extends Error {
41792
+ constructor(code, message) {
41793
+ super(message);
41794
+ this.code = code;
41795
+ }
41796
+ };
41797
+ var PL_RE = /(pl_[0-9a-zA-Z]+)/;
41798
+ var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
41799
+ var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
41800
+ function extractLinkId(urlOrId) {
41801
+ if (typeof urlOrId !== "string" || !urlOrId.trim()) {
41802
+ throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
41803
+ }
41804
+ const s = urlOrId.trim();
41805
+ const session = s.match(SESSION_RE);
41806
+ if (session) return { linkId: session[1], kind: "payment_session" };
41807
+ const link = s.match(PL_RE);
41808
+ if (link) return { linkId: link[1], kind: "payment_link" };
41809
+ if (/commerce\.coinbase\.com\/pay\//.test(s)) {
41810
+ throw new IdError(
41811
+ "LEGACY_COMMERCE_URL",
41812
+ "commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
41813
+ );
41814
+ }
41815
+ throw new IdError(
41816
+ "BAD_LINK",
41817
+ "Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
41818
+ );
41819
+ }
41820
+ function assertRozoPaymentId(value) {
41821
+ const s = String(value || "").trim();
41822
+ if (!UUID_RE.test(s)) {
41823
+ throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
41824
+ }
41825
+ return s;
41826
+ }
41827
+ function isRozoPaymentId(value) {
41828
+ return UUID_RE.test(String(value || "").trim());
41829
+ }
41830
+ function maskAddress2(address) {
41831
+ const s = String(address ?? "").trim();
41832
+ if (!s) return "(none)";
41833
+ if (s.length <= 12) return s;
41834
+ return `${s.slice(0, 6)}...${s.slice(-4)}`;
41835
+ }
41836
+ function maskMemo(memo) {
41837
+ const s = String(memo ?? "").trim();
41838
+ if (!s) return null;
41839
+ if (s.length <= 8) return s;
41840
+ return `${s.slice(0, 4)}...${s.slice(-3)}`;
41841
+ }
41842
+
41772
41843
  // scripts/src/lib/cli-args.mjs
41773
- var COMMANDS = ["pay", "quote", "status", "receipt", "help", "version"];
41844
+ var COMMANDS = ["pay", "quote", "status", "receipt", "resume", "help", "version"];
41774
41845
  var CHAIN_ALIASES = {
41775
41846
  ethereum: "1",
41776
41847
  eth: "1",
@@ -41998,6 +42069,18 @@ function parseCliArgs(argv) {
41998
42069
  }
41999
42070
  return { command, target, json, provider: flags.provider };
42000
42071
  }
42072
+ if (command === "resume") {
42073
+ if (!target) {
42074
+ throw new CliError("MISSING_TARGET", "Usage: rozo-checkout resume <rozoPaymentId>");
42075
+ }
42076
+ if (!isRozoPaymentId(target)) {
42077
+ throw new CliError(
42078
+ "BAD_VALUE",
42079
+ "resume takes the order id (rozoPaymentId, a UUID) that pay printed, not a payment link."
42080
+ );
42081
+ }
42082
+ return { command, target, json, yes: flags.yes === true };
42083
+ }
42001
42084
  if (command === "status") {
42002
42085
  if (!target) {
42003
42086
  throw new CliError(
@@ -42014,7 +42097,9 @@ function parseCliArgs(argv) {
42014
42097
  json,
42015
42098
  provider: flags.provider,
42016
42099
  watch: flags.watch === true,
42017
- timeout: flags.timeout === void 0 ? 600 : Number(flags.timeout)
42100
+ // Left undefined when absent so status can size the window itself:
42101
+ // 600s, or a Lightning invoice's remaining validity (max 60 min).
42102
+ timeout: flags.timeout === void 0 ? void 0 : Number(flags.timeout)
42018
42103
  };
42019
42104
  }
42020
42105
  const bitrefillFlags = ["bitrefill-invoice", "to", "amount", "expires-at"].filter(
@@ -42097,6 +42182,7 @@ function parseCliArgs(argv) {
42097
42182
  dryRun: flags["dry-run"] === true,
42098
42183
  watch: flags["no-watch"] !== true,
42099
42184
  timeout,
42185
+ timeoutExplicit: flags.timeout !== void 0,
42100
42186
  rpc: flags.rpc,
42101
42187
  payer: flags.payer,
42102
42188
  fresh: flags.fresh === true,
@@ -42116,6 +42202,7 @@ USAGE
42116
42202
  npx @rozoai/checkout quote <coinbase-link>
42117
42203
  npx @rozoai/checkout status <rozoPaymentId | coinbase-link>
42118
42204
  npx @rozoai/checkout receipt <rozoPaymentId | coinbase-link>
42205
+ npx @rozoai/checkout resume <rozoPaymentId>
42119
42206
 
42120
42207
  COINS (--with)
42121
42208
  usdt-solana usdc-solana usdt-bnb usdc-bnb
@@ -42143,7 +42230,8 @@ OPTIONS
42143
42230
  --dry-run with --send, show what would be signed and sign nothing
42144
42231
  --json, -j machine-readable output
42145
42232
  --no-watch stop after showing the deposit instructions
42146
- --timeout <s> how long to poll for settlement (default 900)
42233
+ --timeout <s> how long to poll for settlement (default 900; for Lightning,
42234
+ the invoice's remaining validity, at most 60 minutes)
42147
42235
  --bitrefill-invoice <id> pay a Bitrefill invoice created with "USDC on Base"
42148
42236
  instead of a Coinbase link. Needs --to <0x\u2026 address Bitrefill
42149
42237
  shows>, --amount <exact USDC amount> and --expires-at <ISO>
@@ -42180,6 +42268,20 @@ RECEIPT
42180
42268
  nextAction. The older "success" field means the query worked, not that the
42181
42269
  invoice is paid.
42182
42270
 
42271
+ RESUME
42272
+ resume picks up an order you created earlier and did not pay yet. It reads
42273
+ the order (nothing is created or sent), and if it is still unpaid and has
42274
+ enough time left it prints the hosted payment page and, after you confirm
42275
+ (or with --yes), the exact deposit instructions again. A paid order is
42276
+ reported as paid (exit 0); an expired one says so and how to start over.
42277
+
42278
+ PRIVACY
42279
+ Each order carries an anonymous install id: a random UUID created on first
42280
+ run and stored in ~/.rozo-checkout/prefs.json. If OPENROUTER_API_KEY is set,
42281
+ a one-way salted sha256 hash of it is sent too; the key itself is never
42282
+ sent, logged or stored. Delete prefs.json
42283
+ to reset the id; set ROZO_CHECKOUT_ANON_ID=off to send neither.
42284
+
42183
42285
  Creating an order moves no money; an unfunded order simply expires. Nothing is
42184
42286
  paid until you confirm, and --send is required before anything is signed.
42185
42287
 
@@ -42601,7 +42703,42 @@ function prefsPath() {
42601
42703
  const root = process.env.ROZO_CHECKOUT_STATE_DIR ? path2.dirname(stateRoot()) : path2.join(os2.homedir(), ".rozo-checkout");
42602
42704
  return path2.join(root, "prefs.json");
42603
42705
  }
42604
- var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "updatedAt"];
42706
+ var ALLOWED = ["lastPayerAddress", "lastAddressFamily", "lastPreset", "installId", "updatedAt"];
42707
+ var INSTALL_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
42708
+ var LOCK_WAIT_MS2 = 2e3;
42709
+ var LOCK_STALE_MS2 = 1e4;
42710
+ function sleepSync2(ms) {
42711
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
42712
+ }
42713
+ function withPrefsLock(fn) {
42714
+ const lock = `${prefsPath()}.lock`;
42715
+ fs2.mkdirSync(path2.dirname(lock), { recursive: true, mode: 448 });
42716
+ const deadline = Date.now() + LOCK_WAIT_MS2;
42717
+ for (; ; ) {
42718
+ try {
42719
+ fs2.closeSync(fs2.openSync(lock, "wx", 384));
42720
+ break;
42721
+ } catch (err) {
42722
+ if (err.code !== "EEXIST") throw err;
42723
+ }
42724
+ try {
42725
+ if (Date.now() - fs2.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
42726
+ fs2.rmSync(lock, { force: true });
42727
+ continue;
42728
+ }
42729
+ } catch {
42730
+ continue;
42731
+ }
42732
+ if (Date.now() > deadline) return void 0;
42733
+ sleepSync2(10);
42734
+ }
42735
+ try {
42736
+ return fn();
42737
+ } finally {
42738
+ fs2.rmSync(lock, { force: true });
42739
+ }
42740
+ }
42741
+ var USER_FIELDS = ["lastPayerAddress", "lastAddressFamily", "lastPreset"];
42605
42742
  function readPrefs() {
42606
42743
  let raw;
42607
42744
  try {
@@ -42623,21 +42760,26 @@ function readPrefs() {
42623
42760
  }
42624
42761
  return Object.keys(out2).length ? out2 : null;
42625
42762
  }
42626
- function savePrefs(update) {
42627
- const existing = readPrefs() || {};
42628
- const next = { ...existing };
42629
- for (const k of ALLOWED) {
42630
- if (k === "updatedAt") continue;
42631
- const v = update?.[k];
42632
- if (typeof v === "string" && v.trim()) next[k] = v.trim();
42633
- }
42634
- next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
42763
+ function savePrefs(update, { installIdIfAbsent } = {}) {
42635
42764
  try {
42636
- writeAtomic(prefsPath(), next);
42765
+ const saved = withPrefsLock(() => {
42766
+ const existing = readPrefs() || {};
42767
+ const next = { ...existing };
42768
+ for (const k of USER_FIELDS) {
42769
+ const v = update?.[k];
42770
+ if (typeof v === "string" && v.trim()) next[k] = v.trim();
42771
+ }
42772
+ if (!INSTALL_ID_RE.test(String(existing.installId ?? "")) && INSTALL_ID_RE.test(String(installIdIfAbsent ?? ""))) {
42773
+ next.installId = installIdIfAbsent;
42774
+ }
42775
+ next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
42776
+ writeAtomic(prefsPath(), next);
42777
+ return next;
42778
+ });
42779
+ return saved ?? null;
42637
42780
  } catch {
42638
42781
  return null;
42639
42782
  }
42640
- return next;
42641
42783
  }
42642
42784
 
42643
42785
  // scripts/src/lib/blacklist.mjs
@@ -42763,59 +42905,6 @@ function assertNotBlacklisted(targets, blacklist) {
42763
42905
  return true;
42764
42906
  }
42765
42907
 
42766
- // scripts/src/lib/ids.mjs
42767
- var IdError = class extends Error {
42768
- constructor(code, message) {
42769
- super(message);
42770
- this.code = code;
42771
- }
42772
- };
42773
- var PL_RE = /(pl_[0-9a-zA-Z]+)/;
42774
- var SESSION_RE = /(paymentSession_[A-Za-z0-9_-]+)/;
42775
- var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
42776
- function extractLinkId(urlOrId) {
42777
- if (typeof urlOrId !== "string" || !urlOrId.trim()) {
42778
- throw new IdError("BAD_LINK", "A Coinbase payment link URL or id is required.");
42779
- }
42780
- const s = urlOrId.trim();
42781
- const session = s.match(SESSION_RE);
42782
- if (session) return { linkId: session[1], kind: "payment_session" };
42783
- const link = s.match(PL_RE);
42784
- if (link) return { linkId: link[1], kind: "payment_link" };
42785
- if (/commerce\.coinbase\.com\/pay\//.test(s)) {
42786
- throw new IdError(
42787
- "LEGACY_COMMERCE_URL",
42788
- "commerce.coinbase.com/pay/<uuid> uses the legacy protocol; this skill does not handle it."
42789
- );
42790
- }
42791
- throw new IdError(
42792
- "BAD_LINK",
42793
- "Could not find a `pl_*` or `paymentSession_*` id in the supplied value."
42794
- );
42795
- }
42796
- function assertRozoPaymentId(value) {
42797
- const s = String(value || "").trim();
42798
- if (!UUID_RE.test(s)) {
42799
- throw new IdError("BAD_ROZO_PAYMENT_ID", "rozoPaymentId must be a UUID.");
42800
- }
42801
- return s;
42802
- }
42803
- function isRozoPaymentId(value) {
42804
- return UUID_RE.test(String(value || "").trim());
42805
- }
42806
- function maskAddress2(address) {
42807
- const s = String(address ?? "").trim();
42808
- if (!s) return "(none)";
42809
- if (s.length <= 12) return s;
42810
- return `${s.slice(0, 6)}...${s.slice(-4)}`;
42811
- }
42812
- function maskMemo(memo) {
42813
- const s = String(memo ?? "").trim();
42814
- if (!s) return null;
42815
- if (s.length <= 8) return s;
42816
- return `${s.slice(0, 4)}...${s.slice(-3)}`;
42817
- }
42818
-
42819
42908
  // scripts/src/lib/expiry.mjs
42820
42909
  var MINUTE = 6e4;
42821
42910
  var MARGINS_MS = {
@@ -42829,6 +42918,25 @@ var MARGINS_MS = {
42829
42918
  };
42830
42919
  var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
42831
42920
  var DEFAULT_MARGIN_MS = 10 * MINUTE;
42921
+ var DEFAULT_WATCH_MS = 10 * MINUTE;
42922
+ var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
42923
+ function lightningWatchMs(expiresAt, { now = Date.now(), fallbackMs = DEFAULT_WATCH_MS } = {}) {
42924
+ const at = parseDeadline(expiresAt);
42925
+ if (at === null) return fallbackMs;
42926
+ const remaining = at - now;
42927
+ if (!(remaining > 0)) return fallbackMs;
42928
+ return Math.min(remaining, LIGHTNING_WATCH_CAP_MS);
42929
+ }
42930
+ function settlementWatchSeconds(opts, deposit, now = Date.now()) {
42931
+ const fallbackMs = Number(opts.timeout ?? 900) * 1e3;
42932
+ if (opts.timeoutExplicit || !deposit?.lnInvoice) return Math.round(fallbackMs / 1e3);
42933
+ return Math.ceil(lightningWatchMs(deposit.expiresAt, { now, fallbackMs }) / 1e3);
42934
+ }
42935
+ function minutesRemaining(expiresAt, now = Date.now()) {
42936
+ const at = parseDeadline(expiresAt);
42937
+ if (at === null) return null;
42938
+ return Math.max(0, Math.floor((at - now) / MINUTE));
42939
+ }
42832
42940
  function formatRemaining(ms) {
42833
42941
  if (!Number.isFinite(ms)) return "unknown";
42834
42942
  if (ms <= 0) return "expired";
@@ -54532,18 +54640,66 @@ function displayPath(p) {
54532
54640
  return s.startsWith(home + path6.sep) ? `~${s.slice(home.length)}` : s;
54533
54641
  }
54534
54642
 
54535
- // scripts/src/lib/api.mjs
54643
+ // scripts/src/lib/version.mjs
54536
54644
  import { createRequire } from "node:module";
54645
+ var PKG_VERSION = (() => {
54646
+ const requireFrom = createRequire(import.meta.url);
54647
+ for (const candidate of ["../../package.json", "../../../package.json"]) {
54648
+ try {
54649
+ const pkg = requireFrom(candidate);
54650
+ if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
54651
+ } catch {
54652
+ }
54653
+ }
54654
+ return "0.0.0";
54655
+ })();
54537
54656
 
54538
54657
  // scripts/src/lib/http.mjs
54539
54658
  var DEFAULT_TIMEOUT_MS = 2e4;
54540
- var USER_AGENT = "rozo-checkout-skill/1.0";
54541
- async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
54659
+ var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
54660
+ var MAX_RETRIES = 3;
54661
+ var MAX_TOTAL_WAIT_MS = 12e4;
54662
+ var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
54663
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
54664
+ function parseRetryAfter(value, now = Date.now()) {
54665
+ if (value === null || value === void 0) return null;
54666
+ const s = String(value).trim();
54667
+ if (!s) return null;
54668
+ if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
54669
+ const at = Date.parse(s);
54670
+ if (!Number.isFinite(at)) return null;
54671
+ return Math.max(0, at - now);
54672
+ }
54673
+ function bodyRetryAfterMs(json) {
54674
+ const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
54675
+ if (raw === null || raw === void 0 || raw === "") return null;
54676
+ const n = Number(raw);
54677
+ return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
54678
+ }
54679
+ function rateLimitInfo(res) {
54680
+ const pick = (name) => res.headers?.get?.(name) ?? null;
54681
+ const info = {
54682
+ limit: pick("x-ratelimit-limit"),
54683
+ remaining: pick("x-ratelimit-remaining"),
54684
+ tier: pick("x-ratelimit-tier"),
54685
+ scope: pick("x-ratelimit-scope")
54686
+ };
54687
+ const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
54688
+ return Object.keys(present).length ? present : null;
54689
+ }
54690
+ function parseBody(text) {
54691
+ if (!text) return null;
54692
+ try {
54693
+ return JSON.parse(text);
54694
+ } catch {
54695
+ return null;
54696
+ }
54697
+ }
54698
+ async function fetchOnce(method, url, body, timeoutMs) {
54542
54699
  const controller = new AbortController();
54543
54700
  const timer = setTimeout(() => controller.abort(), timeoutMs);
54544
- let res;
54545
54701
  try {
54546
- res = await fetch(url, {
54702
+ const res = await fetch(url, {
54547
54703
  method,
54548
54704
  headers: {
54549
54705
  accept: "application/json",
@@ -54553,6 +54709,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
54553
54709
  body: body !== void 0 ? JSON.stringify(body) : void 0,
54554
54710
  signal: controller.signal
54555
54711
  });
54712
+ const text = await res.text();
54713
+ return { res, text };
54556
54714
  } catch (err) {
54557
54715
  throw new SkillError(
54558
54716
  err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
@@ -54562,31 +54720,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
54562
54720
  } finally {
54563
54721
  clearTimeout(timer);
54564
54722
  }
54565
- const text = await res.text();
54566
- let json = null;
54567
- if (text) {
54568
- try {
54569
- json = JSON.parse(text);
54570
- } catch {
54571
- json = null;
54723
+ }
54724
+ async function request(method, url, {
54725
+ body,
54726
+ timeoutMs = DEFAULT_TIMEOUT_MS,
54727
+ sleep: sleep2 = defaultSleep,
54728
+ now = Date.now,
54729
+ maxTotalWaitMs = MAX_TOTAL_WAIT_MS
54730
+ } = {}) {
54731
+ let waitedMs = 0;
54732
+ let retries = 0;
54733
+ for (; ; ) {
54734
+ const { res, text } = await fetchOnce(method, url, body, timeoutMs);
54735
+ const json = parseBody(text);
54736
+ if (res.ok) {
54737
+ if (json === null) {
54738
+ throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
54739
+ url: redactUrl(url),
54740
+ snippet: redact(text).slice(0, 400)
54741
+ });
54742
+ }
54743
+ return json;
54744
+ }
54745
+ const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
54746
+ const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
54747
+ const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
54748
+ if (retryable && retries < MAX_RETRIES) {
54749
+ const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
54750
+ if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
54751
+ retries += 1;
54752
+ waitedMs += waitMs;
54753
+ await sleep2(waitMs);
54754
+ continue;
54755
+ }
54572
54756
  }
54573
- }
54574
- if (!res.ok) {
54575
54757
  const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
54576
54758
  const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
54759
+ const rateLimit = rateLimitInfo(res);
54577
54760
  throw new SkillError(code, redact(String(message)), {
54578
54761
  httpStatus: res.status,
54579
54762
  url: redactUrl(url),
54580
- body: json ?? redact(text).slice(0, 800)
54581
- });
54582
- }
54583
- if (json === null) {
54584
- throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
54585
- url: redactUrl(url),
54586
- snippet: redact(text).slice(0, 400)
54763
+ body: json ?? redact(text).slice(0, 800),
54764
+ ...retryable ? {
54765
+ retries,
54766
+ retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
54767
+ } : {},
54768
+ ...rateLimit ? { rateLimit } : {}
54587
54769
  });
54588
54770
  }
54589
- return json;
54590
54771
  }
54591
54772
  function redactUrl(url) {
54592
54773
  try {
@@ -54603,31 +54784,65 @@ function postJson(url, body, opts) {
54603
54784
  return request("POST", url, { ...opts, body });
54604
54785
  }
54605
54786
 
54606
- // scripts/src/lib/api.mjs
54607
- var PKG_VERSION = (() => {
54608
- const requireFrom = createRequire(import.meta.url);
54609
- for (const candidate of ["../../package.json", "../../../package.json"]) {
54610
- try {
54611
- const pkg = requireFrom(candidate);
54612
- if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
54613
- } catch {
54614
- }
54787
+ // scripts/src/lib/identity.mjs
54788
+ import crypto7 from "node:crypto";
54789
+ var ACCOUNT_HASH_PREFIX = "rozo-acct-v1:";
54790
+ function isValidInstallId(value) {
54791
+ return typeof value === "string" && INSTALL_ID_RE.test(value);
54792
+ }
54793
+ function attributionDisabled(env = process.env) {
54794
+ const v = String(env.ROZO_CHECKOUT_ANON_ID ?? "").trim().toLowerCase();
54795
+ return ["off", "0", "false", "no"].includes(v);
54796
+ }
54797
+ function getOrCreateInstallId() {
54798
+ const existing = readPrefs()?.installId;
54799
+ if (isValidInstallId(existing)) return existing;
54800
+ const saved = savePrefs({}, { installIdIfAbsent: crypto7.randomUUID().toLowerCase() });
54801
+ if (isValidInstallId(saved?.installId)) return saved.installId;
54802
+ const onDisk = readPrefs()?.installId;
54803
+ return isValidInstallId(onDisk) ? onDisk : crypto7.randomUUID().toLowerCase();
54804
+ }
54805
+ function hashAccount(raw) {
54806
+ return crypto7.createHash("sha256").update(`${ACCOUNT_HASH_PREFIX}${raw}`, "utf8").digest("hex");
54807
+ }
54808
+ function rawApiKey(env) {
54809
+ const v = typeof env.OPENROUTER_API_KEY === "string" ? env.OPENROUTER_API_KEY.trim() : "";
54810
+ return v && v.length <= 1024 ? v : null;
54811
+ }
54812
+ function attributionIdentity(env = process.env) {
54813
+ if (attributionDisabled(env)) return {};
54814
+ const out2 = {};
54815
+ try {
54816
+ const id = getOrCreateInstallId();
54817
+ if (isValidInstallId(id)) out2.install_id = id;
54818
+ } catch {
54615
54819
  }
54616
- return "0.0.0";
54617
- })();
54820
+ const raw = rawApiKey(env);
54821
+ if (raw) out2.account_hash = hashAccount(raw);
54822
+ return out2;
54823
+ }
54824
+
54825
+ // scripts/src/lib/api.mjs
54618
54826
  var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
54619
54827
  var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
54620
54828
  var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
54621
- function buildAttribution({ utmSource } = {}) {
54829
+ function buildAttribution({ utmSource, identity } = {}) {
54622
54830
  const raw = utmSource !== void 0 ? utmSource : process.env.ROZO_CHECKOUT_UTM_SOURCE;
54623
54831
  const utm_source = normalizeUtmSource(raw);
54624
- return { client: ATTRIBUTION_CLIENT, ...utm_source ? { utm_source } : {} };
54832
+ const ids = identity ?? attributionIdentity();
54833
+ return {
54834
+ client: ATTRIBUTION_CLIENT,
54835
+ ...utm_source ? { utm_source } : {},
54836
+ ...ids.install_id ? { install_id: ids.install_id } : {},
54837
+ ...ids.account_hash ? { account_hash: ids.account_hash } : {}
54838
+ };
54625
54839
  }
54626
54840
  var INTENTS_BASE = process.env.ROZO_CHECKOUT_INTENTS_BASE || "https://intentapiv4.rozo.ai/functions/v1/payment-api";
54627
54841
  async function quoteInvoice({ url, linkId }) {
54628
54842
  const body = url ? { url } : { payment_id: linkId };
54629
54843
  return postJson(`${MPP_BASE}/quote-invoice`, body);
54630
54844
  }
54845
+ var QUOTE_RECEIPT_RETRY_BUDGET_MS = 2e4;
54631
54846
  async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, email }) {
54632
54847
  const body = {
54633
54848
  ...url ? { url } : { payment_id: linkId },
@@ -54639,7 +54854,11 @@ async function createInvoice({ url, linkId, source, quoteReceipt, utmSource, ema
54639
54854
  // sent empty, when the user gave none.
54640
54855
  ...email ? { email } : {}
54641
54856
  };
54642
- return postJson(`${MPP_BASE}/create-invoice`, body);
54857
+ return postJson(
54858
+ `${MPP_BASE}/create-invoice`,
54859
+ body,
54860
+ quoteReceipt ? { maxTotalWaitMs: QUOTE_RECEIPT_RETRY_BUDGET_MS } : void 0
54861
+ );
54643
54862
  }
54644
54863
  async function createBitrefillInvoice({ invoice, source, utmSource, email }) {
54645
54864
  const body = {
@@ -56168,7 +56387,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
56168
56387
  intentExpiresAt: payment?.expiresAt ?? deadline,
56169
56388
  coinbaseExpiry: deadline
56170
56389
  });
56171
- return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4) } : { ok: false, code: expiry2.code, reason: expiry2.reason };
56390
+ return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4), deadlineMs: expiry2.effectiveDeadlineMs } : { ok: false, code: expiry2.code, reason: expiry2.reason };
56172
56391
  }
56173
56392
  if (!status) {
56174
56393
  return { ok: false, code: "LINK_PAYABILITY_UNKNOWN", reason: "The Coinbase link state could not be read." };
@@ -56181,7 +56400,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
56181
56400
  intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
56182
56401
  coinbaseExpiry: status?.coinbase?.preApprovalExpiry
56183
56402
  });
56184
- return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4) } : { ok: false, code: expiry.code, reason: expiry.reason };
56403
+ return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4), deadlineMs: expiry.effectiveDeadlineMs } : { ok: false, code: expiry.code, reason: expiry.reason };
56185
56404
  }
56186
56405
  async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
56187
56406
  let provider = resolveProvider(explicitProvider, rozoPaymentId);
@@ -56324,6 +56543,16 @@ function statusGuidance(result) {
56324
56543
  if (result.state === "expired_unfunded") return EXPIRED_UNFUNDED_GUIDANCE;
56325
56544
  return result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
56326
56545
  }
56546
+ function watchTimeoutMs(timeoutArg, result, now = Date.now()) {
56547
+ if (timeoutArg !== void 0 && timeoutArg !== null && timeoutArg !== true) {
56548
+ const n = Number(timeoutArg);
56549
+ if (Number.isFinite(n)) return Math.max(0, n * 1e3);
56550
+ }
56551
+ if (result?.payin?.chain === chainName("lightning")) {
56552
+ return lightningWatchMs(result?.expiry?.expiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
56553
+ }
56554
+ return DEFAULT_WATCH_MS;
56555
+ }
56327
56556
  async function main4(argv) {
56328
56557
  const args = parseArgs(argv);
56329
56558
  const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
@@ -56339,9 +56568,9 @@ async function main4(argv) {
56339
56568
  usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
56340
56569
  }
56341
56570
  const watch = Boolean(args.watch);
56342
- const timeoutMs = Math.max(0, Number(args.timeout ?? 600) * 1e3);
56343
- const deadline = Date.now() + timeoutMs;
56571
+ const started = Date.now();
56344
56572
  let result = await snapshot({ rozoPaymentId, linkId, provider });
56573
+ const deadline = started + watchTimeoutMs(args.timeout, result, started);
56345
56574
  const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
56346
56575
  while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
56347
56576
  await sleep(POLL_INTERVAL_MS);
@@ -56411,6 +56640,241 @@ async function run5(argv = process.argv.slice(2)) {
56411
56640
  return main5(argv);
56412
56641
  }
56413
56642
 
56643
+ // scripts/src/resume.mjs
56644
+ var PAID_STATES = /* @__PURE__ */ new Set([
56645
+ "payin_detected",
56646
+ "payin_confirmed",
56647
+ "bridging",
56648
+ "paying_coinbase",
56649
+ "settled"
56650
+ ]);
56651
+ var EXPIRED_STATES = /* @__PURE__ */ new Set(["expired_unfunded", "invoice_expired"]);
56652
+ function localPaymentLink(id) {
56653
+ try {
56654
+ return readState(id)?.paymentLink ?? null;
56655
+ } catch {
56656
+ return null;
56657
+ }
56658
+ }
56659
+ function resumeVerdict(snap) {
56660
+ const base = {
56661
+ rozoPaymentId: snap.rozoPaymentId,
56662
+ linkId: snap.linkId ?? null,
56663
+ provider: snap.provider,
56664
+ state: snap.state,
56665
+ moneyDetected: Boolean(snap.moneyDetected)
56666
+ };
56667
+ if (snap.escalate) {
56668
+ return { ...base, resumable: false, outcome: "needs_attention", code: "ORDER_NEEDS_ATTENTION", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
56669
+ }
56670
+ if (snap.unknown || !snap.authoritativeView) {
56671
+ return { ...base, resumable: false, outcome: "unknown", code: "ORDER_STATE_UNKNOWN", exitCode: EXIT_ERROR, message: statusGuidance(snap) };
56672
+ }
56673
+ if (snap.moneyDetected || PAID_STATES.has(snap.state)) {
56674
+ const done = snap.state === "settled";
56675
+ return {
56676
+ ...base,
56677
+ resumable: false,
56678
+ outcome: done ? "paid" : "paid_in_progress",
56679
+ code: "ORDER_ALREADY_PAID",
56680
+ exitCode: EXIT_OK,
56681
+ message: done ? "This order is already paid and settled. Nothing to resume. Do not pay again." : `A payment for this order was already detected (state: ${snap.state}). Do NOT pay again. Follow it with: rozo-checkout status ${snap.rozoPaymentId} --watch`
56682
+ };
56683
+ }
56684
+ if (EXPIRED_STATES.has(snap.state)) {
56685
+ return {
56686
+ ...base,
56687
+ resumable: false,
56688
+ outcome: "expired",
56689
+ code: "ORDER_EXPIRED",
56690
+ exitCode: EXIT_ERROR,
56691
+ message: snap.provider === "bitrefill" ? "This order expired before any funds arrived. Nothing was lost. Create a fresh Bitrefill invoice and run pay again." : EXPIRED_UNFUNDED_GUIDANCE
56692
+ };
56693
+ }
56694
+ if (snap.localSend) {
56695
+ return {
56696
+ ...base,
56697
+ resumable: false,
56698
+ outcome: "sent_locally",
56699
+ code: "ALREADY_SENT",
56700
+ exitCode: EXIT_ERROR,
56701
+ message: `This machine already recorded a send for this order (${snap.localSend.status}). Do NOT pay again. Check it with: rozo-checkout status ${snap.rozoPaymentId}`
56702
+ };
56703
+ }
56704
+ if (snap.state !== "awaiting_deposit") {
56705
+ return {
56706
+ ...base,
56707
+ resumable: false,
56708
+ outcome: "not_payable",
56709
+ code: "ORDER_NOT_RESUMABLE",
56710
+ exitCode: EXIT_ERROR,
56711
+ message: `This order is in state "${snap.state}" and cannot be paid now. ${statusGuidance(snap)}`
56712
+ };
56713
+ }
56714
+ if (!snap.sendWindow?.ok) {
56715
+ return {
56716
+ ...base,
56717
+ resumable: false,
56718
+ outcome: "expired",
56719
+ code: snap.sendWindow?.code ?? "SEND_WINDOW_CLOSED",
56720
+ exitCode: EXIT_ERROR,
56721
+ message: `${snap.sendWindow?.reason ?? "Not enough time is left to pay this order safely."} Do not fund it. Let it expire unfunded, then create a new order for the same link.`
56722
+ };
56723
+ }
56724
+ return { ...base, resumable: true, outcome: "awaiting_deposit", code: null, exitCode: EXIT_OK, message: null };
56725
+ }
56726
+ async function main6(argv) {
56727
+ const args = parseArgs(argv);
56728
+ const rozoPaymentId = args["rozo-payment-id"] || args._[0];
56729
+ if (!rozoPaymentId || rozoPaymentId === true) usage("Required: <rozoPaymentId> (the order id printed by pay)");
56730
+ if (!isRozoPaymentId(String(rozoPaymentId))) {
56731
+ usage("resume takes the order id (rozoPaymentId, a UUID) printed by pay, not a payment link.");
56732
+ }
56733
+ const id = String(rozoPaymentId);
56734
+ const confirmed = Boolean(args.confirm);
56735
+ let blacklist;
56736
+ try {
56737
+ blacklist = loadBlacklist();
56738
+ } catch (err) {
56739
+ throw new SkillError("BLACKLIST_UNAVAILABLE", `Compromised-address list unusable: ${err.message} Refusing to proceed.`);
56740
+ }
56741
+ const snap = await snapshot({ rozoPaymentId: id });
56742
+ const verdict = resumeVerdict(snap);
56743
+ if (!verdict.resumable) {
56744
+ emit(
56745
+ {
56746
+ success: verdict.exitCode === EXIT_OK,
56747
+ step: "resume",
56748
+ resumable: false,
56749
+ outcome: verdict.outcome,
56750
+ rozoPaymentId: id,
56751
+ linkId: verdict.linkId,
56752
+ state: verdict.state,
56753
+ moneyDetected: verdict.moneyDetected,
56754
+ ...verdict.exitCode === EXIT_OK ? { message: verdict.message } : { error: { code: verdict.code, message: verdict.message } },
56755
+ payin: snap.payin,
56756
+ ...verdict.exitCode === EXIT_OK ? {} : { support: SUPPORT }
56757
+ },
56758
+ verdict.exitCode
56759
+ );
56760
+ }
56761
+ const payment = await getPayment(id);
56762
+ const source = payment?.source || {};
56763
+ const guard = reuseGuard({ payment, requested: { chainId: source.chainId, tokenSymbol: source.tokenSymbol }, reused: true });
56764
+ if (!guard.ok) {
56765
+ emit(
56766
+ {
56767
+ success: false,
56768
+ step: "resume",
56769
+ resumable: false,
56770
+ outcome: guard.moneyDetected ? "paid_in_progress" : "not_payable",
56771
+ rozoPaymentId: id,
56772
+ moneyDetected: guard.moneyDetected,
56773
+ error: { code: guard.code, message: guard.reason },
56774
+ guidance: guard.moneyDetected ? "A payment for this order was detected. Do NOT pay again." : "This order cannot be paid as it stands. Do not fund it.",
56775
+ support: SUPPORT
56776
+ },
56777
+ EXIT_ERROR
56778
+ );
56779
+ }
56780
+ const finalWindow = checkExpiry({
56781
+ now: Date.now(),
56782
+ chainId: source.chainId,
56783
+ intentExpiresAt: payment?.expiresAt ?? snap.sendWindow?.deadlineMs,
56784
+ coinbaseExpiry: snap.sendWindow?.deadlineMs
56785
+ });
56786
+ if (!finalWindow.ok) {
56787
+ emit(
56788
+ {
56789
+ success: false,
56790
+ step: "resume",
56791
+ resumable: false,
56792
+ outcome: "expired",
56793
+ rozoPaymentId: id,
56794
+ moneyDetected: false,
56795
+ error: { code: finalWindow.code, message: finalWindow.reason },
56796
+ guidance: "Not enough time is left to pay this order safely. Do not fund it; let it expire unfunded and create a new order.",
56797
+ support: SUPPORT
56798
+ },
56799
+ EXIT_ERROR
56800
+ );
56801
+ }
56802
+ const lightning = String(source.chainId) === "lightning";
56803
+ if (!lightning) {
56804
+ try {
56805
+ assertNotBlacklisted(
56806
+ [{ address: source.receiverAddress, family: chainFamily(source.chainId), role: "deposit address" }],
56807
+ blacklist
56808
+ );
56809
+ } catch (err) {
56810
+ emit(
56811
+ {
56812
+ success: false,
56813
+ step: "resume",
56814
+ resumable: false,
56815
+ rozoPaymentId: id,
56816
+ error: { code: err.code, message: err.message },
56817
+ guidance: "Do NOT send anything. Report this to the operator immediately.",
56818
+ support: SUPPORT
56819
+ },
56820
+ EXIT_ERROR
56821
+ );
56822
+ }
56823
+ }
56824
+ const expiresAt = payment?.expiresAt ?? snap.expiry?.expiresAt ?? null;
56825
+ const msRemaining = expiresAt ? Date.parse(expiresAt) - Date.now() : NaN;
56826
+ const expiresIn = Number.isFinite(msRemaining) ? formatRemaining(msRemaining) : null;
56827
+ const paymentLink = payment?.paymentLink ?? localPaymentLink(id);
56828
+ const bolt11 = source.lnInvoice ?? payment?.lnInvoice ?? null;
56829
+ emit({
56830
+ success: true,
56831
+ step: "resume",
56832
+ resumable: true,
56833
+ outcome: "awaiting_deposit",
56834
+ confirmed,
56835
+ rozoPaymentId: id,
56836
+ linkId: snap.linkId ?? payment?.orderId ?? null,
56837
+ provider: snap.provider,
56838
+ state: snap.state,
56839
+ // Hosted pay page for this order: a human can open it and pay from there.
56840
+ // Shown in full only for Rozo's own pay-page hosts; anything else is
56841
+ // reduced to its host by the normal output redaction.
56842
+ paymentLink: publicPayPage(paymentLink),
56843
+ deposit: confirmed ? {
56844
+ chainId: source.chainId,
56845
+ chain: chainName(source.chainId),
56846
+ tokenSymbol: source.tokenSymbol,
56847
+ tokenAddress: source.tokenAddress || null,
56848
+ receiverAddress: lightning ? null : source.receiverAddress,
56849
+ receiverMemo: source.receiverMemo ?? null,
56850
+ receiverMemoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null,
56851
+ amount: source.amount,
56852
+ amountUnit: source.amountUnit ?? null,
56853
+ isSats: isSatsUnit(source.amountUnit),
56854
+ lnInvoice: bolt11 || null,
56855
+ payTo: guard.deposit.payTo,
56856
+ expiresAt,
56857
+ expiresIn
56858
+ } : null,
56859
+ depositWithheld: !confirmed,
56860
+ display: {
56861
+ chain: chainName(source.chainId),
56862
+ token: source.tokenSymbol,
56863
+ amount: formatAmount(source),
56864
+ isSats: isSatsUnit(source.amountUnit),
56865
+ payToMasked: maskAddress2(guard.deposit.payTo),
56866
+ receiverMemoMasked: maskMemo(source.receiverMemo),
56867
+ hasMemo: Boolean(source.receiverMemo),
56868
+ memoType: source.receiverMemo ? STELLAR_MEMO_TYPE : null
56869
+ },
56870
+ expiry: { expiresAt, expiresIn, minutesOfSlack: snap.sendWindow?.minutesOfSlack ?? null },
56871
+ 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."
56872
+ });
56873
+ }
56874
+ async function run6(argv = process.argv.slice(2)) {
56875
+ return main6(argv);
56876
+ }
56877
+
56414
56878
  // scripts/src/lib/passphrase.mjs
56415
56879
  import readline from "node:readline";
56416
56880
  function promptPassphrase(prompt = " Keystore passphrase: ") {
@@ -57127,7 +57591,7 @@ function chainDef(chainId, rpcUrl) {
57127
57591
  rpcUrls: { default: { http: [rpcUrl] } }
57128
57592
  });
57129
57593
  }
57130
- async function main6(argv) {
57594
+ async function main7(argv) {
57131
57595
  const args = parseArgs(argv);
57132
57596
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
57133
57597
  assertNoTrackedDotEnv();
@@ -57370,8 +57834,8 @@ async function main6(argv) {
57370
57834
  outcome.exitCode
57371
57835
  );
57372
57836
  }
57373
- async function run6(argv = process.argv.slice(2)) {
57374
- return main6(argv);
57837
+ async function run7(argv = process.argv.slice(2)) {
57838
+ return main7(argv);
57375
57839
  }
57376
57840
 
57377
57841
  // scripts/src/send-sol.mjs
@@ -57441,7 +57905,7 @@ function createTransferCheckedInstruction(source, mint, destination, owner, amou
57441
57905
  var MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
57442
57906
  var MAINNET_GENESIS = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
57443
57907
  var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
57444
- async function main7(argv) {
57908
+ async function main8(argv) {
57445
57909
  const args = parseArgs(argv);
57446
57910
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
57447
57911
  assertNoTrackedDotEnv();
@@ -57740,8 +58204,8 @@ function base58Encode(buf) {
57740
58204
  }
57741
58205
  return out2;
57742
58206
  }
57743
- async function run7(argv = process.argv.slice(2)) {
57744
- return main7(argv);
58207
+ async function run8(argv = process.argv.slice(2)) {
58208
+ return main8(argv);
57745
58209
  }
57746
58210
 
57747
58211
  // scripts/src/cli.mjs
@@ -57780,6 +58244,9 @@ function printError(payload) {
57780
58244
  function printSupport() {
57781
58245
  out(` ${dim(SUPPORT_TEXT)}`);
57782
58246
  }
58247
+ function printBulkHint() {
58248
+ out(` ${dim(BULK_HINT_TEXT)}`);
58249
+ }
57783
58250
  function printMoneyWarning(payload) {
57784
58251
  if (!payload?.moneyDetected) return;
57785
58252
  out();
@@ -57917,7 +58384,8 @@ async function cmdQuote(opts) {
57917
58384
  return exitCode;
57918
58385
  }
57919
58386
  async function cmdStatus(opts) {
57920
- const argv = [...targetToArgs(opts.target), "--timeout", String(opts.timeout ?? 600)];
58387
+ const argv = [...targetToArgs(opts.target)];
58388
+ if (opts.timeout !== void 0) argv.push("--timeout", String(opts.timeout));
57921
58389
  if (opts.provider) argv.push("--provider", opts.provider);
57922
58390
  if (opts.watch) argv.push("--watch");
57923
58391
  if (!opts.json) out(dim(" Checking payment status\u2026 (no money moves)"));
@@ -57978,9 +58446,66 @@ async function cmdReceipt(opts) {
57978
58446
  out();
57979
58447
  out(` Next: ${r.nextAction.type === "contact_support" ? red(r.nextAction.message) : r.nextAction.message}`);
57980
58448
  if (payload.support) printSupport();
58449
+ if (r.paymentOutcome === "settled") printBulkHint();
57981
58450
  out();
57982
58451
  return exitCode;
57983
58452
  }
58453
+ async function cmdResume(opts) {
58454
+ if (!opts.json) out(dim(" Reading the order\u2026 (no money moves)"));
58455
+ const first = await step(run6, [opts.target]);
58456
+ let payload = first.payload;
58457
+ let exitCode = first.exitCode;
58458
+ if (payload.resumable && !opts.json) {
58459
+ out();
58460
+ out(` Order ${payload.rozoPaymentId} ${dim("(unpaid)")}`);
58461
+ out(` You send ${bold(payload.display?.amount)} on ${bold(payload.display?.chain)}`);
58462
+ out(` To ${payload.display?.payToMasked} ${dim("(full address shown after you confirm)")}`);
58463
+ if (payload.display?.hasMemo) {
58464
+ out(` Memo ${payload.display.receiverMemoMasked} ${dim(`(${payload.display.memoType})`)}`);
58465
+ }
58466
+ if (payload.expiry?.expiresIn) out(` Expires in ${bold(payload.expiry.expiresIn)}`);
58467
+ if (payload.paymentLink) out(` Pay page ${payload.paymentLink}`);
58468
+ out();
58469
+ }
58470
+ if (payload.resumable) {
58471
+ let go = opts.yes;
58472
+ if (!go && !opts.json && process.stdin.isTTY) {
58473
+ go = await askYesNo(` ${bold("Show the deposit details to pay this now?")} [y/N] `);
58474
+ }
58475
+ if (go) {
58476
+ const second = await step(run6, [opts.target, "--confirm"]);
58477
+ payload = second.payload;
58478
+ exitCode = second.exitCode;
58479
+ }
58480
+ }
58481
+ if (opts.json) {
58482
+ printJson(payload);
58483
+ return exitCode;
58484
+ }
58485
+ if (payload.error) {
58486
+ printError(payload);
58487
+ printMoneyWarning(payload);
58488
+ printSupport();
58489
+ out();
58490
+ return exitCode;
58491
+ }
58492
+ if (!payload.resumable) {
58493
+ out();
58494
+ out(` State ${stateLabel(payload.state)}`);
58495
+ out(` ${payload.message}`);
58496
+ out();
58497
+ return exitCode;
58498
+ }
58499
+ if (payload.deposit) {
58500
+ printDeposit(payload.deposit, opts);
58501
+ out(dim(` Then: rozo-checkout status ${payload.rozoPaymentId} --watch`));
58502
+ out();
58503
+ } else {
58504
+ out(dim(" Deposit details withheld. Open the pay page above, or re-run with --yes."));
58505
+ out();
58506
+ }
58507
+ return exitCode;
58508
+ }
57984
58509
  function outcomeLabel(outcome) {
57985
58510
  if (outcome === "settled") return green(bold(outcome));
57986
58511
  if (outcome === "needs_attention" || outcome === "unknown") return red(bold(outcome));
@@ -58171,7 +58696,7 @@ async function cmdPay(opts) {
58171
58696
  }
58172
58697
  if (opts.send) {
58173
58698
  const family = chainFamily(deposit.chainId);
58174
- const sender = family === "evm" ? run6 : family === "solana" ? run7 : null;
58699
+ const sender = family === "evm" ? run7 : family === "solana" ? run8 : null;
58175
58700
  if (!sender) {
58176
58701
  const payload = formatFailure({
58177
58702
  code: "SEND_UNSUPPORTED_CHAIN",
@@ -58232,7 +58757,7 @@ async function cmdPay(opts) {
58232
58757
  rozoPaymentId,
58233
58758
  "--watch",
58234
58759
  "--timeout",
58235
- String(opts.timeout)
58760
+ String(settlementWatchSeconds(opts, deposit))
58236
58761
  ]);
58237
58762
  if (opts.json) {
58238
58763
  printJson({
@@ -58252,6 +58777,7 @@ async function cmdPay(opts) {
58252
58777
  }
58253
58778
  printMoneyWarning(watched.payload);
58254
58779
  if (watched.payload.state !== "settled") printSupport();
58780
+ else printBulkHint();
58255
58781
  out();
58256
58782
  return watched.exitCode;
58257
58783
  }
@@ -58262,6 +58788,10 @@ function printDeposit(deposit, opts) {
58262
58788
  if (deposit.lnInvoice) {
58263
58789
  out(` Amount ${bold(`${deposit.amount} ${isSatsUnit(deposit.amountUnit) ? "sats" : deposit.tokenSymbol}`)}`);
58264
58790
  out(` BOLT11 ${deposit.lnInvoice}`);
58791
+ const validMin = minutesRemaining(deposit.expiresAt);
58792
+ if (validMin !== null) {
58793
+ out(` Valid ${bold(validMin < 1 ? "under 1 more minute" : `${validMin} more minutes`)}`);
58794
+ }
58265
58795
  } else {
58266
58796
  out(` Amount ${bold(`${deposit.amount} ${deposit.tokenSymbol}`)}`);
58267
58797
  out(` Chain ${bold(deposit.chain)}`);
@@ -58283,7 +58813,7 @@ function printDeposit(deposit, opts) {
58283
58813
  out(dim(" Send it exactly once."));
58284
58814
  out();
58285
58815
  }
58286
- async function main8() {
58816
+ async function main9() {
58287
58817
  let opts;
58288
58818
  try {
58289
58819
  opts = parseCliArgs(process.argv.slice(2));
@@ -58314,6 +58844,8 @@ async function main8() {
58314
58844
  return cmdStatus(opts);
58315
58845
  case "receipt":
58316
58846
  return cmdReceipt(opts);
58847
+ case "resume":
58848
+ return cmdResume(opts);
58317
58849
  case "pay":
58318
58850
  try {
58319
58851
  return await cmdPay(opts);
@@ -58334,7 +58866,7 @@ async function main8() {
58334
58866
  return EXIT_USAGE;
58335
58867
  }
58336
58868
  }
58337
- main8().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
58869
+ main9().then((code) => process.exit(code ?? EXIT_OK)).catch((err) => {
58338
58870
  const payload = formatFailure(err);
58339
58871
  if (process.argv.includes("--json")) printJson(payload);
58340
58872
  else {