@veridex/agentic-payments 0.1.1-beta.1

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 (73) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/MIGRATION.md +307 -0
  3. package/README.md +395 -0
  4. package/dist/index.d.mts +2327 -0
  5. package/dist/index.d.ts +2327 -0
  6. package/dist/index.js +5815 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.mjs +5759 -0
  9. package/dist/index.mjs.map +1 -0
  10. package/examples/basic-agent.ts +126 -0
  11. package/examples/mcp-claude.ts +75 -0
  12. package/examples/ucp-checkout.ts +92 -0
  13. package/examples/x402-integration.ts +75 -0
  14. package/package.json +36 -0
  15. package/src/AgentWallet.ts +432 -0
  16. package/src/chains/AptosChainClient.ts +29 -0
  17. package/src/chains/ChainClient.ts +73 -0
  18. package/src/chains/ChainClientFactory.ts +113 -0
  19. package/src/chains/EVMChainClient.ts +39 -0
  20. package/src/chains/SolanaChainClient.ts +37 -0
  21. package/src/chains/StarknetChainClient.ts +36 -0
  22. package/src/chains/SuiChainClient.ts +28 -0
  23. package/src/index.ts +83 -0
  24. package/src/mcp/MCPServer.ts +73 -0
  25. package/src/mcp/schemas.ts +60 -0
  26. package/src/monitoring/AlertManager.ts +258 -0
  27. package/src/monitoring/AuditLogger.ts +86 -0
  28. package/src/monitoring/BalanceCache.ts +44 -0
  29. package/src/monitoring/ComplianceExporter.ts +52 -0
  30. package/src/oracle/PythFeeds.ts +60 -0
  31. package/src/oracle/PythOracle.ts +121 -0
  32. package/src/performance/ConnectionPool.ts +217 -0
  33. package/src/performance/NonceManager.ts +91 -0
  34. package/src/performance/ParallelRouteFinder.ts +438 -0
  35. package/src/performance/TransactionPoller.ts +201 -0
  36. package/src/performance/TransactionQueue.ts +565 -0
  37. package/src/performance/index.ts +46 -0
  38. package/src/react/hooks.ts +298 -0
  39. package/src/routing/BridgeOrchestrator.ts +18 -0
  40. package/src/routing/CrossChainRouter.ts +501 -0
  41. package/src/routing/DEXAggregator.ts +448 -0
  42. package/src/routing/FeeEstimator.ts +43 -0
  43. package/src/session/SessionKeyManager.ts +312 -0
  44. package/src/session/SessionStorage.ts +80 -0
  45. package/src/session/SpendingTracker.ts +71 -0
  46. package/src/types/agent.ts +105 -0
  47. package/src/types/errors.ts +115 -0
  48. package/src/types/mcp.ts +22 -0
  49. package/src/types/ucp.ts +47 -0
  50. package/src/types/x402.ts +170 -0
  51. package/src/ucp/CapabilityNegotiator.ts +44 -0
  52. package/src/ucp/CredentialProvider.ts +73 -0
  53. package/src/ucp/PaymentTokenizer.ts +169 -0
  54. package/src/ucp/TransportAdapter.ts +18 -0
  55. package/src/ucp/UCPClient.ts +143 -0
  56. package/src/x402/NonceManager.ts +26 -0
  57. package/src/x402/PaymentParser.ts +225 -0
  58. package/src/x402/PaymentSigner.ts +305 -0
  59. package/src/x402/X402Client.ts +364 -0
  60. package/src/x402/adapters/CronosFacilitatorAdapter.ts +109 -0
  61. package/tests/alerts.test.ts +208 -0
  62. package/tests/chains.test.ts +242 -0
  63. package/tests/integration.test.ts +315 -0
  64. package/tests/monitoring.test.ts +435 -0
  65. package/tests/performance.test.ts +303 -0
  66. package/tests/property.test.ts +186 -0
  67. package/tests/react-hooks.test.ts +262 -0
  68. package/tests/session.test.ts +376 -0
  69. package/tests/ucp.test.ts +253 -0
  70. package/tests/x402.test.ts +385 -0
  71. package/tsconfig.json +26 -0
  72. package/tsup.config.ts +10 -0
  73. package/vitest.config.ts +16 -0
