@agentx402-ai/core 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The AgentKV Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @agentx402-ai/core
2
+
3
+ Shared x402/EIP-712 platform SDK plumbing — auth, payment, usage, error, and retry handling —
4
+ extracted from `@agentkv/client` for reuse across future `agentx402` service SDKs.
5
+
6
+ ## Error handling
7
+
8
+ `@agentkv/client` re-exports this package's `AgentXError` as `AgentKVError`, so both names refer
9
+ to the same class object and `instanceof AgentKVError` holds across the workspace. This relies on
10
+ a single resolved copy of `@agentx402-ai/core` — if a downstream project ever resolves two
11
+ incompatible majors of it (e.g. one dependency pinning `^0.1.0` and another `^1.0.0`), each copy's
12
+ `AgentXError` is a distinct class, and `instanceof` checks silently fail across them.
13
+
14
+ ## License
15
+
16
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentKVError: () => AgentXError,
24
+ AgentXError: () => AgentXError,
25
+ DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
26
+ EIP712_DOMAIN_NAME: () => EIP712_DOMAIN_NAME,
27
+ EIP712_DOMAIN_VERSION: () => EIP712_DOMAIN_VERSION,
28
+ MAX_AUTH_WINDOW_SEC: () => MAX_AUTH_WINDOW_SEC,
29
+ SpendCapError: () => SpendCapError,
30
+ buildBearerHeaders: () => buildBearerHeaders,
31
+ buildIdentityHeaders: () => buildIdentityHeaders,
32
+ buildPaymentHeader: () => buildPaymentHeader,
33
+ chainIdFromCaip2: () => chainIdFromCaip2,
34
+ challengePriceUsd: () => challengePriceUsd,
35
+ decodeBase64Utf8: () => decodeBase64Utf8,
36
+ fetchWithRetry: () => fetchWithRetry,
37
+ freshNonce: () => freshNonce,
38
+ nonceFromIdempotencyKey: () => nonceFromIdempotencyKey,
39
+ nowSec: () => nowSec,
40
+ retryDelay: () => retryDelay
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/errors.ts
45
+ var AgentXError = class extends Error {
46
+ constructor(message, code, status) {
47
+ super(message);
48
+ this.code = code;
49
+ this.status = status;
50
+ this.name = "AgentKVError";
51
+ }
52
+ code;
53
+ status;
54
+ };
55
+ var SpendCapError = class extends AgentXError {
56
+ constructor(message) {
57
+ super(message, "spend_cap_exceeded");
58
+ this.name = "SpendCapError";
59
+ }
60
+ };
61
+
62
+ // src/idempotency.ts
63
+ var import_viem = require("viem");
64
+ function freshNonce() {
65
+ return (0, import_viem.toHex)(crypto.getRandomValues(new Uint8Array(32)));
66
+ }
67
+ function nonceFromIdempotencyKey(key) {
68
+ return (0, import_viem.keccak256)((0, import_viem.stringToHex)(key));
69
+ }
70
+
71
+ // src/payment.ts
72
+ var import_http = require("@x402/core/http");
73
+ var import_evm = require("@x402/evm");
74
+ var import_viem2 = require("viem");
75
+ var EIP712_DOMAIN_NAME = "AgentKV";
76
+ var EIP712_DOMAIN_VERSION = "1";
77
+ var MAX_AUTH_WINDOW_SEC = 3600;
78
+ function chainIdFromCaip2(network) {
79
+ const parts = network.split(":");
80
+ if (parts.length !== 2 || parts[0] !== "eip155") {
81
+ throw new Error(`unsupported CAIP-2 network: ${network}`);
82
+ }
83
+ const id = Number(parts[1]);
84
+ if (!Number.isInteger(id) || id <= 0) {
85
+ throw new Error(`invalid CAIP-2 chain id: ${network}`);
86
+ }
87
+ return id;
88
+ }
89
+ function decodeBase64Utf8(b64) {
90
+ const binary = atob(b64);
91
+ const bytes = new Uint8Array(binary.length);
92
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
93
+ return new TextDecoder("utf-8").decode(bytes);
94
+ }
95
+ var REQUEST_TYPES = {
96
+ Request: [
97
+ { name: "method", type: "string" },
98
+ { name: "path", type: "string" },
99
+ // host binds the signature to one deployment (prevents cross-deployment replay) —
100
+ // must match the backend's EIP-712 type definitions exactly, or every identity op fails to verify.
101
+ { name: "host", type: "string" },
102
+ { name: "nonce", type: "bytes32" },
103
+ { name: "timestamp", type: "uint256" }
104
+ ]
105
+ };
106
+ function buildBearerHeaders(accountKey) {
107
+ return { Authorization: `Bearer ${accountKey}` };
108
+ }
109
+ function nowSec() {
110
+ return Math.floor(Date.now() / 1e3);
111
+ }
112
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
113
+ TransferWithAuthorization: [
114
+ { name: "from", type: "address" },
115
+ { name: "to", type: "address" },
116
+ { name: "value", type: "uint256" },
117
+ { name: "validAfter", type: "uint256" },
118
+ { name: "validBefore", type: "uint256" },
119
+ { name: "nonce", type: "bytes32" }
120
+ ]
121
+ };
122
+ function decodeChallenge(paymentRequiredHeader) {
123
+ let json;
124
+ try {
125
+ json = decodeBase64Utf8(paymentRequiredHeader);
126
+ } catch {
127
+ throw new Error("PAYMENT-REQUIRED header is not valid base64");
128
+ }
129
+ const parsed = JSON.parse(json);
130
+ if (!parsed || !Array.isArray(parsed.accepts)) {
131
+ throw new Error("invalid PAYMENT-REQUIRED challenge: missing accepts array");
132
+ }
133
+ return parsed;
134
+ }
135
+ function selectRequirement(accepts, amountAtomic) {
136
+ const exact = accepts.filter((a) => a.scheme === "exact");
137
+ if (exact.length === 0)
138
+ throw new Error("no acceptable x402 exact-scheme requirement in challenge");
139
+ if (amountAtomic !== void 0) {
140
+ return { ...exact[0], amount: String(amountAtomic) };
141
+ }
142
+ if (exact.length > 1)
143
+ throw new Error(
144
+ "ambiguous PAYMENT-REQUIRED challenge: multiple exact requirements; specify an amount"
145
+ );
146
+ return exact[0];
147
+ }
148
+ function assertNetworkParity(req, expectedNetwork) {
149
+ if (req.network !== expectedNetwork) {
150
+ throw new AgentXError(
151
+ `payment challenge network "${req.network}" does not match client network "${expectedNetwork}"`,
152
+ "network_mismatch",
153
+ 0
154
+ );
155
+ }
156
+ const asset = (0, import_evm.getDefaultAsset)(expectedNetwork);
157
+ if ((0, import_viem2.getAddress)(req.asset) !== (0, import_viem2.getAddress)(asset.address)) {
158
+ throw new AgentXError(
159
+ `payment challenge asset "${req.asset}" is not the canonical USDC for ${expectedNetwork}`,
160
+ "asset_mismatch",
161
+ 0
162
+ );
163
+ }
164
+ }
165
+ function challengePriceUsd(paymentRequiredHeader, amountAtomic, expectedNetwork) {
166
+ const { accepts } = decodeChallenge(paymentRequiredHeader);
167
+ const req = selectRequirement(accepts, amountAtomic);
168
+ if (expectedNetwork) assertNetworkParity(req, expectedNetwork);
169
+ return Number(req.amount) / 1e6;
170
+ }
171
+ async function buildPaymentHeader(account, paymentRequiredHeader, opts) {
172
+ const { x402Version, accepts } = decodeChallenge(paymentRequiredHeader);
173
+ const req = selectRequirement(accepts, opts?.amountAtomic);
174
+ if (opts?.expectedNetwork) assertNetworkParity(req, opts.expectedNetwork);
175
+ if (opts?.expectedPayTo && (0, import_viem2.getAddress)(req.payTo) !== (0, import_viem2.getAddress)(opts.expectedPayTo)) {
176
+ throw new AgentXError(
177
+ `payment challenge payTo "${req.payTo}" does not match expected "${opts.expectedPayTo}"`,
178
+ "payto_mismatch",
179
+ 0
180
+ );
181
+ }
182
+ const chainId = chainIdFromCaip2(req.network);
183
+ const asset = (0, import_evm.getDefaultAsset)(req.network);
184
+ const nonce = opts?.nonce ?? freshNonce();
185
+ const now = nowSec();
186
+ const window = Math.min(req.maxTimeoutSeconds ?? 300, MAX_AUTH_WINDOW_SEC);
187
+ const validBefore = String(now + window);
188
+ const authorization = {
189
+ from: (0, import_viem2.getAddress)(account.address),
190
+ to: (0, import_viem2.getAddress)(req.payTo),
191
+ value: req.amount,
192
+ validAfter: "0",
193
+ validBefore,
194
+ nonce
195
+ };
196
+ const signature = await account.signTypedData({
197
+ domain: {
198
+ name: asset.name,
199
+ version: asset.version,
200
+ chainId,
201
+ verifyingContract: (0, import_viem2.getAddress)(req.asset)
202
+ },
203
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
204
+ primaryType: "TransferWithAuthorization",
205
+ message: {
206
+ from: (0, import_viem2.getAddress)(account.address),
207
+ to: (0, import_viem2.getAddress)(req.payTo),
208
+ value: BigInt(req.amount),
209
+ validAfter: BigInt(0),
210
+ validBefore: BigInt(validBefore),
211
+ nonce
212
+ }
213
+ });
214
+ const paymentPayload = {
215
+ x402Version,
216
+ accepted: req,
217
+ payload: {
218
+ authorization,
219
+ signature
220
+ }
221
+ };
222
+ return (0, import_http.encodePaymentSignatureHeader)(paymentPayload);
223
+ }
224
+ async function buildIdentityHeaders(account, args) {
225
+ const nonce = freshNonce();
226
+ const timestamp = nowSec();
227
+ const signature = await account.signTypedData({
228
+ domain: {
229
+ name: EIP712_DOMAIN_NAME,
230
+ version: EIP712_DOMAIN_VERSION,
231
+ chainId: chainIdFromCaip2(args.network)
232
+ },
233
+ types: REQUEST_TYPES,
234
+ primaryType: "Request",
235
+ message: {
236
+ method: args.method,
237
+ path: args.path,
238
+ host: args.host,
239
+ nonce,
240
+ timestamp: BigInt(timestamp)
241
+ }
242
+ });
243
+ return {
244
+ "X-AgentKV-Signature": signature,
245
+ "X-AgentKV-Nonce": nonce,
246
+ "X-AgentKV-Timestamp": String(timestamp)
247
+ };
248
+ }
249
+
250
+ // src/retry.ts
251
+ var DEFAULT_TIMEOUT_MS = 3e4;
252
+ function combineSignals(signals) {
253
+ const present = signals.filter((s) => !!s);
254
+ if (present.length === 0) return void 0;
255
+ if (present.length === 1) return present[0];
256
+ if (typeof AbortSignal.any === "function") return AbortSignal.any(present);
257
+ const ctrl = new AbortController();
258
+ for (const s of present) {
259
+ if (s.aborted) {
260
+ ctrl.abort(s.reason);
261
+ break;
262
+ }
263
+ s.addEventListener("abort", () => ctrl.abort(s.reason), { once: true });
264
+ }
265
+ return ctrl.signal;
266
+ }
267
+ async function drainBody(res) {
268
+ try {
269
+ await res.body?.cancel();
270
+ } catch {
271
+ }
272
+ }
273
+ async function fetchWithRetry(url, build, maxRetries, opts = {}) {
274
+ const doFetch = opts.fetchImpl ?? fetch;
275
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
276
+ for (let attempt = 0; ; attempt++) {
277
+ try {
278
+ const init = await build();
279
+ const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
280
+ const signal = combineSignals([init.signal ?? void 0, opts.signal, timeoutSignal]);
281
+ const res = await doFetch(url, { ...init, signal });
282
+ const transient = res.status >= 500 && res.status <= 599 || res.status === 429;
283
+ if (transient && attempt < maxRetries) {
284
+ await drainBody(res);
285
+ await retryDelay(attempt, res);
286
+ continue;
287
+ }
288
+ return res;
289
+ } catch (err) {
290
+ if (opts.signal?.aborted) {
291
+ throw err instanceof Error ? err : new AgentXError(String(err), "aborted", 0);
292
+ }
293
+ if (attempt < maxRetries) {
294
+ await retryDelay(attempt);
295
+ continue;
296
+ }
297
+ throw err instanceof Error ? err : new AgentXError(String(err), "network_error", 0);
298
+ }
299
+ }
300
+ }
301
+ function retryDelay(attempt, res) {
302
+ const base = Math.min(500, 50 * 2 ** attempt);
303
+ let ms = Math.round(base * (0.5 + Math.random() * 0.5));
304
+ const retryAfter = res?.headers.get("Retry-After");
305
+ if (retryAfter) {
306
+ const secs = Number(retryAfter);
307
+ if (Number.isFinite(secs) && secs >= 0) {
308
+ ms = Math.min(2e3, secs * 1e3);
309
+ } else {
310
+ const when = Date.parse(retryAfter);
311
+ if (Number.isFinite(when)) ms = Math.min(2e3, Math.max(0, when - Date.now()));
312
+ }
313
+ }
314
+ return new Promise((resolve) => setTimeout(resolve, ms));
315
+ }
316
+ // Annotate the CommonJS export names for ESM import in node:
317
+ 0 && (module.exports = {
318
+ AgentKVError,
319
+ AgentXError,
320
+ DEFAULT_TIMEOUT_MS,
321
+ EIP712_DOMAIN_NAME,
322
+ EIP712_DOMAIN_VERSION,
323
+ MAX_AUTH_WINDOW_SEC,
324
+ SpendCapError,
325
+ buildBearerHeaders,
326
+ buildIdentityHeaders,
327
+ buildPaymentHeader,
328
+ chainIdFromCaip2,
329
+ challengePriceUsd,
330
+ decodeBase64Utf8,
331
+ fetchWithRetry,
332
+ freshNonce,
333
+ nonceFromIdempotencyKey,
334
+ nowSec,
335
+ retryDelay
336
+ });
337
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/idempotency.ts","../src/payment.ts","../src/retry.ts"],"sourcesContent":["// core/src/index.ts\n//\n// @agentx402-ai/core: shared auth/pay/usage/error/retry plumbing that a future\n// second service package can reuse without re-declaring the error base class,\n// EIP-712/x402 signing helpers, or the retry mechanics.\n\nexport * from \"./errors\";\nexport * from \"./idempotency\";\nexport * from \"./payment\";\nexport * from \"./retry\";\nexport * from \"./usage\";\n","// core/src/errors.ts\n//\n// The SINGLE error base class object for the installed workspace. `@agentkv/client`\n// (and any future second service package) must DEPEND ON and RE-EXPORT this class —\n// never re-declare it — or `err instanceof AgentKVError` breaks for anything caught\n// across a package boundary (two distinct class objects in node_modules).\n\n/** Base error carrying a machine code (mapped to CLI exit codes / MCP errors). */\nexport class AgentXError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status?: number,\n ) {\n super(message);\n // Keep the original runtime `.name` string (\"AgentKVError\") so any existing\n // logging/serialization that reads `err.name` sees no observable change from\n // the pre-extraction class — only the exported IDENTIFIER is renamed.\n this.name = \"AgentKVError\";\n }\n}\n\n/**\n * Back-compat alias: the base class shipped as `AgentKVError` before this\n * extraction. Re-export the SAME reference under the old name (not a second\n * `class AgentKVError extends AgentXError {}` declaration) so\n * `new AgentKVError(...) instanceof AgentKVError` and\n * `new AgentKVError(...) instanceof AgentXError` both hold, and so callers that\n * imported `AgentKVError` before this refactor keep compiling and matching.\n */\nexport { AgentXError as AgentKVError };\n\n/** Thrown when a paying call would exceed a configured spend cap. */\nexport class SpendCapError extends AgentXError {\n constructor(message: string) {\n super(message, \"spend_cap_exceeded\");\n this.name = \"SpendCapError\";\n }\n}\n","// core/src/idempotency.ts\n//\n// Nonce helpers shared by the identity-signing and x402-payment paths.\n\nimport { keccak256, stringToHex, toHex } from \"viem\";\n\n/** A fresh random bytes32 nonce (0x-prefixed, 64 hex chars). */\nexport function freshNonce(): `0x${string}` {\n return toHex(crypto.getRandomValues(new Uint8Array(32)));\n}\n\n/**\n * Derive a deterministic bytes32 EIP-3009 nonce from a caller idempotency key,\n * so retrying a logical write reuses the same authorization and the server's\n * idempotency record is hit (exactly-once across caller retries).\n */\nexport function nonceFromIdempotencyKey(key: string): `0x${string}` {\n return keccak256(stringToHex(key));\n}\n","// core/src/payment.ts\n//\n// Payment/identity header builders, plus the `Signer` interface and EIP-712 domain\n// constants they depend on. These travel together because\n// `buildPaymentHeader`/`buildIdentityHeaders` are not truly standalone from them —\n// splitting the functions from their domain constants would force a circular\n// client<->core dependency.\n\nimport { encodePaymentSignatureHeader } from \"@x402/core/http\";\nimport type { PaymentPayload, PaymentRequirements } from \"@x402/core/types\";\nimport { getDefaultAsset } from \"@x402/evm\";\nimport { getAddress } from \"viem\";\nimport { AgentXError } from \"./errors\";\nimport { freshNonce } from \"./idempotency\";\n\n/** Minimal signer the client needs: a viem account satisfies this structurally. */\nexport interface Signer {\n address: `0x${string}`;\n // viem's signTypedData is generic; accept it structurally.\n signTypedData(args: any): Promise<`0x${string}`>;\n}\n\n/** EIP-712 domain name shared with the server's EIP-712 verifier. */\nexport const EIP712_DOMAIN_NAME = \"AgentKV\";\n\n/** EIP-712 domain version shared with the server's EIP-712 verifier. */\nexport const EIP712_DOMAIN_VERSION = \"1\";\n\n/**\n * Hard cap on the signed EIP-3009 authorization window, regardless of the server-supplied\n * `maxTimeoutSeconds`. A signed authorization is a bearer instrument; a hostile challenge\n * asking for a multi-year window must not yield one that stays spendable indefinitely.\n */\nexport const MAX_AUTH_WINDOW_SEC = 3600;\n\n/** Maps a CAIP-2 network id (e.g. \"eip155:8453\") to its numeric chainId. */\nexport function chainIdFromCaip2(network: string): number {\n const parts = network.split(\":\");\n if (parts.length !== 2 || parts[0] !== \"eip155\") {\n throw new Error(`unsupported CAIP-2 network: ${network}`);\n }\n const id = Number(parts[1]);\n if (!Number.isInteger(id) || id <= 0) {\n throw new Error(`invalid CAIP-2 chain id: ${network}`);\n }\n return id;\n}\n\n/**\n * Decode a base64 header as UTF-8 (atob → char-code bytes → TextDecoder(\"utf-8\")).\n * The backend encodes both PAYMENT-REQUIRED and PAYMENT-RESPONSE with base64/UTF-8 and\n * documents this exact mirror decode; a bare `atob()` decodes Latin-1, which corrupts\n * any non-ASCII code point (e.g. a future \"USD₮\" asset name). Byte-identical for ASCII.\n */\nexport function decodeBase64Utf8(b64: string): string {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder(\"utf-8\").decode(bytes);\n}\n\n/** EIP-712 typed-data shape shared with the server's EIP-712 verifier. */\nconst REQUEST_TYPES = {\n Request: [\n { name: \"method\", type: \"string\" },\n { name: \"path\", type: \"string\" },\n // host binds the signature to one deployment (prevents cross-deployment replay) —\n // must match the backend's EIP-712 type definitions exactly, or every identity op fails to verify.\n { name: \"host\", type: \"string\" },\n { name: \"nonce\", type: \"bytes32\" },\n { name: \"timestamp\", type: \"uint256\" },\n ],\n} as const;\n\n/** Identity headers sent on free/credit operations. */\nexport interface IdentityHeaders {\n \"X-AgentKV-Signature\": string;\n \"X-AgentKV-Nonce\": string;\n \"X-AgentKV-Timestamp\": string;\n}\n\n/**\n * Bearer auth header for account-key mode. The opaque `ak_…` token IS the\n * capability: the server hashes it to name the account's storage and debits its\n * prepaid credits. NO x402, NO EIP-712 — this header replaces both. The raw key\n * travels in the clear over TLS exactly like any bearer token; never log it.\n */\nexport function buildBearerHeaders(accountKey: string): Record<string, string> {\n return { Authorization: `Bearer ${accountKey}` };\n}\n\n/** Current unix time in whole seconds. */\nexport function nowSec(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** EIP-712 typed-data types for EIP-3009 transferWithAuthorization. */\nconst TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n} as const;\n\n/** Each PAYMENT-REQUIRED accept is a full v2 PaymentRequirements. */\ntype ChallengeAccept = PaymentRequirements;\n\n/** Decoded PAYMENT-REQUIRED challenge body. */\ninterface PaymentRequiredChallenge {\n x402Version: number;\n accepts: ChallengeAccept[];\n}\n\n/**\n * Decode and validate a base64-encoded PAYMENT-REQUIRED challenge header.\n */\nfunction decodeChallenge(paymentRequiredHeader: string): PaymentRequiredChallenge {\n let json: string;\n try {\n json = decodeBase64Utf8(paymentRequiredHeader);\n } catch {\n throw new Error(\"PAYMENT-REQUIRED header is not valid base64\");\n }\n const parsed = JSON.parse(json) as PaymentRequiredChallenge;\n if (!parsed || !Array.isArray(parsed.accepts)) {\n throw new Error(\"invalid PAYMENT-REQUIRED challenge: missing accepts array\");\n }\n return parsed;\n}\n\n/** Pick the exact-scheme requirement, by amount when several tiers are offered. */\nfunction selectRequirement(accepts: ChallengeAccept[], amountAtomic?: number): ChallengeAccept {\n const exact = accepts.filter((a) => a.scheme === \"exact\");\n if (exact.length === 0)\n throw new Error(\"no acceptable x402 exact-scheme requirement in challenge\");\n if (amountAtomic !== undefined) {\n // Use the first exact requirement as the asset/payTo/network/domain template and\n // override the amount — supports any top-off / deposit amount, not just advertised tiers.\n return { ...exact[0], amount: String(amountAtomic) };\n }\n if (exact.length > 1)\n throw new Error(\n \"ambiguous PAYMENT-REQUIRED challenge: multiple exact requirements; specify an amount\",\n );\n return exact[0];\n}\n\n/**\n * Enforce that a server-supplied challenge targets the client's configured network AND its\n * canonical USDC contract. Money movement must never be dictated solely by the server: a\n * compromised or spoofed worker could otherwise return a challenge for a different chain\n * (e.g. hand a Base-configured client an Arbitrum challenge, draining the SAME EOA's\n * Arbitrum USDC) or a non-canonical token address, and the client would sign it. This is\n * the payment-path mirror of the host-binding on identity signatures and the domain pin.\n */\nfunction assertNetworkParity(req: ChallengeAccept, expectedNetwork: string): void {\n if (req.network !== expectedNetwork) {\n throw new AgentXError(\n `payment challenge network \"${req.network}\" does not match client network \"${expectedNetwork}\"`,\n \"network_mismatch\",\n 0,\n );\n }\n const asset = getDefaultAsset(expectedNetwork as `${string}:${string}`);\n if (getAddress(req.asset) !== getAddress(asset.address)) {\n throw new AgentXError(\n `payment challenge asset \"${req.asset}\" is not the canonical USDC for ${expectedNetwork}`,\n \"asset_mismatch\",\n 0,\n );\n }\n}\n\n/** Decode a challenge and return the chosen requirement's price in USD. */\nexport function challengePriceUsd(\n paymentRequiredHeader: string,\n amountAtomic?: number,\n expectedNetwork?: string,\n): number {\n const { accepts } = decodeChallenge(paymentRequiredHeader);\n const req = selectRequirement(accepts, amountAtomic);\n if (expectedNetwork) assertNetworkParity(req, expectedNetwork);\n return Number(req.amount) / 1_000_000;\n}\n\n/**\n * Build the base64 PAYMENT-SIGNATURE header from a PAYMENT-REQUIRED challenge.\n *\n * Decodes the v2 challenge, selects the matching `exact`-scheme requirement\n * (by `opts.amountAtomic` when several tiers are offered), and signs an EIP-3009\n * transferWithAuthorization payload (validAfter=0, validBefore=now+window) with\n * the viem account. `opts.nonce` pins a deterministic EIP-3009 nonce so retries\n * reuse the same authorization.\n *\n * Returns the base64-encoded JSON payload as the PAYMENT-SIGNATURE value.\n */\nexport async function buildPaymentHeader(\n account: Signer,\n paymentRequiredHeader: string,\n opts?: {\n nonce?: `0x${string}`;\n amountAtomic?: number;\n expectedNetwork?: string;\n /** Pin the recipient: reject the challenge unless its `payTo` equals this address (high-value ops). */\n expectedPayTo?: string;\n },\n): Promise<string> {\n const { x402Version, accepts } = decodeChallenge(paymentRequiredHeader);\n\n const req = selectRequirement(accepts, opts?.amountAtomic);\n\n // Pin the money-moving challenge to the client's configured network + canonical asset\n // BEFORE signing anything (a signed EIP-3009 authorization is a bearer instrument).\n if (opts?.expectedNetwork) assertNetworkParity(req, opts.expectedNetwork);\n // Optional recipient pin: the client can't know the \"correct\" payTo in general (the amount\n // ceiling is the primary defense), but for high-value ops a caller may pin an expected one.\n if (opts?.expectedPayTo && getAddress(req.payTo) !== getAddress(opts.expectedPayTo)) {\n throw new AgentXError(\n `payment challenge payTo \"${req.payTo}\" does not match expected \"${opts.expectedPayTo}\"`,\n \"payto_mismatch\",\n 0,\n );\n }\n\n const chainId = chainIdFromCaip2(req.network);\n // Look up EIP-712 domain info for the token (name/version).\n // Cast to the x402 Network template-literal type.\n const asset = getDefaultAsset(req.network as `${string}:${string}`);\n\n const nonce = opts?.nonce ?? freshNonce();\n const now = nowSec();\n // Clamp the signed window: never sign an authorization valid longer than MAX_AUTH_WINDOW_SEC,\n // regardless of the server-supplied maxTimeoutSeconds.\n const window = Math.min(req.maxTimeoutSeconds ?? 300, MAX_AUTH_WINDOW_SEC);\n const validBefore = String(now + window);\n\n const authorization = {\n from: getAddress(account.address),\n to: getAddress(req.payTo),\n value: req.amount,\n validAfter: \"0\",\n validBefore,\n nonce,\n };\n\n const signature = await account.signTypedData({\n domain: {\n name: asset.name,\n version: asset.version,\n chainId,\n verifyingContract: getAddress(req.asset),\n },\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: getAddress(account.address),\n to: getAddress(req.payTo),\n value: BigInt(req.amount),\n validAfter: BigInt(0),\n validBefore: BigInt(validBefore),\n nonce,\n },\n });\n\n // v2 PaymentPayload: the chosen requirement + the signed EIP-3009 authorization,\n // encoded with the SDK so the server's decodePaymentSignatureHeader round-trips.\n const paymentPayload: PaymentPayload = {\n x402Version,\n accepted: req,\n payload: {\n authorization,\n signature,\n },\n };\n\n return encodePaymentSignatureHeader(paymentPayload);\n}\n\n/**\n * Build EIP-712 identity headers for a free/credit op (delete, balance).\n * Signs the Request typed data with a fresh nonce + timestamp so the server\n * can recover the wallet address and enforce replay protection.\n */\nexport async function buildIdentityHeaders(\n account: Signer,\n args: { method: string; path: string; host: string; network: string },\n): Promise<IdentityHeaders> {\n const nonce = freshNonce();\n const timestamp = nowSec();\n const signature = await account.signTypedData({\n domain: {\n name: EIP712_DOMAIN_NAME,\n version: EIP712_DOMAIN_VERSION,\n chainId: chainIdFromCaip2(args.network),\n },\n types: REQUEST_TYPES,\n primaryType: \"Request\",\n message: {\n method: args.method,\n path: args.path,\n host: args.host,\n nonce,\n timestamp: BigInt(timestamp),\n },\n });\n\n return {\n \"X-AgentKV-Signature\": signature,\n \"X-AgentKV-Nonce\": nonce,\n \"X-AgentKV-Timestamp\": String(timestamp),\n };\n}\n","// core/src/retry.ts\n//\n// Pure retry helpers factored out of `AgentKV#fetchWithRetry` / `AgentKV#retryDelay`.\n// The original methods only ever read `this.maxRetries` (a plain number) and called\n// `this.retryDelay` — no other instance state — so they extract cleanly into pure\n// functions parameterized by `maxRetries`. `AgentKV` now delegates to these via thin\n// private wrappers so every existing call site (`this.fetchWithRetry(...)`) is unchanged.\n\nimport { AgentXError } from \"./errors\";\n\n/** Default per-attempt request timeout (ms). A hung-open connection would otherwise wedge an op forever. */\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Tuning knobs for {@link fetchWithRetry}. All optional; sensible defaults. */\nexport interface RetryOptions {\n /** Per-ATTEMPT timeout in ms (via `AbortSignal.timeout`). Default 30_000. Pass 0 to disable. */\n timeoutMs?: number;\n /** Injectable `fetch` for proxies / instrumentation / tests. Defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Caller `AbortSignal` to cancel the whole operation (abort is surfaced immediately, never retried). */\n signal?: AbortSignal;\n}\n\n/** Combine 0+ AbortSignals into one that aborts when any input does. Undefined if none supplied. */\nfunction combineSignals(signals: (AbortSignal | undefined)[]): AbortSignal | undefined {\n const present = signals.filter((s): s is AbortSignal => !!s);\n if (present.length === 0) return undefined;\n if (present.length === 1) return present[0];\n // AbortSignal.any is Node >=20.3 / modern browsers; fall back to a manual combiner for Node 20.0–20.2.\n if (typeof AbortSignal.any === \"function\") return AbortSignal.any(present);\n const ctrl = new AbortController();\n for (const s of present) {\n if (s.aborted) {\n ctrl.abort(s.reason);\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(s.reason), { once: true });\n }\n return ctrl.signal;\n}\n\n/** Release a transient response's body so its socket returns to the pool before we retry. */\nasync function drainBody(res: Response): Promise<void> {\n try {\n await res.body?.cancel();\n } catch {\n // best-effort — a body that can't be cancelled is not worth failing the retry over.\n }\n}\n\n/**\n * Issue a request with bounded retry on TRANSIENT failures only: a thrown\n * fetch (network error / lost response / per-attempt timeout) or a 5xx/429\n * response. `build()` is re-invoked per attempt so a caller can re-sign\n * per-attempt state (e.g. a fresh identity nonce) while a stable Idempotency-Key\n * (and pinned EIP-3009 nonce on paid ops) makes a retry of an already-processed\n * request dedupe server-side — so a lost response that the server already charged\n * is recovered without a second charge. NOT retried: any 2xx/4xx (incl. a 402\n * credit->pay handoff, 401, 404) — returned as-is; and a caller-initiated abort,\n * which is surfaced immediately. Honors `Retry-After` (delta-seconds or HTTP-date).\n * Each attempt is bounded by `opts.timeoutMs` (default 30s).\n */\nexport async function fetchWithRetry(\n url: string,\n build: () => RequestInit | Promise<RequestInit>,\n maxRetries: number,\n opts: RetryOptions = {},\n): Promise<Response> {\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n for (let attempt = 0; ; attempt++) {\n try {\n const init = await build();\n const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;\n const signal = combineSignals([init.signal ?? undefined, opts.signal, timeoutSignal]);\n const res = await doFetch(url, { ...init, signal });\n // Retry TRANSIENT statuses: 5xx, and 429 (rate limited).\n const transient = (res.status >= 500 && res.status <= 599) || res.status === 429;\n if (transient && attempt < maxRetries) {\n await drainBody(res);\n await retryDelay(attempt, res);\n continue;\n }\n return res;\n } catch (err) {\n // A caller-initiated cancel is intentional, not transient — surface it immediately.\n if (opts.signal?.aborted) {\n throw err instanceof Error ? err : new AgentXError(String(err), \"aborted\", 0);\n }\n if (attempt < maxRetries) {\n await retryDelay(attempt);\n continue;\n }\n throw err instanceof Error ? err : new AgentXError(String(err), \"network_error\", 0);\n }\n }\n}\n\n/**\n * Short, bounded backoff between retries. Base is 50ms, 100ms, ... capped at 500ms, with\n * full jitter (each delay uniformly in [50%, 100%] of the base) to avoid a synchronized\n * retry herd under a 5xx/429 storm. If the response carries a `Retry-After` — delta-seconds\n * OR an HTTP-date — honor it up to a 2s cap so a re-sent paid authorization still stays\n * comfortably within its validBefore window (jitter is skipped when the server dictates a delay).\n */\nexport function retryDelay(attempt: number, res?: Response): Promise<void> {\n const base = Math.min(500, 50 * 2 ** attempt);\n let ms = Math.round(base * (0.5 + Math.random() * 0.5));\n const retryAfter = res?.headers.get(\"Retry-After\");\n if (retryAfter) {\n const secs = Number(retryAfter);\n if (Number.isFinite(secs) && secs >= 0) {\n ms = Math.min(2000, secs * 1000); // delta-seconds form\n } else {\n const when = Date.parse(retryAfter); // HTTP-date form\n if (Number.isFinite(when)) ms = Math.min(2000, Math.max(0, when - Date.now()));\n }\n }\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":["import_viem"]}
@@ -0,0 +1,152 @@
1
+ /** Base error carrying a machine code (mapped to CLI exit codes / MCP errors). */
2
+ declare class AgentXError extends Error {
3
+ readonly code: string;
4
+ readonly status?: number | undefined;
5
+ constructor(message: string, code: string, status?: number | undefined);
6
+ }
7
+
8
+ /** Thrown when a paying call would exceed a configured spend cap. */
9
+ declare class SpendCapError extends AgentXError {
10
+ constructor(message: string);
11
+ }
12
+
13
+ /** A fresh random bytes32 nonce (0x-prefixed, 64 hex chars). */
14
+ declare function freshNonce(): `0x${string}`;
15
+ /**
16
+ * Derive a deterministic bytes32 EIP-3009 nonce from a caller idempotency key,
17
+ * so retrying a logical write reuses the same authorization and the server's
18
+ * idempotency record is hit (exactly-once across caller retries).
19
+ */
20
+ declare function nonceFromIdempotencyKey(key: string): `0x${string}`;
21
+
22
+ /** Minimal signer the client needs: a viem account satisfies this structurally. */
23
+ interface Signer {
24
+ address: `0x${string}`;
25
+ signTypedData(args: any): Promise<`0x${string}`>;
26
+ }
27
+ /** EIP-712 domain name shared with the server's EIP-712 verifier. */
28
+ declare const EIP712_DOMAIN_NAME = "AgentKV";
29
+ /** EIP-712 domain version shared with the server's EIP-712 verifier. */
30
+ declare const EIP712_DOMAIN_VERSION = "1";
31
+ /**
32
+ * Hard cap on the signed EIP-3009 authorization window, regardless of the server-supplied
33
+ * `maxTimeoutSeconds`. A signed authorization is a bearer instrument; a hostile challenge
34
+ * asking for a multi-year window must not yield one that stays spendable indefinitely.
35
+ */
36
+ declare const MAX_AUTH_WINDOW_SEC = 3600;
37
+ /** Maps a CAIP-2 network id (e.g. "eip155:8453") to its numeric chainId. */
38
+ declare function chainIdFromCaip2(network: string): number;
39
+ /**
40
+ * Decode a base64 header as UTF-8 (atob → char-code bytes → TextDecoder("utf-8")).
41
+ * The backend encodes both PAYMENT-REQUIRED and PAYMENT-RESPONSE with base64/UTF-8 and
42
+ * documents this exact mirror decode; a bare `atob()` decodes Latin-1, which corrupts
43
+ * any non-ASCII code point (e.g. a future "USD₮" asset name). Byte-identical for ASCII.
44
+ */
45
+ declare function decodeBase64Utf8(b64: string): string;
46
+ /** Identity headers sent on free/credit operations. */
47
+ interface IdentityHeaders {
48
+ "X-AgentKV-Signature": string;
49
+ "X-AgentKV-Nonce": string;
50
+ "X-AgentKV-Timestamp": string;
51
+ }
52
+ /**
53
+ * Bearer auth header for account-key mode. The opaque `ak_…` token IS the
54
+ * capability: the server hashes it to name the account's storage and debits its
55
+ * prepaid credits. NO x402, NO EIP-712 — this header replaces both. The raw key
56
+ * travels in the clear over TLS exactly like any bearer token; never log it.
57
+ */
58
+ declare function buildBearerHeaders(accountKey: string): Record<string, string>;
59
+ /** Current unix time in whole seconds. */
60
+ declare function nowSec(): number;
61
+ /** Decode a challenge and return the chosen requirement's price in USD. */
62
+ declare function challengePriceUsd(paymentRequiredHeader: string, amountAtomic?: number, expectedNetwork?: string): number;
63
+ /**
64
+ * Build the base64 PAYMENT-SIGNATURE header from a PAYMENT-REQUIRED challenge.
65
+ *
66
+ * Decodes the v2 challenge, selects the matching `exact`-scheme requirement
67
+ * (by `opts.amountAtomic` when several tiers are offered), and signs an EIP-3009
68
+ * transferWithAuthorization payload (validAfter=0, validBefore=now+window) with
69
+ * the viem account. `opts.nonce` pins a deterministic EIP-3009 nonce so retries
70
+ * reuse the same authorization.
71
+ *
72
+ * Returns the base64-encoded JSON payload as the PAYMENT-SIGNATURE value.
73
+ */
74
+ declare function buildPaymentHeader(account: Signer, paymentRequiredHeader: string, opts?: {
75
+ nonce?: `0x${string}`;
76
+ amountAtomic?: number;
77
+ expectedNetwork?: string;
78
+ /** Pin the recipient: reject the challenge unless its `payTo` equals this address (high-value ops). */
79
+ expectedPayTo?: string;
80
+ }): Promise<string>;
81
+ /**
82
+ * Build EIP-712 identity headers for a free/credit op (delete, balance).
83
+ * Signs the Request typed data with a fresh nonce + timestamp so the server
84
+ * can recover the wallet address and enforce replay protection.
85
+ */
86
+ declare function buildIdentityHeaders(account: Signer, args: {
87
+ method: string;
88
+ path: string;
89
+ host: string;
90
+ network: string;
91
+ }): Promise<IdentityHeaders>;
92
+
93
+ /** Default per-attempt request timeout (ms). A hung-open connection would otherwise wedge an op forever. */
94
+ declare const DEFAULT_TIMEOUT_MS = 30000;
95
+ /** Tuning knobs for {@link fetchWithRetry}. All optional; sensible defaults. */
96
+ interface RetryOptions {
97
+ /** Per-ATTEMPT timeout in ms (via `AbortSignal.timeout`). Default 30_000. Pass 0 to disable. */
98
+ timeoutMs?: number;
99
+ /** Injectable `fetch` for proxies / instrumentation / tests. Defaults to the global `fetch`. */
100
+ fetchImpl?: typeof fetch;
101
+ /** Caller `AbortSignal` to cancel the whole operation (abort is surfaced immediately, never retried). */
102
+ signal?: AbortSignal;
103
+ }
104
+ /**
105
+ * Issue a request with bounded retry on TRANSIENT failures only: a thrown
106
+ * fetch (network error / lost response / per-attempt timeout) or a 5xx/429
107
+ * response. `build()` is re-invoked per attempt so a caller can re-sign
108
+ * per-attempt state (e.g. a fresh identity nonce) while a stable Idempotency-Key
109
+ * (and pinned EIP-3009 nonce on paid ops) makes a retry of an already-processed
110
+ * request dedupe server-side — so a lost response that the server already charged
111
+ * is recovered without a second charge. NOT retried: any 2xx/4xx (incl. a 402
112
+ * credit->pay handoff, 401, 404) — returned as-is; and a caller-initiated abort,
113
+ * which is surfaced immediately. Honors `Retry-After` (delta-seconds or HTTP-date).
114
+ * Each attempt is bounded by `opts.timeoutMs` (default 30s).
115
+ */
116
+ declare function fetchWithRetry(url: string, build: () => RequestInit | Promise<RequestInit>, maxRetries: number, opts?: RetryOptions): Promise<Response>;
117
+ /**
118
+ * Short, bounded backoff between retries. Base is 50ms, 100ms, ... capped at 500ms, with
119
+ * full jitter (each delay uniformly in [50%, 100%] of the base) to avoid a synchronized
120
+ * retry herd under a 5xx/429 storm. If the response carries a `Retry-After` — delta-seconds
121
+ * OR an HTTP-date — honor it up to a 2s cap so a re-sent paid authorization still stays
122
+ * comfortably within its validBefore window (jitter is skipped when the server dictates a delay).
123
+ */
124
+ declare function retryDelay(attempt: number, res?: Response): Promise<void>;
125
+
126
+ /**
127
+ * Machine-readable usage envelope the backend attaches to a paid op's success
128
+ * body. Mirrors the backend's canonical `UsageBlock` FIELD-FOR-FIELD — client and
129
+ * backend are separate packages that can't share an import, so keep this in
130
+ * lockstep by hand with the backend's shape. Kept in @agentx402-ai/core so a second
131
+ * service SDK can reuse the same shape without re-declaring it.
132
+ */
133
+ interface UsageBlock {
134
+ service: string;
135
+ op: string;
136
+ /**
137
+ * USD ACTUALLY charged for THIS op on the taken path (NOT the list price).
138
+ * The credit path spends at the 10x discount, so it does NOT equal
139
+ * `list_price_usd`. A cache hit is a free serve -> `price_usd: 0`.
140
+ */
141
+ price_usd: number;
142
+ /**
143
+ * Pay-per-op LIST price in USD — the un-discounted reference rate a
144
+ * budgeting agent compares `price_usd` against.
145
+ */
146
+ list_price_usd: number;
147
+ /** Prepaid credits debited from the ledger for this op (0 on the x402 pay-per-op path). */
148
+ credits_charged: number;
149
+ cache_hit?: boolean;
150
+ }
151
+
152
+ export { AgentXError as AgentKVError, AgentXError, DEFAULT_TIMEOUT_MS, EIP712_DOMAIN_NAME, EIP712_DOMAIN_VERSION, type IdentityHeaders, MAX_AUTH_WINDOW_SEC, type RetryOptions, type Signer, SpendCapError, type UsageBlock, buildBearerHeaders, buildIdentityHeaders, buildPaymentHeader, chainIdFromCaip2, challengePriceUsd, decodeBase64Utf8, fetchWithRetry, freshNonce, nonceFromIdempotencyKey, nowSec, retryDelay };
@@ -0,0 +1,152 @@
1
+ /** Base error carrying a machine code (mapped to CLI exit codes / MCP errors). */
2
+ declare class AgentXError extends Error {
3
+ readonly code: string;
4
+ readonly status?: number | undefined;
5
+ constructor(message: string, code: string, status?: number | undefined);
6
+ }
7
+
8
+ /** Thrown when a paying call would exceed a configured spend cap. */
9
+ declare class SpendCapError extends AgentXError {
10
+ constructor(message: string);
11
+ }
12
+
13
+ /** A fresh random bytes32 nonce (0x-prefixed, 64 hex chars). */
14
+ declare function freshNonce(): `0x${string}`;
15
+ /**
16
+ * Derive a deterministic bytes32 EIP-3009 nonce from a caller idempotency key,
17
+ * so retrying a logical write reuses the same authorization and the server's
18
+ * idempotency record is hit (exactly-once across caller retries).
19
+ */
20
+ declare function nonceFromIdempotencyKey(key: string): `0x${string}`;
21
+
22
+ /** Minimal signer the client needs: a viem account satisfies this structurally. */
23
+ interface Signer {
24
+ address: `0x${string}`;
25
+ signTypedData(args: any): Promise<`0x${string}`>;
26
+ }
27
+ /** EIP-712 domain name shared with the server's EIP-712 verifier. */
28
+ declare const EIP712_DOMAIN_NAME = "AgentKV";
29
+ /** EIP-712 domain version shared with the server's EIP-712 verifier. */
30
+ declare const EIP712_DOMAIN_VERSION = "1";
31
+ /**
32
+ * Hard cap on the signed EIP-3009 authorization window, regardless of the server-supplied
33
+ * `maxTimeoutSeconds`. A signed authorization is a bearer instrument; a hostile challenge
34
+ * asking for a multi-year window must not yield one that stays spendable indefinitely.
35
+ */
36
+ declare const MAX_AUTH_WINDOW_SEC = 3600;
37
+ /** Maps a CAIP-2 network id (e.g. "eip155:8453") to its numeric chainId. */
38
+ declare function chainIdFromCaip2(network: string): number;
39
+ /**
40
+ * Decode a base64 header as UTF-8 (atob → char-code bytes → TextDecoder("utf-8")).
41
+ * The backend encodes both PAYMENT-REQUIRED and PAYMENT-RESPONSE with base64/UTF-8 and
42
+ * documents this exact mirror decode; a bare `atob()` decodes Latin-1, which corrupts
43
+ * any non-ASCII code point (e.g. a future "USD₮" asset name). Byte-identical for ASCII.
44
+ */
45
+ declare function decodeBase64Utf8(b64: string): string;
46
+ /** Identity headers sent on free/credit operations. */
47
+ interface IdentityHeaders {
48
+ "X-AgentKV-Signature": string;
49
+ "X-AgentKV-Nonce": string;
50
+ "X-AgentKV-Timestamp": string;
51
+ }
52
+ /**
53
+ * Bearer auth header for account-key mode. The opaque `ak_…` token IS the
54
+ * capability: the server hashes it to name the account's storage and debits its
55
+ * prepaid credits. NO x402, NO EIP-712 — this header replaces both. The raw key
56
+ * travels in the clear over TLS exactly like any bearer token; never log it.
57
+ */
58
+ declare function buildBearerHeaders(accountKey: string): Record<string, string>;
59
+ /** Current unix time in whole seconds. */
60
+ declare function nowSec(): number;
61
+ /** Decode a challenge and return the chosen requirement's price in USD. */
62
+ declare function challengePriceUsd(paymentRequiredHeader: string, amountAtomic?: number, expectedNetwork?: string): number;
63
+ /**
64
+ * Build the base64 PAYMENT-SIGNATURE header from a PAYMENT-REQUIRED challenge.
65
+ *
66
+ * Decodes the v2 challenge, selects the matching `exact`-scheme requirement
67
+ * (by `opts.amountAtomic` when several tiers are offered), and signs an EIP-3009
68
+ * transferWithAuthorization payload (validAfter=0, validBefore=now+window) with
69
+ * the viem account. `opts.nonce` pins a deterministic EIP-3009 nonce so retries
70
+ * reuse the same authorization.
71
+ *
72
+ * Returns the base64-encoded JSON payload as the PAYMENT-SIGNATURE value.
73
+ */
74
+ declare function buildPaymentHeader(account: Signer, paymentRequiredHeader: string, opts?: {
75
+ nonce?: `0x${string}`;
76
+ amountAtomic?: number;
77
+ expectedNetwork?: string;
78
+ /** Pin the recipient: reject the challenge unless its `payTo` equals this address (high-value ops). */
79
+ expectedPayTo?: string;
80
+ }): Promise<string>;
81
+ /**
82
+ * Build EIP-712 identity headers for a free/credit op (delete, balance).
83
+ * Signs the Request typed data with a fresh nonce + timestamp so the server
84
+ * can recover the wallet address and enforce replay protection.
85
+ */
86
+ declare function buildIdentityHeaders(account: Signer, args: {
87
+ method: string;
88
+ path: string;
89
+ host: string;
90
+ network: string;
91
+ }): Promise<IdentityHeaders>;
92
+
93
+ /** Default per-attempt request timeout (ms). A hung-open connection would otherwise wedge an op forever. */
94
+ declare const DEFAULT_TIMEOUT_MS = 30000;
95
+ /** Tuning knobs for {@link fetchWithRetry}. All optional; sensible defaults. */
96
+ interface RetryOptions {
97
+ /** Per-ATTEMPT timeout in ms (via `AbortSignal.timeout`). Default 30_000. Pass 0 to disable. */
98
+ timeoutMs?: number;
99
+ /** Injectable `fetch` for proxies / instrumentation / tests. Defaults to the global `fetch`. */
100
+ fetchImpl?: typeof fetch;
101
+ /** Caller `AbortSignal` to cancel the whole operation (abort is surfaced immediately, never retried). */
102
+ signal?: AbortSignal;
103
+ }
104
+ /**
105
+ * Issue a request with bounded retry on TRANSIENT failures only: a thrown
106
+ * fetch (network error / lost response / per-attempt timeout) or a 5xx/429
107
+ * response. `build()` is re-invoked per attempt so a caller can re-sign
108
+ * per-attempt state (e.g. a fresh identity nonce) while a stable Idempotency-Key
109
+ * (and pinned EIP-3009 nonce on paid ops) makes a retry of an already-processed
110
+ * request dedupe server-side — so a lost response that the server already charged
111
+ * is recovered without a second charge. NOT retried: any 2xx/4xx (incl. a 402
112
+ * credit->pay handoff, 401, 404) — returned as-is; and a caller-initiated abort,
113
+ * which is surfaced immediately. Honors `Retry-After` (delta-seconds or HTTP-date).
114
+ * Each attempt is bounded by `opts.timeoutMs` (default 30s).
115
+ */
116
+ declare function fetchWithRetry(url: string, build: () => RequestInit | Promise<RequestInit>, maxRetries: number, opts?: RetryOptions): Promise<Response>;
117
+ /**
118
+ * Short, bounded backoff between retries. Base is 50ms, 100ms, ... capped at 500ms, with
119
+ * full jitter (each delay uniformly in [50%, 100%] of the base) to avoid a synchronized
120
+ * retry herd under a 5xx/429 storm. If the response carries a `Retry-After` — delta-seconds
121
+ * OR an HTTP-date — honor it up to a 2s cap so a re-sent paid authorization still stays
122
+ * comfortably within its validBefore window (jitter is skipped when the server dictates a delay).
123
+ */
124
+ declare function retryDelay(attempt: number, res?: Response): Promise<void>;
125
+
126
+ /**
127
+ * Machine-readable usage envelope the backend attaches to a paid op's success
128
+ * body. Mirrors the backend's canonical `UsageBlock` FIELD-FOR-FIELD — client and
129
+ * backend are separate packages that can't share an import, so keep this in
130
+ * lockstep by hand with the backend's shape. Kept in @agentx402-ai/core so a second
131
+ * service SDK can reuse the same shape without re-declaring it.
132
+ */
133
+ interface UsageBlock {
134
+ service: string;
135
+ op: string;
136
+ /**
137
+ * USD ACTUALLY charged for THIS op on the taken path (NOT the list price).
138
+ * The credit path spends at the 10x discount, so it does NOT equal
139
+ * `list_price_usd`. A cache hit is a free serve -> `price_usd: 0`.
140
+ */
141
+ price_usd: number;
142
+ /**
143
+ * Pay-per-op LIST price in USD — the un-discounted reference rate a
144
+ * budgeting agent compares `price_usd` against.
145
+ */
146
+ list_price_usd: number;
147
+ /** Prepaid credits debited from the ledger for this op (0 on the x402 pay-per-op path). */
148
+ credits_charged: number;
149
+ cache_hit?: boolean;
150
+ }
151
+
152
+ export { AgentXError as AgentKVError, AgentXError, DEFAULT_TIMEOUT_MS, EIP712_DOMAIN_NAME, EIP712_DOMAIN_VERSION, type IdentityHeaders, MAX_AUTH_WINDOW_SEC, type RetryOptions, type Signer, SpendCapError, type UsageBlock, buildBearerHeaders, buildIdentityHeaders, buildPaymentHeader, chainIdFromCaip2, challengePriceUsd, decodeBase64Utf8, fetchWithRetry, freshNonce, nonceFromIdempotencyKey, nowSec, retryDelay };
package/dist/index.js ADDED
@@ -0,0 +1,293 @@
1
+ // src/errors.ts
2
+ var AgentXError = class extends Error {
3
+ constructor(message, code, status) {
4
+ super(message);
5
+ this.code = code;
6
+ this.status = status;
7
+ this.name = "AgentKVError";
8
+ }
9
+ code;
10
+ status;
11
+ };
12
+ var SpendCapError = class extends AgentXError {
13
+ constructor(message) {
14
+ super(message, "spend_cap_exceeded");
15
+ this.name = "SpendCapError";
16
+ }
17
+ };
18
+
19
+ // src/idempotency.ts
20
+ import { keccak256, stringToHex, toHex } from "viem";
21
+ function freshNonce() {
22
+ return toHex(crypto.getRandomValues(new Uint8Array(32)));
23
+ }
24
+ function nonceFromIdempotencyKey(key) {
25
+ return keccak256(stringToHex(key));
26
+ }
27
+
28
+ // src/payment.ts
29
+ import { encodePaymentSignatureHeader } from "@x402/core/http";
30
+ import { getDefaultAsset } from "@x402/evm";
31
+ import { getAddress } from "viem";
32
+ var EIP712_DOMAIN_NAME = "AgentKV";
33
+ var EIP712_DOMAIN_VERSION = "1";
34
+ var MAX_AUTH_WINDOW_SEC = 3600;
35
+ function chainIdFromCaip2(network) {
36
+ const parts = network.split(":");
37
+ if (parts.length !== 2 || parts[0] !== "eip155") {
38
+ throw new Error(`unsupported CAIP-2 network: ${network}`);
39
+ }
40
+ const id = Number(parts[1]);
41
+ if (!Number.isInteger(id) || id <= 0) {
42
+ throw new Error(`invalid CAIP-2 chain id: ${network}`);
43
+ }
44
+ return id;
45
+ }
46
+ function decodeBase64Utf8(b64) {
47
+ const binary = atob(b64);
48
+ const bytes = new Uint8Array(binary.length);
49
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
50
+ return new TextDecoder("utf-8").decode(bytes);
51
+ }
52
+ var REQUEST_TYPES = {
53
+ Request: [
54
+ { name: "method", type: "string" },
55
+ { name: "path", type: "string" },
56
+ // host binds the signature to one deployment (prevents cross-deployment replay) —
57
+ // must match the backend's EIP-712 type definitions exactly, or every identity op fails to verify.
58
+ { name: "host", type: "string" },
59
+ { name: "nonce", type: "bytes32" },
60
+ { name: "timestamp", type: "uint256" }
61
+ ]
62
+ };
63
+ function buildBearerHeaders(accountKey) {
64
+ return { Authorization: `Bearer ${accountKey}` };
65
+ }
66
+ function nowSec() {
67
+ return Math.floor(Date.now() / 1e3);
68
+ }
69
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
70
+ TransferWithAuthorization: [
71
+ { name: "from", type: "address" },
72
+ { name: "to", type: "address" },
73
+ { name: "value", type: "uint256" },
74
+ { name: "validAfter", type: "uint256" },
75
+ { name: "validBefore", type: "uint256" },
76
+ { name: "nonce", type: "bytes32" }
77
+ ]
78
+ };
79
+ function decodeChallenge(paymentRequiredHeader) {
80
+ let json;
81
+ try {
82
+ json = decodeBase64Utf8(paymentRequiredHeader);
83
+ } catch {
84
+ throw new Error("PAYMENT-REQUIRED header is not valid base64");
85
+ }
86
+ const parsed = JSON.parse(json);
87
+ if (!parsed || !Array.isArray(parsed.accepts)) {
88
+ throw new Error("invalid PAYMENT-REQUIRED challenge: missing accepts array");
89
+ }
90
+ return parsed;
91
+ }
92
+ function selectRequirement(accepts, amountAtomic) {
93
+ const exact = accepts.filter((a) => a.scheme === "exact");
94
+ if (exact.length === 0)
95
+ throw new Error("no acceptable x402 exact-scheme requirement in challenge");
96
+ if (amountAtomic !== void 0) {
97
+ return { ...exact[0], amount: String(amountAtomic) };
98
+ }
99
+ if (exact.length > 1)
100
+ throw new Error(
101
+ "ambiguous PAYMENT-REQUIRED challenge: multiple exact requirements; specify an amount"
102
+ );
103
+ return exact[0];
104
+ }
105
+ function assertNetworkParity(req, expectedNetwork) {
106
+ if (req.network !== expectedNetwork) {
107
+ throw new AgentXError(
108
+ `payment challenge network "${req.network}" does not match client network "${expectedNetwork}"`,
109
+ "network_mismatch",
110
+ 0
111
+ );
112
+ }
113
+ const asset = getDefaultAsset(expectedNetwork);
114
+ if (getAddress(req.asset) !== getAddress(asset.address)) {
115
+ throw new AgentXError(
116
+ `payment challenge asset "${req.asset}" is not the canonical USDC for ${expectedNetwork}`,
117
+ "asset_mismatch",
118
+ 0
119
+ );
120
+ }
121
+ }
122
+ function challengePriceUsd(paymentRequiredHeader, amountAtomic, expectedNetwork) {
123
+ const { accepts } = decodeChallenge(paymentRequiredHeader);
124
+ const req = selectRequirement(accepts, amountAtomic);
125
+ if (expectedNetwork) assertNetworkParity(req, expectedNetwork);
126
+ return Number(req.amount) / 1e6;
127
+ }
128
+ async function buildPaymentHeader(account, paymentRequiredHeader, opts) {
129
+ const { x402Version, accepts } = decodeChallenge(paymentRequiredHeader);
130
+ const req = selectRequirement(accepts, opts?.amountAtomic);
131
+ if (opts?.expectedNetwork) assertNetworkParity(req, opts.expectedNetwork);
132
+ if (opts?.expectedPayTo && getAddress(req.payTo) !== getAddress(opts.expectedPayTo)) {
133
+ throw new AgentXError(
134
+ `payment challenge payTo "${req.payTo}" does not match expected "${opts.expectedPayTo}"`,
135
+ "payto_mismatch",
136
+ 0
137
+ );
138
+ }
139
+ const chainId = chainIdFromCaip2(req.network);
140
+ const asset = getDefaultAsset(req.network);
141
+ const nonce = opts?.nonce ?? freshNonce();
142
+ const now = nowSec();
143
+ const window = Math.min(req.maxTimeoutSeconds ?? 300, MAX_AUTH_WINDOW_SEC);
144
+ const validBefore = String(now + window);
145
+ const authorization = {
146
+ from: getAddress(account.address),
147
+ to: getAddress(req.payTo),
148
+ value: req.amount,
149
+ validAfter: "0",
150
+ validBefore,
151
+ nonce
152
+ };
153
+ const signature = await account.signTypedData({
154
+ domain: {
155
+ name: asset.name,
156
+ version: asset.version,
157
+ chainId,
158
+ verifyingContract: getAddress(req.asset)
159
+ },
160
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
161
+ primaryType: "TransferWithAuthorization",
162
+ message: {
163
+ from: getAddress(account.address),
164
+ to: getAddress(req.payTo),
165
+ value: BigInt(req.amount),
166
+ validAfter: BigInt(0),
167
+ validBefore: BigInt(validBefore),
168
+ nonce
169
+ }
170
+ });
171
+ const paymentPayload = {
172
+ x402Version,
173
+ accepted: req,
174
+ payload: {
175
+ authorization,
176
+ signature
177
+ }
178
+ };
179
+ return encodePaymentSignatureHeader(paymentPayload);
180
+ }
181
+ async function buildIdentityHeaders(account, args) {
182
+ const nonce = freshNonce();
183
+ const timestamp = nowSec();
184
+ const signature = await account.signTypedData({
185
+ domain: {
186
+ name: EIP712_DOMAIN_NAME,
187
+ version: EIP712_DOMAIN_VERSION,
188
+ chainId: chainIdFromCaip2(args.network)
189
+ },
190
+ types: REQUEST_TYPES,
191
+ primaryType: "Request",
192
+ message: {
193
+ method: args.method,
194
+ path: args.path,
195
+ host: args.host,
196
+ nonce,
197
+ timestamp: BigInt(timestamp)
198
+ }
199
+ });
200
+ return {
201
+ "X-AgentKV-Signature": signature,
202
+ "X-AgentKV-Nonce": nonce,
203
+ "X-AgentKV-Timestamp": String(timestamp)
204
+ };
205
+ }
206
+
207
+ // src/retry.ts
208
+ var DEFAULT_TIMEOUT_MS = 3e4;
209
+ function combineSignals(signals) {
210
+ const present = signals.filter((s) => !!s);
211
+ if (present.length === 0) return void 0;
212
+ if (present.length === 1) return present[0];
213
+ if (typeof AbortSignal.any === "function") return AbortSignal.any(present);
214
+ const ctrl = new AbortController();
215
+ for (const s of present) {
216
+ if (s.aborted) {
217
+ ctrl.abort(s.reason);
218
+ break;
219
+ }
220
+ s.addEventListener("abort", () => ctrl.abort(s.reason), { once: true });
221
+ }
222
+ return ctrl.signal;
223
+ }
224
+ async function drainBody(res) {
225
+ try {
226
+ await res.body?.cancel();
227
+ } catch {
228
+ }
229
+ }
230
+ async function fetchWithRetry(url, build, maxRetries, opts = {}) {
231
+ const doFetch = opts.fetchImpl ?? fetch;
232
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
233
+ for (let attempt = 0; ; attempt++) {
234
+ try {
235
+ const init = await build();
236
+ const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
237
+ const signal = combineSignals([init.signal ?? void 0, opts.signal, timeoutSignal]);
238
+ const res = await doFetch(url, { ...init, signal });
239
+ const transient = res.status >= 500 && res.status <= 599 || res.status === 429;
240
+ if (transient && attempt < maxRetries) {
241
+ await drainBody(res);
242
+ await retryDelay(attempt, res);
243
+ continue;
244
+ }
245
+ return res;
246
+ } catch (err) {
247
+ if (opts.signal?.aborted) {
248
+ throw err instanceof Error ? err : new AgentXError(String(err), "aborted", 0);
249
+ }
250
+ if (attempt < maxRetries) {
251
+ await retryDelay(attempt);
252
+ continue;
253
+ }
254
+ throw err instanceof Error ? err : new AgentXError(String(err), "network_error", 0);
255
+ }
256
+ }
257
+ }
258
+ function retryDelay(attempt, res) {
259
+ const base = Math.min(500, 50 * 2 ** attempt);
260
+ let ms = Math.round(base * (0.5 + Math.random() * 0.5));
261
+ const retryAfter = res?.headers.get("Retry-After");
262
+ if (retryAfter) {
263
+ const secs = Number(retryAfter);
264
+ if (Number.isFinite(secs) && secs >= 0) {
265
+ ms = Math.min(2e3, secs * 1e3);
266
+ } else {
267
+ const when = Date.parse(retryAfter);
268
+ if (Number.isFinite(when)) ms = Math.min(2e3, Math.max(0, when - Date.now()));
269
+ }
270
+ }
271
+ return new Promise((resolve) => setTimeout(resolve, ms));
272
+ }
273
+ export {
274
+ AgentXError as AgentKVError,
275
+ AgentXError,
276
+ DEFAULT_TIMEOUT_MS,
277
+ EIP712_DOMAIN_NAME,
278
+ EIP712_DOMAIN_VERSION,
279
+ MAX_AUTH_WINDOW_SEC,
280
+ SpendCapError,
281
+ buildBearerHeaders,
282
+ buildIdentityHeaders,
283
+ buildPaymentHeader,
284
+ chainIdFromCaip2,
285
+ challengePriceUsd,
286
+ decodeBase64Utf8,
287
+ fetchWithRetry,
288
+ freshNonce,
289
+ nonceFromIdempotencyKey,
290
+ nowSec,
291
+ retryDelay
292
+ };
293
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/idempotency.ts","../src/payment.ts","../src/retry.ts"],"sourcesContent":["// core/src/errors.ts\n//\n// The SINGLE error base class object for the installed workspace. `@agentkv/client`\n// (and any future second service package) must DEPEND ON and RE-EXPORT this class —\n// never re-declare it — or `err instanceof AgentKVError` breaks for anything caught\n// across a package boundary (two distinct class objects in node_modules).\n\n/** Base error carrying a machine code (mapped to CLI exit codes / MCP errors). */\nexport class AgentXError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status?: number,\n ) {\n super(message);\n // Keep the original runtime `.name` string (\"AgentKVError\") so any existing\n // logging/serialization that reads `err.name` sees no observable change from\n // the pre-extraction class — only the exported IDENTIFIER is renamed.\n this.name = \"AgentKVError\";\n }\n}\n\n/**\n * Back-compat alias: the base class shipped as `AgentKVError` before this\n * extraction. Re-export the SAME reference under the old name (not a second\n * `class AgentKVError extends AgentXError {}` declaration) so\n * `new AgentKVError(...) instanceof AgentKVError` and\n * `new AgentKVError(...) instanceof AgentXError` both hold, and so callers that\n * imported `AgentKVError` before this refactor keep compiling and matching.\n */\nexport { AgentXError as AgentKVError };\n\n/** Thrown when a paying call would exceed a configured spend cap. */\nexport class SpendCapError extends AgentXError {\n constructor(message: string) {\n super(message, \"spend_cap_exceeded\");\n this.name = \"SpendCapError\";\n }\n}\n","// core/src/idempotency.ts\n//\n// Nonce helpers shared by the identity-signing and x402-payment paths.\n\nimport { keccak256, stringToHex, toHex } from \"viem\";\n\n/** A fresh random bytes32 nonce (0x-prefixed, 64 hex chars). */\nexport function freshNonce(): `0x${string}` {\n return toHex(crypto.getRandomValues(new Uint8Array(32)));\n}\n\n/**\n * Derive a deterministic bytes32 EIP-3009 nonce from a caller idempotency key,\n * so retrying a logical write reuses the same authorization and the server's\n * idempotency record is hit (exactly-once across caller retries).\n */\nexport function nonceFromIdempotencyKey(key: string): `0x${string}` {\n return keccak256(stringToHex(key));\n}\n","// core/src/payment.ts\n//\n// Payment/identity header builders, plus the `Signer` interface and EIP-712 domain\n// constants they depend on. These travel together because\n// `buildPaymentHeader`/`buildIdentityHeaders` are not truly standalone from them —\n// splitting the functions from their domain constants would force a circular\n// client<->core dependency.\n\nimport { encodePaymentSignatureHeader } from \"@x402/core/http\";\nimport type { PaymentPayload, PaymentRequirements } from \"@x402/core/types\";\nimport { getDefaultAsset } from \"@x402/evm\";\nimport { getAddress } from \"viem\";\nimport { AgentXError } from \"./errors\";\nimport { freshNonce } from \"./idempotency\";\n\n/** Minimal signer the client needs: a viem account satisfies this structurally. */\nexport interface Signer {\n address: `0x${string}`;\n // viem's signTypedData is generic; accept it structurally.\n signTypedData(args: any): Promise<`0x${string}`>;\n}\n\n/** EIP-712 domain name shared with the server's EIP-712 verifier. */\nexport const EIP712_DOMAIN_NAME = \"AgentKV\";\n\n/** EIP-712 domain version shared with the server's EIP-712 verifier. */\nexport const EIP712_DOMAIN_VERSION = \"1\";\n\n/**\n * Hard cap on the signed EIP-3009 authorization window, regardless of the server-supplied\n * `maxTimeoutSeconds`. A signed authorization is a bearer instrument; a hostile challenge\n * asking for a multi-year window must not yield one that stays spendable indefinitely.\n */\nexport const MAX_AUTH_WINDOW_SEC = 3600;\n\n/** Maps a CAIP-2 network id (e.g. \"eip155:8453\") to its numeric chainId. */\nexport function chainIdFromCaip2(network: string): number {\n const parts = network.split(\":\");\n if (parts.length !== 2 || parts[0] !== \"eip155\") {\n throw new Error(`unsupported CAIP-2 network: ${network}`);\n }\n const id = Number(parts[1]);\n if (!Number.isInteger(id) || id <= 0) {\n throw new Error(`invalid CAIP-2 chain id: ${network}`);\n }\n return id;\n}\n\n/**\n * Decode a base64 header as UTF-8 (atob → char-code bytes → TextDecoder(\"utf-8\")).\n * The backend encodes both PAYMENT-REQUIRED and PAYMENT-RESPONSE with base64/UTF-8 and\n * documents this exact mirror decode; a bare `atob()` decodes Latin-1, which corrupts\n * any non-ASCII code point (e.g. a future \"USD₮\" asset name). Byte-identical for ASCII.\n */\nexport function decodeBase64Utf8(b64: string): string {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder(\"utf-8\").decode(bytes);\n}\n\n/** EIP-712 typed-data shape shared with the server's EIP-712 verifier. */\nconst REQUEST_TYPES = {\n Request: [\n { name: \"method\", type: \"string\" },\n { name: \"path\", type: \"string\" },\n // host binds the signature to one deployment (prevents cross-deployment replay) —\n // must match the backend's EIP-712 type definitions exactly, or every identity op fails to verify.\n { name: \"host\", type: \"string\" },\n { name: \"nonce\", type: \"bytes32\" },\n { name: \"timestamp\", type: \"uint256\" },\n ],\n} as const;\n\n/** Identity headers sent on free/credit operations. */\nexport interface IdentityHeaders {\n \"X-AgentKV-Signature\": string;\n \"X-AgentKV-Nonce\": string;\n \"X-AgentKV-Timestamp\": string;\n}\n\n/**\n * Bearer auth header for account-key mode. The opaque `ak_…` token IS the\n * capability: the server hashes it to name the account's storage and debits its\n * prepaid credits. NO x402, NO EIP-712 — this header replaces both. The raw key\n * travels in the clear over TLS exactly like any bearer token; never log it.\n */\nexport function buildBearerHeaders(accountKey: string): Record<string, string> {\n return { Authorization: `Bearer ${accountKey}` };\n}\n\n/** Current unix time in whole seconds. */\nexport function nowSec(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** EIP-712 typed-data types for EIP-3009 transferWithAuthorization. */\nconst TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n} as const;\n\n/** Each PAYMENT-REQUIRED accept is a full v2 PaymentRequirements. */\ntype ChallengeAccept = PaymentRequirements;\n\n/** Decoded PAYMENT-REQUIRED challenge body. */\ninterface PaymentRequiredChallenge {\n x402Version: number;\n accepts: ChallengeAccept[];\n}\n\n/**\n * Decode and validate a base64-encoded PAYMENT-REQUIRED challenge header.\n */\nfunction decodeChallenge(paymentRequiredHeader: string): PaymentRequiredChallenge {\n let json: string;\n try {\n json = decodeBase64Utf8(paymentRequiredHeader);\n } catch {\n throw new Error(\"PAYMENT-REQUIRED header is not valid base64\");\n }\n const parsed = JSON.parse(json) as PaymentRequiredChallenge;\n if (!parsed || !Array.isArray(parsed.accepts)) {\n throw new Error(\"invalid PAYMENT-REQUIRED challenge: missing accepts array\");\n }\n return parsed;\n}\n\n/** Pick the exact-scheme requirement, by amount when several tiers are offered. */\nfunction selectRequirement(accepts: ChallengeAccept[], amountAtomic?: number): ChallengeAccept {\n const exact = accepts.filter((a) => a.scheme === \"exact\");\n if (exact.length === 0)\n throw new Error(\"no acceptable x402 exact-scheme requirement in challenge\");\n if (amountAtomic !== undefined) {\n // Use the first exact requirement as the asset/payTo/network/domain template and\n // override the amount — supports any top-off / deposit amount, not just advertised tiers.\n return { ...exact[0], amount: String(amountAtomic) };\n }\n if (exact.length > 1)\n throw new Error(\n \"ambiguous PAYMENT-REQUIRED challenge: multiple exact requirements; specify an amount\",\n );\n return exact[0];\n}\n\n/**\n * Enforce that a server-supplied challenge targets the client's configured network AND its\n * canonical USDC contract. Money movement must never be dictated solely by the server: a\n * compromised or spoofed worker could otherwise return a challenge for a different chain\n * (e.g. hand a Base-configured client an Arbitrum challenge, draining the SAME EOA's\n * Arbitrum USDC) or a non-canonical token address, and the client would sign it. This is\n * the payment-path mirror of the host-binding on identity signatures and the domain pin.\n */\nfunction assertNetworkParity(req: ChallengeAccept, expectedNetwork: string): void {\n if (req.network !== expectedNetwork) {\n throw new AgentXError(\n `payment challenge network \"${req.network}\" does not match client network \"${expectedNetwork}\"`,\n \"network_mismatch\",\n 0,\n );\n }\n const asset = getDefaultAsset(expectedNetwork as `${string}:${string}`);\n if (getAddress(req.asset) !== getAddress(asset.address)) {\n throw new AgentXError(\n `payment challenge asset \"${req.asset}\" is not the canonical USDC for ${expectedNetwork}`,\n \"asset_mismatch\",\n 0,\n );\n }\n}\n\n/** Decode a challenge and return the chosen requirement's price in USD. */\nexport function challengePriceUsd(\n paymentRequiredHeader: string,\n amountAtomic?: number,\n expectedNetwork?: string,\n): number {\n const { accepts } = decodeChallenge(paymentRequiredHeader);\n const req = selectRequirement(accepts, amountAtomic);\n if (expectedNetwork) assertNetworkParity(req, expectedNetwork);\n return Number(req.amount) / 1_000_000;\n}\n\n/**\n * Build the base64 PAYMENT-SIGNATURE header from a PAYMENT-REQUIRED challenge.\n *\n * Decodes the v2 challenge, selects the matching `exact`-scheme requirement\n * (by `opts.amountAtomic` when several tiers are offered), and signs an EIP-3009\n * transferWithAuthorization payload (validAfter=0, validBefore=now+window) with\n * the viem account. `opts.nonce` pins a deterministic EIP-3009 nonce so retries\n * reuse the same authorization.\n *\n * Returns the base64-encoded JSON payload as the PAYMENT-SIGNATURE value.\n */\nexport async function buildPaymentHeader(\n account: Signer,\n paymentRequiredHeader: string,\n opts?: {\n nonce?: `0x${string}`;\n amountAtomic?: number;\n expectedNetwork?: string;\n /** Pin the recipient: reject the challenge unless its `payTo` equals this address (high-value ops). */\n expectedPayTo?: string;\n },\n): Promise<string> {\n const { x402Version, accepts } = decodeChallenge(paymentRequiredHeader);\n\n const req = selectRequirement(accepts, opts?.amountAtomic);\n\n // Pin the money-moving challenge to the client's configured network + canonical asset\n // BEFORE signing anything (a signed EIP-3009 authorization is a bearer instrument).\n if (opts?.expectedNetwork) assertNetworkParity(req, opts.expectedNetwork);\n // Optional recipient pin: the client can't know the \"correct\" payTo in general (the amount\n // ceiling is the primary defense), but for high-value ops a caller may pin an expected one.\n if (opts?.expectedPayTo && getAddress(req.payTo) !== getAddress(opts.expectedPayTo)) {\n throw new AgentXError(\n `payment challenge payTo \"${req.payTo}\" does not match expected \"${opts.expectedPayTo}\"`,\n \"payto_mismatch\",\n 0,\n );\n }\n\n const chainId = chainIdFromCaip2(req.network);\n // Look up EIP-712 domain info for the token (name/version).\n // Cast to the x402 Network template-literal type.\n const asset = getDefaultAsset(req.network as `${string}:${string}`);\n\n const nonce = opts?.nonce ?? freshNonce();\n const now = nowSec();\n // Clamp the signed window: never sign an authorization valid longer than MAX_AUTH_WINDOW_SEC,\n // regardless of the server-supplied maxTimeoutSeconds.\n const window = Math.min(req.maxTimeoutSeconds ?? 300, MAX_AUTH_WINDOW_SEC);\n const validBefore = String(now + window);\n\n const authorization = {\n from: getAddress(account.address),\n to: getAddress(req.payTo),\n value: req.amount,\n validAfter: \"0\",\n validBefore,\n nonce,\n };\n\n const signature = await account.signTypedData({\n domain: {\n name: asset.name,\n version: asset.version,\n chainId,\n verifyingContract: getAddress(req.asset),\n },\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: \"TransferWithAuthorization\",\n message: {\n from: getAddress(account.address),\n to: getAddress(req.payTo),\n value: BigInt(req.amount),\n validAfter: BigInt(0),\n validBefore: BigInt(validBefore),\n nonce,\n },\n });\n\n // v2 PaymentPayload: the chosen requirement + the signed EIP-3009 authorization,\n // encoded with the SDK so the server's decodePaymentSignatureHeader round-trips.\n const paymentPayload: PaymentPayload = {\n x402Version,\n accepted: req,\n payload: {\n authorization,\n signature,\n },\n };\n\n return encodePaymentSignatureHeader(paymentPayload);\n}\n\n/**\n * Build EIP-712 identity headers for a free/credit op (delete, balance).\n * Signs the Request typed data with a fresh nonce + timestamp so the server\n * can recover the wallet address and enforce replay protection.\n */\nexport async function buildIdentityHeaders(\n account: Signer,\n args: { method: string; path: string; host: string; network: string },\n): Promise<IdentityHeaders> {\n const nonce = freshNonce();\n const timestamp = nowSec();\n const signature = await account.signTypedData({\n domain: {\n name: EIP712_DOMAIN_NAME,\n version: EIP712_DOMAIN_VERSION,\n chainId: chainIdFromCaip2(args.network),\n },\n types: REQUEST_TYPES,\n primaryType: \"Request\",\n message: {\n method: args.method,\n path: args.path,\n host: args.host,\n nonce,\n timestamp: BigInt(timestamp),\n },\n });\n\n return {\n \"X-AgentKV-Signature\": signature,\n \"X-AgentKV-Nonce\": nonce,\n \"X-AgentKV-Timestamp\": String(timestamp),\n };\n}\n","// core/src/retry.ts\n//\n// Pure retry helpers factored out of `AgentKV#fetchWithRetry` / `AgentKV#retryDelay`.\n// The original methods only ever read `this.maxRetries` (a plain number) and called\n// `this.retryDelay` — no other instance state — so they extract cleanly into pure\n// functions parameterized by `maxRetries`. `AgentKV` now delegates to these via thin\n// private wrappers so every existing call site (`this.fetchWithRetry(...)`) is unchanged.\n\nimport { AgentXError } from \"./errors\";\n\n/** Default per-attempt request timeout (ms). A hung-open connection would otherwise wedge an op forever. */\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Tuning knobs for {@link fetchWithRetry}. All optional; sensible defaults. */\nexport interface RetryOptions {\n /** Per-ATTEMPT timeout in ms (via `AbortSignal.timeout`). Default 30_000. Pass 0 to disable. */\n timeoutMs?: number;\n /** Injectable `fetch` for proxies / instrumentation / tests. Defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Caller `AbortSignal` to cancel the whole operation (abort is surfaced immediately, never retried). */\n signal?: AbortSignal;\n}\n\n/** Combine 0+ AbortSignals into one that aborts when any input does. Undefined if none supplied. */\nfunction combineSignals(signals: (AbortSignal | undefined)[]): AbortSignal | undefined {\n const present = signals.filter((s): s is AbortSignal => !!s);\n if (present.length === 0) return undefined;\n if (present.length === 1) return present[0];\n // AbortSignal.any is Node >=20.3 / modern browsers; fall back to a manual combiner for Node 20.0–20.2.\n if (typeof AbortSignal.any === \"function\") return AbortSignal.any(present);\n const ctrl = new AbortController();\n for (const s of present) {\n if (s.aborted) {\n ctrl.abort(s.reason);\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(s.reason), { once: true });\n }\n return ctrl.signal;\n}\n\n/** Release a transient response's body so its socket returns to the pool before we retry. */\nasync function drainBody(res: Response): Promise<void> {\n try {\n await res.body?.cancel();\n } catch {\n // best-effort — a body that can't be cancelled is not worth failing the retry over.\n }\n}\n\n/**\n * Issue a request with bounded retry on TRANSIENT failures only: a thrown\n * fetch (network error / lost response / per-attempt timeout) or a 5xx/429\n * response. `build()` is re-invoked per attempt so a caller can re-sign\n * per-attempt state (e.g. a fresh identity nonce) while a stable Idempotency-Key\n * (and pinned EIP-3009 nonce on paid ops) makes a retry of an already-processed\n * request dedupe server-side — so a lost response that the server already charged\n * is recovered without a second charge. NOT retried: any 2xx/4xx (incl. a 402\n * credit->pay handoff, 401, 404) — returned as-is; and a caller-initiated abort,\n * which is surfaced immediately. Honors `Retry-After` (delta-seconds or HTTP-date).\n * Each attempt is bounded by `opts.timeoutMs` (default 30s).\n */\nexport async function fetchWithRetry(\n url: string,\n build: () => RequestInit | Promise<RequestInit>,\n maxRetries: number,\n opts: RetryOptions = {},\n): Promise<Response> {\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n for (let attempt = 0; ; attempt++) {\n try {\n const init = await build();\n const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;\n const signal = combineSignals([init.signal ?? undefined, opts.signal, timeoutSignal]);\n const res = await doFetch(url, { ...init, signal });\n // Retry TRANSIENT statuses: 5xx, and 429 (rate limited).\n const transient = (res.status >= 500 && res.status <= 599) || res.status === 429;\n if (transient && attempt < maxRetries) {\n await drainBody(res);\n await retryDelay(attempt, res);\n continue;\n }\n return res;\n } catch (err) {\n // A caller-initiated cancel is intentional, not transient — surface it immediately.\n if (opts.signal?.aborted) {\n throw err instanceof Error ? err : new AgentXError(String(err), \"aborted\", 0);\n }\n if (attempt < maxRetries) {\n await retryDelay(attempt);\n continue;\n }\n throw err instanceof Error ? err : new AgentXError(String(err), \"network_error\", 0);\n }\n }\n}\n\n/**\n * Short, bounded backoff between retries. Base is 50ms, 100ms, ... capped at 500ms, with\n * full jitter (each delay uniformly in [50%, 100%] of the base) to avoid a synchronized\n * retry herd under a 5xx/429 storm. If the response carries a `Retry-After` — delta-seconds\n * OR an HTTP-date — honor it up to a 2s cap so a re-sent paid authorization still stays\n * comfortably within its validBefore window (jitter is skipped when the server dictates a delay).\n */\nexport function retryDelay(attempt: number, res?: Response): Promise<void> {\n const base = Math.min(500, 50 * 2 ** attempt);\n let ms = Math.round(base * (0.5 + Math.random() * 0.5));\n const retryAfter = res?.headers.get(\"Retry-After\");\n if (retryAfter) {\n const secs = Number(retryAfter);\n if (Number.isFinite(secs) && secs >= 0) {\n ms = Math.min(2000, secs * 1000); // delta-seconds form\n } else {\n const when = Date.parse(retryAfter); // HTTP-date form\n if (Number.isFinite(when)) ms = Math.min(2000, Math.max(0, when - Date.now()));\n }\n }\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@agentx402-ai/core",
3
+ "version": "0.1.0",
4
+ "description": "Shared auth/pay/usage/error/retry plumbing extracted from @agentkv/client, for reuse across agentx402 service SDKs",
5
+ "license": "MIT",
6
+ "author": "The AgentKV Authors <contact@agentx402.ai>",
7
+ "homepage": "https://github.com/agentx402-ai/agentkv#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/agentx402-ai/agentkv.git",
11
+ "directory": "core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/agentx402-ai/agentkv/issues"
15
+ },
16
+ "keywords": [
17
+ "agentx402",
18
+ "x402",
19
+ "eip-3009",
20
+ "eip-712",
21
+ "viem",
22
+ "sdk-core"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "import": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/index.d.cts",
37
+ "default": "./dist/index.cjs"
38
+ }
39
+ }
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "test": "tsc --noEmit && vitest run",
50
+ "test:watch": "vitest",
51
+ "test:coverage": "vitest run --coverage",
52
+ "typecheck": "tsc --noEmit"
53
+ },
54
+ "dependencies": {
55
+ "@x402/core": "^2.16.0",
56
+ "@x402/evm": "^2.16.0",
57
+ "viem": "^2.21.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^20.0.0",
61
+ "tsup": "^8.3.0",
62
+ "typescript": "^5.6.0",
63
+ "vitest": "^4.1.0"
64
+ },
65
+ "engines": {
66
+ "node": ">=20"
67
+ }
68
+ }