agentwallet-sdk 2.0.0 → 2.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.
Files changed (38) hide show
  1. package/README.md +88 -2
  2. package/dist/identity/erc8004.d.ts +538 -0
  3. package/dist/identity/erc8004.d.ts.map +1 -0
  4. package/dist/identity/erc8004.js +656 -0
  5. package/dist/identity/erc8004.js.map +1 -0
  6. package/dist/index.d.ts +308 -233
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/x402/__tests__/budget.test.d.ts +2 -0
  11. package/dist/x402/__tests__/budget.test.d.ts.map +1 -0
  12. package/dist/x402/__tests__/budget.test.js +114 -0
  13. package/dist/x402/__tests__/budget.test.js.map +1 -0
  14. package/dist/x402/__tests__/client.test.d.ts +2 -0
  15. package/dist/x402/__tests__/client.test.d.ts.map +1 -0
  16. package/dist/x402/__tests__/client.test.js +107 -0
  17. package/dist/x402/__tests__/client.test.js.map +1 -0
  18. package/dist/x402/budget.d.ts +52 -0
  19. package/dist/x402/budget.d.ts.map +1 -0
  20. package/dist/x402/budget.js +113 -0
  21. package/dist/x402/budget.js.map +1 -0
  22. package/dist/x402/client.d.ts +60 -0
  23. package/dist/x402/client.d.ts.map +1 -0
  24. package/dist/x402/client.js +205 -0
  25. package/dist/x402/client.js.map +1 -0
  26. package/dist/x402/index.d.ts +6 -0
  27. package/dist/x402/index.d.ts.map +1 -0
  28. package/dist/x402/index.js +6 -0
  29. package/dist/x402/index.js.map +1 -0
  30. package/dist/x402/middleware.d.ts +37 -0
  31. package/dist/x402/middleware.d.ts.map +1 -0
  32. package/dist/x402/middleware.js +65 -0
  33. package/dist/x402/middleware.js.map +1 -0
  34. package/dist/x402/types.d.ts +91 -0
  35. package/dist/x402/types.d.ts.map +1 -0
  36. package/dist/x402/types.js +9 -0
  37. package/dist/x402/types.js.map +1 -0
  38. package/package.json +14 -7
