twzrd-x402-gate 0.9.1 → 0.9.3

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 (60) hide show
  1. package/QUICKSTART.md +10 -19
  2. package/README.md +58 -250
  3. package/dist/decision-ledger-file.d.ts +49 -0
  4. package/dist/decision-ledger-file.d.ts.map +1 -0
  5. package/dist/decision-ledger-file.js +95 -0
  6. package/dist/decision-ledger-file.js.map +1 -0
  7. package/dist/decision-token.d.ts +3 -3
  8. package/dist/decision-token.d.ts.map +1 -1
  9. package/dist/decision-token.js +4 -1
  10. package/dist/decision-token.js.map +1 -1
  11. package/dist/index.d.ts +4 -9
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -7
  14. package/dist/index.js.map +1 -1
  15. package/dist/mpp-hook.d.ts +12 -39
  16. package/dist/mpp-hook.d.ts.map +1 -1
  17. package/dist/mpp-hook.js +13 -76
  18. package/dist/mpp-hook.js.map +1 -1
  19. package/dist/policy-runtime.d.ts +4 -4
  20. package/dist/policy-runtime.d.ts.map +1 -1
  21. package/dist/policy-runtime.js +33 -15
  22. package/dist/policy-runtime.js.map +1 -1
  23. package/dist/policy.d.ts +3 -1
  24. package/dist/policy.d.ts.map +1 -1
  25. package/dist/policy.js +7 -0
  26. package/dist/policy.js.map +1 -1
  27. package/dist/resource-bind.d.ts.map +1 -1
  28. package/dist/resource-bind.js +8 -27
  29. package/dist/resource-bind.js.map +1 -1
  30. package/dist/spend-control.d.ts +26 -0
  31. package/dist/spend-control.d.ts.map +1 -1
  32. package/dist/spend-control.js +39 -26
  33. package/dist/spend-control.js.map +1 -1
  34. package/dist/types.d.ts +2 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/x402-client-hook.d.ts +11 -22
  37. package/dist/x402-client-hook.d.ts.map +1 -1
  38. package/dist/x402-client-hook.js +13 -57
  39. package/dist/x402-client-hook.js.map +1 -1
  40. package/package.json +6 -9
  41. package/dist/cloudflare-base.d.ts +0 -49
  42. package/dist/cloudflare-base.d.ts.map +0 -1
  43. package/dist/cloudflare-base.js +0 -113
  44. package/dist/cloudflare-base.js.map +0 -1
  45. package/dist/cloudflare-x402.d.ts +0 -29
  46. package/dist/cloudflare-x402.d.ts.map +0 -1
  47. package/dist/cloudflare-x402.js +0 -31
  48. package/dist/cloudflare-x402.js.map +0 -1
  49. package/dist/paying-fetch.d.ts +0 -15
  50. package/dist/paying-fetch.d.ts.map +0 -1
  51. package/dist/paying-fetch.js +0 -67
  52. package/dist/paying-fetch.js.map +0 -1
  53. package/dist/policy-fetch.d.ts +0 -18
  54. package/dist/policy-fetch.d.ts.map +0 -1
  55. package/dist/policy-fetch.js +0 -77
  56. package/dist/policy-fetch.js.map +0 -1
  57. package/dist/wash-default.d.ts +0 -106
  58. package/dist/wash-default.d.ts.map +0 -1
  59. package/dist/wash-default.js +0 -223
  60. package/dist/wash-default.js.map +0 -1
package/QUICKSTART.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # twzrd-x402-gate — Quickstart (15 minutes, 0 USDC)
2
2
 
3
- **Product:** replace naked `createX402Client({ wallet })` with a client that
4
- **refuses wash before sign**. Free `merchant_card`, no API key. Intel down →
5
- **fail-open** (never invents wash). No Path A in the default path.
3
+ Refuses payment to a wash-flagged merchant **before your wallet signs**. Free intel, no
4
+ API key, no signup, no config — every `TWZRD_*` env var is an optional override. If intel
5
+ is unreachable the gate never *invents* a wash flag (the wash check fails open); a failed
6
+ preflight blocks the payment by default (`TWZRD_FAIL_OPEN=true` to allow).
6
7
 
7
- **Pin:** `twzrd-x402-gate@0.9.0` + `x402-solana@3.0.0`.
8
+ **Pin:** `twzrd-x402-gate@0.9.3` + stock PayAI client `x402-solana@3.0.0` (official
9
+ `beforePayment` seat). `@x402/core` Path E remains supported; refuse script is fallback.
8
10
 
9
11
  ## 1. Stock PayAI client (default seat — copy-paste)
10
12
 
11
13
  ```bash
12
- npm install twzrd-x402-gate@0.9.0 x402-solana@3.0.0
14
+ npm install twzrd-x402-gate@0.9.3 x402-solana@3.0.0
13
15
  ```
14
16
 
15
17
  > **ESM-only.** The package ships `import` conditions only — a CommonJS
