x402-safe-fetch 0.11.0 → 0.11.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.js +7 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -142,11 +142,11 @@ With `shareOutcomes: true`, safe-fetch tells x402 Doctor what happened after it
142
142
  When safe-fetch does **not** pay (a `no_go`, or a `caution` that `onCaution` stops), it reports `refused` with the reason code that stopped it (for example `payto_changed`, `price_changed` or `payee_mismatch`). That is the seller's signal back: Doctor shows sellers how often agents refused to pay their endpoint and why (in the free web check and `GET https://x402-doctor.fizzl.eu/api/v1/refusals?url=…`), as counts only, never who.
143
143
 
144
144
  With `tellSellers: true`, a refusal also goes straight to the seller, when its 402 names a refusal url on its own site (the draft refusal extension, [x402-foundation/x402#4087](https://github.com/x402-foundation/x402/issues/4087)). safe-fetch posts the ready report from Doctor's preflight:
145
- - the reason code (for example `screening_hit` or `price_above_limit`);
145
+ - the reason code (for example `price_above_limit`, `network_unsupported` or `trust_low`);
146
146
  - the sha256 of the refused offer;
147
147
  - the endpoint without its query.
148
148
 
149
- No keys, no amounts from your wallet, nothing about you. It goes only to the endpoint's own site, with redirects refused, at most once a day per endpoint and reason, and never delays the error you get. It works with or without `shareOutcomes`.
149
+ No keys, no amounts from your wallet, nothing about you, and no `by` naming safe-fetch, so buyers aren't grouped by the client they use. A `screening_hit` (sanctioned payout address) isn't sent: the draft (0.2) takes it only signed, and safe-fetch holds no key to sign with. It goes only to the endpoint's own site, with redirects refused, at most once a day per endpoint and reason, and never delays the error you get. It works with or without `shareOutcomes`.
150
150
 
151
151
  ## Signed verdicts
152
152
 
package/index.js CHANGED
@@ -22,8 +22,8 @@
22
22
  // or response content are sent.
23
23
  // With tellSellers, a refusal also goes to the seller itself when its 402 names a refusal url on its own site (the
24
24
  // draft refusal extension, x402-foundation/x402#4087): the ready report from Doctor's preflight (reason code and the
25
- // hash of the refused offer; no query, no keys, nothing about the buyer), once a day per endpoint and reason. Off by
26
- // default.
25
+ // hash of the refused offer; no query, no keys, nothing about the buyer or this client), once a day per endpoint and
26
+ // reason. A screening_hit isn't sent: the draft takes it only signed. Off by default.
27
27
  // Every preflight must carry Doctor's signed receipt for exactly this request
28
28
  // (endpoint, method, budget, network), checked against Doctor's pinned signer:
29
29
  // a missing, changed or forged verdict is never a payment (receipt.js).
@@ -41,7 +41,7 @@ export { verifyReceipt, recoverSigner, canonicalJson, inputHash, certMessage, DO
41
41
  export const DOCTOR_URL = "https://x402-doctor.fizzl.eu";
42
42
  export const PREFLIGHT_CAP = "$0.002"; // the preflight costs $0.001; never more than twice that
43
43
  export const DIAGNOSE_CAP = "$0.02"; // the diagnosis costs $0.01; never more than twice that
44
- export const VERSION = "0.11.0";
44
+ export const VERSION = "0.11.1";
45
45
  // Doctor sees which calls come from this package (in its usage counts), nothing more.
46
46
  const DOCTOR_HEADERS = { accept: "application/json", "user-agent": `x402-safe-fetch/${VERSION}` };
47
47
  export const BASE = "eip155:8453";
@@ -391,6 +391,8 @@ export function createSafeFetch({
391
391
  if (target.protocol !== "https:" && !["localhost", "127.0.0.1"].includes(target.hostname)) return;
392
392
  if (!sameSite(target.hostname, endpoint.hostname)) return;
393
393
  if (refusal.report.resource !== `${endpoint.origin}${endpoint.pathname}`) return;
394
+ // The draft (0.2) takes a screening_hit only signed, and this client holds no key to sign with.
395
+ if (refusal.signature_required || (refusal.report.reason === "screening_hit" && !refusal.report.signature)) return;
394
396
  const key = `${refusal.report.resource} ${refusal.report.reason}`;
395
397
  if (now() - (toldSellers.get(key) ?? -Infinity) < 24 * 60 * 60 * 1000) return;
396
398
  toldSellers.set(key, now());
@@ -399,7 +401,8 @@ export function createSafeFetch({
399
401
  .then(() => baseFetch(target.href, {
400
402
  method: "POST",
401
403
  headers: { "content-type": "application/json" },
402
- body: JSON.stringify({ ...refusal.report, by: `x402-safe-fetch/${VERSION}` }),
404
+ // No "by": buyers aren't grouped by the client they use.
405
+ body: JSON.stringify(refusal.report),
403
406
  redirect: "error",
404
407
  }))
405
408
  .then((r) => r?.body?.cancel?.())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-safe-fetch",
3
- "version": "0.11.0",
3
+ "version": "0.11.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": {