@rozoai/checkout 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/llms.txt +38 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +1073 -70
- package/scripts/dist/create-order.js +106 -34
- package/scripts/dist/quote.js +100 -33
- package/scripts/dist/receipt.js +100 -33
- package/scripts/dist/resume.js +100 -33
- package/scripts/dist/send-evm.js +100 -33
- package/scripts/dist/send-sol.js +100 -33
- package/scripts/dist/status.js +119 -35
package/scripts/dist/status.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -35,6 +37,7 @@ function redact(text) {
|
|
|
35
37
|
return `${scheme}://${host}${hadMore ? "/<redacted>" : ""}`;
|
|
36
38
|
});
|
|
37
39
|
s = s.replace(/\b(bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 <redacted>");
|
|
40
|
+
s = s.replace(/\bak_[A-Za-z0-9_-]{8,}/g, "ak_<redacted>");
|
|
38
41
|
s = s.replace(
|
|
39
42
|
/\b(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|secret|token|password|passwd|pwd)\b(\s*[:=]\s*)("?)[A-Za-z0-9._~+/=-]{6,}\3/gi,
|
|
40
43
|
"$1$2<redacted>"
|
|
@@ -139,18 +142,66 @@ function maskAddress(address) {
|
|
|
139
142
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
// scripts/src/lib/
|
|
145
|
+
// scripts/src/lib/version.mjs
|
|
143
146
|
import { createRequire } from "node:module";
|
|
147
|
+
var PKG_VERSION = (() => {
|
|
148
|
+
const requireFrom = createRequire(import.meta.url);
|
|
149
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
150
|
+
try {
|
|
151
|
+
const pkg = requireFrom(candidate);
|
|
152
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return "0.0.0";
|
|
157
|
+
})();
|
|
144
158
|
|
|
145
159
|
// scripts/src/lib/http.mjs
|
|
146
160
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
147
|
-
var USER_AGENT =
|
|
148
|
-
|
|
161
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
162
|
+
var MAX_RETRIES = 3;
|
|
163
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
164
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
165
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
166
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
167
|
+
if (value === null || value === void 0) return null;
|
|
168
|
+
const s = String(value).trim();
|
|
169
|
+
if (!s) return null;
|
|
170
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
171
|
+
const at = Date.parse(s);
|
|
172
|
+
if (!Number.isFinite(at)) return null;
|
|
173
|
+
return Math.max(0, at - now);
|
|
174
|
+
}
|
|
175
|
+
function bodyRetryAfterMs(json) {
|
|
176
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
177
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
178
|
+
const n = Number(raw);
|
|
179
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
180
|
+
}
|
|
181
|
+
function rateLimitInfo(res) {
|
|
182
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
183
|
+
const info = {
|
|
184
|
+
limit: pick("x-ratelimit-limit"),
|
|
185
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
186
|
+
tier: pick("x-ratelimit-tier"),
|
|
187
|
+
scope: pick("x-ratelimit-scope")
|
|
188
|
+
};
|
|
189
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
190
|
+
return Object.keys(present).length ? present : null;
|
|
191
|
+
}
|
|
192
|
+
function parseBody(text) {
|
|
193
|
+
if (!text) return null;
|
|
194
|
+
try {
|
|
195
|
+
return JSON.parse(text);
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
149
201
|
const controller = new AbortController();
|
|
150
202
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
151
|
-
let res;
|
|
152
203
|
try {
|
|
153
|
-
res = await fetch(url, {
|
|
204
|
+
const res = await fetch(url, {
|
|
154
205
|
method,
|
|
155
206
|
headers: {
|
|
156
207
|
accept: "application/json",
|
|
@@ -160,6 +211,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
160
211
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
161
212
|
signal: controller.signal
|
|
162
213
|
});
|
|
214
|
+
const text = await res.text();
|
|
215
|
+
return { res, text };
|
|
163
216
|
} catch (err) {
|
|
164
217
|
throw new SkillError(
|
|
165
218
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -169,31 +222,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
169
222
|
} finally {
|
|
170
223
|
clearTimeout(timer);
|
|
171
224
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
225
|
+
}
|
|
226
|
+
async function request(method, url, {
|
|
227
|
+
body,
|
|
228
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
229
|
+
sleep: sleep2 = defaultSleep,
|
|
230
|
+
now = Date.now,
|
|
231
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
232
|
+
} = {}) {
|
|
233
|
+
let waitedMs = 0;
|
|
234
|
+
let retries = 0;
|
|
235
|
+
for (; ; ) {
|
|
236
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
237
|
+
const json = parseBody(text);
|
|
238
|
+
if (res.ok) {
|
|
239
|
+
if (json === null) {
|
|
240
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
241
|
+
url: redactUrl(url),
|
|
242
|
+
snippet: redact(text).slice(0, 400)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return json;
|
|
246
|
+
}
|
|
247
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
248
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
249
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
250
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
251
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
252
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
253
|
+
retries += 1;
|
|
254
|
+
waitedMs += waitMs;
|
|
255
|
+
await sleep2(waitMs);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
179
258
|
}
|
|
180
|
-
}
|
|
181
|
-
if (!res.ok) {
|
|
182
259
|
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}`;
|
|
183
260
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
261
|
+
const rateLimit = rateLimitInfo(res);
|
|
184
262
|
throw new SkillError(code, redact(String(message)), {
|
|
185
263
|
httpStatus: res.status,
|
|
186
264
|
url: redactUrl(url),
|
|
187
|
-
body: json ?? redact(text).slice(0, 800)
|
|
265
|
+
body: json ?? redact(text).slice(0, 800),
|
|
266
|
+
...retryable ? {
|
|
267
|
+
retries,
|
|
268
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
269
|
+
} : {},
|
|
270
|
+
...rateLimit ? { rateLimit } : {}
|
|
188
271
|
});
|
|
189
272
|
}
|
|
190
|
-
if (json === null) {
|
|
191
|
-
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
192
|
-
url: redactUrl(url),
|
|
193
|
-
snippet: redact(text).slice(0, 400)
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return json;
|
|
197
273
|
}
|
|
198
274
|
function redactUrl(url) {
|
|
199
275
|
try {
|
|
@@ -259,17 +335,6 @@ function findByLinkId(linkId) {
|
|
|
259
335
|
}
|
|
260
336
|
|
|
261
337
|
// scripts/src/lib/api.mjs
|
|
262
|
-
var PKG_VERSION = (() => {
|
|
263
|
-
const requireFrom = createRequire(import.meta.url);
|
|
264
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
265
|
-
try {
|
|
266
|
-
const pkg = requireFrom(candidate);
|
|
267
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
268
|
-
} catch {
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return "0.0.0";
|
|
272
|
-
})();
|
|
273
338
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
274
339
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
275
340
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -620,6 +685,15 @@ var MARGINS_MS = {
|
|
|
620
685
|
};
|
|
621
686
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
622
687
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
688
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
689
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
690
|
+
function lightningWatchMs(expiresAt, { now = Date.now(), fallbackMs = DEFAULT_WATCH_MS } = {}) {
|
|
691
|
+
const at = parseDeadline(expiresAt);
|
|
692
|
+
if (at === null) return fallbackMs;
|
|
693
|
+
const remaining = at - now;
|
|
694
|
+
if (!(remaining > 0)) return fallbackMs;
|
|
695
|
+
return Math.min(remaining, LIGHTNING_WATCH_CAP_MS);
|
|
696
|
+
}
|
|
623
697
|
function formatRemaining(ms) {
|
|
624
698
|
if (!Number.isFinite(ms)) return "unknown";
|
|
625
699
|
if (ms <= 0) return "expired";
|
|
@@ -1088,6 +1162,16 @@ function statusGuidance(result) {
|
|
|
1088
1162
|
if (result.state === "expired_unfunded") return EXPIRED_UNFUNDED_GUIDANCE;
|
|
1089
1163
|
return result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
|
|
1090
1164
|
}
|
|
1165
|
+
function watchTimeoutMs(timeoutArg, result, now = Date.now()) {
|
|
1166
|
+
if (timeoutArg !== void 0 && timeoutArg !== null && timeoutArg !== true) {
|
|
1167
|
+
const n = Number(timeoutArg);
|
|
1168
|
+
if (Number.isFinite(n)) return Math.max(0, n * 1e3);
|
|
1169
|
+
}
|
|
1170
|
+
if (result?.payin?.chain === chainName("lightning")) {
|
|
1171
|
+
return lightningWatchMs(result?.expiry?.expiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
|
|
1172
|
+
}
|
|
1173
|
+
return DEFAULT_WATCH_MS;
|
|
1174
|
+
}
|
|
1091
1175
|
async function main(argv) {
|
|
1092
1176
|
const args = parseArgs(argv);
|
|
1093
1177
|
const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
|
|
@@ -1103,9 +1187,9 @@ async function main(argv) {
|
|
|
1103
1187
|
usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
|
|
1104
1188
|
}
|
|
1105
1189
|
const watch = Boolean(args.watch);
|
|
1106
|
-
const
|
|
1107
|
-
const deadline = Date.now() + timeoutMs;
|
|
1190
|
+
const started = Date.now();
|
|
1108
1191
|
let result = await snapshot({ rozoPaymentId, linkId, provider });
|
|
1192
|
+
const deadline = started + watchTimeoutMs(args.timeout, result, started);
|
|
1109
1193
|
const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
|
|
1110
1194
|
while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
|
|
1111
1195
|
await sleep(POLL_INTERVAL_MS);
|