@@ -17,16 +19,6 @@ npm install twzrd-x402-gate@0.9.0 x402-solana@3.0.0
17
19
  > fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`. Set `"type": "module"` in your
18
20
  > package.json, use `.mjs`, or bundle ESM.
19
21
 
20
- ```typescript
21
- import { createX402Client } from "x402-solana";
22
- import { createTwzrdPayingClient } from "twzrd-x402-gate";
23
-
24
- // was: createX402Client({ wallet })
25
- const client = createX402Client(createTwzrdPayingClient({ wallet }));
26
- ```
27
-
28
- Or hook-only:
29
-
30
22
  ```typescript
31
23
  import { createX402Client } from "x402-solana";
32
24
  import { createTwzrdBeforePaymentHook } from "twzrd-x402-gate";
@@ -34,18 +26,17 @@ import { createTwzrdBeforePaymentHook } from "twzrd-x402-gate";
34
26
  const client = createX402Client({
35
27
  wallet,
36
28
  network: "solana",
37
- beforePayment: createTwzrdBeforePaymentHook(),
29
+ beforePayment: createTwzrdBeforePaymentHook({ refuseWashFlagged: true }),
38
30
  });
39
31
  ```
40
32
 
41
- Default engine = wash only. Full 0.8 preflight/Path A: `{ engine: "full" }`.
42
-
33
+ Equivalent: `beforePayment: installTwzrdAutoGate("x402-solana", { refuseWashFlagged: true })`.
43
34
  Then: `await client.fetch("https://merchant.example/paid");`
44
35
 
45
36
  ## 1b. Official `@x402/core` client (Path E)
46
37
 
47
38
  ```bash
48
- npm install twzrd-x402-gate@0.9.0 @x402/core @x402/fetch @x402/svm
39
+ npm install twzrd-x402-gate@0.9.3 @x402/core @x402/fetch @x402/svm
49
40
  ```
50
41
 
51
42
  ```typescript
package/README.md CHANGED
@@ -1,72 +1,40 @@
1
1
  # twzrd-x402-gate
2
2
 
3
- **Product: the paying client that already has the brake.**
3
+ **TWZRD is the spend-control SDK for agents paying over x402** — and the default
4
+ `onBeforePaymentCreation` policy engine for official x402 clients.
4
5
 
5
- Agents pay other machines unsupervised. Most 402s are junk or wash. Something has
6
- to say **no** before the signature. TWZRD is that no — inside `beforePayment` /
7
- fetch+sign, not inside PayAI’s SDK defaults, not as a shop at intel.twzrd.xyz.
6
+ ### One call (named export `twzrd`)
8
7
 
