@rozoai/checkout 0.1.5 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozoai/checkout",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -24789,14 +24789,14 @@ var require_node_gyp_build = __commonJS({
24789
24789
  "node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
24790
24790
  var fs7 = __require("fs");
24791
24791
  var path7 = __require("path");
24792
- var os4 = __require("os");
24792
+ var os5 = __require("os");
24793
24793
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
24794
24794
  var vars = process.config && process.config.variables || {};
24795
24795
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
24796
24796
  var abi2 = process.versions.modules;
24797
24797
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
24798
- var arch = process.env.npm_config_arch || os4.arch();
24799
- var platform = process.env.npm_config_platform || os4.platform();
24798
+ var arch = process.env.npm_config_arch || os5.arch();
24799
+ var platform = process.env.npm_config_platform || os5.platform();
24800
24800
  var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
24801
24801
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
24802
24802
  var uv = (process.versions.uv || "").split(".")[0];
@@ -42588,10 +42588,147 @@ 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
- import fs5 from "node:fs";
42593
- import path5 from "node:path";
42594
- import os3 from "node:os";
42729
+ import fs6 from "node:fs";
42730
+ import path6 from "node:path";
42731
+ import os4 from "node:os";
42595
42732
  import crypto5 from "node:crypto";
42596
42733
 
42597
42734
  // node_modules/viem/_esm/utils/getAction.js
@@ -53655,18 +53792,121 @@ function assertNoTrackedDotEnv(cwd = process.cwd()) {
53655
53792
  return { checked: true, tracked: false, candidates };
53656
53793
  }
53657
53794
 
