agent402-client 0.8.18 → 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 +12 -3
- package/index.js +29 -16
- package/known-assets.js +37 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -365,8 +365,13 @@ caps hold at signing by default.
|
|
|
365
365
|
`amount`, so each option is priced from a built-in table of known assets per
|
|
366
366
|
network (`KNOWN_ASSETS`: USDC on each Agent402 rail, USDG on Robinhood Chain,
|
|
367
367
|
with their on-chain decimals), never from a decimals value the seller sends. An
|
|
368
|
-
option in an asset outside the table cannot be priced
|
|
369
|
-
|
|
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
|
|
370
375
|
ceiling against the **larger** of the advertised price (from the seller's
|
|
371
376
|
`/api/pricing`) and the amount the `402` quotes; if no `402` comes back
|
|
372
377
|
(FREE_MODE) it checks the advertised price. Caps hold under **concurrency** too:
|
|
@@ -398,7 +403,11 @@ const payFetch = wrapFetchWithPayment(fetch, client);
|
|
|
398
403
|
Short names map to CAIP-2 (`base`, `solana`, `polygon`, `arbitrum`,
|
|
399
404
|
`robinhood`); unknown entries pass through verbatim so future chains work
|
|
400
405
|
without a package update. If the preference matches none of a seller's
|
|
401
|
-
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.
|
|
402
411
|
|
|
403
412
|
|
|
404
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
|
|
@@ -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);
|
|
@@ -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",
|