@chainpearc/sdk 0.0.0-stage → 0.3.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/dist/index.js ADDED
@@ -0,0 +1,1107 @@
1
+ // src/chainpe.ts
2
+ import { erc20Abi, formatUnits as formatUnits2, parseUnits, getAddress as getAddress2 } from "viem";
3
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
4
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
5
+ import {
6
+ decodePaymentResponseHeader,
7
+ wrapFetchWithPaymentFromConfig
8
+ } from "@x402/fetch";
9
+ import { isBatchPayment } from "@circle-fin/x402-batching";
10
+ import {
11
+ BatchEvmScheme,
12
+ CompositeEvmScheme,
13
+ GatewayClient
14
+ } from "@circle-fin/x402-batching/client";
15
+
16
+ // src/networks.ts
17
+ import { createPublicClient, http, getAddress } from "viem";
18
+ import { arc, arcTestnet } from "viem/chains";
19
+ var USDC_DECIMALS = 6;
20
+ var ARC_USDC = "0x3600000000000000000000000000000000000000";
21
+ var NETWORKS = {
22
+ "arc-testnet": {
23
+ chain: arcTestnet,
24
+ caip2: "eip155:5042002",
25
+ gatewayChain: "arcTestnet",
26
+ rpcUrl: () => process.env.ARC_TESTNET_RPC_URL ?? "https://rpc.testnet.arc.io",
27
+ explorer: "https://explorer.testnet.arc.io",
28
+ accounts: "0xD315322c9D83A6f50A04236A2a242e0259bfB580",
29
+ reputation: "0x8004B663056A597Dffe9eCcC1965A193B7388713"
30
+ },
31
+ arc: {
32
+ chain: arc,
33
+ caip2: "eip155:5042",
34
+ gatewayChain: "arc",
35
+ rpcUrl: () => process.env.ARC_RPC_URL ?? "https://rpc.mainnet.arc.io",
36
+ explorer: "https://explorer.arc.io",
37
+ reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63"
38
+ }
39
+ };
40
+ function info(network) {
41
+ if (!Object.hasOwn(NETWORKS, network)) {
42
+ throw new Error(`Invalid network "${network}" (expected arc-testnet or arc).`);
43
+ }
44
+ return NETWORKS[network];
45
+ }
46
+ function assertNetwork(network) {
47
+ info(network);
48
+ return network;
49
+ }
50
+ function getCaip2(network) {
51
+ return info(network).caip2;
52
+ }
53
+ function getGatewayChain(network) {
54
+ return info(network).gatewayChain;
55
+ }
56
+ function rpcUrlFor(network) {
57
+ return info(network).rpcUrl();
58
+ }
59
+ function explorerFor(network) {
60
+ return info(network).explorer;
61
+ }
62
+ function getUsdcAddress(network) {
63
+ info(network);
64
+ return ARC_USDC;
65
+ }
66
+ function chainFor(network) {
67
+ const { chain, explorer } = info(network);
68
+ return {
69
+ ...chain,
70
+ rpcUrls: { default: { http: [rpcUrlFor(network)] } },
71
+ blockExplorers: { default: { name: "Arc Explorer", url: explorer } }
72
+ };
73
+ }
74
+ function publicClientFor(network) {
75
+ return createPublicClient({
76
+ chain: chainFor(network),
77
+ transport: http(rpcUrlFor(network))
78
+ });
79
+ }
80
+ function resolveAccountsAddress(network, explicit) {
81
+ const candidate = explicit ?? process.env.CHAINPE_ACCOUNTS_ADDRESS ?? info(network).accounts;
82
+ return candidate ? getAddress(candidate) : void 0;
83
+ }
84
+ function resolveReputationRegistry(network, explicit) {
85
+ return getAddress(
86
+ explicit ?? process.env.ERC8004_REPUTATION_REGISTRY ?? info(network).reputation
87
+ );
88
+ }
89
+ function normalizeKey(key) {
90
+ return key.startsWith("0x") ? key : `0x${key}`;
91
+ }
92
+
93
+ // src/discovery.ts
94
+ import { formatUnits } from "viem";
95
+
96
+ // src/registry.ts
97
+ var SERVICE_COMPONENTS = [
98
+ { name: "key", type: "bytes32" },
99
+ { name: "provider", type: "address" },
100
+ { name: "payTo", type: "address" },
101
+ { name: "agentId", type: "uint256" },
102
+ { name: "name", type: "string" },
103
+ { name: "description", type: "string" },
104
+ { name: "tags", type: "string" },
105
+ { name: "endpoint", type: "string" },
106
+ { name: "pricePerRequest", type: "string" },
107
+ { name: "network", type: "string" },
108
+ { name: "acceptsExact", type: "bool" },
109
+ { name: "acceptsGateway", type: "bool" },
110
+ { name: "active", type: "bool" },
111
+ { name: "createdAt", type: "uint64" },
112
+ { name: "updatedAt", type: "uint64" }
113
+ ];
114
+ var ACCOUNTS_ABI = [
115
+ {
116
+ type: "function",
117
+ name: "serviceCount",
118
+ stateMutability: "view",
119
+ inputs: [],
120
+ outputs: [{ name: "", type: "uint256" }]
121
+ },
122
+ {
123
+ type: "function",
124
+ name: "MAX_SERVICES_PER_PAGE",
125
+ stateMutability: "view",
126
+ inputs: [],
127
+ outputs: [{ name: "", type: "uint256" }]
128
+ },
129
+ {
130
+ type: "function",
131
+ name: "getServices",
132
+ stateMutability: "view",
133
+ inputs: [
134
+ { name: "offset", type: "uint256" },
135
+ { name: "limit", type: "uint256" }
136
+ ],
137
+ outputs: [{ name: "page", type: "tuple[]", components: SERVICE_COMPONENTS }]
138
+ }
139
+ ];
140
+ function decodeService(raw, network) {
141
+ const createdAt = Number(raw.createdAt);
142
+ const updatedAt = Number(raw.updatedAt);
143
+ const rails = [];
144
+ if (raw.acceptsGateway) rails.push("gateway");
145
+ if (raw.acceptsExact) rails.push("exact");
146
+ return {
147
+ id: raw.key,
148
+ name: raw.name,
149
+ description: raw.description,
150
+ tags: raw.tags.split(",").map((t) => t.trim()).filter(Boolean),
151
+ endpoint: raw.endpoint,
152
+ pricePerRequest: raw.pricePerRequest,
153
+ paymentToken: "USDC",
154
+ walletAddress: raw.payTo,
155
+ network,
156
+ rails,
157
+ source: "chainpe",
158
+ developer: raw.provider,
159
+ agentId: raw.agentId > 0n ? raw.agentId.toString() : void 0,
160
+ createdAt: createdAt ? new Date(createdAt * 1e3).toISOString() : void 0,
161
+ updatedAt: updatedAt ? new Date(updatedAt * 1e3).toISOString() : void 0
162
+ };
163
+ }
164
+ function filterServices(services, options) {
165
+ let results = [...services];
166
+ if (options.query) {
167
+ const q = options.query.toLowerCase();
168
+ results = results.filter(
169
+ (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.tags.some((tag) => tag.toLowerCase().includes(q))
170
+ );
171
+ }
172
+ if (options.tags?.length) {
173
+ const tl = options.tags.map((t) => t.toLowerCase());
174
+ results = results.filter(
175
+ (s) => s.tags.some((tag) => tl.includes(tag.toLowerCase()))
176
+ );
177
+ }
178
+ if (options.maxPrice) {
179
+ const max = parseFloat(options.maxPrice);
180
+ results = results.filter((s) => parseFloat(s.pricePerRequest) <= max);
181
+ }
182
+ if (options.gatewayOnly) {
183
+ results = results.filter((s) => s.rails.includes("gateway"));
184
+ }
185
+ return results;
186
+ }
187
+ function rankByReputation(services) {
188
+ return [...services].sort((a, b) => {
189
+ const sa = a.reputation?.score;
190
+ const sb = b.reputation?.score;
191
+ const aHas = sa != null;
192
+ const bHas = sb != null;
193
+ if (aHas !== bHas) return aHas ? -1 : 1;
194
+ if (aHas && bHas && sa !== sb) return sb - sa;
195
+ const ca = a.reputation?.count ?? 0;
196
+ const cb = b.reputation?.count ?? 0;
197
+ if (ca !== cb) return cb - ca;
198
+ return parseFloat(a.pricePerRequest) - parseFloat(b.pricePerRequest);
199
+ });
200
+ }
201
+ var RegistryClient = class {
202
+ constructor(network, address) {
203
+ this.network = network;
204
+ this.address = address;
205
+ }
206
+ network;
207
+ address;
208
+ getAddress() {
209
+ return this.address;
210
+ }
211
+ /** Lists every active service. Deactivated listings stay on-chain, so they are dropped here. */
212
+ async listAllServices() {
213
+ const client = publicClientFor(this.network);
214
+ const [count, maxPage] = await Promise.all([
215
+ client.readContract({
216
+ address: this.address,
217
+ abi: ACCOUNTS_ABI,
218
+ functionName: "serviceCount"
219
+ }),
220
+ client.readContract({
221
+ address: this.address,
222
+ abi: ACCOUNTS_ABI,
223
+ functionName: "MAX_SERVICES_PER_PAGE"
224
+ })
225
+ ]);
226
+ const total = Number(count);
227
+ const pageSize = Number(maxPage);
228
+ const out = [];
229
+ for (let offset = 0; offset < total; offset += pageSize) {
230
+ const limit = Math.min(pageSize, total - offset);
231
+ const page = await client.readContract({
232
+ address: this.address,
233
+ abi: ACCOUNTS_ABI,
234
+ functionName: "getServices",
235
+ args: [BigInt(offset), BigInt(limit)]
236
+ });
237
+ for (const raw of page) {
238
+ if (raw.active) out.push(decodeService(raw, this.network));
239
+ }
240
+ }
241
+ return out;
242
+ }
243
+ };
244
+
245
+ // src/reputation.ts
246
+ import { createWalletClient, http as http2 } from "viem";
247
+ import { privateKeyToAccount } from "viem/accounts";
248
+ var ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
249
+ var REPUTATION_ABI = [
250
+ {
251
+ type: "function",
252
+ name: "giveFeedback",
253
+ stateMutability: "nonpayable",
254
+ inputs: [
255
+ { name: "agentId", type: "uint256" },
256
+ { name: "value", type: "int128" },
257
+ { name: "valueDecimals", type: "uint8" },
258
+ { name: "tag1", type: "string" },
259
+ { name: "tag2", type: "string" },
260
+ { name: "endpoint", type: "string" },
261
+ { name: "feedbackURI", type: "string" },
262
+ { name: "feedbackHash", type: "bytes32" }
263
+ ],
264
+ outputs: []
265
+ },
266
+ {
267
+ type: "function",
268
+ name: "getSummary",
269
+ stateMutability: "view",
270
+ inputs: [
271
+ { name: "agentId", type: "uint256" },
272
+ { name: "clientAddresses", type: "address[]" },
273
+ { name: "tag1", type: "string" },
274
+ { name: "tag2", type: "string" }
275
+ ],
276
+ outputs: [
277
+ { name: "count", type: "uint64" },
278
+ { name: "summaryValue", type: "int128" },
279
+ { name: "summaryValueDecimals", type: "uint8" }
280
+ ]
281
+ },
282
+ {
283
+ type: "function",
284
+ name: "getClients",
285
+ stateMutability: "view",
286
+ inputs: [{ name: "agentId", type: "uint256" }],
287
+ outputs: [{ name: "", type: "address[]" }]
288
+ }
289
+ ];
290
+ function summaryToScore(summaryValue, decimals) {
291
+ return Number(summaryValue) / 10 ** decimals;
292
+ }
293
+ async function getReputation(network, registry, agentId) {
294
+ if (!agentId || agentId === "0") return null;
295
+ if (!registry) return null;
296
+ try {
297
+ const client = publicClientFor(network);
298
+ const id = BigInt(agentId);
299
+ const clients = await client.readContract({
300
+ address: registry,
301
+ abi: REPUTATION_ABI,
302
+ functionName: "getClients",
303
+ args: [id]
304
+ });
305
+ if (clients.length === 0) return { count: 0, score: null };
306
+ const [count, summaryValue, decimals] = await client.readContract({
307
+ address: registry,
308
+ abi: REPUTATION_ABI,
309
+ functionName: "getSummary",
310
+ args: [id, clients, "", ""]
311
+ });
312
+ return {
313
+ count: Number(count),
314
+ score: summaryToScore(summaryValue, Number(decimals))
315
+ };
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+ async function giveFeedback(network, registry, privateKey, params) {
321
+ if (!registry) throw new Error("Reputation registry address not configured");
322
+ const account = privateKeyToAccount(normalizeKey(privateKey));
323
+ const wallet = createWalletClient({
324
+ account,
325
+ chain: chainFor(network),
326
+ transport: http2(rpcUrlFor(network))
327
+ });
328
+ const client = publicClientFor(network);
329
+ const { request } = await client.simulateContract({
330
+ account,
331
+ chain: chainFor(network),
332
+ address: registry,
333
+ abi: REPUTATION_ABI,
334
+ functionName: "giveFeedback",
335
+ args: [
336
+ BigInt(params.agentId),
337
+ BigInt(Math.round(params.value)),
338
+ 0,
339
+ "x402",
340
+ params.tag ?? "paid-call",
341
+ params.endpoint ?? "",
342
+ "",
343
+ ZERO_HASH
344
+ ]
345
+ });
346
+ const hash = await wallet.writeContract(request);
347
+ if (params.waitConfirm) {
348
+ const receipt = await client.waitForTransactionReceipt({ hash });
349
+ if (receipt.status !== "success") throw new Error(`Feedback transaction ${hash} reverted.`);
350
+ }
351
+ return hash;
352
+ }
353
+
354
+ // src/discovery.ts
355
+ var CIRCLE_DISCOVERY_URL = "https://api.circle.com/v2/x402/discovery/resources";
356
+ var GATEWAY_SCHEME_NAME = "GatewayWalletBatched";
357
+ var DEFAULT_LIMIT = 20;
358
+ function mapCircleItem(item, network) {
359
+ const caip2 = getCaip2(network);
360
+ const accepts = (item.accepts ?? []).filter((a) => a.network === caip2);
361
+ if (accepts.length === 0) return void 0;
362
+ const cheapest = accepts.reduce(
363
+ (low, a) => BigInt(a.amount) < BigInt(low.amount) ? a : low
364
+ );
365
+ const rails = [];
366
+ if (accepts.some((a) => a.extra?.name === GATEWAY_SCHEME_NAME)) rails.push("gateway");
367
+ if (accepts.some((a) => a.extra?.name !== GATEWAY_SCHEME_NAME)) rails.push("exact");
368
+ const provider = item.metadata?.provider;
369
+ return {
370
+ id: item.resource,
371
+ name: provider?.name ?? new URL(item.resource).hostname,
372
+ description: item.metadata?.description ?? provider?.description ?? "",
373
+ tags: provider?.tags ?? [],
374
+ endpoint: item.resource,
375
+ method: item.metadata?.method,
376
+ pricePerRequest: formatUnits(BigInt(cheapest.amount), USDC_DECIMALS),
377
+ paymentToken: "USDC",
378
+ walletAddress: cheapest.payTo,
379
+ network,
380
+ rails,
381
+ source: "circle",
382
+ category: provider?.category,
383
+ updatedAt: item.lastUpdated
384
+ };
385
+ }
386
+ async function searchCircle(network, options) {
387
+ const url = new URL(CIRCLE_DISCOVERY_URL);
388
+ url.searchParams.set("network", getCaip2(network));
389
+ url.searchParams.set("limit", String(options.limit ?? DEFAULT_LIMIT));
390
+ if (options.query) url.searchParams.set("query", options.query);
391
+ if (options.category) url.searchParams.set("category", options.category);
392
+ if (options.maxPrice) url.searchParams.set("maxUsdPrice", options.maxPrice);
393
+ if (options.gatewayOnly) url.searchParams.set("supportsCircleGateway", "true");
394
+ const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
395
+ if (!res.ok) throw new Error(`Circle Discovery answered ${res.status}.`);
396
+ const body = await res.json();
397
+ return (body.items ?? []).flatMap((item) => mapCircleItem(item, network) ?? []);
398
+ }
399
+ function mergeListings(circle, chainpe) {
400
+ const key = (s) => `${s.walletAddress.toLowerCase()}@${hostOf(s.endpoint)}`;
401
+ const listed = new Set(circle.map(key));
402
+ return [...chainpe.filter((s) => !listed.has(key(s))), ...circle];
403
+ }
404
+ async function discoverServices(network, query = {}, addresses = {}) {
405
+ const options = typeof query === "string" ? { query } : query;
406
+ const accounts = resolveAccountsAddress(network, addresses.accountsAddress);
407
+ const reputationRegistry = resolveReputationRegistry(
408
+ network,
409
+ addresses.reputationRegistry
410
+ );
411
+ const [circle, chainpe] = await Promise.allSettled([
412
+ searchCircle(network, options),
413
+ accounts ? new RegistryClient(network, accounts).listAllServices().then((services2) => filterServices(services2, options)) : Promise.resolve([])
414
+ ]);
415
+ if (circle.status === "rejected" && chainpe.status === "rejected") {
416
+ throw new Error(
417
+ `Could not discover services: ${String(circle.reason)}; ${String(chainpe.reason)}`
418
+ );
419
+ }
420
+ const services = mergeListings(
421
+ circle.status === "fulfilled" ? circle.value : [],
422
+ chainpe.status === "fulfilled" ? chainpe.value : []
423
+ ).slice(0, options.limit ?? DEFAULT_LIMIT);
424
+ const ranked = await Promise.all(
425
+ services.map(async (s) => ({
426
+ ...s,
427
+ reputation: s.agentId ? await getReputation(network, reputationRegistry, s.agentId) : null
428
+ }))
429
+ );
430
+ return rankByReputation(ranked);
431
+ }
432
+ function hostOf(endpoint) {
433
+ try {
434
+ return new URL(endpoint).host;
435
+ } catch {
436
+ return endpoint;
437
+ }
438
+ }
439
+
440
+ // src/types.ts
441
+ var ChainPePaymentError = class extends Error {
442
+ txHash;
443
+ reason;
444
+ constructor(message, { txHash, reason } = {}) {
445
+ super(message);
446
+ this.name = "ChainPePaymentError";
447
+ this.txHash = txHash;
448
+ this.reason = reason;
449
+ }
450
+ };
451
+
452
+ // src/chainpe.ts
453
+ var DEFAULT_RAILS = ["gateway", "exact"];
454
+ var AGENT_ID_HEADER = "x-chainpe-agent-id";
455
+ var TX_HASH = /^0x[0-9a-fA-F]{64}$/;
456
+ var DEFAULT_TIMEOUT_MS = 3e4;
457
+ function railPolicy(rails, gatewayAvailable) {
458
+ return (_version, requirements) => {
459
+ for (const rail of rails) {
460
+ const options = rail === "gateway" ? requirements.filter(
461
+ (r) => isBatchPayment(r) && BigInt(r.amount) <= gatewayAvailable
462
+ ) : requirements.filter((r) => !isBatchPayment(r));
463
+ if (options.length > 0) return options;
464
+ }
465
+ return [];
466
+ };
467
+ }
468
+ var ChainPe = class {
469
+ network;
470
+ rails;
471
+ accountsAddress;
472
+ reputationRegistry;
473
+ privateKey;
474
+ account;
475
+ maxPerCallAtomic;
476
+ autoFeedback;
477
+ gatewayClient;
478
+ constructor(options) {
479
+ if (!options.privateKey) {
480
+ throw new Error("ChainPe: `privateKey` is required.");
481
+ }
482
+ this.privateKey = normalizeKey(options.privateKey);
483
+ this.network = assertNetwork(options.network ?? "arc-testnet");
484
+ this.rails = options.rails?.length ? options.rails : DEFAULT_RAILS;
485
+ for (const rail of this.rails) {
486
+ if (rail !== "gateway" && rail !== "exact") {
487
+ throw new Error(`Invalid rail "${String(rail)}" (expected gateway or exact).`);
488
+ }
489
+ }
490
+ this.accountsAddress = resolveAccountsAddress(this.network, options.accountsAddress);
491
+ this.reputationRegistry = resolveReputationRegistry(
492
+ this.network,
493
+ options.reputationRegistry
494
+ );
495
+ this.account = privateKeyToAccount2(this.privateKey).address;
496
+ this.maxPerCallAtomic = parseUnits(options.maxPerCall ?? "1", USDC_DECIMALS);
497
+ this.autoFeedback = options.autoFeedback ?? false;
498
+ }
499
+ /** Echoes the resolved configuration (no secrets). */
500
+ get config() {
501
+ return {
502
+ network: this.network,
503
+ address: this.account,
504
+ rails: this.rails,
505
+ accountsAddress: this.accountsAddress,
506
+ reputationRegistry: this.reputationRegistry,
507
+ maxPerCall: formatUnits2(this.maxPerCallAtomic, USDC_DECIMALS)
508
+ };
509
+ }
510
+ /** The wallet address that signs payments and feedback. */
511
+ getAddress() {
512
+ return this.account;
513
+ }
514
+ /** Reads the wallet's USDC and the Circle Gateway deposit (human units). */
515
+ async balance() {
516
+ const usdc = await publicClientFor(this.network).readContract({
517
+ address: getUsdcAddress(this.network),
518
+ abi: erc20Abi,
519
+ functionName: "balanceOf",
520
+ args: [getAddress2(this.account)]
521
+ });
522
+ return {
523
+ usdc: formatUnits2(usdc, USDC_DECIMALS),
524
+ gateway: formatUnits2(await this.gatewayAvailable(), USDC_DECIMALS)
525
+ };
526
+ }
527
+ /** Deposits USDC into Circle Gateway, to pay on the Gateway rail with no gas. */
528
+ async depositGateway(amount) {
529
+ if (!/^\d+(\.\d{1,6})?$/.test(amount) || parseUnits(amount, USDC_DECIMALS) <= 0n) {
530
+ throw new Error(`Invalid amount "${amount}" (expected positive USDC, e.g. 1.50).`);
531
+ }
532
+ const result = await this.gateway().deposit(amount);
533
+ return { txHash: result.depositTxHash, amount: result.formattedAmount };
534
+ }
535
+ /**
536
+ * Drop-in `fetch` that automatically pays an x402 `402 Payment Required` in
537
+ * USDC and returns the final live {@link Response}.
538
+ */
539
+ async fetch(url, opts = {}) {
540
+ const { result, autoFeedback } = await this.execute(url, opts);
541
+ if (result.payment && (autoFeedback ?? this.autoFeedback)) {
542
+ await this.tryAutoFeedback(url, result.payment.recipient, result.agentId);
543
+ }
544
+ return result.response;
545
+ }
546
+ /**
547
+ * Like {@link fetch}, but returns the live, unread {@link Response} together
548
+ * with the payment details. Never posts feedback.
549
+ */
550
+ async request(url, opts = {}) {
551
+ return (await this.execute(url, opts)).result;
552
+ }
553
+ /**
554
+ * Like {@link fetch}, but returns a structured, body-parsed result with the
555
+ * payment + (optional) reputation details. Convenient for agents.
556
+ */
557
+ async pay(url, opts = {}) {
558
+ const { result, autoFeedback } = await this.execute(url, opts);
559
+ const { response } = result;
560
+ let reputation;
561
+ if (result.payment && (autoFeedback ?? this.autoFeedback)) {
562
+ reputation = await this.tryAutoFeedback(
563
+ url,
564
+ result.payment.recipient,
565
+ result.agentId
566
+ );
567
+ }
568
+ const data = await this.parseBody(response);
569
+ return {
570
+ status: response.status,
571
+ ok: response.ok,
572
+ headers: headersToObject(response.headers),
573
+ data,
574
+ payment: result.payment,
575
+ reputation
576
+ };
577
+ }
578
+ /**
579
+ * Finds services through Circle's Discovery API and ChainPeAccounts, annotated
580
+ * with ERC-8004 reputation and ranked. ChainPeAccounts also covers providers
581
+ * Circle has not listed yet. Throws only when neither source can be read.
582
+ */
583
+ async discover(query = {}) {
584
+ return discoverServices(this.network, query, {
585
+ accountsAddress: this.accountsAddress,
586
+ reputationRegistry: this.reputationRegistry
587
+ });
588
+ }
589
+ /** Reads a provider agent's aggregate ERC-8004 reputation. */
590
+ async getReputation(agentId) {
591
+ return getReputation(this.network, this.reputationRegistry, agentId);
592
+ }
593
+ /** Posts ERC-8004 feedback (0–100) for a provider agent. Returns the tx hash. */
594
+ async giveFeedback(agentId, score, opts = {}) {
595
+ return giveFeedback(this.network, this.reputationRegistry, this.privateKey, {
596
+ agentId,
597
+ value: score,
598
+ ...opts
599
+ });
600
+ }
601
+ // ─── internals ─────────────────────────────────────────────────────────────
602
+ gateway() {
603
+ this.gatewayClient ??= new GatewayClient({
604
+ chain: getGatewayChain(this.network),
605
+ privateKey: this.privateKey,
606
+ rpcUrl: rpcUrlFor(this.network)
607
+ });
608
+ return this.gatewayClient;
609
+ }
610
+ /** A deposit we cannot read counts as empty, so payment falls back to the wallet. */
611
+ async gatewayAvailable() {
612
+ try {
613
+ return (await this.gateway().getBalances()).gateway.available;
614
+ } catch {
615
+ return 0n;
616
+ }
617
+ }
618
+ /**
619
+ * Runs the x402 flow: request → on 402, cap the price, choose a rail, sign →
620
+ * retry. Spend limits run before anything is signed.
621
+ */
622
+ async execute(url, opts) {
623
+ const { autoFeedback, timeout = DEFAULT_TIMEOUT_MS, ...init } = opts;
624
+ let selected;
625
+ let agentId;
626
+ let gatewayAvailable = 0n;
627
+ const baseFetch = async (input, requestInit) => {
628
+ const caller = requestInit?.signal ?? (input instanceof Request ? input.signal : void 0);
629
+ const expiry = AbortSignal.timeout(timeout);
630
+ const response2 = await fetch(input, {
631
+ ...requestInit,
632
+ signal: caller ? AbortSignal.any([caller, expiry]) : expiry
633
+ });
634
+ agentId = response2.headers.get(AGENT_ID_HEADER) ?? agentId;
635
+ if (response2.status === 402 && !selected && this.rails.includes("gateway")) {
636
+ gatewayAvailable = await this.gatewayAvailable();
637
+ }
638
+ return response2;
639
+ };
640
+ const account = privateKeyToAccount2(this.privateKey);
641
+ const client = new CompositeEvmScheme(
642
+ new BatchEvmScheme(account),
643
+ new ExactEvmScheme(account)
644
+ );
645
+ const caip2 = getCaip2(this.network);
646
+ const payingFetch = wrapFetchWithPaymentFromConfig(baseFetch, {
647
+ schemes: [{ network: caip2, client }],
648
+ // CompositeEvmScheme cannot name default assets, so Arc USDC is listed with its own cap.
649
+ spendControls: {
650
+ maxAmountPerPayment: false,
651
+ allowedAssets: [
652
+ {
653
+ network: caip2,
654
+ asset: getUsdcAddress(this.network),
655
+ maxAmountPerPayment: this.maxPerCallAtomic.toString()
656
+ }
657
+ ]
658
+ },
659
+ policies: [
660
+ (version, requirements) => {
661
+ const options = railPolicy(this.rails, gatewayAvailable)(version, requirements);
662
+ selected = options[0];
663
+ return options;
664
+ }
665
+ ]
666
+ });
667
+ let response;
668
+ try {
669
+ response = await payingFetch(url, init);
670
+ } catch (err) {
671
+ throw this.explainPaymentError(err, url);
672
+ }
673
+ if (response.status === 402) {
674
+ const reason = await readFailureReason(response);
675
+ throw new ChainPePaymentError(
676
+ `x402: payment signed but the server did not accept it (${reason}). Check the USDC on ${this.network} or the Gateway deposit.`,
677
+ { reason }
678
+ );
679
+ }
680
+ return {
681
+ result: {
682
+ response,
683
+ payment: selected ? this.paymentInfo(selected, response) : void 0,
684
+ agentId
685
+ },
686
+ autoFeedback
687
+ };
688
+ }
689
+ paymentInfo(selected, response) {
690
+ const payment = {
691
+ amount: formatUnits2(BigInt(selected.amount), USDC_DECIMALS),
692
+ recipient: selected.payTo,
693
+ network: this.network,
694
+ rail: isBatchPayment(selected) ? "gateway" : "exact"
695
+ };
696
+ const header = response.headers.get("PAYMENT-RESPONSE");
697
+ if (header) {
698
+ try {
699
+ const receipt = decodePaymentResponseHeader(header);
700
+ payment.settlement = receipt;
701
+ if (TX_HASH.test(receipt.transaction)) payment.txHash = receipt.transaction;
702
+ else if (receipt.transaction) payment.transferId = receipt.transaction;
703
+ } catch {
704
+ }
705
+ }
706
+ return payment;
707
+ }
708
+ explainPaymentError(err, url) {
709
+ const message = err instanceof Error ? err.message : String(err);
710
+ if (/no network\/scheme registered/i.test(message)) {
711
+ return new Error(`x402: ${url} does not accept payment on ${this.network}.`);
712
+ }
713
+ if (/spend|maxAmountPerPayment|allowedAssets|exceed/i.test(message)) {
714
+ return new Error(
715
+ `x402: the price for ${url} exceeds maxPerCall ${formatUnits2(this.maxPerCallAtomic, USDC_DECIMALS)} USDC. Nothing was signed.`
716
+ );
717
+ }
718
+ if (/filtered|policy|policies/i.test(message)) {
719
+ return new ChainPePaymentError(
720
+ `x402: ${url} has no payment option for rails [${this.rails.join(", ")}]. A Gateway-only price needs a Gateway deposit.`,
721
+ { reason: "no_usable_rail" }
722
+ );
723
+ }
724
+ return err;
725
+ }
726
+ /**
727
+ * Best-effort positive feedback for the provider's agent after a paid call.
728
+ * Prefers the agent id the provider advertises; otherwise looks for its `payTo`
729
+ * in ChainPeAccounts. Never throws (self-feedback, no gas, etc. are swallowed).
730
+ */
731
+ async tryAutoFeedback(url, payTo, advertisedAgentId) {
732
+ try {
733
+ let agentId = advertisedAgentId;
734
+ if ((!agentId || agentId === "0") && this.accountsAddress) {
735
+ const registry = new RegistryClient(this.network, this.accountsAddress);
736
+ const services = await registry.listAllServices();
737
+ agentId = services.find(
738
+ (s) => s.walletAddress.toLowerCase() === payTo.toLowerCase() && s.agentId
739
+ )?.agentId;
740
+ }
741
+ if (!agentId || agentId === "0") return void 0;
742
+ const txHash = await giveFeedback(
743
+ this.network,
744
+ this.reputationRegistry,
745
+ this.privateKey,
746
+ {
747
+ agentId,
748
+ value: 100,
749
+ endpoint: new URL(url).origin,
750
+ tag: "x402-success"
751
+ }
752
+ );
753
+ return { agentId, score: 100, txHash };
754
+ } catch {
755
+ return void 0;
756
+ }
757
+ }
758
+ async parseBody(response) {
759
+ const contentType = response.headers.get("content-type") ?? "";
760
+ if (contentType.includes("application/json")) {
761
+ try {
762
+ return await response.json();
763
+ } catch {
764
+ return null;
765
+ }
766
+ }
767
+ return response.text();
768
+ }
769
+ };
770
+ async function readFailureReason(response) {
771
+ const header = response.headers.get("PAYMENT-RESPONSE");
772
+ if (header) {
773
+ try {
774
+ const receipt = decodePaymentResponseHeader(header);
775
+ if (receipt.errorReason) return receipt.errorReason;
776
+ } catch {
777
+ }
778
+ }
779
+ try {
780
+ const body = await response.clone().json();
781
+ return body.reason ?? body.error ?? "settlement refused";
782
+ } catch {
783
+ return "settlement refused";
784
+ }
785
+ }
786
+ function headersToObject(headers) {
787
+ const obj = {};
788
+ headers.forEach((value, key) => {
789
+ obj[key] = value;
790
+ });
791
+ return obj;
792
+ }
793
+
794
+ // src/policyvault.ts
795
+ import {
796
+ createWalletClient as createWalletClient2,
797
+ http as http3,
798
+ parseUnits as parseUnits2,
799
+ formatUnits as formatUnits3,
800
+ getAddress as getAddress3
801
+ } from "viem";
802
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
803
+ var VAULT_ABI = [
804
+ {
805
+ type: "function",
806
+ name: "deposit",
807
+ stateMutability: "nonpayable",
808
+ inputs: [{ name: "amount", type: "uint256" }],
809
+ outputs: []
810
+ },
811
+ {
812
+ type: "function",
813
+ name: "withdraw",
814
+ stateMutability: "nonpayable",
815
+ inputs: [{ name: "amount", type: "uint256" }],
816
+ outputs: []
817
+ },
818
+ {
819
+ type: "function",
820
+ name: "setPolicy",
821
+ stateMutability: "nonpayable",
822
+ inputs: [
823
+ { name: "sessionKey", type: "address" },
824
+ { name: "maxPerCall", type: "uint256" },
825
+ { name: "dailyCap", type: "uint256" },
826
+ { name: "totalBudget", type: "uint256" },
827
+ { name: "expiry", type: "uint64" },
828
+ { name: "allowlistOnly", type: "bool" }
829
+ ],
830
+ outputs: []
831
+ },
832
+ {
833
+ type: "function",
834
+ name: "setAllowlist",
835
+ stateMutability: "nonpayable",
836
+ inputs: [
837
+ { name: "to", type: "address" },
838
+ { name: "allowed", type: "bool" }
839
+ ],
840
+ outputs: []
841
+ },
842
+ {
843
+ type: "function",
844
+ name: "revokeSession",
845
+ stateMutability: "nonpayable",
846
+ inputs: [],
847
+ outputs: []
848
+ },
849
+ {
850
+ type: "function",
851
+ name: "spend",
852
+ stateMutability: "nonpayable",
853
+ inputs: [
854
+ { name: "owner", type: "address" },
855
+ { name: "to", type: "address" },
856
+ { name: "amount", type: "uint256" },
857
+ { name: "deadline", type: "uint256" },
858
+ { name: "sessionSig", type: "bytes" }
859
+ ],
860
+ outputs: []
861
+ },
862
+ {
863
+ type: "function",
864
+ name: "balanceOf",
865
+ stateMutability: "view",
866
+ inputs: [{ name: "owner", type: "address" }],
867
+ outputs: [{ name: "", type: "uint256" }]
868
+ },
869
+ {
870
+ type: "function",
871
+ name: "nonces",
872
+ stateMutability: "view",
873
+ inputs: [{ name: "owner", type: "address" }],
874
+ outputs: [{ name: "", type: "uint256" }]
875
+ },
876
+ {
877
+ type: "function",
878
+ name: "dailyRemaining",
879
+ stateMutability: "view",
880
+ inputs: [{ name: "owner", type: "address" }],
881
+ outputs: [{ name: "", type: "uint256" }]
882
+ },
883
+ {
884
+ type: "function",
885
+ name: "budgetRemaining",
886
+ stateMutability: "view",
887
+ inputs: [{ name: "owner", type: "address" }],
888
+ outputs: [{ name: "", type: "uint256" }]
889
+ }
890
+ ];
891
+ var SPEND_TYPES = {
892
+ Spend: [
893
+ { name: "owner", type: "address" },
894
+ { name: "to", type: "address" },
895
+ { name: "amount", type: "uint256" },
896
+ { name: "nonce", type: "uint256" },
897
+ { name: "deadline", type: "uint256" }
898
+ ]
899
+ };
900
+ var PolicyVaultClient = class {
901
+ network;
902
+ vault;
903
+ account;
904
+ constructor(options) {
905
+ if (!options.privateKey) throw new Error("PolicyVaultClient: privateKey required");
906
+ if (!options.vaultAddress) throw new Error("PolicyVaultClient: vaultAddress required");
907
+ this.network = options.network ?? "arc-testnet";
908
+ this.vault = getAddress3(options.vaultAddress);
909
+ this.account = privateKeyToAccount3(normalizeKey(options.privateKey));
910
+ }
911
+ getAddress() {
912
+ return this.account.address;
913
+ }
914
+ wallet() {
915
+ return createWalletClient2({
916
+ account: this.account,
917
+ chain: chainFor(this.network),
918
+ transport: http3(rpcUrlFor(this.network))
919
+ });
920
+ }
921
+ usdc(human) {
922
+ return parseUnits2(human, USDC_DECIMALS);
923
+ }
924
+ // ─── reads ────────────────────────────────────────────────────────────────
925
+ async balanceOf(owner) {
926
+ const raw = await publicClientFor(this.network).readContract({
927
+ address: this.vault,
928
+ abi: VAULT_ABI,
929
+ functionName: "balanceOf",
930
+ args: [getAddress3(owner)]
931
+ });
932
+ return formatUnits3(raw, USDC_DECIMALS);
933
+ }
934
+ async dailyRemaining(owner) {
935
+ const raw = await publicClientFor(this.network).readContract({
936
+ address: this.vault,
937
+ abi: VAULT_ABI,
938
+ functionName: "dailyRemaining",
939
+ args: [getAddress3(owner)]
940
+ });
941
+ return formatUnits3(raw, USDC_DECIMALS);
942
+ }
943
+ async budgetRemaining(owner) {
944
+ const raw = await publicClientFor(this.network).readContract({
945
+ address: this.vault,
946
+ abi: VAULT_ABI,
947
+ functionName: "budgetRemaining",
948
+ args: [getAddress3(owner)]
949
+ });
950
+ return formatUnits3(raw, USDC_DECIMALS);
951
+ }
952
+ async nonce(owner) {
953
+ return await publicClientFor(this.network).readContract({
954
+ address: this.vault,
955
+ abi: VAULT_ABI,
956
+ functionName: "nonces",
957
+ args: [getAddress3(owner)]
958
+ });
959
+ }
960
+ // ─── owner ops (this key is the owner) ──────────────────────────────────────
961
+ async deposit(human) {
962
+ return this.wallet().writeContract({
963
+ account: this.account,
964
+ chain: chainFor(this.network),
965
+ address: this.vault,
966
+ abi: VAULT_ABI,
967
+ functionName: "deposit",
968
+ args: [this.usdc(human)]
969
+ });
970
+ }
971
+ async withdraw(human) {
972
+ return this.wallet().writeContract({
973
+ account: this.account,
974
+ chain: chainFor(this.network),
975
+ address: this.vault,
976
+ abi: VAULT_ABI,
977
+ functionName: "withdraw",
978
+ args: [this.usdc(human)]
979
+ });
980
+ }
981
+ async setPolicy(p) {
982
+ return this.wallet().writeContract({
983
+ account: this.account,
984
+ chain: chainFor(this.network),
985
+ address: this.vault,
986
+ abi: VAULT_ABI,
987
+ functionName: "setPolicy",
988
+ args: [
989
+ getAddress3(p.sessionKey),
990
+ this.usdc(p.maxPerCall),
991
+ this.usdc(p.dailyCap),
992
+ this.usdc(p.totalBudget),
993
+ BigInt(p.expiry),
994
+ p.allowlistOnly ?? false
995
+ ]
996
+ });
997
+ }
998
+ async setAllowlist(to, allowed) {
999
+ return this.wallet().writeContract({
1000
+ account: this.account,
1001
+ chain: chainFor(this.network),
1002
+ address: this.vault,
1003
+ abi: VAULT_ABI,
1004
+ functionName: "setAllowlist",
1005
+ args: [getAddress3(to), allowed]
1006
+ });
1007
+ }
1008
+ async revokeSession() {
1009
+ return this.wallet().writeContract({
1010
+ account: this.account,
1011
+ chain: chainFor(this.network),
1012
+ address: this.vault,
1013
+ abi: VAULT_ABI,
1014
+ functionName: "revokeSession",
1015
+ args: []
1016
+ });
1017
+ }
1018
+ // ─── agent: sign a spend (gasless — no tx) ──────────────────────────────────
1019
+ /**
1020
+ * Signs a spend authorization with this key (the session key). Reads the
1021
+ * owner's current nonce so the relayer can submit it. `ttlSeconds` bounds how
1022
+ * long the authorization stays valid (default 5 min).
1023
+ */
1024
+ async signSpend(params) {
1025
+ const owner = getAddress3(params.owner);
1026
+ const to = getAddress3(params.to);
1027
+ const amountAtomic = this.usdc(params.amount);
1028
+ const nonce = await this.nonce(owner);
1029
+ const deadline = Math.floor(Date.now() / 1e3) + (params.ttlSeconds ?? 300);
1030
+ const signature = await this.account.signTypedData({
1031
+ domain: {
1032
+ name: "ChainPePolicyVault",
1033
+ version: "1",
1034
+ chainId: chainFor(this.network).id,
1035
+ verifyingContract: this.vault
1036
+ },
1037
+ types: SPEND_TYPES,
1038
+ primaryType: "Spend",
1039
+ message: {
1040
+ owner,
1041
+ to,
1042
+ amount: amountAtomic,
1043
+ nonce,
1044
+ deadline: BigInt(deadline)
1045
+ }
1046
+ });
1047
+ return {
1048
+ owner,
1049
+ to,
1050
+ amount: params.amount,
1051
+ amountAtomic: amountAtomic.toString(),
1052
+ nonce: nonce.toString(),
1053
+ deadline,
1054
+ signature
1055
+ };
1056
+ }
1057
+ // ─── relayer: submit a signed spend (pays gas) ──────────────────────────────
1058
+ /**
1059
+ * Submits a session-signed spend. Throws with the on-chain revert reason if a
1060
+ * policy limit is violated (e.g. "over per-call cap").
1061
+ */
1062
+ async relaySpend(auth) {
1063
+ return this.wallet().writeContract({
1064
+ account: this.account,
1065
+ chain: chainFor(this.network),
1066
+ address: this.vault,
1067
+ abi: VAULT_ABI,
1068
+ functionName: "spend",
1069
+ args: [
1070
+ getAddress3(auth.owner),
1071
+ getAddress3(auth.to),
1072
+ BigInt(auth.amountAtomic),
1073
+ BigInt(auth.deadline),
1074
+ auth.signature
1075
+ ]
1076
+ });
1077
+ }
1078
+ };
1079
+ export {
1080
+ CIRCLE_DISCOVERY_URL,
1081
+ ChainPe,
1082
+ ChainPePaymentError,
1083
+ PolicyVaultClient,
1084
+ RegistryClient,
1085
+ USDC_DECIMALS,
1086
+ chainFor,
1087
+ decodeService,
1088
+ discoverServices,
1089
+ explorerFor,
1090
+ filterServices,
1091
+ getCaip2,
1092
+ getReputation,
1093
+ getUsdcAddress,
1094
+ giveFeedback,
1095
+ mapCircleItem,
1096
+ mergeListings,
1097
+ normalizeKey,
1098
+ publicClientFor,
1099
+ railPolicy,
1100
+ rankByReputation,
1101
+ resolveAccountsAddress,
1102
+ resolveReputationRegistry,
1103
+ rpcUrlFor,
1104
+ searchCircle,
1105
+ summaryToScore
1106
+ };
1107
+ //# sourceMappingURL=index.js.map