@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 +1 -1
- package/scripts/dist/cli.js +293 -239
- package/scripts/dist/send-evm.js +129 -110
- package/scripts/dist/send-sol.js +132 -113
- package/docs/review-history.md +0 -77
package/package.json
CHANGED
package/scripts/dist/cli.js
CHANGED
|
@@ -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
|
|
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 ||
|
|
24799
|
-
var platform = process.env.npm_config_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
|
|
42593
|
-
import
|
|
42594
|
-
import
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
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
|
|
53943
|
-
if (s.startsWith("~/")) return
|
|
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 =
|
|
54192
|
+
const home = os4.homedir();
|
|
53948
54193
|
const s = String(p);
|
|
53949
|
-
return s.startsWith(home +
|
|
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({
|
|
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({
|
|
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
|
-
|
|
58103
|
-
|
|
58104
|
-
|
|
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;
|