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 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.0";
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.0",
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": {