agent402-client 0.8.17 → 0.8.19
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 +31 -9
- package/index.js +32 -19
- package/known-assets.js +37 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ instance) - the buy side of [Agentic Finance](https://agent402.tools/agentic-fin
|
|
|
5
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
7
|
dependencies); wallet-only tools settle via an x402- or MPP-wrapped fetch you
|
|
8
|
-
provide, or through a prepaid credits key
|
|
8
|
+
provide, or through a prepaid credits key. Results are cached, and every
|
|
9
9
|
send carries an `Idempotency-Key` that is stable per client and per operation, so
|
|
10
10
|
a retried lost response replays the paid answer on the credits and proof-of-work
|
|
11
11
|
paths (the wallet path is different: see "Retries and double charges").
|
|
@@ -71,11 +71,14 @@ const a = new Agent402({ fetch: payFetch });
|
|
|
71
71
|
const article = await a.call("extract", { url: "https://agent402.tools/guides/x402-in-5-minutes" });
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
##
|
|
74
|
+
## Paying with a credits key
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
A prepaid credits key (`a402_...`; agent402.tools/credits says whether packs are on
|
|
77
|
+
sale) pays for Agent402's own services: pass it
|
|
77
78
|
as `creditsKey` and the SDK sends `Authorization: Bearer a402_...` on wallet-only
|
|
78
|
-
calls (the wallet-scoped routes, memory and usage, refuse it and need a wallet
|
|
79
|
+
calls (the wallet-scoped routes, memory and usage, refuse it and need a wallet, as do
|
|
80
|
+
the routes that buy from an outside seller: the route-execute tiers,
|
|
81
|
+
`seller-payability`, `decide-execute` and the flight routes); the server authorizes
|
|
79
82
|
against the key's balance before the handler runs and debits the list price only
|
|
80
83
|
on a successful (200) response (the `X-Credits-Balance` header carries what is
|
|
81
84
|
left; credits never expire). A refused call throws with the balance; nothing is
|
|
@@ -116,7 +119,17 @@ What the server does with it depends on how the call was paid:
|
|
|
116
119
|
dedupe). So on the wallet path a retry after a lost response can settle twice;
|
|
117
120
|
the `Idempotency-Key` cannot prevent that on its own. If that matters for your
|
|
118
121
|
workload, use a credits key for the tools you retry, or keep the signed
|
|
119
|
-
request and resend it yourself.
|
|
122
|
+
request and resend it yourself. A resent signed request needs no key: the
|
|
123
|
+
server answers a request re-sent with the same signed EVM payment, to the same
|
|
124
|
+
route with the same body, from its first answer inside the replay window,
|
|
125
|
+
with no second settlement, and marks it `X-Replayed-Answer: 1` (identity-bound
|
|
126
|
+
routes, routes with a lasting effect and streamed calls excepted).
|
|
127
|
+
- **Circle Gateway batched option:** where the server offers it on Arc or Base,
|
|
128
|
+
the 402 carries a second accept whose `extra.name` is `GatewayWalletBatched`.
|
|
129
|
+
This package pays the plain accept on those networks (the stock x402 client
|
|
130
|
+
takes the first accept it can pay, and the plain one is first); the batched
|
|
131
|
+
one needs a client that signs under the Gateway domain against a Circle
|
|
132
|
+
Gateway deposit.
|
|
120
133
|
|
|
121
134
|
## Workflows (skill packs)
|
|
122
135
|
|
|
@@ -352,8 +365,13 @@ caps hold at signing by default.
|
|
|
352
365
|
`amount`, so each option is priced from a built-in table of known assets per
|
|
353
366
|
network (`KNOWN_ASSETS`: USDC on each Agent402 rail, USDG on Robinhood Chain,
|
|
354
367
|
with their on-chain decimals), never from a decimals value the seller sends. An
|
|
355
|
-
option in an asset outside the table cannot be priced
|
|
356
|
-
|
|
368
|
+
option in an asset outside the table cannot be priced. With the `x402Client`
|
|
369
|
+
passed, the signing guard drops such an option before anything is signed, so
|
|
370
|
+
it is skipped beside an option that can be priced and the call pays that one;
|
|
371
|
+
the call is refused only when no option can be priced. With a pre-wrapped
|
|
372
|
+
`fetch` and no `x402Client`, nothing stops the wallet from signing it, so a
|
|
373
|
+
capped call is refused before the wallet runs when any option in the `402`
|
|
374
|
+
cannot be priced. When a cap is set, the client preflights the `402` and checks the
|
|
357
375
|
ceiling against the **larger** of the advertised price (from the seller's
|
|
358
376
|
`/api/pricing`) and the amount the `402` quotes; if no `402` comes back
|
|
359
377
|
(FREE_MODE) it checks the advertised price. Caps hold under **concurrency** too:
|
|
@@ -361,7 +379,7 @@ each call reserves its amount synchronously, so N simultaneous calls can't each
|
|
|
361
379
|
pass against the same pre-commit total.
|
|
362
380
|
|
|
363
381
|
- **Zero dependencies** for the free/proof-of-work path (uses `node:crypto`).
|
|
364
|
-
- **Non-custodial:** paid settlement is your `@x402/fetch` / `mppx` fetch + wallet (or a prepaid credits key
|
|
382
|
+
- **Non-custodial:** paid settlement is your `@x402/fetch` / `mppx` fetch + wallet (or a prepaid credits key); this client never sees a private key.
|
|
365
383
|
- MIT licensed. Part of [Agent402](https://github.com/MikeyPetrillo/Agent402).
|
|
366
384
|
|
|
367
385
|
## Pick the settlement chain (`withNetworkPreference`)
|
|
@@ -385,7 +403,11 @@ const payFetch = wrapFetchWithPayment(fetch, client);
|
|
|
385
403
|
Short names map to CAIP-2 (`base`, `solana`, `polygon`, `arbitrum`,
|
|
386
404
|
`robinhood`); unknown entries pass through verbatim so future chains work
|
|
387
405
|
without a package update. If the preference matches none of a seller's
|
|
388
|
-
payment options it throws **before** any payment is signed.
|
|
406
|
+
payment options it throws **before** any payment is signed. Since 0.8.19 a
|
|
407
|
+
name resolves through the known-asset table (`NETWORK_NAMES`) to every id the
|
|
408
|
+
chain is spelled with on the wire, and an id to the ids of its chain, so
|
|
409
|
+
`["algorand"]`, or either spelling of Algorand mainnet, matches a seller that
|
|
410
|
+
uses the other.
|
|
389
411
|
|
|
390
412
|
|
|
391
413
|
## Only pay who you meant to (`withPayeeAllowlist`)
|
package/index.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
// const a = new Agent402({ fetch: payFetch });
|
|
20
20
|
import { createHash, randomBytes } from "node:crypto";
|
|
21
21
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
22
|
-
import { quoteOf, guardSigning } from "./known-assets.js";
|
|
23
|
-
export { KNOWN_ASSETS, knownAsset, acceptUsd } from "./known-assets.js";
|
|
22
|
+
import { quoteOf, guardSigning, networkIdsOf } from "./known-assets.js";
|
|
23
|
+
export { KNOWN_ASSETS, NETWORK_NAMES, knownAsset, acceptUsd, networkIdsOf } from "./known-assets.js";
|
|
24
24
|
import { distrustFetch } from "./tls-distrust.js";
|
|
25
25
|
export { distrustFetch, distrustDispatcher, distrustCheckServerIdentity, DISTRUSTED_SPKI_SHA256 } from "./tls-distrust.js";
|
|
26
26
|
import { awaitJob, shouldRequestAsync, AsyncJobPendingError, DEFAULT_ASYNC_TIMEOUT_MS } from "./async-job.js";
|
|
@@ -30,7 +30,7 @@ export { AsyncJobPendingError } from "./async-job.js";
|
|
|
30
30
|
// `User-Agent: agent402-client/<version>` - a standard header, no extra
|
|
31
31
|
// network calls - so a seller can attribute traffic (and settled payments)
|
|
32
32
|
// to this SDK. Product token only; nothing about the caller rides along.
|
|
33
|
-
const VERSION = "0.8.
|
|
33
|
+
const VERSION = "0.8.19";
|
|
34
34
|
const USER_AGENT = `agent402-client/${VERSION}`;
|
|
35
35
|
// 32MB: about a hundred times any realistic response from this catalog (the
|
|
36
36
|
// largest is a base64 image at a few MB), so it cannot break a legitimate
|
|
@@ -94,8 +94,8 @@ export class Agent402 {
|
|
|
94
94
|
* @param {string[]} [opts.payNetworks] the chains your pre-wrapped `fetch` pays on (e.g. ["base"], the
|
|
95
95
|
* names withNetworkPreference takes): without an x402Client the
|
|
96
96
|
* caps count the dearest accept on these chains, not on every chain
|
|
97
|
-
* @param {string} [opts.creditsKey] a prepaid card-credits key (a402_...)
|
|
98
|
-
*
|
|
97
|
+
* @param {string} [opts.creditsKey] a prepaid card-credits key (a402_...) - pays wallet-only tools on
|
|
98
|
+
* Agent402's own services when no payFetch is given
|
|
99
99
|
* @param {number|null} [opts.maxResponseBytes=33554432]
|
|
100
100
|
* Hard ceiling on a response body, enforced BEFORE it is parsed. null disables it.
|
|
101
101
|
* @param {{id:string, validate:function}} [opts.outputValidator]
|
|
@@ -160,7 +160,7 @@ export class Agent402 {
|
|
|
160
160
|
if (x402Client) this.guardX402Client(x402Client);
|
|
161
161
|
// The chains a pre-wrapped fetch pays on, as CAIP-2 ids (null = unknown).
|
|
162
162
|
this._payNetworks = Array.isArray(payNetworks) && payNetworks.length
|
|
163
|
-
? new Set(payNetworks.
|
|
163
|
+
? new Set(payNetworks.flatMap((n) => networkIdsOf(n)).map((id) => id.toLowerCase()))
|
|
164
164
|
: null;
|
|
165
165
|
// A response-size ceiling, enforced before parsing. See _readJson.
|
|
166
166
|
this.maxResponseBytes = maxResponseBytes === null ? null : (numOrNull(maxResponseBytes) ?? DEFAULT_MAX_RESPONSE_BYTES);
|
|
@@ -581,7 +581,7 @@ export class Agent402 {
|
|
|
581
581
|
}
|
|
582
582
|
const r = await send(); // no wallet - succeeds only on a FREE_MODE instance
|
|
583
583
|
if (r.ok) return this._deliverResponse(slug, r, cacheKey, cache, validator, { maxResponseBytes, paid: false });
|
|
584
|
-
throw this._fail(slug, r, `call "${slug}" failed: HTTP ${r.status} - wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch) or { creditsKey } (a prepaid credits key
|
|
584
|
+
throw this._fail(slug, r, `call "${slug}" failed: HTTP ${r.status} - wallet-only tool; construct with { fetch: payFetch } (an @x402/fetch-wrapped fetch) or { creditsKey } (a prepaid credits key; routes that buy from an outside seller need a wallet)`);
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
// Free (compute-payable) tool: succeeds plainly on a FREE_MODE instance,
|
|
@@ -656,8 +656,9 @@ export class Agent402 {
|
|
|
656
656
|
* "guarded" (the signing guard holds every signature to the caps, so the
|
|
657
657
|
* cheapest known accept is what can be paid, and unknown ones are dropped
|
|
658
658
|
* there), "wallet" (no guard: the wallet may sign any accept, so the dearest
|
|
659
|
-
* one counts, among the chains in `payNetworks` when they are given, and
|
|
660
|
-
*
|
|
659
|
+
* one counts, among the chains in `payNetworks` when they are given, and an
|
|
660
|
+
* accept this client cannot price is refused here, since nothing stops the
|
|
661
|
+
* wallet from signing it), or "credits" (nothing is signed:
|
|
661
662
|
* the credits gate debits the route's own quote, and no accept is below it,
|
|
662
663
|
* since a chain premium or price floor only raises an accept, so the
|
|
663
664
|
* cheapest known accept counts).
|
|
@@ -682,11 +683,19 @@ export class Agent402 {
|
|
|
682
683
|
return listUsd; /* no readable 402: the advertised price */
|
|
683
684
|
}
|
|
684
685
|
if (!q) return listUsd;
|
|
685
|
-
|
|
686
|
+
// An option in an asset this client cannot price: with no guard the wallet
|
|
687
|
+
// may sign it, for an amount no cap was checked against, so one such
|
|
688
|
+
// option refuses the call; the guard drops it before signing, so beside a
|
|
689
|
+
// priced option it is skipped there; a credits gate signs nothing, so it
|
|
690
|
+
// is ignored. An offer with nothing priceable is refused under a cap in
|
|
691
|
+
// either signing mode.
|
|
692
|
+
const unpriceable = q.max == null || (mode === "wallet" && q.unknown > 0);
|
|
693
|
+
if (unpriceable) {
|
|
686
694
|
if (mode === "credits") return listUsd;
|
|
687
|
-
throw new SpendingLimitError(
|
|
688
|
-
`refusing to pay for "${slug}" -
|
|
689
|
-
|
|
695
|
+
throw new SpendingLimitError(q.max == null
|
|
696
|
+
? `refusing to pay for "${slug}" - no payment option it offers is in an asset this client can price, and a capped wallet signs only what it can price${mode === "wallet" ? " (pass your x402Client to guard signing and pay the options it can price)" : ""}`
|
|
697
|
+
: `refusing to pay for "${slug}" - ${q.unknown} of its ${q.total} payment options ${q.unknown === 1 ? "is" : "are"} in an asset this client cannot price, and without the signing guard the wallet may sign any of them (pass your x402Client to guard signing and pay the options it can price)`,
|
|
698
|
+
{ limit: "knownAsset", slug, priceUsd: null, unknownOptions: q.unknown });
|
|
690
699
|
}
|
|
691
700
|
const quoted = mode === "wallet" ? q.max : q.min;
|
|
692
701
|
// Larger of the two: an understated catalog must not lower the ceiling
|
|
@@ -961,9 +970,12 @@ function resultCacheKey(slug, params, validator) {
|
|
|
961
970
|
* withNetworkPreference(x402client, ["robinhood"]); // or ["eip155:4663"]
|
|
962
971
|
* const payFetch = wrapFetchWithPayment(fetch, x402client);
|
|
963
972
|
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
973
|
+
* A name resolves through the known-asset table to every id that chain is
|
|
974
|
+
* spelled with on the wire (./known-assets.js NETWORK_NAMES), and an id to
|
|
975
|
+
* the ids of its chain, so either spelling of a chain matches the other;
|
|
976
|
+
* unknown entries pass through verbatim so future chains work without a
|
|
977
|
+
* package update. Throws (before paying) when the preference matches none of
|
|
978
|
+
* a seller's payment options.
|
|
967
979
|
*/
|
|
968
980
|
export const NETWORK_CAIP2 = {
|
|
969
981
|
base: "eip155:8453",
|
|
@@ -980,16 +992,17 @@ export const NETWORK_CAIP2 = {
|
|
|
980
992
|
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
981
993
|
"solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
982
994
|
stellar: "stellar:pubnet",
|
|
983
|
-
algorand: "algorand:
|
|
995
|
+
algorand: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k",
|
|
984
996
|
};
|
|
985
997
|
|
|
986
998
|
export function withNetworkPreference(client, networks) {
|
|
987
|
-
|
|
999
|
+
// Every id each preference names, in preference order, once each.
|
|
1000
|
+
const prefs = [...new Set((networks || []).flatMap((n) => networkIdsOf(n)))];
|
|
988
1001
|
if (!prefs.length) return client;
|
|
989
1002
|
const orig = client.createPaymentPayload.bind(client);
|
|
990
1003
|
client.createPaymentPayload = (paymentRequired) => {
|
|
991
1004
|
const list = Array.isArray(paymentRequired?.accepts) ? paymentRequired.accepts : [];
|
|
992
|
-
const picked = prefs.flatMap((
|
|
1005
|
+
const picked = prefs.flatMap((id) => list.filter((a) => String(a?.network || "").toLowerCase() === id.toLowerCase()));
|
|
993
1006
|
if (!picked.length) {
|
|
994
1007
|
const offered = [...new Set(list.map((a) => a?.network).filter(Boolean))];
|
|
995
1008
|
throw new Error(`network preference [${prefs.join(", ")}] matched none of the seller's payment options [${offered.join(", ")}]`);
|
package/known-assets.js
CHANGED
|
@@ -32,10 +32,46 @@ export const KNOWN_ASSETS = Object.freeze({
|
|
|
32
32
|
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": [{ asset: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", decimals: 6, symbol: "USDC" }], // devnet
|
|
33
33
|
// Stellar: the USDC Stellar Asset Contract (7 decimals on Stellar).
|
|
34
34
|
"stellar:pubnet": [{ asset: "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", decimals: 7, symbol: "USDC" }],
|
|
35
|
-
// Algorand: USDC is ASA 31566704.
|
|
35
|
+
// Algorand: USDC is ASA 31566704. Mainnet is named by its CAIP-2 id (the
|
|
36
|
+
// first 32 characters of the base64 genesis hash) and, by sellers that
|
|
37
|
+
// predate it, by the full hash.
|
|
38
|
+
"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k": [{ asset: "31566704", decimals: 6, symbol: "USDC" }],
|
|
36
39
|
"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=": [{ asset: "31566704", decimals: 6, symbol: "USDC" }],
|
|
37
40
|
});
|
|
38
41
|
|
|
42
|
+
/** Chain name -> every network id in the table that names that chain. A
|
|
43
|
+
* chain spelled two ways on the wire lists both, so a buyer's preference by
|
|
44
|
+
* name, or by either spelling, matches a seller's accept in the other. */
|
|
45
|
+
export const NETWORK_NAMES = Object.freeze({
|
|
46
|
+
base: ["eip155:8453"],
|
|
47
|
+
"base-sepolia": ["eip155:84532"],
|
|
48
|
+
polygon: ["eip155:137"],
|
|
49
|
+
arbitrum: ["eip155:42161"],
|
|
50
|
+
monad: ["eip155:143"],
|
|
51
|
+
celo: ["eip155:42220"],
|
|
52
|
+
avalanche: ["eip155:43114"],
|
|
53
|
+
sei: ["eip155:1329"],
|
|
54
|
+
optimism: ["eip155:10"],
|
|
55
|
+
arc: ["eip155:5042"],
|
|
56
|
+
robinhood: ["eip155:4663"],
|
|
57
|
+
solana: ["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"],
|
|
58
|
+
"solana-devnet": ["solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"],
|
|
59
|
+
stellar: ["stellar:pubnet"],
|
|
60
|
+
algorand: ["algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k", "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/** The network ids a preference names: a chain name's ids; for an id, every
|
|
64
|
+
* id of the chain it belongs to (so either spelling matches the other); an
|
|
65
|
+
* id the table does not know passes through as itself. */
|
|
66
|
+
export function networkIdsOf(pref) {
|
|
67
|
+
const p = String(pref || "").trim();
|
|
68
|
+
if (!p) return [];
|
|
69
|
+
const byName = NETWORK_NAMES[p.toLowerCase()];
|
|
70
|
+
if (byName) return [...byName];
|
|
71
|
+
for (const ids of Object.values(NETWORK_NAMES)) if (ids.some((id) => id.toLowerCase() === p.toLowerCase())) return [...ids];
|
|
72
|
+
return [p];
|
|
73
|
+
}
|
|
74
|
+
|
|
39
75
|
const EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
40
76
|
// EVM addresses compare without case (checksum casing is presentation);
|
|
41
77
|
// base58, Stellar and Algorand identifiers compare exactly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.19",
|
|
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",
|