agent402-client 0.6.10 → 0.7.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 +64 -18
- package/index.js +52 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
A tiny buyer-side client for [Agent402](https://agent402.tools) (and any Agent402
|
|
4
4
|
instance) - the buy side of [Agentic Finance](https://agent402.tools/agentic-finance),
|
|
5
|
-
agents paying per request over x402 or MPP. **Resolve a task to a tool, then call it
|
|
5
|
+
agents paying per request over x402 or MPP. **Resolve a task to a tool, then call it - with payment handled for
|
|
6
6
|
you.** Free pure-CPU tools settle with a built-in proof-of-work (no wallet, zero
|
|
7
|
-
dependencies); wallet-only tools settle via an x402-wrapped fetch you
|
|
8
|
-
|
|
9
|
-
never double-charges.
|
|
7
|
+
dependencies); wallet-only tools settle via an x402- or MPP-wrapped fetch you
|
|
8
|
+
provide, or by card through a prepaid credits key. Results are cached, and
|
|
9
|
+
retries reuse an `Idempotency-Key` so a lost response never double-charges.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install agent402-client
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Runnable copy of the free-tier quickstart below: [`examples/hello-agent402.js`](https://github.com/MikeyPetrillo/Agent402/blob/main/examples/hello-agent402.js)
|
|
15
|
+
Runnable copy of the free-tier quickstart below: [`examples/hello-agent402.js`](https://github.com/MikeyPetrillo/Agent402/blob/main/examples/hello-agent402.js) - discover a tool and call it in ~15 lines, no wallet.
|
|
16
16
|
|
|
17
17
|
## Free tier (proof-of-work, no wallet)
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ const a = new Agent402(); // → https://agent402.tools
|
|
|
25
25
|
const matches = await a.find("extract the article from a url");
|
|
26
26
|
// → [{ slug: "extract", route, price, inputSchema, example, … }]
|
|
27
27
|
|
|
28
|
-
// Call it
|
|
28
|
+
// Call it - proof-of-work is solved automatically for free tools.
|
|
29
29
|
const out = await a.call("hash", { text: "hello world", algo: "sha256" });
|
|
30
30
|
console.log(out.hex);
|
|
31
31
|
```
|
|
@@ -69,10 +69,39 @@ const a = new Agent402({ fetch: payFetch });
|
|
|
69
69
|
const article = await a.call("extract", { url: "https://example.com/article" });
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Pay by card instead of a wallet (prepaid credits)
|
|
73
|
+
|
|
74
|
+
Buy a credits pack ($20 / $50 / $100) at https://agent402.tools/credits, claim the
|
|
75
|
+
`a402_...` key once, and pass it as `creditsKey`. The SDK then sends
|
|
76
|
+
`Authorization: Bearer a402_...` on wallet-only calls; the server authorizes
|
|
77
|
+
against the key's balance before the handler runs and debits the list price only
|
|
78
|
+
on a successful (200) response (the `X-Credits-Balance` header carries what is
|
|
79
|
+
left; credits never expire). A refused call throws with the balance and a top-up
|
|
80
|
+
link; nothing is debited.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const client = new Agent402({ creditsKey: "a402_..." });
|
|
84
|
+
await client.call("whois", { domain: "example.com" }); // debited per successful call
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The same `maxPerCallUsd` / `dailyLimitUsd` / `maxPerHostUsd` caps apply (a
|
|
88
|
+
credits call reserves its price like a wallet call). A payment `fetch` still wins
|
|
89
|
+
when both are given, and free pure-CPU tools keep settling with proof-of-work.
|
|
90
|
+
The same Bearer header also reads the balance directly, e.g. `curl -H "Authorization: Bearer a402_..." https://agent402.tools/api/credits/balance`.
|
|
91
|
+
|
|
92
|
+
## Retries never double-charge
|
|
93
|
+
|
|
94
|
+
Every paid call the SDK makes carries an `Idempotency-Key`, so a retry of a
|
|
95
|
+
call whose response was lost replays the original result instead of paying
|
|
96
|
+
again. If your x402 client attaches the standard `payment-identifier`
|
|
97
|
+
extension to its payloads instead, Agent402 honours that id the same way (an
|
|
98
|
+
exact retry with the same credential replays; a fresh authorization with the
|
|
99
|
+
same id is a new payment).
|
|
100
|
+
|
|
72
101
|
## Workflows (skill packs)
|
|
73
102
|
|
|
74
|
-
For jobs that no single tool covers
|
|
75
|
-
brief"
|
|
103
|
+
For jobs that no single tool covers - e.g. "audit a domain", "build a stock
|
|
104
|
+
brief" - Agent402 ships curated multi-tool **skill packs**: 5–7 catalog tools
|
|
76
105
|
composed into a Claude-ready task template. Discover them the same way you'd
|
|
77
106
|
discover a tool:
|
|
78
107
|
|
|
@@ -87,7 +116,7 @@ const { messages } = await a.getWorkflowPrompt("security-audit", { domain: "stri
|
|
|
87
116
|
|
|
88
117
|
## Discover the live x402 economy
|
|
89
118
|
|
|
90
|
-
Want to see who's actually getting paid on x402 right now
|
|
119
|
+
Want to see who's actually getting paid on x402 right now - not just what tools
|
|
91
120
|
this service exposes? `topSellers()` returns the live leaderboard of sellers
|
|
92
121
|
settling USDC (primarily on Base) in the last ~24h, derived from on-chain transfers. Free
|
|
93
122
|
to call (no payment, no proof-of-work):
|
|
@@ -104,12 +133,12 @@ await a.topSellers({ sort: "calls", include: "all" });
|
|
|
104
133
|
|
|
105
134
|
| Method | What |
|
|
106
135
|
|---|---|
|
|
107
|
-
| `new Agent402({ baseUrl?, fetch?, cache?, fetchImpl?, maxPerCallUsd?, dailyLimitUsd?, maxPerHostUsd? })` | `fetch` is your x402-wrapped fetch for paid tools (optional); `cache` (default `true`) memoizes deterministic results; the three USD caps set optional spending limits (see below) |
|
|
136
|
+
| `new Agent402({ baseUrl?, fetch?, creditsKey?, cache?, fetchImpl?, maxPerCallUsd?, dailyLimitUsd?, maxPerHostUsd? })` | `fetch` is your x402- or MPP-wrapped fetch for paid tools (optional); `creditsKey` is a prepaid card-credits key (`a402_...`) used for paid tools when no `fetch` is given; `cache` (default `true`) memoizes deterministic results; the three USD caps set optional spending limits (see below) |
|
|
108
137
|
| `await a.find(task, { k = 5 })` | Resolve a plain-language task to the best-matching tools (route, price, schema, example) |
|
|
109
138
|
| `await a.findWorkflows(task, { k = 2 })` | Resolve a task to matching multi-tool workflow templates (skill packs) |
|
|
110
139
|
| `await a.getWorkflowPrompt(slug, args)` | Fetch the rendered prompt messages for a skill pack with arguments substituted in |
|
|
111
140
|
| `await a.topSellers({ limit?, sort?, include? })` | Live x402 leaderboard: which sellers are settling the most USDC (primarily on Base) in the last ~24h (free, no payment) |
|
|
112
|
-
| `await a.call(slug, params, { idempotencyKey?, cache? })` | Call a tool; auto-pays (PoW for free tools
|
|
141
|
+
| `await a.call(slug, params, { idempotencyKey?, cache? })` | Call a tool; auto-pays (PoW for free tools; your payment `fetch` or the credits key for wallet-only); returns the JSON result |
|
|
113
142
|
| `Agent402.solvePow(pow)` | Solve a proof-of-work challenge object → an `X-Pow-Solution` value |
|
|
114
143
|
| `a.spendingSummary()` | Rolling-24h paid spend so far: `{ dailyUsd, calls, byHost, limits }` |
|
|
115
144
|
| `a.clearCache()` | Drop the in-memory result cache |
|
|
@@ -118,7 +147,7 @@ await a.topSellers({ sort: "calls", include: "all" });
|
|
|
118
147
|
|
|
119
148
|
By default the client pays whatever a tool costs. Set optional hard ceilings and a
|
|
120
149
|
call that would exceed one is **refused before any payment is signed** (it throws
|
|
121
|
-
`SpendingLimitError`
|
|
150
|
+
`SpendingLimitError` - no funds move):
|
|
122
151
|
|
|
123
152
|
```js
|
|
124
153
|
import { Agent402, SpendingLimitError } from "agent402-client";
|
|
@@ -137,29 +166,29 @@ try {
|
|
|
137
166
|
}
|
|
138
167
|
```
|
|
139
168
|
|
|
140
|
-
Only **settled** paid calls count against the rolling window
|
|
169
|
+
Only **settled** paid calls count against the rolling window - a blocked or failed
|
|
141
170
|
call never consumes budget. Free proof-of-work calls are never counted. Omit a cap
|
|
142
171
|
(or leave it `null`) for no limit; with none set, behavior is unchanged.
|
|
143
172
|
|
|
144
173
|
**What the caps check.** When a cap is set, the client preflights the `402` and
|
|
145
174
|
checks the ceiling against the **larger** of the advertised price (from the
|
|
146
|
-
seller's `/api/pricing`) and the amount the `402` challenge actually quotes
|
|
175
|
+
seller's `/api/pricing`) and the amount the `402` challenge actually quotes - so a
|
|
147
176
|
server that under-advertises and then quotes more in the `402` is refused *before*
|
|
148
177
|
your wallet fetch signs anything. If the `402` can't be read (FREE_MODE, or an
|
|
149
178
|
unrecognized challenge shape) it falls back to the advertised price rather than
|
|
150
179
|
block a legitimate payment. Caps hold under **concurrency** too: each call reserves
|
|
151
180
|
its amount synchronously, so N simultaneous calls can't each pass against the same
|
|
152
|
-
pre-commit total. (The `402` amount is derived assuming stablecoin settlement
|
|
153
|
-
`atomic / 10^decimals ≈ USD`
|
|
181
|
+
pre-commit total. (The `402` amount is derived assuming stablecoin settlement -
|
|
182
|
+
`atomic / 10^decimals ≈ USD` - which matches x402's USDC/USDG rails.)
|
|
154
183
|
|
|
155
184
|
- **Zero dependencies** for the free/proof-of-work path (uses `node:crypto`).
|
|
156
|
-
- **Non-custodial:** paid settlement is your `@x402/fetch` + wallet; this client never sees
|
|
185
|
+
- **Non-custodial:** paid settlement is your `@x402/fetch` / `mppx` fetch + wallet (or a prepaid credits key you bought); this client never sees a private key.
|
|
157
186
|
- MIT licensed. Part of [Agent402](https://github.com/MikeyPetrillo/Agent402).
|
|
158
187
|
|
|
159
188
|
## Pick the settlement chain (`withNetworkPreference`)
|
|
160
189
|
|
|
161
190
|
Multi-chain sellers list Base first, so an unmodified x402 client effectively
|
|
162
|
-
always settles there. To pin a chain
|
|
191
|
+
always settles there. To pin a chain - e.g. **USDG on Robinhood Chain** -
|
|
163
192
|
wrap your client before building the fetch:
|
|
164
193
|
|
|
165
194
|
```js
|
|
@@ -179,6 +208,23 @@ Short names map to CAIP-2 (`base`, `solana`, `polygon`, `arbitrum`,
|
|
|
179
208
|
without a package update. If the preference matches none of a seller's
|
|
180
209
|
payment options it throws **before** any payment is signed.
|
|
181
210
|
|
|
211
|
+
|
|
212
|
+
## Only pay who you meant to (`withPayeeAllowlist`)
|
|
213
|
+
|
|
214
|
+
The buyer-side mirror of a spend control: bound WHO gets paid, not just how
|
|
215
|
+
much. Wrap your x402 client before `wrapFetchWithPayment` and any 402 whose
|
|
216
|
+
`accepts` would send funds to an address outside the list is refused before a
|
|
217
|
+
signature exists (a routed or redirected seller can never collect).
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
import { withPayeeAllowlist } from "agent402-client";
|
|
221
|
+
withPayeeAllowlist(client, ["0xYourSellerPayTo", "0xAnother"]); // 0x addresses compare case-insensitively
|
|
222
|
+
const payFetch = wrapFetchWithPayment(fetch, client);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Pairs with `maxPerCallUsd` / `dailyLimitUsd` (how much) and
|
|
226
|
+
`withNetworkPreference` (which chain).
|
|
227
|
+
|
|
182
228
|
## Legal
|
|
183
229
|
|
|
184
230
|
Use of the hosted instance at agent402.tools is subject to its [Terms of Service](https://agent402.tools/terms) (acceptable-use policy included) and [Privacy Policy](https://agent402.tools/privacy). This package is MIT-licensed; the hosted server is AGPL-3.0. Both are provided as-is without warranty, and self-hosted deployments are their operator's responsibility.
|
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { createHash } from "node:crypto";
|
|
|
18
18
|
// `User-Agent: agent402-client/<version>` — a standard header, no extra
|
|
19
19
|
// network calls — so a seller can attribute traffic (and settled payments)
|
|
20
20
|
// to this SDK. Product token only; nothing about the caller rides along.
|
|
21
|
-
const VERSION = "0.
|
|
21
|
+
const VERSION = "0.7.0";
|
|
22
22
|
const USER_AGENT = `agent402-client/${VERSION}`;
|
|
23
23
|
|
|
24
24
|
const leadingZeroBits = (buf) => { let n = 0; for (const b of buf) { if (b === 0) { n += 8; continue; } n += Math.clz32(b) - 24; break; } return n; };
|
|
@@ -33,9 +33,12 @@ export class Agent402 {
|
|
|
33
33
|
* @param {number} [opts.maxPerCallUsd] hard ceiling on a single paid call (USD); over → SpendingLimitError before paying
|
|
34
34
|
* @param {number} [opts.dailyLimitUsd] hard ceiling on rolling-24h paid spend (USD)
|
|
35
35
|
* @param {number} [opts.maxPerHostUsd] hard ceiling on rolling-24h paid spend to one seller host (USD)
|
|
36
|
+
* @param {string} [opts.creditsKey] a prepaid card-credits key (a402_...) from agent402.tools/credits -
|
|
37
|
+
* pays wallet-only tools by card when no payFetch is given
|
|
36
38
|
*/
|
|
37
39
|
constructor({ baseUrl = "https://agent402.tools", fetch: payFetch, cache = true, fetchImpl = globalThis.fetch,
|
|
38
|
-
maxPerCallUsd = null, dailyLimitUsd = null, maxPerHostUsd = null } = {}) {
|
|
40
|
+
maxPerCallUsd = null, dailyLimitUsd = null, maxPerHostUsd = null, creditsKey = null } = {}) {
|
|
41
|
+
this.creditsKey = typeof creditsKey === "string" && /^a402_[A-Za-z0-9_-]{32,64}$/.test(creditsKey) ? creditsKey : null;
|
|
39
42
|
if (typeof fetchImpl !== "function") throw new Error("No fetch available — pass { fetchImpl } on Node < 18");
|
|
40
43
|
this.baseUrl = String(baseUrl).replace(/\/$/, "");
|
|
41
44
|
this.payFetch = payFetch || null;
|
|
@@ -227,9 +230,27 @@ export class Agent402 {
|
|
|
227
230
|
throw e;
|
|
228
231
|
}
|
|
229
232
|
}
|
|
233
|
+
if (this.creditsKey) {
|
|
234
|
+
// Prepaid card credits: the server authorizes against the key's balance
|
|
235
|
+
// before the handler and debits only on a 200 (X-Credits-Balance tells
|
|
236
|
+
// what is left). The same spend caps apply as on the wallet path.
|
|
237
|
+
const host = hostOf(this.baseUrl);
|
|
238
|
+
const usd = parseUsd(tool.price);
|
|
239
|
+
const reservation = this._spendReserve(host, usd, slug);
|
|
240
|
+
try {
|
|
241
|
+
const r = await send({ Authorization: `Bearer ${this.creditsKey}` });
|
|
242
|
+
if (r.status === 402) {
|
|
243
|
+
const body = await r.json().catch(() => ({}));
|
|
244
|
+
throw new Error(`call "${slug}" refused by credits: ${body.error || "payment required"}${body.balanceUsd != null ? ` (balance $${body.balanceUsd})` : ""} - top up at ${body.topup || `${this.baseUrl}/credits`}`);
|
|
245
|
+
}
|
|
246
|
+
if (!r.ok) throw new Error(`call "${slug}" failed: HTTP ${r.status}`);
|
|
247
|
+
this._spendSettle(reservation);
|
|
248
|
+
return this._store(cacheKey, await r.json(), cache);
|
|
249
|
+
} catch (e) { this._spendRelease(reservation); throw e; }
|
|
250
|
+
}
|
|
230
251
|
const r = await send(); // no wallet — succeeds only on a FREE_MODE instance
|
|
231
252
|
if (r.ok) return this._store(cacheKey, await r.json(), cache);
|
|
232
|
-
throw new Error(`call "${slug}" failed: HTTP ${r.status} — wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch)`);
|
|
253
|
+
throw new Error(`call "${slug}" failed: HTTP ${r.status} — wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch) or { creditsKey } (prepaid card credits from ${this.baseUrl}/credits)`);
|
|
233
254
|
}
|
|
234
255
|
|
|
235
256
|
// Free (compute-payable) tool: succeeds plainly on a FREE_MODE instance,
|
|
@@ -402,4 +423,32 @@ export function withNetworkPreference(client, networks) {
|
|
|
402
423
|
return client;
|
|
403
424
|
}
|
|
404
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Payee allowlist: refuse to pay ANY 402 whose accepts would send funds to an
|
|
428
|
+
* address outside `payees` - the buyer-side mirror of a spend control (CDP's
|
|
429
|
+
* CdpX402Client bounds amounts and networks; this bounds WHO gets paid). Same
|
|
430
|
+
* wrapping style as withNetworkPreference: the payment-aware fetch sees a
|
|
431
|
+
* filtered `accepts`, so a quote that names an unknown payTo is never paid -
|
|
432
|
+
* it throws before any signature exists. Addresses compare case-insensitively
|
|
433
|
+
* for 0x (EVM) and exactly otherwise (base58/Stellar are case-sensitive).
|
|
434
|
+
* Works on any x402Client (createPaymentPayload) - register it before
|
|
435
|
+
* wrapFetchWithPayment.
|
|
436
|
+
*/
|
|
437
|
+
export function withPayeeAllowlist(client, payees) {
|
|
438
|
+
const norm = (a) => (typeof a === "string" && /^0x[0-9a-fA-F]{40}$/.test(a) ? a.toLowerCase() : String(a || "").trim());
|
|
439
|
+
const allowed = new Set((payees || []).map(norm).filter(Boolean));
|
|
440
|
+
if (!allowed.size) throw new Error("withPayeeAllowlist: at least one payee address is required");
|
|
441
|
+
const orig = client.createPaymentPayload.bind(client);
|
|
442
|
+
client.createPaymentPayload = (paymentRequired) => {
|
|
443
|
+
const list = Array.isArray(paymentRequired?.accepts) ? paymentRequired.accepts : [];
|
|
444
|
+
const picked = list.filter((a) => allowed.has(norm(a?.payTo)));
|
|
445
|
+
if (!picked.length) {
|
|
446
|
+
const offered = [...new Set(list.map((a) => a?.payTo).filter(Boolean))];
|
|
447
|
+
throw new Error(`payee allowlist refused this quote: the seller asks to be paid at [${offered.join(", ")}], none of which is allowlisted`);
|
|
448
|
+
}
|
|
449
|
+
return orig({ ...paymentRequired, accepts: picked });
|
|
450
|
+
};
|
|
451
|
+
return client;
|
|
452
|
+
}
|
|
453
|
+
|
|
405
454
|
export default Agent402;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Agent402 buyer SDK: resolve a task to a tool across 500+ pay-per-call endpoints and call it with payment handled - free via proof-of-work, or over x402 or MPP (a stock mppx fetch works) with a wallet you keep; the buy side of Agentic Finance. Caching, idempotent retries (Idempotency-Key), non-custodial, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|