53795
+ // scripts/src/lib/dotenv.mjs
53796
+ import fs5 from "node:fs";
53797
+ import os3 from "node:os";
53798
+ import path5 from "node:path";
53799
+ var ALLOWED_KEYS = [
53800
+ "ROZO_CHECKOUT_EVM_KEY",
53801
+ "ROZO_CHECKOUT_SOL_KEY",
53802
+ "ROZO_CHECKOUT_EVM_KEYSTORE",
53803
+ "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
53804
+ ];
53805
+ var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
53806
+ function isAllowedKey(key) {
53807
+ return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
53808
+ }
53809
+ function parseDotenv(text) {
53810
+ const vars = {};
53811
+ const lines = String(text).split(/\r?\n/);
53812
+ lines.forEach((rawLine, i) => {
53813
+ const lineNo = i + 1;
53814
+ const line = rawLine.trim();
53815
+ if (!line || line.startsWith("#")) return;
53816
+ const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
53817
+ const eq = withoutExport.indexOf("=");
53818
+ if (eq === -1) {
53819
+ throw new SkillError(
53820
+ "BAD_ENV_FILE",
53821
+ `Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
53822
+ );
53823
+ }
53824
+ const key = withoutExport.slice(0, eq).trim();
53825
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
53826
+ throw new SkillError(
53827
+ "BAD_ENV_FILE",
53828
+ `Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
53829
+ );
53830
+ }
53831
+ let value = withoutExport.slice(eq + 1).trim();
53832
+ if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
53833
+ value = value.slice(1, -1);
53834
+ }
53835
+ vars[key] = value;
53836
+ });
53837
+ return vars;
53838
+ }
53839
+ function filterAllowed(vars) {
53840
+ const out2 = {};
53841
+ for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out2[k] = v;
53842
+ return out2;
53843
+ }
53844
+ function envFileCandidates({ cwd = process.cwd(), home = os3.homedir() } = {}) {
53845
+ const candidates = [path5.join(cwd, ".env")];
53846
+ const owned = path5.join(home, ".rozo-checkout", ".env");
53847
+ if (owned !== candidates[0]) candidates.push(owned);
53848
+ return candidates;
53849
+ }
53850
+ function resolveEnvFile({ file, cwd = process.cwd(), home = os3.homedir() } = {}) {
53851
+ if (file) {
53852
+ const p = path5.resolve(file);
53853
+ if (!fs5.existsSync(p)) {
53854
+ throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
53855
+ }
53856
+ return p;
53857
+ }
53858
+ return envFileCandidates({ cwd, home }).find((p) => fs5.existsSync(p)) ?? null;
53859
+ }
53860
+ function applyDotenv({
53861
+ file,
53862
+ cwd = process.cwd(),
53863
+ home = os3.homedir(),
53864
+ env = process.env
53865
+ } = {}) {
53866
+ const target = resolveEnvFile({ file, cwd, home });
53867
+ if (!target) return null;
53868
+ let stat;
53869
+ try {
53870
+ stat = fs5.statSync(target);
53871
+ } catch (err) {
53872
+ throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
53873
+ }
53874
+ if (process.platform !== "win32" && stat.mode & 63) {
53875
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
53876
+ throw new SkillError(
53877
+ "ENV_FILE_PERMISSIONS",
53878
+ `That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
53879
+ );
53880
+ }
53881
+ assertNotTrackedByGit(target);
53882
+ const parsed = parseDotenv(fs5.readFileSync(target, "utf8"));
53883
+ const allowed = filterAllowed(parsed);
53884
+ const applied = [];
53885
+ for (const [k, v] of Object.entries(allowed)) {
53886
+ const existing = env[k];
53887
+ if (existing !== void 0 && String(existing).trim() !== "") continue;
53888
+ env[k] = v;
53889
+ applied.push(k);
53890
+ }
53891
+ return {
53892
+ path: target,
53893
+ applied,
53894
+ ignored: Object.keys(parsed).length - Object.keys(allowed).length
53895
+ };
53896
+ }
53897
+
53658
53898
  // scripts/src/lib/key-source.mjs
53659
53899
  var EVM_KEY_ENV = "ROZO_CHECKOUT_EVM_KEY";
53660
53900
  var SOL_KEY_ENV = "ROZO_CHECKOUT_SOL_KEY";
53661
53901
  var EVM_KEYSTORE_ENV = "ROZO_CHECKOUT_EVM_KEYSTORE";
53662
53902
  var KEYSTORE_PASSPHRASE_ENV = "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE";
53663
53903
  function defaultSolanaKeypairPath() {
53664
- return path5.join(os3.homedir(), ".config", "solana", "id.json");
53904
+ return path6.join(os4.homedir(), ".config", "solana", "id.json");
53665
53905
  }
53666
53906
  function assertKeyfileSafe(file) {
53667
53907
  let stat;
53668
53908
  try {
53669
- stat = fs5.statSync(file);
53909
+ stat = fs6.statSync(file);
53670
53910
  } catch (err) {
53671
53911
  throw new SkillError(
53672
53912
  "KEYFILE_UNREADABLE",
@@ -53843,12 +54083,16 @@ function looksLikeKeystore(text) {
53843
54083
  return false;
53844
54084
  }
53845
54085
  }
53846
- function planKeySource({ family, keyfile, env = process.env }) {
54086
+ function searchedSuffix(searched) {
54087
+ if (!searched || !searched.length) return "";
54088
+ return ` Looked for a .env in: ${searched.map(displayPath).join(", ")}.`;
54089
+ }
54090
+ function planKeySource({ family, keyfile, env = process.env, searched }) {
53847
54091
  if (keyfile) {
53848
54092
  const resolved = expandHome(keyfile);
53849
54093
  let text;
53850
54094
  try {
53851
- text = fs5.readFileSync(resolved, "utf8");
54095
+ text = fs6.readFileSync(resolved, "utf8");
53852
54096
  } catch (err) {
53853
54097
  throw new SkillError(
53854
54098
  "KEYFILE_UNREADABLE",
@@ -53872,13 +54116,13 @@ function planKeySource({ family, keyfile, env = process.env }) {
53872
54116
  }
53873
54117
  if (family === "solana") {
53874
54118
  const standard = defaultSolanaKeypairPath();
53875
- if (fs5.existsSync(standard)) {
54119
+ if (fs6.existsSync(standard)) {
53876
54120
  return { kind: "keypair-file", path: standard, label: displayPath(standard) };
53877
54121
  }
53878
54122
  if (env[SOL_KEY_ENV]) return { kind: "env", label: SOL_KEY_ENV };
53879
54123
  throw new SkillError(
53880
54124
  "NO_KEY_SOURCE",
53881
- `No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.`
54125
+ `No signing key found. Either create one with solana-keygen (writes ${displayPath(standard)}), pass --keyfile <path>, or set ${SOL_KEY_ENV}.` + searchedSuffix(searched)
53882
54126
  );
53883
54127
  }
53884
54128
  if (family === "evm") {
@@ -53890,7 +54134,7 @@ function planKeySource({ family, keyfile, env = process.env }) {
53890
54134
  if (env[EVM_KEY_ENV]) return { kind: "env", label: EVM_KEY_ENV };
53891
54135
  throw new SkillError(
53892
54136
  "NO_KEY_SOURCE",
53893
- `No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.`
54137
+ `No signing key found. Either point ${EVM_KEYSTORE_ENV} at an encrypted JSON keystore, pass --keyfile <path>, or set ${EVM_KEY_ENV}.${searchedSuffix(searched)}`
53894
54138
  );
53895
54139
  }
53896
54140
  throw new SkillError("NO_KEY_SOURCE", `No key source for family "${family}".`);
@@ -53908,7 +54152,7 @@ async function loadKeySource(plan, { family, env = process.env, askPassphrase }
53908
54152
  return { secretKey: decodeSolanaEnvKey(raw), label: plan.label, kind: plan.kind };
53909
54153
  }
53910
54154
  assertKeyfileSafe(plan.path);
53911
- const text = fs5.readFileSync(plan.path, "utf8");
54155
+ const text = fs6.readFileSync(plan.path, "utf8");
53912
54156
  if (plan.kind === "keypair-file") {
53913
54157
  return { secretKey: parseSolanaKeypairJson(text), label: plan.label, kind: plan.kind };
53914
54158
  }
@@ -53935,110 +54179,19 @@ function planSignability({
53935
54179
  }) {
53936
54180
  const probeEnv = { ...env };
53937
54181
  if (applyEnvFile) applyEnvFile({ file: envFile, cwd, env: probeEnv });
53938
- return planKeySource({ family, keyfile, env: probeEnv });
54182
+ const searched = envFile ? null : envFileCandidates({ cwd });
54183
+ return planKeySource({ family, keyfile, env: probeEnv, searched });
53939
54184
  }
53940
54185
  function expandHome(p) {
53941
54186
  const s = String(p);
53942
- if (s === "~") return os3.homedir();
53943
- if (s.startsWith("~/")) return path5.join(os3.homedir(), s.slice(2));
54187
+ if (s === "~") return os4.homedir();
54188
+ if (s.startsWith("~/")) return path6.join(os4.homedir(), s.slice(2));
53944
54189
  return s;
53945
54190
  }
53946
54191
  function displayPath(p) {
53947
- const home = os3.homedir();
54192
+ const home = os4.homedir();
53948
54193
  const s = String(p);
53949
- return s.startsWith(home + path5.sep) ? `~${s.slice(home.length)}` : s;
53950
- }
53951
-
53952
- // scripts/src/lib/dotenv.mjs
53953
- import fs6 from "node:fs";
53954
- import path6 from "node:path";
53955
- var ALLOWED_KEYS = [
53956
- "ROZO_CHECKOUT_EVM_KEY",
53957
- "ROZO_CHECKOUT_SOL_KEY",
53958
- "ROZO_CHECKOUT_EVM_KEYSTORE",
53959
- "ROZO_CHECKOUT_KEYSTORE_PASSPHRASE"
53960
- ];
53961
- var ALLOWED_PATTERN = /^ROZO_CHECKOUT_RPC_[A-Za-z0-9_]+$/;
53962
- function isAllowedKey(key) {
53963
- return ALLOWED_KEYS.includes(key) || ALLOWED_PATTERN.test(key);
53964
- }
53965
- function parseDotenv(text) {
53966
- const vars = {};
53967
- const lines = String(text).split(/\r?\n/);
53968
- lines.forEach((rawLine, i) => {
53969
- const lineNo = i + 1;
53970
- const line = rawLine.trim();
53971
- if (!line || line.startsWith("#")) return;
53972
- const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
53973
- const eq = withoutExport.indexOf("=");
53974
- if (eq === -1) {
53975
- throw new SkillError(
53976
- "BAD_ENV_FILE",
53977
- `Malformed .env: line ${lineNo} is not KEY=VALUE. (Content withheld \u2014 it may be a secret.)`
53978
- );
53979
- }
53980
- const key = withoutExport.slice(0, eq).trim();
53981
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
53982
- throw new SkillError(
53983
- "BAD_ENV_FILE",
53984
- `Malformed .env: line ${lineNo} has an invalid key name. (Content withheld.)`
53985
- );
53986
- }
53987
- let value = withoutExport.slice(eq + 1).trim();
53988
- if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
53989
- value = value.slice(1, -1);
53990
- }
53991
- vars[key] = value;
53992
- });
53993
- return vars;
53994
- }
53995
- function filterAllowed(vars) {
53996
- const out2 = {};
53997
- for (const [k, v] of Object.entries(vars)) if (isAllowedKey(k)) out2[k] = v;
53998
- return out2;
53999
- }
54000
- function resolveEnvFile({ file, cwd = process.cwd() } = {}) {
54001
- if (file) {
54002
- const p = path6.resolve(file);
54003
- if (!fs6.existsSync(p)) {
54004
- throw new SkillError("ENV_FILE_MISSING", `No such env file: ${file}`);
54005
- }
54006
- return p;
54007
- }
54008
- const fallback = path6.join(cwd, ".env");
54009
- return fs6.existsSync(fallback) ? fallback : null;
54010
- }
54011
- function applyDotenv({ file, cwd = process.cwd(), env = process.env } = {}) {
54012
- const target = resolveEnvFile({ file, cwd });
54013
- if (!target) return null;
54014
- let stat;
54015
- try {
54016
- stat = fs6.statSync(target);
54017
- } catch (err) {
54018
- throw new SkillError("ENV_FILE_MISSING", `Cannot read env file (${err.code || "error"}).`);
54019
- }
54020
- if (process.platform !== "win32" && stat.mode & 63) {
54021
- const mode = (stat.mode & 511).toString(8).padStart(3, "0");
54022
- throw new SkillError(
54023
- "ENV_FILE_PERMISSIONS",
54024
- `That .env is readable by other users (mode ${mode}). Run: chmod 600 ${target}`
54025
- );
54026
- }
54027
- assertNotTrackedByGit(target);
54028
- const parsed = parseDotenv(fs6.readFileSync(target, "utf8"));
54029
- const allowed = filterAllowed(parsed);
54030
- const applied = [];
54031
- for (const [k, v] of Object.entries(allowed)) {
54032
- const existing = env[k];
54033
- if (existing !== void 0 && String(existing).trim() !== "") continue;
54034
- env[k] = v;
54035
- applied.push(k);
54036
- }
54037
- return {
54038
- path: target,
54039
- applied,
54040
- ignored: Object.keys(parsed).length - Object.keys(allowed).length
54041
- };
54194
+ return s.startsWith(home + path6.sep) ? `~${s.slice(home.length)}` : s;
54042
54195
  }
54043
54196
 
54044
54197
  // scripts/src/lib/http.mjs
@@ -54155,128 +54308,6 @@ function snapshotFromQuote(quote) {
54155
54308
  };
54156
54309
  }
54157
54310
 
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
54311
  // scripts/src/lib/guards.mjs
54281
54312
  var UNPAID_STATUS = "payment_unpaid";
54282
54313
  function receiptSignal(source) {
@@ -55577,7 +55608,11 @@ async function main4(argv) {
55577
55608
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
55578
55609
  assertNoTrackedDotEnv();
55579
55610
  const dotenv = applyDotenv({ file: args["env-file"] });
55580
- const plan = planKeySource({ family: "evm", keyfile: args.keyfile });
55611
+ const plan = planKeySource({
55612
+ family: "evm",
55613
+ keyfile: args.keyfile,
55614
+ searched: args["env-file"] ? null : envFileCandidates()
55615
+ });
55581
55616
  const loaded = await loadKeySource(plan, { family: "evm", askPassphrase: promptPassphrase });
55582
55617
  const account = privateKeyToAccount(loaded.privateKey);
55583
55618
  const sender = account.address;
@@ -57521,7 +57556,11 @@ async function main5(argv) {
57521
57556
  const rozoPaymentId = assertRozoPaymentId(args["rozo-payment-id"] || args._[0]);
57522
57557
  assertNoTrackedDotEnv();
57523
57558
  const dotenv = applyDotenv({ file: args["env-file"] });
57524
- const plan = planKeySource({ family: "solana", keyfile: args.keyfile });
57559
+ const plan = planKeySource({
57560
+ family: "solana",
57561
+ keyfile: args.keyfile,
57562
+ searched: args["env-file"] ? null : envFileCandidates()
57563
+ });
57525
57564
  const loaded = await loadKeySource(plan, { family: "solana", askPassphrase: promptPassphrase });
57526
57565
  const secret = loaded.secretKey;
57527
57566
  const keypair = secret.length === 64 ? import_web36.Keypair.fromSecretKey(secret) : import_web36.Keypair.fromSeed(secret);
@@ -58057,6 +58096,16 @@ async function cmdPay(opts) {
58057
58096
  }
58058
58097
  if (!result.ok && !opts.json) {
58059
58098
  out(` ${yellow(`Could not check balances (${result.reason}). Continuing.`)}`);
58099
+ } else if (result.ok && !opts.json) {
58100
+ const hit = (result.options ?? []).find(
58101
+ (o) => o.chainId === String(opts.source.chainId) && o.tokenSymbol === String(opts.source.tokenSymbol).toUpperCase()
58102
+ );
58103
+ const short = `${opts.payer.slice(0, 6)}\u2026${opts.payer.slice(-4)}`;
58104
+ if (hit?.balanceUsd != null) {
58105
+ out(` ${dim(`Payer ${short} holds about $${hit.balanceUsd} of ${opts.source.tokenSymbol} \u2014 enough for this invoice.`)}`);
58106
+ } else {
58107
+ out(` ${dim(`Payer ${short}: balance not reported for this coin, so it was not verified.`)}`);
58108
+ }
58060
58109
  }
58061
58110
  payer = { address: opts.payer, family };
58062
58111
  }
@@ -58099,10 +58148,12 @@ async function cmdPay(opts) {
58099
58148
  if (p.display?.hasMemo) {
58100
58149
  out(` Memo ${p.display.receiverMemoMasked} ${dim(`(${p.display.memoType})`)}`);
58101
58150
  }
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
- }
58151
+ const deadline = formatDeadline(p.expiry?.effectiveDeadlineIso);
58152
+ out(` Expires ${bold(deadline ?? `in ${p.expiry?.expiresIn ?? "?"}`)}`);
58153
+ out(` ${dim("After that this order cannot be used and a new one must be created.")}`);
58154
+ out(
58155
+ p.reused ? ` ${dim(`Reusing existing unpaid order ${p.rozoPaymentId} \u2014 nothing new was created.`)}` : ` ${dim(`Order created: ${p.rozoPaymentId}`)}`
58156
+ );
58106
58157
  out(` ${dim("An order you never fund simply expires and costs nothing.")}`);
58107
58158
  out();
58108
58159
  out(dim(` The amount you send includes bridge and network fees, so it is`));
@@ -58138,6 +58189,9 @@ async function cmdPay(opts) {
58138
58189
  return confirmed.exitCode;
58139
58190
  }
58140
58191
  const deposit = confirmed.payload.deposit;
58192
+ if (!opts.json) {
58193
+ out(` ${dim(`Confirming order ${rozoPaymentId} \u2014 no second order was created.`)}`);
58194
+ }
58141
58195
  if (opts.send) {
58142
58196
  const family = chainFamily(deposit.chainId);
58143
58197
  const sender = family === "evm" ? run4 : family === "solana" ? run5 : null;