x402-safe-fetch 0.18.0 → 0.19.0

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
@@ -30,7 +30,7 @@ npx x402-safe-fetch pay <url> --max 0.05 --daily 1
30
30
  npx x402-safe-fetch spent # what was paid today, and what is left
31
31
  ```
32
32
 
33
- **No key to copy.** `connect` makes a wallet for this computer (its key stays in `~/.x402-safe-fetch/wallet.json`, readable by you only) and shows a link and a code: open it on your phone, sign in to [wallet.fizzl.eu](https://wallet.fizzl.eu) and approve. From then on every payment is first reserved on your Fizzl wallet: your limits there apply, and above them you get Approve / Deny on your phone (Telegram or the dashboard) before anything is signed; the key never leaves this computer. Send a few dollars of USDC on Base to the address it shows (`wallet` shows it again, with the balance). `init` makes the wallet without linking it, and `EVM_PRIVATE_KEY` still overrides both.
33
+ **No key to copy.** `connect` makes a wallet for this computer (its key stays in `~/.x402-safe-fetch/wallet.json`, readable by you only) and shows a link and a code: open it on your phone, sign in to [wallet.fizzl.eu](https://wallet.fizzl.eu) and approve. From then on every payment is first reserved on your Fizzl wallet: your limits there apply, and above them you get Approve / Deny on your phone (Telegram or the dashboard) before anything is signed; the key never leaves this computer. Then add a few dollars of USDC on Base to the address it shows (`wallet` shows it again, with the balance): no crypto yet? It also prints a link to buy $5 of USDC with a card at Coinbase, sent straight to that address (the link starts a Coinbase Onramp session through x402 Doctor; you pay Coinbase, Fizzl never holds the money). Or send USDC on Base from Coinbase, a wallet app or an exchange. `init` makes the wallet without linking it, and `EVM_PRIVATE_KEY` still overrides both.
34
34
 
35
35
  `pay` pays USDC on Base, only when x402 Doctor's preflight says go: the cheapest option within `--max`, all calls of the UTC day together within `--daily` (preflights included), and the paid answer checked for instructions aimed at your agent (`--no-answer-check` turns that off). An endpoint that took a payment without delivering isn't paid again for 30 minutes, also by the next command and whatever is added to its URL. `--method POST --data '{"q":1}'` sends a body, `--dry-run` is `check`, and `--json` prints one line of JSON (with the answer, the transaction hash and the day's spend). Exit code 0 is done, 1 is not paid (a check said no, or a limit), 2 is a usage error. The day's spend is kept in `~/.x402-safe-fetch/state.json` (`X402_SAFE_FETCH_HOME` moves it). Use a dedicated wallet with a few dollars.
36
36
 
package/cli.js CHANGED
@@ -18,7 +18,7 @@
18
18
  import fs from "node:fs";
19
19
  import os from "node:os";
20
20
  import path from "node:path";
21
- import { ensureWallet, readWallet, writeWallet, pair, serverSigner, usdcBalance, WALLET_URL } from "./wallet.js";
21
+ import { ensureWallet, readWallet, writeWallet, pair, serverSigner, usdcBalance, buyUsdcUrl, WALLET_URL } from "./wallet.js";
22
22
  import {
23
23
  createSafeFetch, SafePayError, receiptOf, readOffer, usdOfOption, answerCheckOf, purchaseKey, paidTwiceMessage,
24
24
  PAID_ERROR_MS, DOCTOR_URL, VERSION,
@@ -31,7 +31,7 @@ const HELP = `x402-safe-fetch ${VERSION}: pay for an API call from the command l
31
31
 
32
32
  connect Start here. Makes a wallet for this computer and links it to your Fizzl wallet
33
33
  (wallet.fizzl.eu): approve the code on your phone. Your limits and approvals then apply.
34
- init Only the wallet for this computer, not linked: shows the address to send USDC on Base to.
34
+ init Only the wallet for this computer, not linked: shows its address and a link to buy USDC with a card.
35
35
  wallet This computer's wallet: address, USDC balance, and whether it is linked.
