@rozoai/checkout 0.1.17 → 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 +15 -2
- package/llms.txt +5 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +152 -40
- package/scripts/dist/create-order.js +105 -34
- package/scripts/dist/quote.js +99 -33
- package/scripts/dist/receipt.js +99 -33
- package/scripts/dist/resume.js +99 -33
- package/scripts/dist/send-evm.js +99 -33
- package/scripts/dist/send-sol.js +99 -33
- package/scripts/dist/status.js +118 -35
package/scripts/dist/send-evm.js
CHANGED
|
@@ -9982,6 +9982,8 @@ var SUPPORT = Object.freeze({
|
|
|
9982
9982
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
9983
9983
|
});
|
|
9984
9984
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
9985
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
9986
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
9985
9987
|
|
|
9986
9988
|
// scripts/src/lib/output.mjs
|
|
9987
9989
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -21849,18 +21851,66 @@ function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
|
21849
21851
|
});
|
|
21850
21852
|
}
|
|
21851
21853
|
|
|
21852
|
-
// scripts/src/lib/
|
|
21854
|
+
// scripts/src/lib/version.mjs
|
|
21853
21855
|
import { createRequire } from "node:module";
|
|
21856
|
+
var PKG_VERSION = (() => {
|
|
21857
|
+
const requireFrom = createRequire(import.meta.url);
|
|
21858
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
21859
|
+
try {
|
|
21860
|
+
const pkg = requireFrom(candidate);
|
|
21861
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
21862
|
+
} catch {
|
|
21863
|
+
}
|
|
21864
|
+
}
|
|
21865
|
+
return "0.0.0";
|
|
21866
|
+
})();
|
|
21854
21867
|
|
|
21855
21868
|
// scripts/src/lib/http.mjs
|
|
21856
21869
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
21857
|
-
var USER_AGENT =
|
|
21858
|
-
|
|
21870
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
21871
|
+
var MAX_RETRIES = 3;
|
|
21872
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
21873
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
21874
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21875
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
21876
|
+
if (value === null || value === void 0) return null;
|
|
21877
|
+
const s = String(value).trim();
|
|
21878
|
+
if (!s) return null;
|
|
21879
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
21880
|
+
const at = Date.parse(s);
|
|
21881
|
+
if (!Number.isFinite(at)) return null;
|
|
21882
|
+
return Math.max(0, at - now);
|
|
21883
|
+
}
|
|
21884
|
+
function bodyRetryAfterMs(json) {
|
|
21885
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
21886
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
21887
|
+
const n = Number(raw);
|
|
21888
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
21889
|
+
}
|
|
21890
|
+
function rateLimitInfo(res) {
|
|
21891
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
21892
|
+
const info = {
|
|
21893
|
+
limit: pick("x-ratelimit-limit"),
|
|
21894
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
21895
|
+
tier: pick("x-ratelimit-tier"),
|
|
21896
|
+
scope: pick("x-ratelimit-scope")
|
|
21897
|
+
};
|
|
21898
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
21899
|
+
return Object.keys(present).length ? present : null;
|
|
21900
|
+
}
|
|
21901
|
+
function parseBody(text) {
|
|
21902
|
+
if (!text) return null;
|
|
21903
|
+
try {
|
|
21904
|
+
return JSON.parse(text);
|
|
21905
|
+
} catch {
|
|
21906
|
+
return null;
|
|
21907
|
+
}
|
|
21908
|
+
}
|
|
21909
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
21859
21910
|
const controller = new AbortController();
|
|
21860
21911
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
21861
|
-
let res;
|
|
21862
21912
|
try {
|
|
21863
|
-
res = await fetch(url, {
|
|
21913
|
+
const res = await fetch(url, {
|
|
21864
21914
|
method,
|
|
21865
21915
|
headers: {
|
|
21866
21916
|
accept: "application/json",
|
|
@@ -21870,6 +21920,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
21870
21920
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
21871
21921
|
signal: controller.signal
|
|
21872
21922
|
});
|
|
21923
|
+
const text = await res.text();
|
|
21924
|
+
return { res, text };
|
|
21873
21925
|
} catch (err) {
|
|
21874
21926
|
throw new SkillError(
|
|
21875
21927
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -21879,31 +21931,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
21879
21931
|
} finally {
|
|
21880
21932
|
clearTimeout(timer);
|
|
21881
21933
|
}
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
21934
|
+
}
|
|
21935
|
+
async function request(method, url, {
|
|
21936
|
+
body,
|
|
21937
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
21938
|
+
sleep = defaultSleep,
|
|
21939
|
+
now = Date.now,
|
|
21940
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
21941
|
+
} = {}) {
|
|
21942
|
+
let waitedMs = 0;
|
|
21943
|
+
let retries = 0;
|
|
21944
|
+
for (; ; ) {
|
|
21945
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
21946
|
+
const json = parseBody(text);
|
|
21947
|
+
if (res.ok) {
|
|
21948
|
+
if (json === null) {
|
|
21949
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
21950
|
+
url: redactUrl(url),
|
|
21951
|
+
snippet: redact(text).slice(0, 400)
|
|
21952
|
+
});
|
|
21953
|
+
}
|
|
21954
|
+
return json;
|
|
21955
|
+
}
|
|
21956
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
21957
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
21958
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
21959
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
21960
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
21961
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
21962
|
+
retries += 1;
|
|
21963
|
+
waitedMs += waitMs;
|
|
21964
|
+
await sleep(waitMs);
|
|
21965
|
+
continue;
|
|
21966
|
+
}
|
|
21889
21967
|
}
|
|
21890
|
-
}
|
|
21891
|
-
if (!res.ok) {
|
|
21892
21968
|
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}`;
|
|
21893
21969
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
21970
|
+
const rateLimit = rateLimitInfo(res);
|
|
21894
21971
|
throw new SkillError(code, redact(String(message)), {
|
|
21895
21972
|
httpStatus: res.status,
|
|
21896
21973
|
url: redactUrl(url),
|
|
21897
|
-
body: json ?? redact(text).slice(0, 800)
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
snippet: redact(text).slice(0, 400)
|
|
21974
|
+
body: json ?? redact(text).slice(0, 800),
|
|
21975
|
+
...retryable ? {
|
|
21976
|
+
retries,
|
|
21977
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
21978
|
+
} : {},
|
|
21979
|
+
...rateLimit ? { rateLimit } : {}
|
|
21904
21980
|
});
|
|
21905
21981
|
}
|
|
21906
|
-
return json;
|
|
21907
21982
|
}
|
|
21908
21983
|
function redactUrl(url) {
|
|
21909
21984
|
try {
|
|
@@ -22111,17 +22186,6 @@ function assertPaymentLimit(invoiceUsd) {
|
|
|
22111
22186
|
}
|
|
22112
22187
|
|
|
22113
22188
|
// scripts/src/lib/api.mjs
|
|
22114
|
-
var PKG_VERSION = (() => {
|
|
22115
|
-
const requireFrom = createRequire(import.meta.url);
|
|
22116
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
22117
|
-
try {
|
|
22118
|
-
const pkg = requireFrom(candidate);
|
|
22119
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
22120
|
-
} catch {
|
|
22121
|
-
}
|
|
22122
|
-
}
|
|
22123
|
-
return "0.0.0";
|
|
22124
|
-
})();
|
|
22125
22189
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
22126
22190
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
22127
22191
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -22359,6 +22423,8 @@ var MARGINS_MS = {
|
|
|
22359
22423
|
};
|
|
22360
22424
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
22361
22425
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
22426
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
22427
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
22362
22428
|
function marginFor(chainId) {
|
|
22363
22429
|
const key = String(chainId);
|
|
22364
22430
|
return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
|
package/scripts/dist/send-sol.js
CHANGED
|
@@ -32150,6 +32150,8 @@ var SUPPORT = Object.freeze({
|
|
|
32150
32150
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
32151
32151
|
});
|
|
32152
32152
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
32153
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
32154
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
32153
32155
|
|
|
32154
32156
|
// scripts/src/lib/output.mjs
|
|
32155
32157
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -33026,18 +33028,66 @@ function promptPassphrase(prompt = " Keystore passphrase: ") {
|
|
|
33026
33028
|
});
|
|
33027
33029
|
}
|
|
33028
33030
|
|
|
33029
|
-
// scripts/src/lib/
|
|
33031
|
+
// scripts/src/lib/version.mjs
|
|
33030
33032
|
import { createRequire } from "node:module";
|
|
33033
|
+
var PKG_VERSION = (() => {
|
|
33034
|
+
const requireFrom = createRequire(import.meta.url);
|
|
33035
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
33036
|
+
try {
|
|
33037
|
+
const pkg = requireFrom(candidate);
|
|
33038
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
33039
|
+
} catch {
|
|
33040
|
+
}
|
|
33041
|
+
}
|
|
33042
|
+
return "0.0.0";
|
|
33043
|
+
})();
|
|
33031
33044
|
|
|
33032
33045
|
// scripts/src/lib/http.mjs
|
|
33033
33046
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
33034
|
-
var USER_AGENT =
|
|
33035
|
-
|
|
33047
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
33048
|
+
var MAX_RETRIES = 3;
|
|
33049
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
33050
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
33051
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
33052
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
33053
|
+
if (value === null || value === void 0) return null;
|
|
33054
|
+
const s = String(value).trim();
|
|
33055
|
+
if (!s) return null;
|
|
33056
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
33057
|
+
const at = Date.parse(s);
|
|
33058
|
+
if (!Number.isFinite(at)) return null;
|
|
33059
|
+
return Math.max(0, at - now);
|
|
33060
|
+
}
|
|
33061
|
+
function bodyRetryAfterMs(json) {
|
|
33062
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
33063
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
33064
|
+
const n = Number(raw);
|
|
33065
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
33066
|
+
}
|
|
33067
|
+
function rateLimitInfo(res) {
|
|
33068
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
33069
|
+
const info = {
|
|
33070
|
+
limit: pick("x-ratelimit-limit"),
|
|
33071
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
33072
|
+
tier: pick("x-ratelimit-tier"),
|
|
33073
|
+
scope: pick("x-ratelimit-scope")
|
|
33074
|
+
};
|
|
33075
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
33076
|
+
return Object.keys(present).length ? present : null;
|
|
33077
|
+
}
|
|
33078
|
+
function parseBody(text) {
|
|
33079
|
+
if (!text) return null;
|
|
33080
|
+
try {
|
|
33081
|
+
return JSON.parse(text);
|
|
33082
|
+
} catch {
|
|
33083
|
+
return null;
|
|
33084
|
+
}
|
|
33085
|
+
}
|
|
33086
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
33036
33087
|
const controller = new AbortController();
|
|
33037
33088
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
33038
|
-
let res;
|
|
33039
33089
|
try {
|
|
33040
|
-
res = await fetch(url, {
|
|
33090
|
+
const res = await fetch(url, {
|
|
33041
33091
|
method,
|
|
33042
33092
|
headers: {
|
|
33043
33093
|
accept: "application/json",
|
|
@@ -33047,6 +33097,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
33047
33097
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
33048
33098
|
signal: controller.signal
|
|
33049
33099
|
});
|
|
33100
|
+
const text = await res.text();
|
|
33101
|
+
return { res, text };
|
|
33050
33102
|
} catch (err) {
|
|
33051
33103
|
throw new SkillError(
|
|
33052
33104
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -33056,31 +33108,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
33056
33108
|
} finally {
|
|
33057
33109
|
clearTimeout(timer);
|
|
33058
33110
|
}
|
|
33059
|
-
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
|
|
33065
|
-
|
|
33111
|
+
}
|
|
33112
|
+
async function request(method, url, {
|
|
33113
|
+
body,
|
|
33114
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
33115
|
+
sleep = defaultSleep,
|
|
33116
|
+
now = Date.now,
|
|
33117
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
33118
|
+
} = {}) {
|
|
33119
|
+
let waitedMs = 0;
|
|
33120
|
+
let retries = 0;
|
|
33121
|
+
for (; ; ) {
|
|
33122
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
33123
|
+
const json = parseBody(text);
|
|
33124
|
+
if (res.ok) {
|
|
33125
|
+
if (json === null) {
|
|
33126
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
33127
|
+
url: redactUrl(url),
|
|
33128
|
+
snippet: redact(text).slice(0, 400)
|
|
33129
|
+
});
|
|
33130
|
+
}
|
|
33131
|
+
return json;
|
|
33132
|
+
}
|
|
33133
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
33134
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
33135
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
33136
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
33137
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
33138
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
33139
|
+
retries += 1;
|
|
33140
|
+
waitedMs += waitMs;
|
|
33141
|
+
await sleep(waitMs);
|
|
33142
|
+
continue;
|
|
33143
|
+
}
|
|
33066
33144
|
}
|
|
33067
|
-
}
|
|
33068
|
-
if (!res.ok) {
|
|
33069
33145
|
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}`;
|
|
33070
33146
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
33147
|
+
const rateLimit = rateLimitInfo(res);
|
|
33071
33148
|
throw new SkillError(code, redact(String(message)), {
|
|
33072
33149
|
httpStatus: res.status,
|
|
33073
33150
|
url: redactUrl(url),
|
|
33074
|
-
body: json ?? redact(text).slice(0, 800)
|
|
33075
|
-
|
|
33076
|
-
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
snippet: redact(text).slice(0, 400)
|
|
33151
|
+
body: json ?? redact(text).slice(0, 800),
|
|
33152
|
+
...retryable ? {
|
|
33153
|
+
retries,
|
|
33154
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
33155
|
+
} : {},
|
|
33156
|
+
...rateLimit ? { rateLimit } : {}
|
|
33081
33157
|
});
|
|
33082
33158
|
}
|
|
33083
|
-
return json;
|
|
33084
33159
|
}
|
|
33085
33160
|
function redactUrl(url) {
|
|
33086
33161
|
try {
|
|
@@ -33288,17 +33363,6 @@ function assertPaymentLimit(invoiceUsd) {
|
|
|
33288
33363
|
}
|
|
33289
33364
|
|
|
33290
33365
|
// scripts/src/lib/api.mjs
|
|
33291
|
-
var PKG_VERSION = (() => {
|
|
33292
|
-
const requireFrom = createRequire(import.meta.url);
|
|
33293
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
33294
|
-
try {
|
|
33295
|
-
const pkg = requireFrom(candidate);
|
|
33296
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
33297
|
-
} catch {
|
|
33298
|
-
}
|
|
33299
|
-
}
|
|
33300
|
-
return "0.0.0";
|
|
33301
|
-
})();
|
|
33302
33366
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
33303
33367
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
33304
33368
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -33536,6 +33600,8 @@ var MARGINS_MS = {
|
|
|
33536
33600
|
};
|
|
33537
33601
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
33538
33602
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
33603
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
33604
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
33539
33605
|
function marginFor(chainId) {
|
|
33540
33606
|
const key = String(chainId);
|
|
33541
33607
|
return MARGINS_MS[key] ?? MARGINS_MS[chainId] ?? DEFAULT_MARGIN_MS;
|
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]);
|
|
@@ -139,18 +141,66 @@ function maskAddress(address) {
|
|
|
139
141
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
// scripts/src/lib/
|
|
144
|
+
// scripts/src/lib/version.mjs
|
|
143
145
|
import { createRequire } from "node:module";
|
|
146
|
+
var PKG_VERSION = (() => {
|
|
147
|
+
const requireFrom = createRequire(import.meta.url);
|
|
148
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
149
|
+
try {
|
|
150
|
+
const pkg = requireFrom(candidate);
|
|
151
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return "0.0.0";
|
|
156
|
+
})();
|
|
144
157
|
|
|
145
158
|
// scripts/src/lib/http.mjs
|
|
146
159
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
147
|
-
var USER_AGENT =
|
|
148
|
-
|
|
160
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
161
|
+
var MAX_RETRIES = 3;
|
|
162
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
163
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
164
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
165
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
166
|
+
if (value === null || value === void 0) return null;
|
|
167
|
+
const s = String(value).trim();
|
|
168
|
+
if (!s) return null;
|
|
169
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
170
|
+
const at = Date.parse(s);
|
|
171
|
+
if (!Number.isFinite(at)) return null;
|
|
172
|
+
return Math.max(0, at - now);
|
|
173
|
+
}
|
|
174
|
+
function bodyRetryAfterMs(json) {
|
|
175
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
176
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
177
|
+
const n = Number(raw);
|
|
178
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
179
|
+
}
|
|
180
|
+
function rateLimitInfo(res) {
|
|
181
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
182
|
+
const info = {
|
|
183
|
+
limit: pick("x-ratelimit-limit"),
|
|
184
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
185
|
+
tier: pick("x-ratelimit-tier"),
|
|
186
|
+
scope: pick("x-ratelimit-scope")
|
|
187
|
+
};
|
|
188
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
189
|
+
return Object.keys(present).length ? present : null;
|
|
190
|
+
}
|
|
191
|
+
function parseBody(text) {
|
|
192
|
+
if (!text) return null;
|
|
193
|
+
try {
|
|
194
|
+
return JSON.parse(text);
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
149
200
|
const controller = new AbortController();
|
|
150
201
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
151
|
-
let res;
|
|
152
202
|
try {
|
|
153
|
-
res = await fetch(url, {
|
|
203
|
+
const res = await fetch(url, {
|
|
154
204
|
method,
|
|
155
205
|
headers: {
|
|
156
206
|
accept: "application/json",
|
|
@@ -160,6 +210,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
160
210
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
161
211
|
signal: controller.signal
|
|
162
212
|
});
|
|
213
|
+
const text = await res.text();
|
|
214
|
+
return { res, text };
|
|
163
215
|
} catch (err) {
|
|
164
216
|
throw new SkillError(
|
|
165
217
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -169,31 +221,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
169
221
|
} finally {
|
|
170
222
|
clearTimeout(timer);
|
|
171
223
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
224
|
+
}
|
|
225
|
+
async function request(method, url, {
|
|
226
|
+
body,
|
|
227
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
228
|
+
sleep: sleep2 = defaultSleep,
|
|
229
|
+
now = Date.now,
|
|
230
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
231
|
+
} = {}) {
|
|
232
|
+
let waitedMs = 0;
|
|
233
|
+
let retries = 0;
|
|
234
|
+
for (; ; ) {
|
|
235
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
236
|
+
const json = parseBody(text);
|
|
237
|
+
if (res.ok) {
|
|
238
|
+
if (json === null) {
|
|
239
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
240
|
+
url: redactUrl(url),
|
|
241
|
+
snippet: redact(text).slice(0, 400)
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return json;
|
|
245
|
+
}
|
|
246
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
247
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
248
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
249
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
250
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
251
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
252
|
+
retries += 1;
|
|
253
|
+
waitedMs += waitMs;
|
|
254
|
+
await sleep2(waitMs);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
179
257
|
}
|
|
180
|
-
}
|
|
181
|
-
if (!res.ok) {
|
|
182
258
|
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
259
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
260
|
+
const rateLimit = rateLimitInfo(res);
|
|
184
261
|
throw new SkillError(code, redact(String(message)), {
|
|
185
262
|
httpStatus: res.status,
|
|
186
263
|
url: redactUrl(url),
|
|
187
|
-
body: json ?? redact(text).slice(0, 800)
|
|
264
|
+
body: json ?? redact(text).slice(0, 800),
|
|
265
|
+
...retryable ? {
|
|
266
|
+
retries,
|
|
267
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
268
|
+
} : {},
|
|
269
|
+
...rateLimit ? { rateLimit } : {}
|
|
188
270
|
});
|
|
189
271
|
}
|
|
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
272
|
}
|
|
198
273
|
function redactUrl(url) {
|
|
199
274
|
try {
|
|
@@ -259,17 +334,6 @@ function findByLinkId(linkId) {
|
|
|
259
334
|
}
|
|
260
335
|
|
|
261
336
|
// 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
337
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
274
338
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
275
339
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -620,6 +684,15 @@ var MARGINS_MS = {
|
|
|
620
684
|
};
|
|
621
685
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
622
686
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
687
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
688
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
689
|
+
function lightningWatchMs(expiresAt, { now = Date.now(), fallbackMs = DEFAULT_WATCH_MS } = {}) {
|
|
690
|
+
const at = parseDeadline(expiresAt);
|
|
691
|
+
if (at === null) return fallbackMs;
|
|
692
|
+
const remaining = at - now;
|
|
693
|
+
if (!(remaining > 0)) return fallbackMs;
|
|
694
|
+
return Math.min(remaining, LIGHTNING_WATCH_CAP_MS);
|
|
695
|
+
}
|
|
623
696
|
function formatRemaining(ms) {
|
|
624
697
|
if (!Number.isFinite(ms)) return "unknown";
|
|
625
698
|
if (ms <= 0) return "expired";
|
|
@@ -1088,6 +1161,16 @@ function statusGuidance(result) {
|
|
|
1088
1161
|
if (result.state === "expired_unfunded") return EXPIRED_UNFUNDED_GUIDANCE;
|
|
1089
1162
|
return result.terminal ? "Done." : "Still in flight. Poll again in ~10s.";
|
|
1090
1163
|
}
|
|
1164
|
+
function watchTimeoutMs(timeoutArg, result, now = Date.now()) {
|
|
1165
|
+
if (timeoutArg !== void 0 && timeoutArg !== null && timeoutArg !== true) {
|
|
1166
|
+
const n = Number(timeoutArg);
|
|
1167
|
+
if (Number.isFinite(n)) return Math.max(0, n * 1e3);
|
|
1168
|
+
}
|
|
1169
|
+
if (result?.payin?.chain === chainName("lightning")) {
|
|
1170
|
+
return lightningWatchMs(result?.expiry?.expiresAt, { now, fallbackMs: DEFAULT_WATCH_MS });
|
|
1171
|
+
}
|
|
1172
|
+
return DEFAULT_WATCH_MS;
|
|
1173
|
+
}
|
|
1091
1174
|
async function main(argv) {
|
|
1092
1175
|
const args = parseArgs(argv);
|
|
1093
1176
|
const rozoPaymentId = args["rozo-payment-id"] || (isRozoPaymentId(args._[0]) ? args._[0] : null);
|
|
@@ -1103,9 +1186,9 @@ async function main(argv) {
|
|
|
1103
1186
|
usage("Required: --rozo-payment-id <uuid> and/or --link-id <pl_* | paymentSession_*>");
|
|
1104
1187
|
}
|
|
1105
1188
|
const watch = Boolean(args.watch);
|
|
1106
|
-
const
|
|
1107
|
-
const deadline = Date.now() + timeoutMs;
|
|
1189
|
+
const started = Date.now();
|
|
1108
1190
|
let result = await snapshot({ rozoPaymentId, linkId, provider });
|
|
1191
|
+
const deadline = started + watchTimeoutMs(args.timeout, result, started);
|
|
1109
1192
|
const history = [{ at: (/* @__PURE__ */ new Date()).toISOString(), state: result.state }];
|
|
1110
1193
|
while (watch && !result.terminal && !result.escalate && !result.unknown && Date.now() < deadline) {
|
|
1111
1194
|
await sleep(POLL_INTERVAL_MS);
|