@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.
- package/CHANGELOG.md +108 -0
- package/MIGRATION.md +307 -0
- package/README.md +395 -0
- package/dist/index.d.mts +2327 -0
- package/dist/index.d.ts +2327 -0
- package/dist/index.js +5815 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5759 -0
- package/dist/index.mjs.map +1 -0
- package/examples/basic-agent.ts +126 -0
- package/examples/mcp-claude.ts +75 -0
- package/examples/ucp-checkout.ts +92 -0
- package/examples/x402-integration.ts +75 -0
- package/package.json +36 -0
- package/src/AgentWallet.ts +432 -0
- package/src/chains/AptosChainClient.ts +29 -0
- package/src/chains/ChainClient.ts +73 -0
- package/src/chains/ChainClientFactory.ts +113 -0
- package/src/chains/EVMChainClient.ts +39 -0
- package/src/chains/SolanaChainClient.ts +37 -0
- package/src/chains/StarknetChainClient.ts +36 -0
- package/src/chains/SuiChainClient.ts +28 -0
- package/src/index.ts +83 -0
- package/src/mcp/MCPServer.ts +73 -0
- package/src/mcp/schemas.ts +60 -0
- package/src/monitoring/AlertManager.ts +258 -0
- package/src/monitoring/AuditLogger.ts +86 -0
- package/src/monitoring/BalanceCache.ts +44 -0
- package/src/monitoring/ComplianceExporter.ts +52 -0
- package/src/oracle/PythFeeds.ts +60 -0
- package/src/oracle/PythOracle.ts +121 -0
- package/src/performance/ConnectionPool.ts +217 -0
- package/src/performance/NonceManager.ts +91 -0
- package/src/performance/ParallelRouteFinder.ts +438 -0
- package/src/performance/TransactionPoller.ts +201 -0
- package/src/performance/TransactionQueue.ts +565 -0
- package/src/performance/index.ts +46 -0
- package/src/react/hooks.ts +298 -0
- package/src/routing/BridgeOrchestrator.ts +18 -0
- package/src/routing/CrossChainRouter.ts +501 -0
- package/src/routing/DEXAggregator.ts +448 -0
- package/src/routing/FeeEstimator.ts +43 -0
- package/src/session/SessionKeyManager.ts +312 -0
- package/src/session/SessionStorage.ts +80 -0
- package/src/session/SpendingTracker.ts +71 -0
- package/src/types/agent.ts +105 -0
- package/src/types/errors.ts +115 -0
- package/src/types/mcp.ts +22 -0
- package/src/types/ucp.ts +47 -0
- package/src/types/x402.ts +170 -0
- package/src/ucp/CapabilityNegotiator.ts +44 -0
- package/src/ucp/CredentialProvider.ts +73 -0
- package/src/ucp/PaymentTokenizer.ts +169 -0
- package/src/ucp/TransportAdapter.ts +18 -0
- package/src/ucp/UCPClient.ts +143 -0
- package/src/x402/NonceManager.ts +26 -0
- package/src/x402/PaymentParser.ts +225 -0
- package/src/x402/PaymentSigner.ts +305 -0
- package/src/x402/X402Client.ts +364 -0
- package/src/x402/adapters/CronosFacilitatorAdapter.ts +109 -0
- package/tests/alerts.test.ts +208 -0
- package/tests/chains.test.ts +242 -0
- package/tests/integration.test.ts +315 -0
- package/tests/monitoring.test.ts +435 -0
- package/tests/performance.test.ts +303 -0
- package/tests/property.test.ts +186 -0
- package/tests/react-hooks.test.ts +262 -0
- package/tests/session.test.ts +376 -0
- package/tests/ucp.test.ts +253 -0
- package/tests/x402.test.ts +385 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +10 -0
- 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
|
+
}
|