@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.
- package/README.md +51 -2
- package/llms.txt +5 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +655 -123
- package/scripts/dist/create-order.js +423 -217
- package/scripts/dist/quote.js +99 -33
- package/scripts/dist/receipt.js +149 -83
- package/scripts/dist/resume.js +1552 -0
- package/scripts/dist/send-evm.js +298 -232
- package/scripts/dist/send-sol.js +298 -232
- package/scripts/dist/status.js +168 -85
package/scripts/dist/quote.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]);
|
|
@@ -152,18 +154,66 @@ function extractLinkId(urlOrId) {
|
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
// scripts/src/lib/
|
|
157
|
+
// scripts/src/lib/version.mjs
|
|
156
158
|
import { createRequire } from "node:module";
|
|
159
|
+
var PKG_VERSION = (() => {
|
|
160
|
+
const requireFrom = createRequire(import.meta.url);
|
|
161
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
162
|
+
try {
|
|
163
|
+
const pkg = requireFrom(candidate);
|
|
164
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return "0.0.0";
|
|
169
|
+
})();
|
|
157
170
|
|
|
158
171
|
// scripts/src/lib/http.mjs
|
|
159
172
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
160
|
-
var USER_AGENT =
|
|
161
|
-
|
|
173
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
174
|
+
var MAX_RETRIES = 3;
|
|
175
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
176
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
177
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
178
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
179
|
+
if (value === null || value === void 0) return null;
|
|
180
|
+
const s = String(value).trim();
|
|
181
|
+
if (!s) return null;
|
|
182
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
183
|
+
const at = Date.parse(s);
|
|
184
|
+
if (!Number.isFinite(at)) return null;
|
|
185
|
+
return Math.max(0, at - now);
|
|
186
|
+
}
|
|
187
|
+
function bodyRetryAfterMs(json) {
|
|
188
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
189
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
190
|
+
const n = Number(raw);
|
|
191
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
192
|
+
}
|
|
193
|
+
function rateLimitInfo(res) {
|
|
194
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
195
|
+
const info = {
|
|
196
|
+
limit: pick("x-ratelimit-limit"),
|
|
197
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
198
|
+
tier: pick("x-ratelimit-tier"),
|
|
199
|
+
scope: pick("x-ratelimit-scope")
|
|
200
|
+
};
|
|
201
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
202
|
+
return Object.keys(present).length ? present : null;
|
|
203
|
+
}
|
|
204
|
+
function parseBody(text) {
|
|
205
|
+
if (!text) return null;
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(text);
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
162
213
|
const controller = new AbortController();
|
|
163
214
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
164
|
-
let res;
|
|
165
215
|
try {
|
|
166
|
-
res = await fetch(url, {
|
|
216
|
+
const res = await fetch(url, {
|
|
167
217
|
method,
|
|
168
218
|
headers: {
|
|
169
219
|
accept: "application/json",
|
|
@@ -173,6 +223,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
173
223
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
174
224
|
signal: controller.signal
|
|
175
225
|
});
|
|
226
|
+
const text = await res.text();
|
|
227
|
+
return { res, text };
|
|
176
228
|
} catch (err) {
|
|
177
229
|
throw new SkillError(
|
|
178
230
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -182,31 +234,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
182
234
|
} finally {
|
|
183
235
|
clearTimeout(timer);
|
|
184
236
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
237
|
+
}
|
|
238
|
+
async function request(method, url, {
|
|
239
|
+
body,
|
|
240
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
241
|
+
sleep = defaultSleep,
|
|
242
|
+
now = Date.now,
|
|
243
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
244
|
+
} = {}) {
|
|
245
|
+
let waitedMs = 0;
|
|
246
|
+
let retries = 0;
|
|
247
|
+
for (; ; ) {
|
|
248
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
249
|
+
const json = parseBody(text);
|
|
250
|
+
if (res.ok) {
|
|
251
|
+
if (json === null) {
|
|
252
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
253
|
+
url: redactUrl(url),
|
|
254
|
+
snippet: redact(text).slice(0, 400)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return json;
|
|
258
|
+
}
|
|
259
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
260
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
261
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
262
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
263
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
264
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
265
|
+
retries += 1;
|
|
266
|
+
waitedMs += waitMs;
|
|
267
|
+
await sleep(waitMs);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
192
270
|
}
|
|
193
|
-
}
|
|
194
|
-
if (!res.ok) {
|
|
195
271
|
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}`;
|
|
196
272
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
273
|
+
const rateLimit = rateLimitInfo(res);
|
|
197
274
|
throw new SkillError(code, redact(String(message)), {
|
|
198
275
|
httpStatus: res.status,
|
|
199
276
|
url: redactUrl(url),
|
|
200
|
-
body: json ?? redact(text).slice(0, 800)
|
|
277
|
+
body: json ?? redact(text).slice(0, 800),
|
|
278
|
+
...retryable ? {
|
|
279
|
+
retries,
|
|
280
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
281
|
+
} : {},
|
|
282
|
+
...rateLimit ? { rateLimit } : {}
|
|
201
283
|
});
|
|
202
284
|
}
|
|
203
|
-
if (json === null) {
|
|
204
|
-
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
205
|
-
url: redactUrl(url),
|
|
206
|
-
snippet: redact(text).slice(0, 400)
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return json;
|
|
210
285
|
}
|
|
211
286
|
function redactUrl(url) {
|
|
212
287
|
try {
|
|
@@ -221,17 +296,6 @@ function postJson(url, body, opts) {
|
|
|
221
296
|
}
|
|
222
297
|
|
|
223
298
|
// scripts/src/lib/api.mjs
|
|
224
|
-
var PKG_VERSION = (() => {
|
|
225
|
-
const requireFrom = createRequire(import.meta.url);
|
|
226
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
227
|
-
try {
|
|
228
|
-
const pkg = requireFrom(candidate);
|
|
229
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
230
|
-
} catch {
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return "0.0.0";
|
|
234
|
-
})();
|
|
235
299
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
236
300
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
237
301
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -300,6 +364,8 @@ var MARGINS_MS = {
|
|
|
300
364
|
};
|
|
301
365
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
302
366
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
367
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
368
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
303
369
|
function parseDeadline(value) {
|
|
304
370
|
if (value === null || value === void 0 || value === "") return null;
|
|
305
371
|
if (typeof value === "number" && Number.isFinite(value)) {
|
package/scripts/dist/receipt.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]);
|
|
@@ -138,18 +140,66 @@ function maskAddress(address) {
|
|
|
138
140
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
// scripts/src/lib/
|
|
143
|
+
// scripts/src/lib/version.mjs
|
|
142
144
|
import { createRequire } from "node:module";
|
|
145
|
+
var PKG_VERSION = (() => {
|
|
146
|
+
const requireFrom = createRequire(import.meta.url);
|
|
147
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
148
|
+
try {
|
|
149
|
+
const pkg = requireFrom(candidate);
|
|
150
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return "0.0.0";
|
|
155
|
+
})();
|
|
143
156
|
|
|
144
157
|
// scripts/src/lib/http.mjs
|
|
145
158
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
146
|
-
var USER_AGENT =
|
|
147
|
-
|
|
159
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
160
|
+
var MAX_RETRIES = 3;
|
|
161
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
162
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
163
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
164
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
165
|
+
if (value === null || value === void 0) return null;
|
|
166
|
+
const s = String(value).trim();
|
|
167
|
+
if (!s) return null;
|
|
168
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
169
|
+
const at = Date.parse(s);
|
|
170
|
+
if (!Number.isFinite(at)) return null;
|
|
171
|
+
return Math.max(0, at - now);
|
|
172
|
+
}
|
|
173
|
+
function bodyRetryAfterMs(json) {
|
|
174
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
175
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
176
|
+
const n = Number(raw);
|
|
177
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
178
|
+
}
|
|
179
|
+
function rateLimitInfo(res) {
|
|
180
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
181
|
+
const info = {
|
|
182
|
+
limit: pick("x-ratelimit-limit"),
|
|
183
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
184
|
+
tier: pick("x-ratelimit-tier"),
|
|
185
|
+
scope: pick("x-ratelimit-scope")
|
|
186
|
+
};
|
|
187
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
188
|
+
return Object.keys(present).length ? present : null;
|
|
189
|
+
}
|
|
190
|
+
function parseBody(text) {
|
|
191
|
+
if (!text) return null;
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(text);
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
148
199
|
const controller = new AbortController();
|
|
149
200
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
150
|
-
let res;
|
|
151
201
|
try {
|
|
152
|
-
res = await fetch(url, {
|
|
202
|
+
const res = await fetch(url, {
|
|
153
203
|
method,
|
|
154
204
|
headers: {
|
|
155
205
|
accept: "application/json",
|
|
@@ -159,6 +209,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
159
209
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
160
210
|
signal: controller.signal
|
|
161
211
|
});
|
|
212
|
+
const text = await res.text();
|
|
213
|
+
return { res, text };
|
|
162
214
|
} catch (err) {
|
|
163
215
|
throw new SkillError(
|
|
164
216
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -168,31 +220,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
168
220
|
} finally {
|
|
169
221
|
clearTimeout(timer);
|
|
170
222
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
223
|
+
}
|
|
224
|
+
async function request(method, url, {
|
|
225
|
+
body,
|
|
226
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
227
|
+
sleep = defaultSleep,
|
|
228
|
+
now = Date.now,
|
|
229
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
230
|
+
} = {}) {
|
|
231
|
+
let waitedMs = 0;
|
|
232
|
+
let retries = 0;
|
|
233
|
+
for (; ; ) {
|
|
234
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
235
|
+
const json = parseBody(text);
|
|
236
|
+
if (res.ok) {
|
|
237
|
+
if (json === null) {
|
|
238
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
239
|
+
url: redactUrl(url),
|
|
240
|
+
snippet: redact(text).slice(0, 400)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return json;
|
|
244
|
+
}
|
|
245
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
246
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
247
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
248
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
249
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
250
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
251
|
+
retries += 1;
|
|
252
|
+
waitedMs += waitMs;
|
|
253
|
+
await sleep(waitMs);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
178
256
|
}
|
|
179
|
-
}
|
|
180
|
-
if (!res.ok) {
|
|
181
257
|
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}`;
|
|
182
258
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
259
|
+
const rateLimit = rateLimitInfo(res);
|
|
183
260
|
throw new SkillError(code, redact(String(message)), {
|
|
184
261
|
httpStatus: res.status,
|
|
185
262
|
url: redactUrl(url),
|
|
186
|
-
body: json ?? redact(text).slice(0, 800)
|
|
263
|
+
body: json ?? redact(text).slice(0, 800),
|
|
264
|
+
...retryable ? {
|
|
265
|
+
retries,
|
|
266
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
267
|
+
} : {},
|
|
268
|
+
...rateLimit ? { rateLimit } : {}
|
|
187
269
|
});
|
|
188
270
|
}
|
|
189
|
-
if (json === null) {
|
|
190
|
-
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
191
|
-
url: redactUrl(url),
|
|
192
|
-
snippet: redact(text).slice(0, 400)
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return json;
|
|
196
271
|
}
|
|
197
272
|
function redactUrl(url) {
|
|
198
273
|
try {
|
|
@@ -206,18 +281,58 @@ function getJson(url, opts) {
|
|
|
206
281
|
return request("GET", url, opts);
|
|
207
282
|
}
|
|
208
283
|
|
|
209
|
-
// scripts/src/lib/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
// scripts/src/lib/state.mjs
|
|
285
|
+
import fs from "node:fs";
|
|
286
|
+
import path from "node:path";
|
|
287
|
+
import os from "node:os";
|
|
288
|
+
function stateRoot() {
|
|
289
|
+
return process.env.ROZO_CHECKOUT_STATE_DIR || path.join(os.homedir(), ".rozo-checkout", "state");
|
|
290
|
+
}
|
|
291
|
+
function statePath(rozoPaymentId) {
|
|
292
|
+
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
|
|
293
|
+
throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
|
|
294
|
+
}
|
|
295
|
+
return path.join(stateRoot(), `${rozoPaymentId}.json`);
|
|
296
|
+
}
|
|
297
|
+
function readState(rozoPaymentId) {
|
|
298
|
+
const file = statePath(rozoPaymentId);
|
|
299
|
+
let raw;
|
|
300
|
+
try {
|
|
301
|
+
raw = fs.readFileSync(file, "utf8");
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (err.code === "ENOENT") return null;
|
|
304
|
+
throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
return JSON.parse(raw);
|
|
308
|
+
} catch {
|
|
309
|
+
throw new SkillError(
|
|
310
|
+
"STATE_CORRUPT",
|
|
311
|
+
"The local state file for this order is corrupt. Refusing to act; inspect it manually."
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function findByLinkId(linkId) {
|
|
316
|
+
const dir = stateRoot();
|
|
317
|
+
let files = [];
|
|
318
|
+
try {
|
|
319
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
let best = null;
|
|
324
|
+
for (const f of files) {
|
|
213
325
|
try {
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
326
|
+
const s = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
327
|
+
if (s?.linkId !== linkId) continue;
|
|
328
|
+
if (!best || String(s.createdAt) > String(best.createdAt)) best = s;
|
|
216
329
|
} catch {
|
|
217
330
|
}
|
|
218
331
|
}
|
|
219
|
-
return
|
|
220
|
-
}
|
|
332
|
+
return best;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// scripts/src/lib/api.mjs
|
|
221
336
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
222
337
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
223
338
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -568,6 +683,8 @@ var MARGINS_MS = {
|
|
|
568
683
|
};
|
|
569
684
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
570
685
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
686
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
687
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
571
688
|
function formatRemaining(ms) {
|
|
572
689
|
if (!Number.isFinite(ms)) return "unknown";
|
|
573
690
|
if (ms <= 0) return "expired";
|
|
@@ -677,57 +794,6 @@ function checkExpiry({
|
|
|
677
794
|
return { ...withDeadline, ok: true, code: null, reason: null };
|
|
678
795
|
}
|
|
679
796
|
|
|
680
|
-
// scripts/src/lib/state.mjs
|
|
681
|
-
import fs from "node:fs";
|
|
682
|
-
import path from "node:path";
|
|
683
|
-
import os from "node:os";
|
|
684
|
-
function stateRoot() {
|
|
685
|
-
return process.env.ROZO_CHECKOUT_STATE_DIR || path.join(os.homedir(), ".rozo-checkout", "state");
|
|
686
|
-
}
|
|
687
|
-
function statePath(rozoPaymentId) {
|
|
688
|
-
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(rozoPaymentId || ""))) {
|
|
689
|
-
throw new SkillError("BAD_ROZO_PAYMENT_ID", "Refusing to build a state path from that id.");
|
|
690
|
-
}
|
|
691
|
-
return path.join(stateRoot(), `${rozoPaymentId}.json`);
|
|
692
|
-
}
|
|
693
|
-
function readState(rozoPaymentId) {
|
|
694
|
-
const file = statePath(rozoPaymentId);
|
|
695
|
-
let raw;
|
|
696
|
-
try {
|
|
697
|
-
raw = fs.readFileSync(file, "utf8");
|
|
698
|
-
} catch (err) {
|
|
699
|
-
if (err.code === "ENOENT") return null;
|
|
700
|
-
throw new SkillError("STATE_UNREADABLE", `Cannot read local state: ${err.code}`);
|
|
701
|
-
}
|
|
702
|
-
try {
|
|
703
|
-
return JSON.parse(raw);
|
|
704
|
-
} catch {
|
|
705
|
-
throw new SkillError(
|
|
706
|
-
"STATE_CORRUPT",
|
|
707
|
-
"The local state file for this order is corrupt. Refusing to act; inspect it manually."
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
function findByLinkId(linkId) {
|
|
712
|
-
const dir = stateRoot();
|
|
713
|
-
let files = [];
|
|
714
|
-
try {
|
|
715
|
-
files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
716
|
-
} catch {
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
let best = null;
|
|
720
|
-
for (const f of files) {
|
|
721
|
-
try {
|
|
722
|
-
const s = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
723
|
-
if (s?.linkId !== linkId) continue;
|
|
724
|
-
if (!best || String(s.createdAt) > String(best.createdAt)) best = s;
|
|
725
|
-
} catch {
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return best;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
797
|
// scripts/src/lib/bitrefill.mjs
|
|
732
798
|
var PROVIDER_BITREFILL = "bitrefill";
|
|
733
799
|
var BITREFILL_DESTINATION = Object.freeze({ chainId: "8453", tokenSymbol: "USDC" });
|
|
@@ -1017,7 +1083,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
|
|
|
1017
1083
|
intentExpiresAt: payment?.expiresAt ?? deadline,
|
|
1018
1084
|
coinbaseExpiry: deadline
|
|
1019
1085
|
});
|
|
1020
|
-
return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4) } : { ok: false, code: expiry2.code, reason: expiry2.reason };
|
|
1086
|
+
return expiry2.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry2.msOfSlack / 6e4), deadlineMs: expiry2.effectiveDeadlineMs } : { ok: false, code: expiry2.code, reason: expiry2.reason };
|
|
1021
1087
|
}
|
|
1022
1088
|
if (!status) {
|
|
1023
1089
|
return { ok: false, code: "LINK_PAYABILITY_UNKNOWN", reason: "The Coinbase link state could not be read." };
|
|
@@ -1030,7 +1096,7 @@ function sendWindowFor({ provider, chainId, payment, status, bitrefillExpiry, no
|
|
|
1030
1096
|
intentExpiresAt: payment?.expiresAt ?? status?.rozoPayment?.expiresAt,
|
|
1031
1097
|
coinbaseExpiry: status?.coinbase?.preApprovalExpiry
|
|
1032
1098
|
});
|
|
1033
|
-
return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4) } : { ok: false, code: expiry.code, reason: expiry.reason };
|
|
1099
|
+
return expiry.ok ? { ok: true, code: null, minutesOfSlack: Math.floor(expiry.msOfSlack / 6e4), deadlineMs: expiry.effectiveDeadlineMs } : { ok: false, code: expiry.code, reason: expiry.reason };
|
|
1034
1100
|
}
|
|
1035
1101
|
async function snapshot({ rozoPaymentId, linkId, provider: explicitProvider }) {
|
|
1036
1102
|
let provider = resolveProvider(explicitProvider, rozoPaymentId);
|