@@ -0,0 +1,432 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module AgentWallet
4
+ * @description
5
+ * The core orchestration class for the Veridex Agent SDK.
6
+ *
7
+ * The AgentWallet serves as the central hub for all agentic payment operations, coordinating
8
+ * session management, x402 protocol negotiation, UCP credential issuance, and multi-chain execution.
9
+ *
10
+ * Key Features:
11
+ * - **Session Management**: Automatically handles session key lifecycle, spending limits, and expiration.
12
+ * - **x402 Client**: Intercepts HTTP 402 responses to perform autonomous payments.
13
+ * - **Multi-Chain Support**: Routes transactions to appropriate chain adapters (EVM, Starknet, Solana, etc.).
14
+ * - **Monitoring**: Provides audit logging and real-time spending alerts.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { createAgentWallet } from '@veridex/agentic-payments';
19
+ *
20
+ * const agent = await createAgentWallet({
21
+ * session: { dailyLimitUSD: 100 }
22
+ * });
23
+ *
24
+ * // Autonomous payment via x402
25
+ * await agent.fetch('https://paid-resource.com');
26
+ * ```
27
+ */
28
+ import { VeridexSDK, TokenBalance, PortfolioBalance, createSDK, ChainName, PasskeyCredential } from '@veridex/sdk';
29
+ import { AgentWalletConfig, PaymentParams, PaymentReceipt, SessionStatus, HistoryOptions } from './types/agent';
30
+ import { SessionKeyManager } from './session/SessionKeyManager';
31
+ import { SessionKeyConfig, StoredSession } from './session/SessionStorage';
32
+ import { X402Client } from './x402/X402Client';
33
+ import { UCPCredentialProvider } from './ucp/CredentialProvider';
34
+ import { MCPServer } from './mcp/MCPServer';
35
+ import { CrossChainRouter } from './routing/CrossChainRouter';
36
+ import { AuditLogger, PaymentRecord } from './monitoring/AuditLogger';
37
+ import { AlertManager } from './monitoring/AlertManager';
38
+ import { ComplianceExporter } from './monitoring/ComplianceExporter';
39
+ import { BalanceCache } from './monitoring/BalanceCache';
40
+ import { AgentPaymentError, AgentPaymentErrorCode } from './types/errors';
41
+ import { SpendingAlert } from './types/agent';
42
+ import { ethers } from 'ethers';
43
+
44
+ export class AgentWallet {
45
+ private sessionManager: SessionKeyManager;
46
+ private x402Client: X402Client;
47
+ private ucpProvider: UCPCredentialProvider;
48
+ private mcpServer?: MCPServer;
49
+ private router: CrossChainRouter;
50
+ private auditLogger: AuditLogger;
51
+ private alertManager: AlertManager;
52
+ private complianceExporter: ComplianceExporter;
53
+ private balanceCache: BalanceCache;
54
+ private coreSDK!: VeridexSDK;
55
+ private currentSession?: StoredSession;
56
+
57
+ constructor(private config: AgentWalletConfig) {
58
+ this.sessionManager = new SessionKeyManager();
59
+ // coreSDK will be initialized in init() or first use
60
+ this.ucpProvider = new UCPCredentialProvider(this.sessionManager);
61
+ this.router = new CrossChainRouter();
62
+ this.auditLogger = new AuditLogger();
63
+ this.alertManager = new AlertManager();
64
+ this.complianceExporter = new ComplianceExporter();
65
+ this.balanceCache = new BalanceCache();
66
+ // x402Client needs coreSDK, so we'll lazy-init it
67
+ this.x402Client = new X402Client(this.sessionManager, null as any, config.x402);
68
+
69
+ if (config.mcp?.enabled) {
70
+ this.mcpServer = new MCPServer(this);
71
+ }
72
+ }
73
+
74
+ async init(): Promise<void> {
75
+ // Default to Base if no chains specified
76
+ const hubChain = 'base' as ChainName;
77
+ this.coreSDK = createSDK(hubChain, {
78
+ relayerUrl: this.config.relayerUrl,
79
+ relayerApiKey: this.config.relayerApiKey,
80
+ });
81
+
82
+ // Update x402Client with initialized coreSDK
83
+ (this.x402Client as any).coreSDK = this.coreSDK;
84
+
85
+ this.currentSession = await this.createSession({
86
+ dailyLimitUSD: this.config.session.dailyLimitUSD,
87
+ perTransactionLimitUSD: this.config.session.perTransactionLimitUSD,
88
+ expiryTimestamp: Date.now() + (this.config.session.expiryHours * 60 * 60 * 1000),
89
+ allowedChains: this.config.session.allowedChains
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Create a new session key with specific config
95
+ */
96
+ async createSession(config: SessionKeyConfig): Promise<StoredSession> {
97
+ return await this.sessionManager.createSession(this.config.masterCredential, config);
98
+ }
99
+
100
+ async fetch(url: string, options?: RequestInit): Promise<Response> {
101
+ if (!this.currentSession) await this.init();
102
+
103
+ return await this.withRetry(async () => {
104
+ return await this.x402Client.handleFetch(url, options, this.currentSession!);
105
+ });
106
+ }
107
+
108
+ async pay(params: PaymentParams): Promise<PaymentReceipt> {
109
+ // #region agent log
110
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:pay:entry', message: 'pay() called', data: { params, hasSession: !!this.currentSession }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H2,H3,H4' }) }).catch(() => { });
111
+ // #endregion
112
+
113
+ if (!this.currentSession) await this.init();
114
+
115
+ // Check limits
116
+ const amountBig = BigInt(params.amount);
117
+ let decimals = 18;
118
+ const tokenUpper = params.token.toUpperCase();
119
+
120
+ // Handle common stablecoins
121
+ if (['USDC', 'USDT'].includes(tokenUpper)) {
122
+ decimals = 6;
123
+ }
124
+
125
+ // Calculate estimated USD value
126
+ // Note: For non-stablecoins, this assumes 1 Token = $1 which is inaccurate but safer than atomic units.
127
+ // Real implementation would need a price oracle or CoinGecko API here.
128
+ const divisor = BigInt(10) ** BigInt(decimals);
129
+ const amountUSD = Number(amountBig) / Number(divisor);
130
+
131
+ const limitCheck = this.sessionManager.checkLimits(this.currentSession!, amountUSD);
132
+ if (!limitCheck.allowed) {
133
+ throw AgentPaymentError.fromLimitExceeded(limitCheck.reason || `Transaction amount $${amountUSD.toFixed(2)} exceeds limit`);
134
+ }
135
+
136
+ // Get signer from session (handles encryption properly)
137
+ const signer = await this.sessionManager.getSessionWallet(
138
+ this.currentSession!,
139
+ this.currentSession!.masterKeyHash || this.config.masterCredential.credentialId
140
+ );
141
+
142
+ // #region agent log
143
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:pay:beforeDirectTransfer', message: 'About to execute direct transfer', data: { signerAddress: signer.address, targetChain: params.chain, token: params.token, amount: params.amount }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H2,H4,H5' }) }).catch(() => { });
144
+ // #endregion
145
+
146
+ // Execute direct transfer using session wallet (bypasses passkey requirement)
147
+ const receipt = await this.withRetry(async () => {
148
+ return await this.executeDirectTransfer(signer, params);
149
+ });
150
+
151
+ // Record spending
152
+ await this.sessionManager.recordSpending(this.currentSession!, amountUSD);
153
+
154
+ // Check for alerts
155
+ this.alertManager.checkSpending(
156
+ this.currentSession!.keyHash,
157
+ this.currentSession!.metadata.dailySpentUSD,
158
+ this.currentSession!.config.dailyLimitUSD
159
+ );
160
+
161
+ const paymentReceipt: PaymentReceipt = {
162
+ txHash: receipt.transactionHash,
163
+ status: 'confirmed',
164
+ chain: params.chain,
165
+ token: params.token,
166
+ amount: BigInt(params.amount),
167
+ recipient: params.recipient,
168
+ protocol: params.protocol || 'direct',
169
+ timestamp: Date.now()
170
+ };
171
+
172
+ await this.auditLogger.log(paymentReceipt, this.currentSession!.keyHash);
173
+ return paymentReceipt;
174
+ }
175
+
176
+ async getBalance(chain?: number): Promise<TokenBalance[]> {
177
+ if (!this.coreSDK) await this.init();
178
+ if (!this.currentSession) return [];
179
+
180
+ const address = ethers.computeAddress(this.currentSession.publicKey);
181
+ const targetChain = chain || 30; // Default to Base
182
+
183
+ // #region agent log
184
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:getBalance', message: 'getBalance called', data: { chain, targetChain, address }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H1' }) }).catch(() => { });
185
+ // #endregion
186
+
187
+ // Check cache
188
+ const cached = this.balanceCache.get(address, targetChain);
189
+ if (cached) return cached;
190
+
191
+ const result = await this.coreSDK.balance.getPortfolioBalance(targetChain, address);
192
+ this.balanceCache.set(address, targetChain, result.tokens);
193
+
194
+ return result.tokens;
195
+ }
196
+
197
+ async getMultiChainBalance(): Promise<PortfolioBalance> {
198
+ if (!this.coreSDK) await this.init();
199
+ if (!this.currentSession) throw new Error("Session not initialized");
200
+
201
+ const address = ethers.computeAddress(this.currentSession.publicKey);
202
+ // Preset chains to check
203
+ const chains = [2, 30, 1, 3]; // Eth, Base, Sol, etc.
204
+ const results = await this.coreSDK.balance.getMultiChainBalances(address, chains);
205
+
206
+ // Aggregate results into one PortfolioBalance or return the first one?
207
+ // The interface expects PortfolioBalance (singular).
208
+ // Maybe we just return the total USD value and list of all tokens?
209
+
210
+ const combinedTokens = results.flatMap(r => r.tokens);
211
+ const totalUsd = results.reduce((sum, r) => sum + (r.totalUsdValue || 0), 0);
212
+
213
+ return {
214
+ wormholeChainId: 0, // Multi-chain
215
+ chainName: 'Multi-Chain',
216
+ address,
217
+ tokens: combinedTokens,
218
+ totalUsdValue: totalUsd,
219
+ lastUpdated: Date.now()
220
+ };
221
+ }
222
+
223
+ async getPaymentHistory(options?: HistoryOptions): Promise<PaymentRecord[]> {
224
+ return this.auditLogger.getLogs(options);
225
+ }
226
+
227
+ async revokeSession(): Promise<void> {
228
+ if (this.currentSession) {
229
+ await this.sessionManager.revokeSession(this.currentSession.keyHash);
230
+ this.currentSession = undefined;
231
+ }
232
+ }
233
+
234
+ public getSessionStatus(): SessionStatus {
235
+ if (!this.currentSession) throw new Error('No active session');
236
+ return {
237
+ isValid: this.sessionManager.isSessionValid(this.currentSession),
238
+ keyHash: this.currentSession.keyHash,
239
+ expiry: this.currentSession.config.expiryTimestamp,
240
+ remainingDailyLimitUSD: this.currentSession.config.dailyLimitUSD - this.currentSession.metadata.dailySpentUSD,
241
+ totalSpentUSD: this.currentSession.metadata.totalSpentUSD,
242
+ address: this.currentSession.walletAddress,
243
+ limits: {
244
+ dailyLimitUSD: this.currentSession.config.dailyLimitUSD,
245
+ perTransactionLimitUSD: this.currentSession.config.perTransactionLimitUSD
246
+ }
247
+ };
248
+ }
249
+
250
+ async importSession(sessionData: any): Promise<void> {
251
+ // Validate session data structure structure roughly
252
+ if (!sessionData.keyHash || !sessionData.encryptedPrivateKey) {
253
+ throw new Error("Invalid session data");
254
+ }
255
+
256
+ // Save to storage
257
+ await this.sessionManager.importSession(sessionData);
258
+
259
+ // Set as current
260
+ this.currentSession = sessionData;
261
+ console.log(`[AgentWallet] Imported session ${sessionData.keyHash} for master ${sessionData.masterKeyHash}`);
262
+ }
263
+
264
+ // Audit and monitoring
265
+ async exportAuditLog(format: 'csv' | 'json' = 'json'): Promise<string> {
266
+ const logs = await this.auditLogger.getLogs({ limit: 1000 }); // Export last 1000
267
+ if (format === 'csv') {
268
+ return this.complianceExporter.exportToCSV(logs);
269
+ }
270
+ return this.complianceExporter.exportToJSON(logs);
271
+ }
272
+
273
+ onSpendingAlert(callback: (alert: SpendingAlert) => void): void {
274
+ this.alertManager.onAlert(callback);
275
+ }
276
+
277
+ getMCPTools(): any[] {
278
+ return this.mcpServer ? this.mcpServer.getTools() : [];
279
+ }
280
+
281
+ /**
282
+ * Execute a direct token transfer using the session wallet.
283
+ * This bypasses the Veridex protocol (no passkey required) and uses the session key directly.
284
+ */
285
+ private async executeDirectTransfer(
286
+ signer: ethers.Wallet,
287
+ params: PaymentParams
288
+ ): Promise<{ transactionHash: string }> {
289
+ // RPC URLs for testnet chains (Wormhole Chain IDs)
290
+ const RPC_URLS: Record<number, string> = {
291
+ 10002: 'https://ethereum-sepolia-rpc.publicnode.com',
292
+ 10003: 'https://sepolia-rollup.arbitrum.io/rpc',
293
+ 10004: 'https://sepolia.base.org',
294
+ 10005: 'https://sepolia.optimism.io',
295
+ };
296
+
297
+ // Token addresses for testnets (USDC)
298
+ const USDC_ADDRESSES: Record<number, string> = {
299
+ 10002: '0x7A7754A2089df825801A0a8d95a9801928bFb22A', // Ethereum Sepolia USDC (Aave testnet USDC)
300
+ 10003: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d', // Arbitrum Sepolia USDC
301
+ 10004: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Base Sepolia USDC
302
+ 10005: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7', // Optimism Sepolia USDC
303
+ };
304
+
305
+ const rpcUrl = RPC_URLS[params.chain];
306
+ if (!rpcUrl) {
307
+ throw new AgentPaymentError(
308
+ AgentPaymentErrorCode.CHAIN_NOT_SUPPORTED,
309
+ `Chain ${params.chain} is not supported. Use: 10002 (Eth Sepolia), 10003 (Arb Sepolia), 10004 (Base Sepolia), 10005 (Op Sepolia)`,
310
+ 'Use a supported testnet chain ID.',
311
+ false
312
+ );
313
+ }
314
+
315
+ // Connect signer to provider
316
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
317
+ const connectedSigner = signer.connect(provider);
318
+
319
+ const tokenUpper = params.token.toUpperCase();
320
+ const amount = BigInt(params.amount);
321
+
322
+ let tx: ethers.TransactionResponse;
323
+
324
+ if (tokenUpper === 'ETH' || tokenUpper === 'NATIVE') {
325
+ // Check ETH balance first
326
+ const ethBalance = await provider.getBalance(signer.address);
327
+ // #region agent log
328
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:executeDirectTransfer:ethBalance', message: 'ETH balance check', data: { signerAddress: signer.address, ethBalance: ethBalance.toString(), requestedAmount: amount.toString() }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H6' }) }).catch(() => { });
329
+ // #endregion
330
+
331
+ if (ethBalance < amount) {
332
+ throw new AgentPaymentError(
333
+ AgentPaymentErrorCode.INSUFFICIENT_BALANCE,
334
+ `Insufficient ETH balance: have ${ethers.formatEther(ethBalance)} ETH, need ${ethers.formatEther(amount)} ETH`,
335
+ `Fund your wallet ${signer.address} with more ETH on chain ${params.chain}.`,
336
+ false
337
+ );
338
+ }
339
+
340
+ // Native ETH transfer
341
+ tx = await connectedSigner.sendTransaction({
342
+ to: params.recipient,
343
+ value: amount,
344
+ });
345
+ } else if (tokenUpper === 'USDC') {
346
+ // ERC20 transfer
347
+ const tokenAddress = USDC_ADDRESSES[params.chain];
348
+ if (!tokenAddress) {
349
+ throw new AgentPaymentError(
350
+ AgentPaymentErrorCode.TOKEN_NOT_SUPPORTED,
351
+ `USDC not configured for chain ${params.chain}`,
352
+ 'Use a supported token on this chain.',
353
+ false
354
+ );
355
+ }
356
+
357
+ const erc20Abi = [
358
+ 'function transfer(address to, uint256 amount) returns (bool)',
359
+ 'function balanceOf(address owner) view returns (uint256)',
360
+ 'function symbol() view returns (string)',
361
+ ];
362
+ const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, connectedSigner);
363
+
364
+ // Check USDC balance first
365
+ const usdcBalance = await tokenContract.balanceOf(signer.address);
366
+ // #region agent log
367
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:executeDirectTransfer:usdcBalance', message: 'USDC balance check', data: { signerAddress: signer.address, usdcContract: tokenAddress, usdcBalance: usdcBalance.toString(), requestedAmount: amount.toString(), chain: params.chain }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H6' }) }).catch(() => { });
368
+ // #endregion
369
+
370
+ if (usdcBalance < amount) {
371
+ throw new AgentPaymentError(
372
+ AgentPaymentErrorCode.INSUFFICIENT_BALANCE,
373
+ `Insufficient USDC balance: have ${Number(usdcBalance) / 1e6} USDC (Circle USDC at ${tokenAddress}), need ${Number(amount) / 1e6} USDC. Note: Your wallet may have a different USDC token - only Circle's official testnet USDC is supported.`,
374
+ `Get Circle USDC from https://faucet.circle.com for your wallet ${signer.address}.`,
375
+ false
376
+ );
377
+ }
378
+
379
+ tx = await tokenContract.transfer(params.recipient, amount);
380
+ } else {
381
+ throw new AgentPaymentError(
382
+ AgentPaymentErrorCode.TOKEN_NOT_SUPPORTED,
383
+ `Token ${params.token} is not supported. Use 'eth', 'native', or 'usdc'.`,
384
+ 'Use a supported token symbol.',
385
+ false
386
+ );
387
+ }
388
+
389
+ // Wait for confirmation
390
+ const receipt = await tx.wait();
391
+
392
+ // #region agent log
393
+ fetch('http://127.0.0.1:7242/ingest/c6390672-1465-4a0d-bb12-57e7bed0bb2e', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location: 'AgentWallet.ts:executeDirectTransfer:success', message: 'Transfer confirmed', data: { txHash: tx.hash, blockNumber: receipt?.blockNumber }, timestamp: Date.now(), sessionId: 'debug-session', hypothesisId: 'H2,H5' }) }).catch(() => { });
394
+ // #endregion
395
+
396
+ return { transactionHash: tx.hash };
397
+ }
398
+
399
+ /**
400
+ * Helper for retrying operations with exponential backoff.
401
+ * Requirement 8.7
402
+ */
403
+ private async withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
404
+ let lastError: any;
405
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
406
+ try {
407
+ return await fn();
408
+ } catch (error: any) {
409
+ lastError = error;
410
+ // Don't retry on certain errors (e.g. limit exceeded)
411
+ if (error instanceof AgentPaymentError && !error.retryable) {
412
+ throw error;
413
+ }
414
+
415
+ if (attempt < maxRetries) {
416
+ const delay = Math.pow(2, attempt) * 1000;
417
+ console.warn(`[AgentWallet] Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
418
+ await new Promise(r => setTimeout(r, delay));
419
+ }
420
+ }
421
+ }
422
+
423
+ if (lastError instanceof AgentPaymentError) throw lastError;
424
+ throw new AgentPaymentError(
425
+ AgentPaymentErrorCode.NETWORK_ERROR,
426
+ `Operation failed after ${maxRetries} retries: ${lastError.message}`,
427
+ 'Check network connectivity or relayer status.',
428
+ true,
429
+ { originalError: lastError }
430
+ );
431
+ }
432
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module AptosChainClient
4
+ * @description
5
+ * Agent adapter for the Aptos blockchain (Move VM).
6
+ *
7
+ * Extends the core SDK to support agent operations on Aptos.
8
+ */
9
+ import { AptosClient as CoreAptosClient, AptosClientConfig as CoreAptosClientConfig } from '@veridex/sdk/chains/aptos';
10
+ import { BaseAgentChainClient } from './ChainClient';
11
+ import { Aptos } from '@aptos-labs/ts-sdk';
12
+
13
+ /**
14
+ * Agent-specific Aptos chain client.
15
+ */
16
+ export class AptosChainClient extends BaseAgentChainClient {
17
+ private aptosCore: CoreAptosClient;
18
+
19
+ constructor(config: CoreAptosClientConfig) {
20
+ const core = new CoreAptosClient(config);
21
+ super(core);
22
+ this.aptosCore = core;
23
+ }
24
+
25
+
26
+ getClient(): Aptos {
27
+ return this.aptosCore.getClient();
28
+ }
29
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module ChainClient
4
+ * @description
5
+ * Defines the unified interface for Agent-specific blockchain interactions.
6
+ *
7
+ * This module provides the `AgentChainClient` interface and base classes that abstract
8
+ * the differences between various blockchains (EVM, Solana, Starknet, etc.).
9
+ * It extends the core SDK's chain clients with agent-specific capabilities, such as:
10
+ * - Real-time token pricing (USD) via Pyth Network.
11
+ * - Gas estimation for agent operations.
12
+ * - Unified transaction payload building.
13
+ */
14
+ import { ChainClient as CoreChainClient, ChainConfig } from '@veridex/sdk';
15
+ import { PythOracle } from '../oracle/PythOracle';
16
+
17
+ /**
18
+ * Unified interface for agent-specific chain client operations.
19
+ * Wraps the core Veridex ChainClient and adds agent-specific needs.
20
+ */
21
+ export interface AgentChainClient extends CoreChainClient {
22
+ /**
23
+ * Get the USD price of the native token on this chain.
24
+ */
25
+ getNativeTokenPriceUSD(): Promise<number>;
26
+
27
+ /**
28
+ * Get the USD price of a specific token on this chain.
29
+ */
30
+ getTokenPriceUSD(tokenAddress: string): Promise<number>;
31
+ }
32
+
33
+ /**
34
+ * Common base for all agent chain clients.
35
+ */
36
+ export abstract class BaseAgentChainClient implements AgentChainClient {
37
+ constructor(protected coreClient: CoreChainClient) { }
38
+
39
+ // Delegate core methods to the wrapped client
40
+ getConfig(): ChainConfig { return this.coreClient.getConfig(); }
41
+ async getNonce(userKeyHash: string): Promise<bigint> { return this.coreClient.getNonce(userKeyHash); }
42
+ async getMessageFee(): Promise<bigint> { return this.coreClient.getMessageFee(); }
43
+ async buildTransferPayload(params: any): Promise<string> { return this.coreClient.buildTransferPayload(params); }
44
+ async buildExecutePayload(params: any): Promise<string> { return this.coreClient.buildExecutePayload(params); }
45
+ async buildBridgePayload(params: any): Promise<string> { return this.coreClient.buildBridgePayload(params); }
46
+ async dispatch(sig: any, x: bigint, y: bigint, target: number, pay: string, nonce: bigint, signer: any): Promise<any> {
47
+ return this.coreClient.dispatch(sig, x, y, target, pay, nonce, signer);
48
+ }
49
+ async getVaultAddress(userKeyHash: string): Promise<string | null> { return this.coreClient.getVaultAddress(userKeyHash); }
50
+ computeVaultAddress(userKeyHash: string): string { return this.coreClient.computeVaultAddress(userKeyHash); }
51
+ async vaultExists(userKeyHash: string): Promise<boolean> { return this.coreClient.vaultExists(userKeyHash); }
52
+ async createVault(userKeyHash: string, signer: any): Promise<any> { return this.coreClient.createVault(userKeyHash, signer); }
53
+ async estimateVaultCreationGas(userKeyHash: string): Promise<bigint> { return this.coreClient.estimateVaultCreationGas(userKeyHash); }
54
+ getFactoryAddress(): string | undefined { return this.coreClient.getFactoryAddress(); }
55
+ getImplementationAddress(): string | undefined { return this.coreClient.getImplementationAddress(); }
56
+
57
+ // New agent-specific methods
58
+ async getNativeTokenPriceUSD(): Promise<number> {
59
+ const config = this.getConfig();
60
+ const price = await PythOracle.getInstance().getNativeTokenPrice(config.name);
61
+ if (price > 0) return price;
62
+
63
+ console.warn(`[BaseAgentChainClient] Failed to get native price for ${config.name}, returning fallback.`);
64
+ return 1.0; // Fallback
65
+ }
66
+
67
+ async getTokenPriceUSD(tokenAddress: string): Promise<number> {
68
+ // TODO: Implement token address to Feed ID mapping
69
+ // For now, check if it's USDC or similar known tokens?
70
+ // Or simply fail/return default.
71
+ return 1.0;
72
+ }
73
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module ChainClientFactory
4
+ * @description
5
+ * Factory pattern for creating chain-specific Agent Clients.
6
+ *
7
+ * This factory abstracts the instantiation complexity of various chain clients (EVM, Solana, Starknet, etc.).
8
+ * It uses the Veridex `ChainPreset` configuration to automatically inject the correct contract addresses,
9
+ * RPC URLs, and bridge configurations for the requested network environment (Mainnet/Testnet).
10
+ *
11
+ * Usage:
12
+ * ```typescript
13
+ * const client = ChainClientFactory.createClient('base', 'mainnet');
14
+ * ```
15
+ */
16
+ import {
17
+ getChainPreset,
18
+ ChainName,
19
+ NetworkType,
20
+ } from '@veridex/sdk';
21
+ import { EVMClientConfig } from '@veridex/sdk/chains/evm';
22
+ import { SolanaClientConfig } from '@veridex/sdk/chains/solana';
23
+ import { AptosClientConfig } from '@veridex/sdk/chains/aptos';
24
+ import { SuiClientConfig } from '@veridex/sdk/chains/sui';
25
+ import { StarknetClientConfig } from '@veridex/sdk/chains/starknet';
26
+ import { AgentChainClient } from './ChainClient';
27
+ import { EVMChainClient } from './EVMChainClient';
28
+ import { SolanaChainClient } from './SolanaChainClient';
29
+ import { AptosChainClient } from './AptosChainClient';
30
+ import { SuiChainClient } from './SuiChainClient';
31
+ import { StarknetChainClient } from './StarknetChainClient';
32
+
33
+ /**
34
+ * Factory for creating AgentChainClient instances.
35
+ */
36
+ export class ChainClientFactory {
37
+ /**
38
+ * Create an agent chain client for a specific chain and network.
39
+ */
40
+ static createClient(
41
+ chain: ChainName,
42
+ network: NetworkType = 'testnet',
43
+ customRpcUrl?: string
44
+ ): AgentChainClient {
45
+ const preset = getChainPreset(chain);
46
+ const config = preset[network];
47
+ const rpcUrl = customRpcUrl || config.rpcUrl;
48
+
49
+ const requireString = (value: string | undefined, label: string): string => {
50
+ if (!value) {
51
+ throw new Error(`Missing ${label} for chain "${chain}" on network "${network}"`);
52
+ }
53
+ return value;
54
+ };
55
+
56
+ switch (preset.type) {
57
+ case 'evm':
58
+ return new EVMChainClient({
59
+ chainId: config.chainId,
60
+ wormholeChainId: config.wormholeChainId,
61
+ rpcUrl,
62
+ hubContractAddress: requireString(config.contracts.hub, 'hub contract address'),
63
+ wormholeCoreBridge: requireString(config.contracts.wormholeCoreBridge, 'Wormhole core bridge address'),
64
+ vaultFactory: config.contracts.vaultFactory,
65
+ vaultImplementation: config.contracts.vaultImplementation,
66
+ tokenBridge: config.contracts.tokenBridge,
67
+ name: config.name,
68
+ explorerUrl: config.explorerUrl,
69
+ } as EVMClientConfig);
70
+
71
+ case 'solana':
72
+ return new SolanaChainClient({
73
+ rpcUrl,
74
+ programId: requireString(config.contracts.hub, 'programId'),
75
+ wormholeCoreBridge: requireString(config.contracts.wormholeCoreBridge, 'Wormhole core bridge address'),
76
+ tokenBridge: requireString(config.contracts.tokenBridge, 'token bridge address'),
77
+ wormholeChainId: config.wormholeChainId,
78
+ network: network === 'testnet' ? 'devnet' : 'mainnet',
79
+ } as SolanaClientConfig);
80
+
81
+ case 'aptos':
82
+ return new AptosChainClient({
83
+ rpcUrl,
84
+ moduleAddress: requireString(config.contracts.hub, 'moduleAddress'),
85
+ wormholeCoreBridge: requireString(config.contracts.wormholeCoreBridge, 'Wormhole core bridge address'),
86
+ tokenBridge: requireString(config.contracts.tokenBridge, 'token bridge address'),
87
+ wormholeChainId: config.wormholeChainId,
88
+ network: network,
89
+ } as AptosClientConfig);
90
+
91
+ case 'sui':
92
+ return new SuiChainClient({
93
+ rpcUrl,
94
+ packageId: requireString(config.contracts.hub, 'packageId'),
95
+ wormholeCoreBridge: requireString(config.contracts.wormholeCoreBridge, 'Wormhole core bridge address'),
96
+ wormholeChainId: config.wormholeChainId,
97
+ network: network,
98
+ } as SuiClientConfig);
99
+
100
+ case 'starknet':
101
+ return new StarknetChainClient({
102
+ rpcUrl,
103
+ spokeContractAddress: config.contracts.hub,
104
+ bridgeContractAddress: config.contracts.wormholeCoreBridge,
105
+ wormholeChainId: config.wormholeChainId,
106
+ network: network === 'testnet' ? 'sepolia' : 'mainnet',
107
+ } as StarknetClientConfig);
108
+
109
+ default:
110
+ throw new Error(`Chain type "${preset.type}" is not supported by legacy AgentChainClient.`);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module EVMChainClient
4
+ * @description
5
+ * Agent adapter for Ethereum Virtual Machine (EVM) compatible chains.
6
+ *
7
+ * This class wraps the core `EVMClient` to provide agent-specific functionality for
8
+ * chains like Ethereum, Base, Optimism, and Arbitrum.
9
+ *
10
+ * Capabilities:
11
+ * - Uses `ethers.js` for RPC interactions.
12
+ * - Supports EIP-712 signing for x402 payments.
13
+ * - Integration with Pyth Oracle for pricing.
14
+ */
15
+ import { EVMClient as CoreEVMClient, EVMClientConfig as CoreEVMClientConfig } from '@veridex/sdk/chains/evm';
16
+ import { BaseAgentChainClient } from './ChainClient';
17
+ import { ethers } from 'ethers';
18
+
19
+ /**
20
+ * Agent-specific EVM chain client.
21
+ * Extends the core EVM client with agent-centric features like price estimation.
22
+ */
23
+ export class EVMChainClient extends BaseAgentChainClient {
24
+ private evmCore: CoreEVMClient;
25
+
26
+ constructor(config: CoreEVMClientConfig) {
27
+ const core = new CoreEVMClient(config);
28
+ super(core);
29
+ this.evmCore = core;
30
+ }
31
+
32
+
33
+ /**
34
+ * Helper to get the underlying ethers provider
35
+ */
36
+ getProvider(): ethers.BrowserProvider | ethers.JsonRpcProvider {
37
+ return this.evmCore.getProvider();
38
+ }
39
+ }