x402-safe-fetch 0.15.0 → 0.15.1
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 +1 -0
- package/index.d.ts +3 -0
- package/index.js +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,7 @@ x402 Doctor signs every paid preflight (EIP-191 over canonical JSON, with the re
|
|
|
229
229
|
- With `diagnoseOnFailure`: $0.01 per diagnosis, only when a payment fails (once per endpoint per `cacheMs`).
|
|
230
230
|
- Bodies must be strings, Buffers or `URLSearchParams`: the request is sent once unpaid (to see the 402) and once paid.
|
|
231
231
|
- If Doctor is unreachable, nothing is paid (`SafePayError` with code `preflight_failed`).
|
|
232
|
+
- Bitcoin Lightning (`lnbtc`) isn't paid: its price is in BTC, so the dollar budget can't hold. `createSafeFetch({ network: "lnbtc:…" })` throws, and a Lightning option is never picked. x402 Doctor does check Lightning 402s (the invoice's amount, network, expiry, signer and request hash).
|
|
232
233
|
- The preflight checks whether a payment can succeed and is sensible; it does not guarantee what the service delivers after payment.
|
|
233
234
|
|
|
234
235
|
Try it without code first: [safe-pay](../safe-pay) is a runnable script with the same logic. MIT licensed.
|
package/index.d.ts
CHANGED
|
@@ -108,6 +108,9 @@ export interface SafeFetch {
|
|
|
108
108
|
}
|
|
109
109
|
export declare function createSafeFetch(options: SafeFetchOptions): SafeFetch;
|
|
110
110
|
/** What an option costs in USD on `network` (USDC and USD₮0: 6 decimals; Stellar USDC: 7; RLUSD: decimal dollars), or null. */
|
|
111
|
+
/** Why createSafeFetch refuses a Bitcoin Lightning (lnbtc) network: its price is in BTC, so the dollar budget can't hold. */
|
|
112
|
+
export declare const LIGHTNING_UNSUPPORTED: string;
|
|
113
|
+
/** What an option costs in USD on `network` (Lightning: null). */
|
|
111
114
|
export declare function usdOfOption(option: { amount?: unknown; maxAmountRequired?: unknown }, network: string): number | null;
|
|
112
115
|
export declare function usdCap(maxUsd: number | string): string;
|
|
113
116
|
export declare function preflightUrl(target: string, options?: { method?: string; maxUsd?: number | string; network?: string; doctorUrl?: string }): string;
|
package/index.js
CHANGED
|
@@ -47,7 +47,7 @@ export { verifyReceipt, recoverSigner, canonicalJson, inputHash, certMessage, DO
|
|
|
47
47
|
export const DOCTOR_URL = "https://x402-doctor.fizzl.eu";
|
|
48
48
|
export const PREFLIGHT_CAP = "$0.002"; // the preflight costs $0.001; never more than twice that
|
|
49
49
|
export const DIAGNOSE_CAP = "$0.02"; // the diagnosis costs $0.01; never more than twice that
|
|
50
|
-
export const VERSION = "0.15.
|
|
50
|
+
export const VERSION = "0.15.1";
|
|
51
51
|
// Doctor sees which calls come from this package (in its usage counts), nothing more.
|
|
52
52
|
const DOCTOR_HEADERS = { accept: "application/json", "user-agent": `x402-safe-fetch/${VERSION}` };
|
|
53
53
|
export const BASE = "eip155:8453";
|
|
@@ -118,6 +118,7 @@ export async function readOffer(res) {
|
|
|
118
118
|
// What an option paid on `network` costs in US dollars, or null when it can't be read: RLUSD amounts are decimal
|
|
119
119
|
// dollars, Circle's USDC on Stellar has 7 decimals, and every other token safe-fetch pays (USDC, USD₮0) has 6.
|
|
120
120
|
export function usdOfOption(option, network) {
|
|
121
|
+
if (isLightning(network) || isLightning(option?.network)) return null; // millisatoshis: a BTC price, not dollars
|
|
121
122
|
const raw = String(option?.amount ?? option?.maxAmountRequired ?? "");
|
|
122
123
|
if (RLUSD_ISSUER[network]) return /^\d+(\.\d+)?$/.test(raw) ? Number(raw) : null;
|
|
123
124
|
if (!/^\d+$/.test(raw)) return null;
|
|
@@ -126,7 +127,15 @@ export function usdOfOption(option, network) {
|
|
|
126
127
|
// Without an offer to compare (no readable x402 challenge), a verdict is reused for at most 10 minutes.
|
|
127
128
|
const UNREAD_OFFER_MS = 10 * 60 * 1000;
|
|
128
129
|
|
|
130
|
+
// Bitcoin Lightning (x402 "exact" on lnbtc: a BOLT11 invoice, priced in millisatoshis). Not paid by safe-fetch: the
|
|
131
|
+
// budget is in dollars and x402's spend controls have no dollar token there, so neither cap would hold.
|
|
132
|
+
export const LIGHTNING_UNSUPPORTED = "Bitcoin Lightning (lnbtc) isn't paid by x402-safe-fetch: its price is in BTC, so the dollar budget can't hold. Pay a USDC option on another network, or check the endpoint with x402 Doctor (it checks Lightning invoices) and pay with your Lightning client.";
|
|
133
|
+
function isLightning(network) {
|
|
134
|
+
return String(network ?? "").startsWith("lnbtc:");
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
export function payableOption(accepts, network, maxUsd) {
|
|
138
|
+
if (isLightning(network)) return null;
|
|
130
139
|
const cap = Number(String(maxUsd).replace(/^\$/, ""));
|
|
131
140
|
const on = (accepts ?? []).filter((a) => algorandId(a?.network) === algorandId(network));
|
|
132
141
|
const usdc = ALGORAND_USDC[algorandId(network)];
|
|
@@ -301,6 +310,7 @@ export function createSafeFetch({
|
|
|
301
310
|
now = Date.now,
|
|
302
311
|
} = {}) {
|
|
303
312
|
const payNetwork = NETWORK_ALIASES[network] ?? network;
|
|
313
|
+
if (isLightning(payNetwork)) throw new TypeError(LIGHTNING_UNSUPPORTED);
|
|
304
314
|
const budget = usdCap(maxUsd);
|
|
305
315
|
const perCall = Number(budget.slice(1));
|
|
306
316
|
const totalCap = maxTotalUsd === undefined || maxTotalUsd === null ? null : Number(usdCap(maxTotalUsd).slice(1));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-safe-fetch",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "A fetch for AI agents that checks an unknown x402 endpoint ($0.001 x402 Doctor preflight) before paying it, and never pays above your budget.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|