36
36
  find <need> Free. Who sells it (Fizzl Buy's pick): URL, price, and how often it delivered.
37
37
  check <url> Free, pays nothing. The price per network and x402 Doctor's free check of the endpoint.
@@ -238,7 +238,7 @@ async function pay(args, { fetch, env, store, createPayingFetch, safeFetchOption
238
238
  async function init(args, { store, fetch }) {
239
239
  const { wallet, created } = await ensureWallet(store.home);
240
240
  const usdc = await usdcBalance(wallet.address, { fetch });
241
- return { ok: true, created, address: wallet.address, usdc_base: usdc, linked: wallet.server ? { url: wallet.server.url, agent: wallet.server.agent } : null, file: path.join(store.home, "wallet.json") };
241
+ return { ok: true, created, address: wallet.address, usdc_base: usdc, buy_usdc_url: buyUsdcUrl(wallet.address), linked: wallet.server ? { url: wallet.server.url, agent: wallet.server.agent } : null, file: path.join(store.home, "wallet.json") };
242
242
  }
243
243
 
244
244
  async function connect(args, { store, fetch, say, sleep }) {
@@ -251,14 +251,14 @@ async function connect(args, { store, fetch, say, sleep }) {
251
251
  });
252
252
  writeWallet(store.home, { ...wallet, server: linked });
253
253
  const usdc = await usdcBalance(wallet.address, { fetch });
254
- return { ok: true, address: wallet.address, linked: { url: linked.url, agent: linked.agent }, usdc_base: usdc };
254
+ return { ok: true, address: wallet.address, linked: { url: linked.url, agent: linked.agent }, usdc_base: usdc, buy_usdc_url: buyUsdcUrl(wallet.address) };
255
255
  }
256
256
 
257
257
  async function walletInfo(args, { store, fetch }) {
258
258
  const wallet = readWallet(store.home);
259
259
  if (!wallet) return { ok: false, code: "no_wallet", message: "no wallet yet: run npx x402-safe-fetch connect (or init)" };
260
260
  const usdc = await usdcBalance(wallet.address, { fetch });
261
- return { ok: true, address: wallet.address, usdc_base: usdc, linked: wallet.server ? { url: wallet.server.url, agent: wallet.server.agent } : null };
261
+ return { ok: true, address: wallet.address, usdc_base: usdc, buy_usdc_url: buyUsdcUrl(wallet.address), linked: wallet.server ? { url: wallet.server.url, agent: wallet.server.agent } : null };
262
262
  }
263
263
 
264
264
  function spent(args, { store }) {
@@ -302,7 +302,13 @@ function text(cmd, r) {
302
302
  if (cmd === "connect") lines.push(`Linked to your Fizzl wallet${r.linked?.agent ? ` as "${r.linked.agent}"` : ""}: your limits and approvals now apply.`);
303
303
  lines.push(`Address: ${r.address}`, `USDC on Base: ${r.usdc_base === null ? "?" : `$${r.usdc_base}`}`);
304
304
  if (cmd !== "connect") lines.push(r.linked ? `Linked to ${r.linked.url}${r.linked.agent ? ` as "${r.linked.agent}"` : ""}` : "Not linked to a Fizzl wallet (run connect for limits and phone approvals).");
305
- if (!r.usdc_base) lines.push("To pay, send a few dollars of USDC on Base to the address above (from Coinbase, a wallet app or an exchange; choose the Base network).");
305
+ if (!r.usdc_base) {
306
+ lines.push(
307
+ "To pay, add a few dollars of USDC on Base to the address above:",
308
+ ` with a card (Coinbase, $5; open it in your browser): ${r.buy_usdc_url}`,
309
+ " or send USDC from Coinbase, a wallet app or an exchange (choose the Base network).",
310
+ );
311
+ }
306
312
  lines.push('Then: npx x402-safe-fetch find "what you need"');
307
313
  } else if (cmd === "spent") {
308
314
  lines.push(`${r.day}: $${r.spent_today_usd} paid in ${r.calls} call(s), $${r.left_usd} left of $${r.daily_limit_usd}`);
package/index.js CHANGED
@@ -51,7 +51,7 @@ export const PRESIGN_GUARD_URL = "https://presign-guard.fizzl.eu";
51
51
  export const INJECTION_CAP = "$0.02"; // presign-guard's injection check costs $0.01; never more than twice that
52
52
  // The header a checked answer carries: green, orange, red (only with onInjection "flag" or a function) or unchecked.
53
53
  export const ANSWER_CHECK_HEADER = "x-answer-check";
54
- export const VERSION = "0.18.0";
54
+ export const VERSION = "0.19.0";
55
55
  // Doctor sees which calls come from this package (in its usage counts), nothing more.
56
56
  const DOCTOR_HEADERS = { accept: "application/json", "user-agent": `x402-safe-fetch/${VERSION}` };
57
57
  export const BASE = "eip155:8453";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-safe-fetch",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
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
  "bin": {
package/wallet.js CHANGED
@@ -13,6 +13,13 @@ import { SafePayError } from "./index.js";
13
13
  export const WALLET_URL = "https://wallet.fizzl.eu";
14
14
  export const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
15
15
  const BASE_RPC = "https://mainnet.base.org";
16
+ export const ONRAMP_URL = "https://x402-doctor.fizzl.eu/api/onramp";
17
+
18
+ // A link to buy USDC on Base with a card at Coinbase, delivered to `address`. Each visit starts a new Coinbase session
19
+ // (x402 Doctor asks Coinbase Onramp for it); the buyer pays Coinbase and Fizzl never holds the money.
20
+ export function buyUsdcUrl(address, { amount = 5 } = {}) {
21
+ return `${ONRAMP_URL}?${new URLSearchParams({ address, amount: String(amount) })}`;
22
+ }
16
23
 
17
24
  export function walletFile(home) {
18
25
  return path.join(home, "wallet.json");