9
- The other reason the no must live at the signature: **attaching a funded wallet
10
- to an agent that reads untrusted web content is a drain vector** — indirect
11
- prompt injection can spend anything the agent can sign. The gate closes it by
12
- attaching the wallet to a policy runtime the injected content cannot
13
- renegotiate: per-request cap, rolling daily ceiling, wash brake, endpoint
14
- mandates, and a tamper-evident spend ledger that survives restarts. A fully
15
- compromised agent spends at most what policy allows, to whom it allows, with
16
- every decision signed.
8
+ ```js
9
+ import { twzrd } from "twzrd-x402-gate";
17
10
 
18
- ### Replace naked `createX402Client({ wallet })` (5 lines)
19
-
20
- ```bash
21
- npm install twzrd-x402-gate@0.9.0 x402-solana@3.0.0
22
- ```
23
-
24
- ```typescript
25
- import { createX402Client } from "x402-solana";
26
- import { createTwzrdPayingClient } from "twzrd-x402-gate";
27
-
28
- // was: createX402Client({ wallet })
29
- const client = createX402Client(createTwzrdPayingClient({ wallet }));
30
- ```
31
-
32
- Equivalent one-hook form:
33
-
34
- ```typescript
35
- import { createX402Client } from "x402-solana";
36
- import { createTwzrdBeforePaymentHook } from "twzrd-x402-gate";
37
-
38
- const client = createX402Client({
39
- wallet,
40
- network: "solana",
41
- beforePayment: createTwzrdBeforePaymentHook(), // wash default
11
+ const result = await twzrd.safeFetch(url, {
12
+ maxSpend: "0.10", // per-call cap AND cumulative budget
13
+ allowNetworks: ["solana", "base"],
14
+ requireOfferBinding: true, // requires the prepare/submit split below
42
15
  });
16
+ // result.verdict: "allow" | "warn" | "block" — blocks have signerInvocations === 0
17
+ // result.receipt: { strength: "hard"|"soft"|"refuse", leaf_hash, fact_type: "resource_bound" }
43
18
  ```
44
19
 
45
- **Default path (engine `"wash"`):**
46
- `GET /v1/intel/merchant_card/{payTo}` → abort **iff** `wash_flagged === true` →
47
- **fail-open** on timeout / non-2xx / throw (never invent wash).
48
- No Path A, no requireReceipt, no second 402, no payment-control tokens.
49
-
50
- **Full 0.8.x engine (named opt-in):** preflight + optional Path A / escalate /
51
- paymentControl:
52
-
53
- ```typescript
54
- createTwzrdBeforePaymentHook({ engine: "full", /* … */ })
55
- // or createTwzrdFullBeforePaymentHook({ … })
56
- ```
20
+ `requireOfferBinding` is deliberately fail-closed: an opaque `pay()` callback
21
+ can sign before this library can inspect the transaction, so it is rejected for
22
+ bound payments. Use `prepareBoundPayment` to build an unsigned transaction with
23
+ the supplied `memo`, then `submitBoundPayment` to sign and submit the exact
24
+ validated `transactionBase64`. This SDK never holds keys.
57
25
 
58
- Customer = whoever ships `createX402Client({ wallet })` with no hook (Eliza, MCP
59
- hosts, agent wallets). Crawlers will never `npm install`.
26
+ Full walkthrough: [QUICKSTART.md](https://github.com/twzrd-sol/twzrd-trust/blob/main/QUICKSTART.md) ·
27
+ verify receipts yourself: [REVIEW.md](https://github.com/twzrd-sol/twzrd-trust/blob/main/REVIEW.md)
60
28
 
61
- > **ESM-only:** CommonJS `require()` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`
62
- > — set `"type": "module"` (or use `.mjs` / an ESM bundler).
29
+ **Core product (buyer gate):** after the client selects the exact payment requirement and
30
+ **before** payment payload creation / wallet signing — free preflight + merchant_card wash
31
+ refuse. Protects the **payer** from a risky **merchant** (`payTo`). Chain-neutral envelope;
32
+ **Solana-deep** reputation only (Base/EVM = explicit `unknown`).
63
33
 
64
- ### Default-on AutoGate (alternate seats)
34
+ ### Default-on AutoGate (5 lines)
65
35
 
66
36
  ```bash
67
- npm install twzrd-x402-gate@0.9.0 x402-solana@3.0.0
68
- # official @x402/* path (alternate seat):
69
- # npm install twzrd-x402-gate@0.9.0 @x402/core @x402/fetch @x402/svm
37
+ npm install twzrd-x402-gate @x402/core @x402/fetch @x402/svm
70
38
  ```
71
39
 
72
40
  ```typescript
@@ -74,28 +42,26 @@ import { x402Client } from "@x402/core/client";
74
42
  import { installTwzrdAutoGate } from "twzrd-x402-gate";
75
43
 
76
44
  const client = new x402Client();
77
- // stock solana seat still defaults to wash via createTwzrdBeforePaymentHook()
45
+ // refuseWashFlagged defaults true; gateOnCanSpend stays false unless you opt in
78
46
  installTwzrdAutoGate(client, { refuseWashFlagged: true });
79
47
  // then register schemes + wrapFetchWithPayment as usual
80
48
  ```
81
49
 
82
- **Intercept proof (0 USDC, bad seller never reaches signer):**
50
+ **Intercept proof (0 USDC, wash seller never reaches signer):**
83
51
 
84
52
  ```bash
85
53
  cd packages/twzrd-x402-gate && npm run autogate-block-proof
86
54
  # writes block-proof-<run_id>.json (schema twzrd.autogate_block_proof.v1)
87
- # public reason: "TWZRD_TRUST_GATE_BLOCK: wash_flagged" (wash basis)
88
- # or "TWZRD_TRUST_GATE_BLOCK: decision_block" (readiness block basis)
89
- # fixture resolves live: wash_flagged seller if available, else decision=block
55
+ # public reason: TWZRD_TRUST_GATE_BLOCK: wash_flagged
90
56
  ```
91
57
 
92
- `gateOnCanSpend` remains **opt-in** (`false` by default; set `true` or `TWZRD_GATE_ON_CAN_SPEND=1` only when you want hard cap enforcement). Full-engine preflight outage default stays fail-closed unless `failOpen: true` / `TWZRD_FAIL_OPEN=1`. The **product wash path always fail-opens** on intel outage.
58
+ `gateOnCanSpend` remains **opt-in** (`false` by default; set `true` or `TWZRD_GATE_ON_CAN_SPEND=1` only when you want hard cap enforcement).
93
59
 
94
- **Optional:** a resource-server settle hook so merchants can apply **customer policy**
60
+ **Optional (0.8.1):** a resource-server settle hook so merchants can apply **customer policy**
95
61
  before they settle and serve (abuse, sanctions, bots, “don’t serve this payer”). Not an equal
96
62
  mirror of the buyer problem — settled USDC is final; wash resistance is mainly TWZRD scoring.
97
63
 
98
- ## Seller settle guard (`onBeforeSettle`) — optional
64
+ ## Seller settle guard (`onBeforeSettle`) — optional 0.8.1
99
65
 
100
66
  Resource servers can screen the **payer** before *they* settle an inbound payment and serve
101
67
  the resource. Use for merchant policy (abuse / sanctions / bots / customer selection). TWZRD is
@@ -107,7 +73,7 @@ transfer on-chain. Wash/sybil edges are primarily discounted in TWZRD scoring, n
107
73
  revenue refusal.
108
74
 
109
75
  ```bash
110
- npm install twzrd-x402-gate@0.9.0
76
+ npm install twzrd-x402-gate@0.9.3
111
77
  ```
112
78
 
113
79
  ```typescript
@@ -139,23 +105,7 @@ Offline demo: `npx tsx examples/seller-settle-guard.ts`
139
105
  Fixture-backed SVM extract tests live in `test/seller-hook.test.ts` +
140
106
  `test/fixtures/exact-svm-transfer-checked.ts`.
141
107
 
142
- **PayAI x402-solana@2.1.0+** (stock client - primary Solana seat via `beforePayment`).
143
- Both 2.1.0 and 3.0.0 are supported and produce an identical refuse transcript; 3.0.0
144
- is the current release and what the install lines pin. The hook absorbs the one
145
- breaking change between them (`declaredResource` is a string on 2.1.0, `{ url }` on
146
- 3.0.0) - see `src/x402-client-hook.ts` and `test/x402-solana-before-payment.test.ts`.
147
-
148
- ```typescript
149
- import { createX402Client } from "x402-solana";
150
- import { createTwzrdPayingClient } from "twzrd-x402-gate";
151
-
152
- const client = createX402Client(createTwzrdPayingClient({ wallet }));
153
- ```
154
-
155
- Also: `installTwzrdAutoGate("x402-solana", opts)` returns the same wash-default hook.
156
- Prove refuse-before-sign: `npm run x402-solana-before-payment-proof`.
157
-
158
- **PayAI agentic-payments** (alternate PayAI surface; prefer x402-solana@3.0.0 when available):
108
+ **PayAI agentic-payments** (the active PayAI SDK, not the dormant x402-solana):
159
109
  `npx tsx examples/payai-agentic-onPaymentVerified.ts` — wire `onPaymentVerified`
160
110
  → `toPayaiVerifyResult` to screen payers before serving. Fail-open by default.
161
111
 
@@ -167,7 +117,7 @@ Prove refuse-before-sign: `npm run x402-solana-before-payment-proof`.
167
117
  Install the published gate and run against wash fixtures:
168
118
 
169
119
  ```bash
170
- npm install twzrd-x402-gate@0.9.0
120
+ npm install twzrd-x402-gate@0.9.3
171
121
  # from package root after install, or from a checkout:
172
122
  npm run wash-dogfood
173
123
  ```
@@ -187,7 +137,7 @@ Gate dogfood: `approved=false` `reason=twzrd_decision_block`, **USDC spent = 0**
187
137
 
188
138
  **Accurate paste line:**
189
139
 
190
- > Preflight returned decision=block on wash seller 7G73PL… / HuSiSpc… (preflight_id 378468 / 378469, wash_flagged=true). Gate approved=false reason=twzrd_decision_block. No USDC spent. No tx broadcast. Repro: `npm i twzrd-x402-gate@0.9.0 && npm run wash-dogfood` or gist above.
140
+ > Preflight returned decision=block on wash seller 7G73PL… / HuSiSpc… (preflight_id 378468 / 378469, wash_flagged=true). Gate approved=false reason=twzrd_decision_block. No USDC spent. No tx broadcast. Repro: `npm i twzrd-x402-gate@0.8.5 && npm run wash-dogfood` or gist above.
191
141
 
192
142
  This is a **reproducible demonstration** that the free gate blocks known wash sellers with stamped `preflight_id`s and zero spend. It is **not** proof that external agents already default to this path at scale.
193
143
 
@@ -265,61 +215,11 @@ No AgentCash. No marketplace. No second probe. No TWZRD custody.
265
215
  Wire `twzrdOnPaymentRequested` / prefer `onPaymentRequired` + `onBeforePayment` per
266
216
  [lifecycle hooks](https://docs.x402.org/advanced-concepts/lifecycle-hooks). Same policy core.
267
217
 
268
- ### Cloudflare Agents x402 (Base)
269
-
270
- Cloudflare's `withX402Client` takes an `onPaymentRequired` callback before its
271
- automatic payment retry. Use the adapter below at that boundary:
272
-
273
- ```ts
274
- import { createTwzrdCloudflareX402Approval } from "twzrd-x402-gate";
275
-
276
- const approvePayment = createTwzrdCloudflareX402Approval({
277
- // Base/EVM has no TWZRD behavioral reputation yet. Refuse it rather than
278
- // presenting a policy allow as a trust verdict.
279
- unsupportedNetworkMode: "strict",
280
- });
281
-
282
- await this.x402Client.callTool(approvePayment, {
283
- name: "paid_tool",
284
- arguments: {},
285
- });
286
- ```
287
-
288
- `observe` is available for an agent that intentionally permits unscored Base
289
- payments, but it returns policy allow with `decision=unknown`; it is never a
290
- TWZRD reputation approval. This adapter protects the Cloudflare x402 path only.
291
- MPP is a separate protocol and belongs on its own `onChallenge` /
292
- `onPaymentRequired` control path.
293
-
294
- ### Cloudflare Worker / Base x402 (edge-safe preflight)
295
-
296
- For a Worker, import the dedicated subpath rather than the package root. It has
297
- no Node-native imports and sends the exact Base `payTo` plus `chain_id: 8453` to
298
- `/v1/intel/preflight` before a Viem account signs:
299
-
300
- ```ts
301
- import { withTwzrdBasePreflight } from "twzrd-x402-gate/cloudflare-base";
302
-
303
- const signature = await withTwzrdBasePreflight(
304
- paymentRequirements, // accepts: [{ network: "eip155:8453", payTo: "0x..." }]
305
- { intelBase: "https://intel.twzrd.xyz" },
306
- () => account.signTypedData(eip3009Authorization),
307
- );
308
- ```
309
-
310
- `block` throws `TwzrdBasePaymentBlockedError` and never invokes the signing
311
- callback. `allow` and `warn` both proceed; enforce any additional amount or
312
- mandate policy in the caller. Base is not presented as reputation-scored.
313
- Checksummed and lowercase EVM addresses are accepted unchanged. The fixture in
314
- [`test/cloudflare-base-edge.test.ts`](./test/cloudflare-base-edge.test.ts) proves
315
- `block → zero signTypedData calls` and the compiled subpath is checked for Node
316
- runtime leaks.
317
-
318
218
  ### Raw-fetch composition (injectible pay client only)
319
219
 
320
220
  ```typescript
321
221
  import { installTwzrdAutoGate } from "twzrd-x402-gate";
322
- import { wrapFetchWithPayment } from "@x402/fetch"; // or @x402/svm helper
222
+ import { wrapFetchWithPayment } from "@x402/fetch";
323
223
 
324
224
  // Guard RAW fetch, then hand to a client that still surfaces 402 to the guard layer
325
225
  // — OR installTwzrdAutoGate(x402Client) (canonical) / installTwzrdX402ClientHook alias.
@@ -362,13 +262,11 @@ Dogfood (one public live proof path):
362
262
  ## Install
363
263
 
364
264
  ```bash
365
- npm install twzrd-x402-gate@0.9.0 x402-solana@3.0.0
265
+ npm install twzrd-x402-gate@0.9.3
366
266
  ```
367
267
 
368
- Install pin is the published version (`GATE_PACKAGE_PIN` in
369
- `packages/twzrd-agent-intel/src/twzrd_agent_intel/pins.py`). Repo `package.json`
370
- may be one patch ahead until that release is on npm. Confirm with
371
- `npm view twzrd-x402-gate version`.
268
+ Do not hardcode a version in this doc — every past pin here (**0.5.4**, **0.7.1**, **0.8.5**,
269
+ **0.8.6**) has gone stale. Check `npm view twzrd-x402-gate version` if in doubt.
372
270
 
373
271
  Optional settle guard (resource-server **payer** policy): see **Seller settle guard
374
272
  (`onBeforeSettle`) — optional 0.8.1** above. Do not confuse with facilitator
@@ -462,16 +360,15 @@ installTwzrdX402ClientHook(client, {
462
360
  - Hook binding test:
463
361
  [`test/intent-binding.test.ts`](./test/intent-binding.test.ts).
464
362
 
465
- ### On MPP (Machine Payments Protocol) — Solana and narrow Base EVM charge
363
+ ### On MPP (Machine Payments Protocol) — Solana charge only
466
364
 
467
- `createTwzrdMppOnChallenge` guards `Mppx.create({ onChallenge })`. It supports
468
- `solana/charge` and the published native MPP `evm/charge` shape only for
469
- Base-mainnet native USDC (EIP-3009 authorization). `onChallenge` is the last
470
- deterministic checkpoint before `createCredential()` makes the credential, so a
471
- TWZRD block is an exception that means **`createCredential()` never runs and
472
- nothing signs**. On allow, the guard creates the credential for the exact
473
- challenge it evaluated; other methods/intents fail closed
474
- (`allowUnevaluated: true` is an explicit ungated opt-out).
365
+ `createTwzrdMppOnChallenge` guards `Mppx.create({ onChallenge })`. The mppx
366
+ Solana method signs AND broadcasts the transaction inside `createCredential()`,
367
+ so `onChallenge` is the last deterministic checkpoint before money moves - and
368
+ mppx re-throws `onChallenge` errors, so a TWZRD block is an exception that means
369
+ **`createCredential()` never runs and nothing signs**. On allow, the guard
370
+ creates the credential for the exact challenge it evaluated; non-`solana/charge`
371
+ challenges fail closed (`allowUnevaluated: true` to opt out).
475
372
 
476
373
  ```typescript
477
374
  import { Mppx } from "mppx/client";
@@ -487,30 +384,6 @@ const mppx = Mppx.create({
487
384
  });
488
385
  ```
489
386
 
490
- For Base, use mppx's EVM client with explicit local spend policy:
491
-
492
- ```typescript
493
- import { Mppx } from "mppx/client";
494
- import { evm } from "mppx/evm/client";
495
- import { createTwzrdMppOnChallenge, createLocalDecisionSigner } from "twzrd-x402-gate";
496
-
497
- const mppx = Mppx.create({
498
- methods: [evm({ account: wallet })],
499
- onChallenge: createTwzrdMppOnChallenge({
500
- signer: createLocalDecisionSigner(),
501
- policy: {
502
- allowedNetworks: ["eip155:8453"],
503
- allowedAssets: ["0x833589fCD6EDb6E08f4c7C32D4f71b54bdA02913"],
504
- maxAmountUsd: "1.00",
505
- },
506
- }),
507
- });
508
- ```
509
-
510
- Base is **not reputation-scored**. The EVM path provides an actual pre-credential
511
- local control point for Base USDC; it does not call a Solana-derived score or
512
- present a local policy allow as a TWZRD trust verdict.
513
-
514
387
  **Scope limit (honest):** the guard is authoritative only when no
515
388
  `onChallengeReceived` event handler supplies a credential. mppx resolves
516
389
  `eventCredential ?? onChallenge(...)`, so an event handler returning a credential
@@ -526,8 +399,6 @@ fails closed rather than approve a payment it cannot bind:
526
399
  | Non-USD-pegged asset | `UNPRICED_ASSET` | Policy ceilings are USD; the wire amount is base-unit tokens. Pricing SOL would store `asset: solana:native` beside a **dollar** `amount` and lose the token quantity actually transferred. (A 1.5 SOL charge of `1500000000` at 9 decimals would otherwise evaluate as "$1.50" and sail under a $5 ceiling while moving ~$270.) USDC/USDT only until an intent version carries token amount + quote. |
527
400
  | Unknown cluster | `UNKNOWN_CLUSTER` | mppx-solana's `resolveEndpoint` returns an unrecognized `cluster` **verbatim as the RPC endpoint URL**, and a network string containing "solana" is otherwise scored as mainnet - so `solana:https://seller-rpc.example` would inherit mainnet reputation for a chain never observed. Known cluster names only. |
528
401
  | Misdeclared decimals | `MALFORMED_CHALLENGE` | A known stablecoin declaring the wrong decimals is a discount attempt, not a rounding error. |
529
- | Non-Base EVM chain | `UNSUPPORTED_EVM_NETWORK` | EVM MPP is narrow: Base mainnet only. A testnet or another EVM network cannot inherit a Base USDC valuation. |
530
- | EVM split recipients | `MULTI_LEG_CHARGE` | mppx EVM permits `splits`; each is an additional recipient. PaymentIntent v1 binds one amount to one payTo, so the guard refuses rather than approve transfers it cannot represent. |
531
402
 
532
403
  The intent binds a **digest of the entire normalized challenge**, not just
533
404
  `realm:id` - swapping `recipient` or `amount` under the same challenge id changes
@@ -580,10 +451,8 @@ Canonical entry point (design: `docs/strategy/install-autogate-design.md`). One
580
451
  | Call | Adapter |
581
452
  |------|---------|
582
453
  | `installTwzrdAutoGate(payWrap, opts?)` | Fetch: guard raw fetch → pay client |
583
- | `installTwzrdAutoGate(x402Client, opts?)` | Official `@x402/core` `onBeforePaymentCreation` |
584
- | `installTwzrdAutoGate("x402-solana", opts?)` | PayAI stock client `beforePayment` (2.1.0+) |
454
+ | `installTwzrdAutoGate(x402Client, opts?)` | Official x402 `onBeforePaymentCreation` |
585
455
  | `installTwzrdAutoGate("mpp", opts)` | MPP `onChallenge` (returns handler) |
586
- | `createTwzrdBeforePaymentHook(opts?)` | Same as `"x402-solana"` — pass to `createX402Client` |
587
456
 
588
457
  Aliases: `installTwzrdX402ClientHook`, `createTwzrdMppOnChallenge` remain; docs prefer AutoGate.
589
458
  Kill switch: `TWZRD_GATE_ENABLED=false` or `TWZRD_AUTO_GATE=0`. Uninstall x402 installs with `uninstallTwzrdAutoGate(client)`.
@@ -596,7 +465,7 @@ ever gets a chance to sign.
596
465
 
597
466
  ```typescript
598
467
  import { installTwzrdAutoGate } from "twzrd-x402-gate";
599
- import { wrapFetchWithPayment } from "@x402/svm";
468
+ import { wrapFetchWithPayment } from "@x402/fetch";
600
469
 
601
470
  const payingFetch = installTwzrdAutoGate((guarded) => wrapFetchWithPayment(guarded, buyerWallet));
602
471
 
@@ -647,7 +516,7 @@ manage the raw/paying composition yourself:
647
516
 
648
517
  ```typescript
649
518
  import { withTwzrdGuard } from "twzrd-x402-gate";
650
- import { wrapFetchWithPayment } from "@x402/svm";
519
+ import { wrapFetchWithPayment } from "@x402/fetch";
651
520
 
652
521
  const raw = globalThis.fetch; // MUST still surface HTTP 402
653
522
  const guarded = withTwzrdGuard(raw); // guard sits upstream
@@ -682,49 +551,15 @@ const safeFetch = withTwzrdGuard(x402Fetch, {
682
551
  });
683
552
  ```
684
553
 
685
- ### Path A — default on the buyer seat when a paying fetch is wired
686
-
687
- Free preflight still decides `allow|warn|block`. **Facilitator `onBeforeSettle`
688
- stays free** (abort on `block` only — we do not tax their rail).
689
-
690
- On the **buyer** install, if you pass `x402Fetch` (or use
691
- `installTwzrdAutoGate(payWrap)`, which auto-wires `payWrap(raw)`), Path A
692
- defaults on:
693
-
694
- | Free decision | Resource price | What fires |
695
- |---------------|----------------|------------|
696
- | `block` | any | free refuse |
697
- | `warn` | ≥ $2.50 | $0.05 V6 (`requireReceipt`) |
698
- | `warn` | < $2.50 | $0.001 quick re-decide (`escalateOnWarn`) |
699
- | `allow` | > $2.50 | $0.05 V6 |
700
- | `allow` | ≤ $2.50 | free proceed |
701
-
702
- ```typescript
703
- import { wrapFetchWithPayment } from "@x402/svm";
704
- import { createTwzrdBeforePaymentHook } from "twzrd-x402-gate";
705
-
706
- const x402Fetch = wrapFetchWithPayment(fetch, buyerWallet);
707
- const client = createX402Client({
708
- wallet: buyerWallet,
709
- network: "solana",
710
- beforePayment: createTwzrdBeforePaymentHook({
711
- refuseWashFlagged: true,
712
- x402Fetch, // turns Path A defaults on
713
- }),
714
- });
715
- ```
716
-
717
- Opt out: `requireReceipt: false` and/or `escalateOnWarn: false`.
718
- Refuse-only seats (no `x402Fetch`) stay free.
719
-
720
- `evaluate_x402_resource` itself stays opt-in. `autoReceipt: true` still buys
721
- $0.05 on every non-block (broader than the default ladder).
554
+ `autoReceipt` is **off by default** — it spends the **buyer's** USDC, so you opt in. When on,
555
+ every warn/allow verdict settles $0.05 USDC to TWZRD and returns a signed V6 trust credential
556
+ for the counterparty before you pay the resource.
722
557
 
723
558
  **`x402Fetch` is yours to supply** (this package is dependency-free). Wire the proven
724
559
  `@x402/svm` sponsored-feePayer client — the same one `twzrd-mcp-server` uses:
725
560
 
726
561
  ```typescript
727
- import { wrapFetchWithPayment } from "@x402/svm";
562
+ import { wrapFetchWithPayment } from "@x402/fetch";
728
563
  const x402Fetch = wrapFetchWithPayment(fetch, buyerWallet); // settles 402 challenges
729
564
  ```
730
565
 
@@ -916,31 +751,17 @@ const gatedFetch = wrapFetchWithTwzrdGate(fetch, resolveConfig());
916
751
 
917
752
  A payment is **blocked** when:
918
753
  1. `decision ∈ blockDecisions` (default: `["block"]`)
919
- 2. `trust_score < preflightMinScore` (default: `40`) — note a card with **no**
920
- `trust_score` is scored `0` (`policy.ts`: `card.trust_score ?? 0`), so a
921
- missing score blocks under the default floor
754
+ 2. `trust_score < preflightMinScore` (default: `40`)
922
755
  3. `can_spend === false` — **only** when `gateOnCanSpend: true` (default `false`, opt-in)
923
- 4. `merchant_card.wash_flagged === true` — **on by default**
924
- (`refuseWashFlagged`, `TWZRD_REFUSE_WASH_FLAGGED`); forces `verdict: "block"`
925
- with `reason: twzrd_wash_flagged`. Only tightens, and fails **open** when the
926
- card is unreachable or `wash_flagged` is `null` (never evaluated ≠ clean).
927
- Soft-cap instead of hard refuse with `washMaxUsdc` / `TWZRD_WASH_MAX_USDC`.
928
756
 
929
757
  `warn` is allowed unless overridden. Preflight network failure **fails closed** by default (a preflight outage blocks the payment, so an intel hiccup never silently approves a spend); set `failOpen: true` / `TWZRD_FAIL_OPEN=true` to opt into legacy allow-on-outage.
930
758
 
931
759
  A 402 whose payment requirements yield **no identifiable seller wallet** (missing/empty `payTo`, or an unparseable `accepts[]`) is a different case from "unknown seller" — it always **blocks** with `reason: twzrd_unidentifiable_payment_recipient`, without ever calling the preflight network. This is unconditional (not affected by `failOpen`): `failOpen` governs what happens when the TWZRD *service* is unreachable, not what happens when the caller can't say who they're paying.
932
760
 
933
761
  > **`can_spend` note:** the free preflight returns `can_spend=false` for most sellers
934
- > not yet in the TWZRD corpus, including legitimate ones. That **`can_spend` leg
935
- > alone** is opt-in — it is ignored unless you set `gateOnCanSpend: true` — so an
936
- > unknown seller on a platform like Agentic.Market is not blocked *on that basis*.
937
- >
938
- > This is **not** a blanket "unknown sellers are allowed by default". Conditions 1,
939
- > 2 and 4 above are all active by default, and condition 2 is the one that catches
940
- > unknown sellers: a card carrying no `trust_score` evaluates as `0` and blocks
941
- > under the default `preflightMinScore: 40` with `twzrd_score_0_below_40`. To
942
- > actually let unscored sellers through, lower or disable `preflightMinScore` —
943
- > setting `gateOnCanSpend` does not govern it.
762
+ > not yet in the TWZRD corpus, including legitimate ones. The default is decision-only
763
+ > gating so unknown sellers on platforms like Agentic.Market are not blocked by default.
764
+ > Set `gateOnCanSpend: true` for strict mode.
944
765
 
945
766
  ## Config
946
767
 
@@ -951,24 +772,11 @@ A 402 whose payment requirements yield **no identifiable seller wallet** (missin
951
772
  | `blockDecisions` | `TWZRD_BLOCK_DECISIONS` | `block` | Decisions that throw |
952
773
  | `failOpen` | `TWZRD_FAIL_OPEN` | `false` | `true` opts into legacy allow-on-outage; default blocks (fail-closed) |
953
774
  | `gateOnCanSpend` | `TWZRD_GATE_ON_CAN_SPEND` | `false` | Also block when `can_spend=false` |
954
- | `refuseWashFlagged` | `TWZRD_REFUSE_WASH_FLAGGED` | **`true`** | Refuse when `merchant_card.wash_flagged === true` (Policy 4). Set `false` / `0` to opt out |
955
- | `washMaxUsdc` | `TWZRD_WASH_MAX_USDC` | — | Soft cap instead of hard refuse on wash: allow up to this USDC amount |
956
- | `unsupportedNetworkMode` | `TWZRD_UNSUPPORTED_NETWORK_MODE` | `observe` | Behavior on a non-Solana / unrecognized network |
957
- | `fetch` | — | global `fetch` | Injected fetch used for preflight + merchant card (testing / custom agents) |
958
- | `onWarnUpsell` | — | — | Callback fired on a `warn` verdict (Path A upsell hook) |
959
- | `attribution` | `TWZRD_ATTRIBUTION_INTEGRATION` + `TWZRD_ATTRIBUTION_RUN_ID` | — | Opt-in run attribution (see below) |
960
-
961
- The four options below are **not** `TwzrdGateConfig` fields — they belong to
962
- `TwzrdGuardOptions` / `InstallAutoGateOptions` (`withTwzrdGuard` /
963
- `installTwzrdAutoGate`). Passing them to `createTwzrdGate({...})` is a type error
964
- and is silently dropped at runtime:
965
-
966
- | Option | Env | Default | Description |
967
- |---|---|---|---|
968
775
  | `autoReceipt` | — | `false` | Auto-buy $0.05 TWZRD receipt on warn/allow |
969
776
  | `x402Fetch` | — | — | x402-capable fetch for `autoReceipt` |
970
777
  | `onReceipt` | — | — | Callback after receipt is captured |
971
778
  | `disabled` (`installTwzrdAutoGate` only) | `TWZRD_AUTO_GATE=0`/`false` | `false` | Bypass the guard entirely — `payWrap` gets the raw, unguarded fetch |
779
+ | `attribution` | `TWZRD_ATTRIBUTION_INTEGRATION` + `TWZRD_ATTRIBUTION_RUN_ID` | — | Opt-in run attribution (see below) |
972
780
 
973
781
  ## Gate adoption proof (no-spend harness)
974
782
 
@@ -0,0 +1,49 @@
1
+ export declare const DECISION_LEDGER_SCHEMA_VERSION = 1;
2
+ export type DecisionLedgerRow = {
3
+ schema_version: typeof DECISION_LEDGER_SCHEMA_VERSION;
4
+ decision_id: string;
5
+ at_unix_ms: number;
6
+ outcome: "allow" | "warn" | "block" | "error";
7
+ reason_codes: string[];
8
+ policy_version: string;
9
+ input: {
10
+ /** Public chain address, retained for settlement and counterparty joins. */
11
+ pay_to?: string;
12
+ resource_origin?: string;
13
+ network?: string;
14
+ /** Exact value retained for mandate/cap reconciliation. */
15
+ amount_micro?: string;
16
+ };
17
+ signer_invocations: number;
18
+ latency_ms?: number;
19
+ error?: {
20
+ code: string;
21
+ message?: string;
22
+ };
23
+ settlement?: {
24
+ status: "pending" | "settled" | "failed";
25
+ tx?: string;
26
+ };
27
+ };
28
+ export type RecordDecisionInput = Omit<DecisionLedgerRow, "schema_version" | "decision_id" | "at_unix_ms" | "input"> & {
29
+ decision_id?: string;
30
+ at_unix_ms?: number;
31
+ input: {
32
+ pay_to?: string;
33
+ resource?: string;
34
+ network?: string;
35
+ amount_micro?: string;
36
+ };
37
+ };
38
+ export type FileDecisionLedgerOptions = {
39
+ maxBufferedRows?: number;
40
+ rotateBytes?: number;
41
+ /** Opt in only when this library owns the host process lifecycle. Default: false. */
42
+ flushOnSignals?: boolean;
43
+ };
44
+ /** `record` never blocks the payment path. `flush` fsyncs an async batch. */
45
+ export declare function createFileDecisionLedger(filePath: string, options?: FileDecisionLedgerOptions): {
46
+ record(input: RecordDecisionInput): DecisionLedgerRow;
47
+ flush: () => Promise<void>;
48
+ };
49
+ //# sourceMappingURL=decision-ledger-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-ledger-file.d.ts","sourceRoot":"","sources":["../src/decision-ledger-file.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,OAAO,8BAA8B,CAAC;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC9C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QACL,4EAA4E;QAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,2DAA2D;QAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,iBAAiB,EAAE,gBAAgB,GAAG,aAAa,GAAG,YAAY,GAAG,OAAO,CAAC,GAAG;IACrH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxF,CAAC;AAaF,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B;kBAqChF,mBAAmB,GAAG,iBAAiB;iBA9B/B,OAAO,CAAC,IAAI,CAAC;EA4CtC"}