@@ -0,0 +1,656 @@
1
+ /**
2
+ * ERC-8004: Trustless Agents — Identity Registry Integration
3
+ *
4
+ * Implements the Identity Registry component of ERC-8004, which provides every
5
+ * AI agent with a portable, censorship-resistant on-chain identifier using ERC-721
6
+ * with URIStorage extension.
7
+ *
8
+ * Spec: https://eips.ethereum.org/EIPS/eip-8004
9
+ * Status: DRAFT (August 2025). No official mainnet singleton deployed yet —
10
+ * registry address is configurable per deployment.
11
+ *
12
+ * Key Principle: Non-custodial. All signing happens locally via the caller's
13
+ * WalletClient. Keys never leave the device.
14
+ */
15
+ import { createPublicClient, getContract, http, } from 'viem';
16
+ import { base, baseSepolia, mainnet, arbitrum, polygon } from 'viem/chains';
17
+ // ─── ABI ─────────────────────────────────────────────────────────────────────
18
+ /**
19
+ * Minimal ABI for the ERC-8004 Identity Registry.
20
+ * Extends ERC-721 with agent-specific registration functions.
21
+ */
22
+ export const ERC8004IdentityRegistryAbi = [
23
+ // Registration
24
+ {
25
+ name: 'register',
26
+ type: 'function',
27
+ stateMutability: 'nonpayable',
28
+ inputs: [{ name: 'agentURI', type: 'string' }],
29
+ outputs: [{ name: 'agentId', type: 'uint256' }],
30
+ },
31
+ {
32
+ name: 'registerWithMetadata',
33
+ type: 'function',
34
+ stateMutability: 'nonpayable',
35
+ inputs: [
36
+ { name: 'agentURI', type: 'string' },
37
+ {
38
+ name: 'metadata',
39
+ type: 'tuple[]',
40
+ components: [
41
+ { name: 'metadataKey', type: 'string' },
42
+ { name: 'metadataValue', type: 'bytes' },
43
+ ],
44
+ },
45
+ ],
46
+ outputs: [{ name: 'agentId', type: 'uint256' }],
47
+ },
48
+ {
49
+ name: 'registerEmpty',
50
+ type: 'function',
51
+ stateMutability: 'nonpayable',
52
+ inputs: [],
53
+ outputs: [{ name: 'agentId', type: 'uint256' }],
54
+ },
55
+ // URI management
56
+ {
57
+ name: 'setAgentURI',
58
+ type: 'function',
59
+ stateMutability: 'nonpayable',
60
+ inputs: [
61
+ { name: 'agentId', type: 'uint256' },
62
+ { name: 'newURI', type: 'string' },
63
+ ],
64
+ outputs: [],
65
+ },
66
+ {
67
+ name: 'tokenURI',
68
+ type: 'function',
69
+ stateMutability: 'view',
70
+ inputs: [{ name: 'agentId', type: 'uint256' }],
71
+ outputs: [{ name: '', type: 'string' }],
72
+ },
73
+ // On-chain metadata
74
+ {
75
+ name: 'getMetadata',
76
+ type: 'function',
77
+ stateMutability: 'view',
78
+ inputs: [
79
+ { name: 'agentId', type: 'uint256' },
80
+ { name: 'metadataKey', type: 'string' },
81
+ ],
82
+ outputs: [{ name: '', type: 'bytes' }],
83
+ },
84
+ {
85
+ name: 'setMetadata',
86
+ type: 'function',
87
+ stateMutability: 'nonpayable',
88
+ inputs: [
89
+ { name: 'agentId', type: 'uint256' },
90
+ { name: 'metadataKey', type: 'string' },
91
+ { name: 'metadataValue', type: 'bytes' },
92
+ ],
93
+ outputs: [],
94
+ },
95
+ // Agent wallet (payment address)
96
+ {
97
+ name: 'getAgentWallet',
98
+ type: 'function',
99
+ stateMutability: 'view',
100
+ inputs: [{ name: 'agentId', type: 'uint256' }],
101
+ outputs: [{ name: '', type: 'address' }],
102
+ },
103
+ {
104
+ name: 'setAgentWallet',
105
+ type: 'function',
106
+ stateMutability: 'nonpayable',
107
+ inputs: [
108
+ { name: 'agentId', type: 'uint256' },
109
+ { name: 'newWallet', type: 'address' },
110
+ { name: 'deadline', type: 'uint256' },
111
+ { name: 'signature', type: 'bytes' },
112
+ ],
113
+ outputs: [],
114
+ },
115
+ {
116
+ name: 'unsetAgentWallet',
117
+ type: 'function',
118
+ stateMutability: 'nonpayable',
119
+ inputs: [{ name: 'agentId', type: 'uint256' }],
120
+ outputs: [],
121
+ },
122
+ // ERC-721 ownership
123
+ {
124
+ name: 'ownerOf',
125
+ type: 'function',
126
+ stateMutability: 'view',
127
+ inputs: [{ name: 'tokenId', type: 'uint256' }],
128
+ outputs: [{ name: '', type: 'address' }],
129
+ },
130
+ // Events
131
+ {
132
+ name: 'Registered',
133
+ type: 'event',
134
+ inputs: [
135
+ { name: 'agentId', type: 'uint256', indexed: true },
136
+ { name: 'agentURI', type: 'string', indexed: false },
137
+ { name: 'owner', type: 'address', indexed: true },
138
+ ],
139
+ },
140
+ {
141
+ name: 'URIUpdated',
142
+ type: 'event',
143
+ inputs: [
144
+ { name: 'agentId', type: 'uint256', indexed: true },
145
+ { name: 'newURI', type: 'string', indexed: false },
146
+ { name: 'updatedBy', type: 'address', indexed: true },
147
+ ],
148
+ },
149
+ {
150
+ name: 'MetadataSet',
151
+ type: 'event',
152
+ inputs: [
153
+ { name: 'agentId', type: 'uint256', indexed: true },
154
+ { name: 'indexedMetadataKey', type: 'string', indexed: true },
155
+ { name: 'metadataKey', type: 'string', indexed: false },
156
+ { name: 'metadataValue', type: 'bytes', indexed: false },
157
+ ],
158
+ },
159
+ ];
160
+ // ─── Reserved metadata keys (per spec) ───────────────────────────────────────
161
+ export const METADATA_KEYS = {
162
+ /** Reserved by spec — set via setAgentWallet(), not setMetadata() */
163
+ AGENT_WALLET: 'agentWallet',
164
+ /** Optional: AI model identifier */
165
+ MODEL: 'agentModel',
166
+ /** Optional: model provider */
167
+ MODEL_PROVIDER: 'agentModelProvider',
168
+ /** Optional: agent version string */
169
+ VERSION: 'agentVersion',
170
+ /** Optional: JSON-encoded capability tags */
171
+ CAPABILITIES: 'agentCapabilities',
172
+ /** Optional: agent framework name */
173
+ FRAMEWORK: 'agentFramework',
174
+ };
175
+ /** ERC-8004 registration file type string (MUST match this exactly) */
176
+ export const REGISTRATION_FILE_TYPE = 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1';
177
+ /**
178
+ * Known testnet deployments of ERC-8004 Identity Registry.
179
+ * Mainnet singleton not yet deployed (spec is DRAFT as of Feb 2026).
180
+ * Update these addresses once official deployments are announced.
181
+ */
182
+ export const KNOWN_REGISTRY_ADDRESSES = {
183
+ // Placeholder — replace with official addresses once deployed
184
+ 'base-sepolia': '0x0000000000000000000000000000000000000000',
185
+ };
186
+ // ─── Chain map (mirrors index.ts) ─────────────────────────────────────────────
187
+ const CHAINS = {
188
+ base,
189
+ 'base-sepolia': baseSepolia,
190
+ ethereum: mainnet,
191
+ arbitrum,
192
+ polygon,
193
+ };
194
+ // ─── Client ──────────────────────────────────────────────────────────────────
195
+ /**
196
+ * ERC-8004 Identity Registry client.
197
+ *
198
+ * Provides type-safe, non-custodial access to the ERC-8004 Identity Registry.
199
+ * All write operations require a WalletClient — keys never leave the caller's device.
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * import { ERC8004Client } from '@agentwallet/sdk';
204
+ *
205
+ * const identity = new ERC8004Client({
206
+ * registryAddress: '0xYOUR_REGISTRY',
207
+ * chain: 'base',
208
+ * });
209
+ *
210
+ * // Register a new agent identity
211
+ * const { txHash, agentId } = await identity.registerAgent(
212
+ * walletClient,
213
+ * {
214
+ * name: 'MyTradingAgent',
215
+ * description: 'Autonomous DeFi trading agent',
216
+ * services: [{ name: 'A2A', endpoint: 'https://agent.example/.well-known/agent-card.json' }],
217
+ * x402Support: true,
218
+ * active: true,
219
+ * },
220
+ * 'ipfs://QmYourCID'
221
+ * );
222
+ *
223
+ * // Lookup an agent
224
+ * const agentIdentity = await identity.lookupAgentIdentity(walletClient.account!.address);
225
+ * ```
226
+ */
227
+ export class ERC8004Client {
228
+ constructor(config) {
229
+ this.config = config;
230
+ const chain = CHAINS[config.chain];
231
+ if (!chain)
232
+ throw new Error(`ERC8004Client: Unsupported chain "${config.chain}"`);
233
+ this.chain = chain;
234
+ this.publicClient = createPublicClient({
235
+ chain,
236
+ transport: http(config.rpcUrl),
237
+ });
238
+ }
239
+ /** @internal Get a viem contract instance for read operations */
240
+ getReadContract() {
241
+ return getContract({
242
+ address: this.config.registryAddress,
243
+ abi: ERC8004IdentityRegistryAbi,
244
+ client: this.publicClient,
245
+ });
246
+ }
247
+ /** @internal Get a viem contract instance for read+write operations */
248
+ getWriteContract(walletClient) {
249
+ return getContract({
250
+ address: this.config.registryAddress,
251
+ abi: ERC8004IdentityRegistryAbi,
252
+ client: { public: this.publicClient, wallet: walletClient },
253
+ });
254
+ }
255
+ // ─── Registration ───────────────────────────────────────────────────────
256
+ /**
257
+ * Register a new agent identity on-chain.
258
+ *
259
+ * Builds a spec-compliant registration file JSON and registers it via
260
+ * the Identity Registry. The URI can be:
261
+ * - An IPFS URI you've already pinned: "ipfs://QmYourCID"
262
+ * - A base64 data URI for fully on-chain storage (auto-generated if not provided)
263
+ * - An HTTPS URI pointing to a static JSON file
264
+ *
265
+ * Non-custodial: the walletClient signs the transaction locally.
266
+ *
267
+ * @param walletClient - WalletClient controlling the agent owner key
268
+ * @param metadata - Agent metadata to include in the registration file
269
+ * @param agentURI - URI for the registration file (auto-builds data URI if omitted)
270
+ * @param onChainMetadata - Optional extra on-chain key/value pairs
271
+ * @returns txHash and agentId (from Registered event)
272
+ */
273
+ async registerAgent(walletClient, metadata, agentURI, onChainMetadata) {
274
+ if (!walletClient.account)
275
+ throw new Error('ERC8004Client: WalletClient has no account');
276
+ const registrationFile = {
277
+ type: REGISTRATION_FILE_TYPE,
278
+ ...metadata,
279
+ };
280
+ // Build data URI if no external URI provided
281
+ const resolvedURI = agentURI ??
282
+ buildDataURI(registrationFile);
283
+ const contract = this.getWriteContract(walletClient);
284
+ let txHash;
285
+ if (onChainMetadata && Object.keys(onChainMetadata).length > 0) {
286
+ const entries = encodeMetadataEntries(onChainMetadata);
287
+ txHash = await contract.write.registerWithMetadata([resolvedURI, entries], {
288
+ account: walletClient.account,
289
+ chain: this.chain,
290
+ });
291
+ }
292
+ else {
293
+ txHash = await contract.write.register([resolvedURI], {
294
+ account: walletClient.account,
295
+ chain: this.chain,
296
+ });
297
+ }
298
+ // Extract agentId from Registered event in receipt
299
+ let agentId = null;
300
+ try {
301
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash: txHash });
302
+ const registeredEvent = receipt.logs.find((log) => log.address.toLowerCase() === this.config.registryAddress.toLowerCase() &&
303
+ log.topics[0] === REGISTERED_TOPIC);
304
+ if (registeredEvent?.topics[1]) {
305
+ agentId = BigInt(registeredEvent.topics[1]);
306
+ }
307
+ }
308
+ catch {
309
+ // agentId remains null; caller can use lookupAgentsByOwner after confirmation
310
+ }
311
+ return { txHash, agentId };
312
+ }
313
+ // ─── Lookup ─────────────────────────────────────────────────────────────
314
+ /**
315
+ * Look up an agent's full identity by their on-chain token ID.
316
+ *
317
+ * Fetches: owner, URI, payment wallet, and attempts to parse the
318
+ * registration file. Also reads standard on-chain model metadata if present.
319
+ *
320
+ * @param agentId - The ERC-721 tokenId assigned during registration
321
+ */
322
+ async lookupAgentIdentity(agentId) {
323
+ const contract = this.getReadContract();
324
+ const [owner, agentURI, agentWallet] = await Promise.all([
325
+ contract.read.ownerOf([agentId]),
326
+ contract.read.tokenURI([agentId]),
327
+ contract.read.getAgentWallet([agentId]),
328
+ ]);
329
+ // Attempt to parse registration file from URI
330
+ let registrationFile = null;
331
+ try {
332
+ registrationFile = await resolveAgentURI(agentURI);
333
+ }
334
+ catch {
335
+ // URI may be an external HTTPS/IPFS that's unavailable in this context
336
+ }
337
+ // Read optional on-chain model metadata
338
+ const modelMetadata = await this.readModelMetadata(agentId);
339
+ return {
340
+ agentId,
341
+ owner,
342
+ agentURI,
343
+ agentWallet,
344
+ registrationFile,
345
+ modelMetadata,
346
+ };
347
+ }
348
+ /**
349
+ * Look up an agent's identity by their wallet/owner address.
350
+ * Scans the Registered event log to find the most recent agentId for this owner.
351
+ *
352
+ * @param ownerAddress - The agent owner's Ethereum address
353
+ * @param fromBlock - Start scanning from this block (default: 0)
354
+ */
355
+ async lookupAgentByOwner(ownerAddress, fromBlock = 0n) {
356
+ const logs = await this.publicClient.getLogs({
357
+ address: this.config.registryAddress,
358
+ event: {
359
+ type: 'event',
360
+ name: 'Registered',
361
+ inputs: [
362
+ { name: 'agentId', type: 'uint256', indexed: true },
363
+ { name: 'agentURI', type: 'string', indexed: false },
364
+ { name: 'owner', type: 'address', indexed: true },
365
+ ],
366
+ },
367
+ args: {
368
+ owner: ownerAddress,
369
+ },
370
+ fromBlock,
371
+ toBlock: 'latest',
372
+ });
373
+ if (logs.length === 0)
374
+ return null;
375
+ // Use the most recently registered agentId
376
+ const latestLog = logs[logs.length - 1];
377
+ const agentId = BigInt(latestLog.args?.agentId ?? 0);
378
+ return this.lookupAgentIdentity(agentId);
379
+ }
380
+ // ─── URI Management ─────────────────────────────────────────────────────
381
+ /**
382
+ * Update the agent's registration file URI.
383
+ * Only callable by the NFT owner or an approved operator.
384
+ *
385
+ * @param walletClient - Owner/operator WalletClient
386
+ * @param agentId - The agent's tokenId
387
+ * @param newURI - New URI pointing to updated registration file
388
+ */
389
+ async updateAgentURI(walletClient, agentId, newURI) {
390
+ if (!walletClient.account)
391
+ throw new Error('ERC8004Client: WalletClient has no account');
392
+ const contract = this.getWriteContract(walletClient);
393
+ return contract.write.setAgentURI([agentId, newURI], {
394
+ account: walletClient.account,
395
+ chain: this.chain,
396
+ });
397
+ }
398
+ // ─── On-chain Metadata ──────────────────────────────────────────────────
399
+ /**
400
+ * Read a single on-chain metadata value for an agent.
401
+ *
402
+ * @param agentId - The agent's tokenId
403
+ * @param key - Metadata key (see METADATA_KEYS for standard keys)
404
+ * @returns Raw bytes value, or null if not set
405
+ */
406
+ async getOnChainMetadata(agentId, key) {
407
+ const contract = this.getReadContract();
408
+ const value = await contract.read.getMetadata([agentId, key]);
409
+ return value === '0x' || value === null ? null : value;
410
+ }
411
+ /**
412
+ * Write a single on-chain metadata value for an agent.
413
+ * Note: the reserved 'agentWallet' key cannot be set via this method.
414
+ *
415
+ * @param walletClient - Owner/operator WalletClient
416
+ * @param agentId - The agent's tokenId
417
+ * @param key - Metadata key
418
+ * @param value - UTF-8 string value (will be hex-encoded)
419
+ */
420
+ async setOnChainMetadata(walletClient, agentId, key, value) {
421
+ if (!walletClient.account)
422
+ throw new Error('ERC8004Client: WalletClient has no account');
423
+ if (key === METADATA_KEYS.AGENT_WALLET) {
424
+ throw new Error('ERC8004Client: "agentWallet" is reserved — use setAgentWallet() instead');
425
+ }
426
+ const contract = this.getWriteContract(walletClient);
427
+ const hexValue = stringToHex(value);
428
+ return contract.write.setMetadata([agentId, key, hexValue], {
429
+ account: walletClient.account,
430
+ chain: this.chain,
431
+ });
432
+ }
433
+ /**
434
+ * Write model metadata on-chain for an agent.
435
+ * Uses the standard METADATA_KEYS constants.
436
+ *
437
+ * @param walletClient - Owner/operator WalletClient
438
+ * @param agentId - The agent's tokenId
439
+ * @param model - Model metadata object
440
+ */
441
+ async setModelMetadata(walletClient, agentId, model) {
442
+ const hashes = [];
443
+ const entries = [];
444
+ if (model.model)
445
+ entries.push([METADATA_KEYS.MODEL, model.model]);
446
+ if (model.provider)
447
+ entries.push([METADATA_KEYS.MODEL_PROVIDER, model.provider]);
448
+ if (model.version)
449
+ entries.push([METADATA_KEYS.VERSION, model.version]);
450
+ if (model.framework)
451
+ entries.push([METADATA_KEYS.FRAMEWORK, model.framework]);
452
+ if (model.capabilities?.length) {
453
+ entries.push([METADATA_KEYS.CAPABILITIES, JSON.stringify(model.capabilities)]);
454
+ }
455
+ for (const [key, val] of entries) {
456
+ const hash = await this.setOnChainMetadata(walletClient, agentId, key, val);
457
+ hashes.push(hash);
458
+ }
459
+ return hashes;
460
+ }
461
+ /**
462
+ * Read all standard model metadata for an agent.
463
+ * Returns null if no model metadata has been set.
464
+ */
465
+ async readModelMetadata(agentId) {
466
+ const keys = [
467
+ METADATA_KEYS.MODEL,
468
+ METADATA_KEYS.MODEL_PROVIDER,
469
+ METADATA_KEYS.VERSION,
470
+ METADATA_KEYS.FRAMEWORK,
471
+ METADATA_KEYS.CAPABILITIES,
472
+ ];
473
+ const values = await Promise.all(keys.map((k) => this.getOnChainMetadata(agentId, k)));
474
+ const [modelHex, providerHex, versionHex, frameworkHex, capabilitiesHex] = values;
475
+ // Check if any model metadata is set
476
+ if (!modelHex && !providerHex && !versionHex && !frameworkHex && !capabilitiesHex) {
477
+ return null;
478
+ }
479
+ const result = {};
480
+ if (modelHex)
481
+ result.model = hexToString(modelHex);
482
+ if (providerHex)
483
+ result.provider = hexToString(providerHex);
484
+ if (versionHex)
485
+ result.version = hexToString(versionHex);
486
+ if (frameworkHex)
487
+ result.framework = hexToString(frameworkHex);
488
+ if (capabilitiesHex) {
489
+ try {
490
+ result.capabilities = JSON.parse(hexToString(capabilitiesHex));
491
+ }
492
+ catch {
493
+ result.capabilities = [];
494
+ }
495
+ }
496
+ return result;
497
+ }
498
+ // ─── Agent Wallet ────────────────────────────────────────────────────────
499
+ /**
500
+ * Get the configured payment wallet for an agent.
501
+ * Returns zero address if not set (payment defaults to NFT owner).
502
+ *
503
+ * @param agentId - The agent's tokenId
504
+ */
505
+ async getAgentWallet(agentId) {
506
+ const contract = this.getReadContract();
507
+ return contract.read.getAgentWallet([agentId]);
508
+ }
509
+ /**
510
+ * Set the payment wallet for an agent.
511
+ * Requires an EIP-712 signature from the new wallet to prove control.
512
+ * This prevents draining payments to an attacker-controlled address.
513
+ *
514
+ * Non-custodial: the signature is generated locally by the walletClient.
515
+ *
516
+ * @param walletClient - Owner WalletClient (must own the agent NFT)
517
+ * @param agentId - The agent's tokenId
518
+ * @param newWallet - New payment wallet address
519
+ * @param deadline - Signature deadline (Unix timestamp)
520
+ * @param signature - EIP-712 or ERC-1271 signature from newWallet proving control
521
+ */
522
+ async setAgentWallet(walletClient, agentId, newWallet, deadline, signature) {
523
+ if (!walletClient.account)
524
+ throw new Error('ERC8004Client: WalletClient has no account');
525
+ const contract = this.getWriteContract(walletClient);
526
+ return contract.write.setAgentWallet([agentId, newWallet, deadline, signature], { account: walletClient.account, chain: this.chain });
527
+ }
528
+ /**
529
+ * Clear the agent wallet (resets payment address to NFT owner).
530
+ *
531
+ * @param walletClient - Owner WalletClient
532
+ * @param agentId - The agent's tokenId
533
+ */
534
+ async unsetAgentWallet(walletClient, agentId) {
535
+ if (!walletClient.account)
536
+ throw new Error('ERC8004Client: WalletClient has no account');
537
+ const contract = this.getWriteContract(walletClient);
538
+ return contract.write.unsetAgentWallet([agentId], {
539
+ account: walletClient.account,
540
+ chain: this.chain,
541
+ });
542
+ }
543
+ }
544
+ // ─── Standalone helpers ───────────────────────────────────────────────────────
545
+ /**
546
+ * Build a base64 data URI from an agent registration file.
547
+ * Enables fully on-chain metadata storage without IPFS or HTTPS dependencies.
548
+ *
549
+ * @example
550
+ * const uri = buildDataURI({ name: 'MyAgent', description: '...', ... });
551
+ * // → "data:application/json;base64,eyJ0..."
552
+ */
553
+ export function buildDataURI(registrationFile) {
554
+ // Ensure type field isn't duplicated if registrationFile already has it
555
+ const payload = { ...registrationFile, type: REGISTRATION_FILE_TYPE };
556
+ const json = JSON.stringify(payload);
557
+ // btoa requires Latin1 — use encodeURIComponent to handle UTF-8 safely
558
+ const b64 = btoa(unescape(encodeURIComponent(json)));
559
+ return `data:application/json;base64,${b64}`;
560
+ }
561
+ /**
562
+ * Parse a data URI or plain JSON string into an AgentRegistrationFile.
563
+ * For HTTPS/IPFS URIs, use resolveAgentURI() instead.
564
+ */
565
+ export function parseDataURI(uri) {
566
+ if (uri.startsWith('data:application/json;base64,')) {
567
+ const b64 = uri.replace('data:application/json;base64,', '');
568
+ const json = decodeURIComponent(escape(atob(b64)));
569
+ return JSON.parse(json);
570
+ }
571
+ if (uri.startsWith('{')) {
572
+ return JSON.parse(uri);
573
+ }
574
+ throw new Error(`parseDataURI: Cannot parse URI scheme — use resolveAgentURI for HTTP/IPFS: ${uri.substring(0, 50)}`);
575
+ }
576
+ /**
577
+ * Resolve an agentURI to its registration file.
578
+ * Supports: data URIs (inline), HTTPS (fetch), IPFS (not fetched — throws).
579
+ */
580
+ export async function resolveAgentURI(uri) {
581
+ if (uri.startsWith('data:')) {
582
+ return parseDataURI(uri);
583
+ }
584
+ if (uri.startsWith('https://') || uri.startsWith('http://')) {
585
+ const response = await fetch(uri);
586
+ if (!response.ok)
587
+ throw new Error(`resolveAgentURI: HTTP ${response.status} for ${uri}`);
588
+ return response.json();
589
+ }
590
+ throw new Error(`resolveAgentURI: Unsupported URI scheme. Pin to IPFS or use data URI: ${uri}`);
591
+ }
592
+ /**
593
+ * Validate an AgentRegistrationFile against ERC-8004 requirements.
594
+ * Returns an array of validation errors (empty = valid).
595
+ */
596
+ export function validateRegistrationFile(file) {
597
+ const errors = [];
598
+ if (file.type !== REGISTRATION_FILE_TYPE) {
599
+ errors.push(`type must be "${REGISTRATION_FILE_TYPE}"`);
600
+ }
601
+ if (!file.name || typeof file.name !== 'string' || file.name.trim() === '') {
602
+ errors.push('name is required and must be a non-empty string');
603
+ }
604
+ if (!file.description || typeof file.description !== 'string') {
605
+ errors.push('description is required');
606
+ }
607
+ if (file.services) {
608
+ file.services.forEach((svc, i) => {
609
+ if (!svc.name)
610
+ errors.push(`services[${i}].name is required`);
611
+ if (!svc.endpoint)
612
+ errors.push(`services[${i}].endpoint is required`);
613
+ });
614
+ }
615
+ if (file.registrations) {
616
+ file.registrations.forEach((reg, i) => {
617
+ if (reg.agentId === undefined)
618
+ errors.push(`registrations[${i}].agentId is required`);
619
+ if (!reg.agentRegistry)
620
+ errors.push(`registrations[${i}].agentRegistry is required`);
621
+ });
622
+ }
623
+ return errors;
624
+ }
625
+ /**
626
+ * Format an agent registry identifier per ERC-8004 spec.
627
+ *
628
+ * @param chainId - Chain ID (e.g. 8453 for Base)
629
+ * @param registryAddress - Identity Registry contract address
630
+ * @returns "{namespace}:{chainId}:{identityRegistry}" e.g. "eip155:8453:0x742..."
631
+ */
632
+ export function formatAgentRegistry(chainId, registryAddress) {
633
+ return `eip155:${chainId}:${registryAddress}`;
634
+ }
635
+ // ─── Internal utilities ───────────────────────────────────────────────────────
636
+ /** keccak256 topic for Registered(uint256 indexed, string, address indexed) */
637
+ const REGISTERED_TOPIC = '0x6f3c9b7e8c3a5a5f2c3f9b7e8c3a5a5f2c3f9b7e8c3a5a5f2c3f9b7e8c3a5a';
638
+ function stringToHex(str) {
639
+ const bytes = new TextEncoder().encode(str);
640
+ const hexStr = Array.from(bytes)
641
+ .map((b) => b.toString(16).padStart(2, '0'))
642
+ .join('');
643
+ return ('0x' + hexStr);
644
+ }
645
+ function hexToString(hex) {
646
+ const stripped = hex.startsWith('0x') ? hex.slice(2) : hex;
647
+ const bytes = new Uint8Array(stripped.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
648
+ return new TextDecoder('utf-8').decode(bytes);
649
+ }
650
+ function encodeMetadataEntries(entries) {
651
+ return Object.entries(entries).map(([key, value]) => ({
652
+ metadataKey: key,
653
+ metadataValue: stringToHex(value),
654
+ }));
655
+ }
656
+ //# sourceMappingURL=erc8004.js.map