@odingard/guardian-x402 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,270 @@
1
- # Temporary Holding Version
1
+ # @odingard/guardian-x402
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Guardian for [x402](https://github.com/x402-foundation/x402) payments. Before
4
+ the agent's wallet signs an x402 payment, Guardian decides ALLOW, ASK or DENY
5
+ on the exact payment terms. The wallet signs only after an ALLOW or a person's
6
+ approval, only once, and only those terms.
7
+
8
+ **Decision-gated, not enforced outside your process.** Your agent's process
9
+ holds the wallet key. The guard governs payments made through the clients it
10
+ builds. It cannot stop code that signs with the key some other way. See
11
+ [Not governed](#not-governed).
12
+
13
+ **Status.** Works with USDC on Base Sepolia (testnet). Base mainnet is off by
14
+ default and unproven.
15
+
16
+ ## What it covers
17
+
18
+ - The official x402 TypeScript clients, version 2.30.x (the guard refuses to
19
+ start with any other version): `wrapFetchWithPayment`
20
+ (`@x402/fetch`), `wrapAxiosWithPayment` (`@x402/axios`) and `x402MCPClient`
21
+ (`@x402/mcp`). The guard builds them; you do not pass your own.
22
+ - x402 versions 1 and 2 over HTTP; version 2 over MCP.
23
+ - Scheme `exact`, transfer method `eip3009`, payment flow `authorization`.
24
+ - USDC on Base Sepolia (`eip155:84532`). USDC on Base mainnet
25
+ (`eip155:8453`) only when you name it in `enabledNetworks`.
26
+
27
+ Everything else is refused, not ignored: other schemes (`upto`, …), `permit2`
28
+ and `erc7710`, `upfront` and `escrow`, `extra.splits`, unknown `extra` fields,
29
+ other networks and assets, and terms the guard cannot read exactly.
30
+
31
+ ## Setup
32
+
33
+ 1. In the Guardian console, on a tool connection, install and enable two
34
+ record-transport catalog entries for your agent: `x402.pay` and
35
+ `x402.pay.above_cap`. Without them Guardian refuses (`TOOL_NOT_IN_CATALOG`
36
+ or `TOOL_NOT_GRANTED`) and nothing is signed. The guard never installs
37
+ them itself.
38
+ 2. Allow `x402.pay` with a rule if small payments may go through without a
39
+ person. `x402.pay.above_cap` always needs a person: the guard refuses a
40
+ rule ALLOW for it.
41
+
42
+ ```bash
43
+ npm install @odingard/guardian-x402 @odingard/guardian-sdk @x402/core @x402/evm @x402/fetch
44
+ ```
45
+
46
+ ```ts
47
+ import { createGuardianClient } from "@odingard/guardian-sdk";
48
+ import { createX402Guard } from "@odingard/guardian-x402";
49
+ import { guardedFetch } from "@odingard/guardian-x402/fetch";
50
+ import { privateKeyToAccount } from "viem/accounts";
51
+
52
+ const guard = createX402Guard({
53
+ guardian: createGuardianClient({
54
+ baseUrl: "https://guardian-api.odingard.com",
55
+ credential: process.env.GUARDIAN_AGENT_CREDENTIAL!,
56
+ }),
57
+ connectionId: "pcn_…", // the connection holding x402.pay and x402.pay.above_cap
58
+ signer: privateKeyToAccount(process.env.TESTNET_WALLET_KEY as `0x${string}`),
59
+ limits: [
60
+ {
61
+ network: "eip155:84532",
62
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // USDC, Base Sepolia
63
+ askAbove: "100000", // 0.10 USDC, in atomic units
64
+ max: "1000000", // 1.00 USDC: above this, refused locally
65
+ },
66
+ ],
67
+ store: myDurableStore, // optional; see "Live authorizations"
68
+ });
69
+
70
+ const pay = guardedFetch(guard, fetch);
71
+ const res = await pay("https://api.example.com/paid");
72
+ ```
73
+
74
+ Axios and MCP:
75
+
76
+ ```ts
77
+ import { guardedAxios } from "@odingard/guardian-x402/axios";
78
+ import { guardedMcpClient } from "@odingard/guardian-x402/mcp";
79
+
80
+ const api = guardedAxios(guard, axios.create()); // use the returned instance only
81
+ const mcp = guardedMcpClient(guard, new Client({ name: "agent", version: "1.0.0" }));
82
+ await mcp.connect(transport);
83
+ await mcp.callTool("get_weather", { city: "Oslo" });
84
+ ```
85
+
86
+ ## How a payment is decided
87
+
88
+ The guard registers its hooks on the official `x402Client` and hands x402 a
89
+ wrapped signer. For each payment:
90
+
91
+ 1. x402 applies its own `spendControls` (default cap: 1 USDC per payment).
92
+ The guard keeps them on and cannot turn them off. A `max` or `askAbove`
93
+ above 1 USDC is never reached unless you raise `maxAmountPerPayment`.
94
+ 2. The guard's policy drops every entry it cannot pay, so a mixed list picks
95
+ a supported one.
96
+ 3. In `onBeforePaymentCreation`, the guard reads the selected entry into one
97
+ canonical terms object (below), copies and freezes it, and runs local
98
+ checks: `max`, the `payTo` allowlist, live authorizations, the budget.
99
+ 4. It asks Guardian (`tool.invoke` on `x402.pay` or `x402.pay.above_cap`, with
100
+ the terms as arguments). On ASK it polls `claim` until a person decides or
101
+ the request expires (default 120 s, at most 300 s). A claim answer that
102
+ decides nothing (Guardian unreachable, `RATE_LIMITED`, `DB_BUSY`, HTTP 429
103
+ or 5xx) keeps the request held: the guard keeps waiting for the person
104
+ until the request expires, and signs nothing meanwhile. On ALLOW it calls
105
+ `execute`, where Guardian checks single use, expiry and revocation. For an
106
+ above-cap payment it then reads the receipt and continues only if it is
107
+ the receipt for this request and shows an ASK that a person approved
108
+ (approval state `approved`, or `consumed` once `execute` redeemed it).
109
+ 5. It stores a single-use signing token for this payment (valid 30 s by
110
+ default, `signingWindowMs`, at most 120 s).
111
+ 6. The wrapped signer signs only EIP-3009 `TransferWithAuthorization` typed
112
+ data that matches the frozen terms field by field: types, domain (`name`,
113
+ `version`, `chainId`, `verifyingContract`, nothing else), `from`, `to`,
114
+ `value`, `validAfter`, `validBefore` and a 32-byte nonce. It refuses if the
115
+ token is missing, used or expired, or if the selected entry changed after
116
+ the decision. It refuses every other signing call; it does not sign
117
+ transactions.
118
+
119
+ An unreachable Guardian, a timeout, malformed answers, a failed `execute`,
120
+ an approval that never comes, or a refusal at any step means no signature.
121
+
122
+ Each payment runs in its own flow (`AsyncLocalStorage`), so concurrent
123
+ payments never share an approval.
124
+
125
+ ### What Guardian sees
126
+
127
+ ```json
128
+ {
129
+ "schema": "odingard.guardian.x402.pay.v1",
130
+ "x402Version": 2,
131
+ "scheme": "exact",
132
+ "network": "eip155:84532",
133
+ "networkName": "Base Sepolia",
134
+ "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
135
+ "assetSymbol": "USDC",
136
+ "amount": "10000",
137
+ "amountDisplay": "0.01 USDC",
138
+ "payTo": "0x…",
139
+ "maxTimeoutSeconds": 60,
140
+ "extra": { "name": "USDC", "version": "2", "assetTransferMethod": "eip3009", "paymentFlow": "authorization" },
141
+ "resourceUrl": "https://api.example.com/paid",
142
+ "request": { "transport": "http", "method": "GET", "originalUrl": "https://api.example.com/paid", "finalUrl": "https://api.example.com/paid" },
143
+ "reviewReasons": []
144
+ }
145
+ ```
146
+
147
+ The receipt for each decision records these terms. Amounts are atomic units
148
+ of the asset; the display amount uses the guard's own pinned decimals. USDC is
149
+ never shown or treated as USD.
150
+
151
+ ### What a receipt means
152
+
153
+ A receipt says Guardian authorized and redeemed one payment on these exact
154
+ terms. It does not say a payment was signed or settled. If signing fails after
155
+ `execute`, the decision is spent and the guard records "redeemed, not
156
+ signed"; a retry needs a new decision. If `execute` was sent but its outcome
157
+ is unknown (no answer, a 5xx, an unreadable receipt; sdk 0.4.0
158
+ `outcomeUnknown`), nothing is signed. The refusal (`EXECUTE_FAILED`) says the
159
+ outcome is unknown and names the request id; its record, and the
160
+ `X402GuardRefused` the guard's own client throws, carry `outcomeUnknown: true`.
161
+ Through `guardedFetch` and `guardedAxios`, x402 turns the refusal into a plain
162
+ error with only the message, so `err.outcomeUnknown` is not set there: read the
163
+ record (`records()` or `onRecord`) or the message instead.
164
+ The guard records "redeemed or unknown, not signed": the decision may be spent,
165
+ so look the request up before retrying. The guard's local records (`records()`
166
+ or `onRecord`) link the Guardian request id to the signed nonce and
167
+ `validBefore`, then to the settlement (`success`, `transaction`, `network`).
168
+ They never hold a key, a signature or a payment payload.
169
+
170
+ ## Local rules
171
+
172
+ - **`limits`** per (network, asset), in atomic units: above `max` is refused
173
+ without asking Guardian; above `askAbove` goes to `x402.pay.above_cap`. An
174
+ asset with no limit: every payment needs a person. One asset's limit never
175
+ applies to another.
176
+ - **`payTo` allowlist** (optional, in a limit). Guardian rules on `x402.pay`
177
+ match the tool, not the recipient: a console ALLOW rule allows every
178
+ `payTo` up to `askAbove`. Use the allowlist for recipient control.
179
+ - **Resource host.** The host of `resource.url` must equal the host of the URL
180
+ the agent asked for (before redirects). A mismatch is refused.
181
+ - **MCP resource.** On MCP, `resource.url` must name the tool the agent
182
+ called, exactly. A paid MCP server must use one of:
183
+ - `mcp://tool/<tool name>`. Set it yourself:
184
+ `createPaymentWrapper(..., { resource: { url: "mcp://tool/get_weather" } })`.
185
+ If you leave `resource.url` unset, `@x402/mcp` 2.30.0 names
186
+ `mcp://tool/paid_tool` whatever the tool is called, and the guard refuses it;
187
+ - `<origin of serverUrl>/tools/<tool name>` (no query, no fragment, tool
188
+ name percent-encoded), accepted only when you pass `serverUrl` to
189
+ `guardedMcpClient`. For `serverUrl: "https://mcp.example.com/mcp"` and
190
+ tool `get_weather`: `https://mcp.example.com/tools/get_weather`.
191
+
192
+ Anything else is refused with `RESOURCE_HOST_MISMATCH` before Guardian is
193
+ asked, including another tool (`mcp://tool/other_tool`), another path on
194
+ the server host, or the right path with a query.
195
+ - **`maxTimeoutSeconds`** above `maxTimeoutSecondsLimit` (default 300) is
196
+ refused.
197
+
198
+ ### Live authorizations
199
+
200
+ A signed authorization can be settled until its `validBefore`, whatever the
201
+ server says. The guard counts every signed, unexpired authorization as live.
202
+ A second payment for the same request while one is live goes to
203
+ `x402.pay.above_cap` and needs a person. Same request means: any payment made
204
+ while serving the same `fetch`, axios or `callTool` call, whatever resource
205
+ the server names; the same method, original URL and `resource.url` over HTTP;
206
+ the same tool over MCP. This covers x402's retry paths (fetch and axios `recovered`, MCP
207
+ corrective 402). In fetch, x402 can sign and then stop with "Payment already
208
+ attempted"; the guard records that authorization as signed, not sent, and
209
+ keeps it live.
210
+
211
+ With a `store`, live authorizations survive a restart. **Without a store**,
212
+ the guard cannot know what an earlier process signed, so every payment in the
213
+ first `maxTimeoutSecondsLimit` seconds after start needs a person.
214
+
215
+ ### Budgets
216
+
217
+ Optional, per (network, asset), per UTC day or month, in atomic units. They
218
+ need a `store` with an atomic compare-and-set:
219
+
220
+ ```ts
221
+ interface X402GuardStore {
222
+ get(key: string): Promise<{ value: string; version: string } | null>;
223
+ compareAndSet(key: string, expectedVersion: string | null, value: string): Promise<boolean>;
224
+ }
225
+ ```
226
+
227
+ The amount is reserved before Guardian is asked. It is released only if
228
+ nothing was signed, or when `reconcile()` finds `validBefore` passed by more
229
+ than 5 minutes (the chain checks block time, which can lag your clock) and the
230
+ token's `authorizationState(from, nonce)` shows the nonce unused (pass
231
+ `authorizationState`, for example `authorizationStateReader` with a viem
232
+ public client's `readContract`). Otherwise it stays spent, including after
233
+ `success: false`. If the store cannot be read or written, the payment is
234
+ refused. `reconcile()` also releases a reservation whose payment never
235
+ reached signing, but only after the longest time that payment could still be
236
+ deciding; a payment whose reservation was released is never signed. These are
237
+ local budgets, not Guardian-enforced budgets.
238
+
239
+ ## Not governed
240
+
241
+ - Code that signs with the wallet key directly, or any x402 client the guard
242
+ did not build.
243
+ - Hooks you add to the guard's clients afterwards. The guarded client refuses
244
+ new schemes, extensions and `onPaymentCreationFailure` hooks
245
+ (`GUARD_SEALED`), because a failure hook could return its own payload in
246
+ place of a refusal. Other hooks still run as you write them.
247
+ - `callToolWithPayment` on the MCP client with a payload made outside the
248
+ guard, and anything sent through the MCP client's `client` getter.
249
+ - Calls on the original axios instance instead of the one `guardedAxios`
250
+ returns. The guard refuses payments it sees there (no original URL), but
251
+ it cannot see code that bypasses the interceptors.
252
+ - Console USD-cent caps, spending budgets and recipient rules: they do not
253
+ apply to x402 payments.
254
+
255
+ Do not turn off x402's `spendControls`.
256
+
257
+ ## Refusal codes
258
+
259
+ Refusals throw `X402GuardRefused` (through x402 the message reads
260
+ `Failed to create payment payload: [guardian-x402 CODE] …`). Guardian's own
261
+ reason follows the code, for example `GUARDIAN_DENIED:TOOL_NOT_GRANTED`.
262
+
263
+ ## Example
264
+
265
+ [`examples/x402/simulated-payments.mjs`](../../examples/x402) (SIMULATED:
266
+ stand-in Guardian, in-memory server, throwaway key, no network).
267
+
268
+ ## License
269
+
270
+ Apache-2.0
package/dist/axios.js ADDED
@@ -0,0 +1,85 @@
1
+ import {
2
+ assertSupportedX402,
3
+ requestStorage
4
+ } from "./chunk-DJIAG2IA.js";
5
+
6
+ // src/axios.ts
7
+ import { randomBytes } from "node:crypto";
8
+ import { decodePaymentResponseHeader } from "@x402/core/http";
9
+ import { wrapAxiosWithPayment } from "@x402/axios";
10
+ var URL_ONLY = /* @__PURE__ */ new Set(["get", "delete", "head", "options"]);
11
+ var URL_DATA = /* @__PURE__ */ new Set(["post", "put", "patch", "postForm", "putForm", "patchForm"]);
12
+ function settleFrom(headers) {
13
+ const h = headers;
14
+ const raw = h?.["x-payment-response"] ?? h?.["X-PAYMENT-RESPONSE"];
15
+ if (typeof raw !== "string") return null;
16
+ try {
17
+ return decodePaymentResponseHeader(raw);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function guardedAxios(guard, instance) {
23
+ assertSupportedX402("@x402/axios");
24
+ wrapAxiosWithPayment(instance, guard.httpClient());
25
+ const run = (config, call) => {
26
+ let originalUrl = "";
27
+ try {
28
+ originalUrl = new URL(instance.getUri(config)).href;
29
+ } catch {
30
+ originalUrl = "";
31
+ }
32
+ const method = String(config.method ?? instance.defaults.method ?? "get").toUpperCase();
33
+ const ctx = {
34
+ id: `x4r_${randomBytes(8).toString("hex")}`,
35
+ info: { transport: "http", method, originalUrl, finalUrl: null },
36
+ signedEntries: [],
37
+ versions: /* @__PURE__ */ new Map()
38
+ };
39
+ return requestStorage.run(ctx, async () => {
40
+ const response = await call();
41
+ if (ctx.signedEntries.some((id) => ctx.versions.get(id) === 1)) {
42
+ await guard.recordV1Settlement(
43
+ ctx,
44
+ settleFrom(response?.headers)
45
+ );
46
+ }
47
+ return response;
48
+ });
49
+ };
50
+ return new Proxy(instance, {
51
+ apply(target, thisArg, args) {
52
+ const config = typeof args[0] === "string" ? { ...args[1] ?? {}, url: args[0] } : args[0] ?? {};
53
+ return run(config, () => Reflect.apply(target, thisArg, args));
54
+ },
55
+ get(target, prop, receiver) {
56
+ const value = Reflect.get(target, prop, receiver);
57
+ if (typeof prop !== "string" || typeof value !== "function") return value;
58
+ if (prop === "request") {
59
+ return (config) => run(config ?? {}, () => target.request(config));
60
+ }
61
+ if (URL_ONLY.has(prop)) {
62
+ return (url, config) => run(
63
+ { ...config ?? {}, url, method: prop },
64
+ () => value.call(target, url, config)
65
+ );
66
+ }
67
+ if (URL_DATA.has(prop)) {
68
+ const method = prop.replace("Form", "");
69
+ return (url, data, config) => run(
70
+ { ...config ?? {}, url, method },
71
+ () => value.call(
72
+ target,
73
+ url,
74
+ data,
75
+ config
76
+ )
77
+ );
78
+ }
79
+ return value;
80
+ }
81
+ });
82
+ }
83
+ export {
84
+ guardedAxios
85
+ };