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 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, and a capped wallet never
369
- signs it. When a cap is set, the client preflights the `402` and checks the
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.18";
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.map((n) => String(NETWORK_CAIP2[String(n).trim().toLowerCase()] || String(n).trim()).toLowerCase()))
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 one
660
- * that cannot be priced is refused here), or "credits" (nothing is signed:
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
- if (q.max == null || (mode === "wallet" && q.unknown > 0)) {
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}" - ${q.max == null ? "no" : "a"} payment option it offers is in an asset this client cannot 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)" : ""}`,
689
- { limit: "knownAsset", slug, priceUsd: null, unknownOptions: q.unknown });
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
- * Short names map to CAIP-2; unknown entries pass through verbatim so future
965
- * chains work without a package update. Throws (before paying) when the
966
- * preference matches none of a seller's payment options.
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:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
995
+ algorand: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k",
984
996
  };
985
997
 
986
998
  export function withNetworkPreference(client, networks) {
987
- const prefs = (networks || []).map((n) => NETWORK_CAIP2[String(n).trim().toLowerCase()] || String(n).trim());
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((caip2) => list.filter((a) => String(a?.network || "").toLowerCase() === caip2.toLowerCase()));
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.18",
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",