@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,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module DEXAggregator
|
|
4
|
+
* @description
|
|
5
|
+
* DEX Integration Layer for Token Swapping & Cross-Chain Aggregation.
|
|
6
|
+
*
|
|
7
|
+
* Provides a unified interface for token swaps across different chains, abstracting
|
|
8
|
+
* specific DEX implementations (Uniswap, Jupiter, etc.).
|
|
9
|
+
*
|
|
10
|
+
* Key Features:
|
|
11
|
+
* - **Swap**: executes simulated or real swaps on EVM and Solana.
|
|
12
|
+
* - **Cross-Chain Route Finding**: Finds paths like (Swap -> Bridge -> Swap).
|
|
13
|
+
* - **CCTP Support**: Natively supports Circle's Cross-Chain Transfer Protocol for USDC.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: All chain IDs in this module are Wormhole Chain IDs by default.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface SwapQuote {
|
|
19
|
+
id: string;
|
|
20
|
+
protocol: string;
|
|
21
|
+
fromToken: string;
|
|
22
|
+
toToken: string;
|
|
23
|
+
fromAmount: string;
|
|
24
|
+
toAmount: string;
|
|
25
|
+
toAmountMin: string;
|
|
26
|
+
priceImpact: number;
|
|
27
|
+
estimatedGasUSD: number;
|
|
28
|
+
route: SwapHop[];
|
|
29
|
+
expiresAt: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SwapHop {
|
|
33
|
+
protocol: string;
|
|
34
|
+
poolAddress: string;
|
|
35
|
+
fromToken: string;
|
|
36
|
+
toToken: string;
|
|
37
|
+
fee: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CrossChainQuote {
|
|
41
|
+
id: string;
|
|
42
|
+
sourceChain: number;
|
|
43
|
+
targetChain: number;
|
|
44
|
+
fromToken: string;
|
|
45
|
+
toToken: string;
|
|
46
|
+
fromAmount: string;
|
|
47
|
+
estimatedToAmount: string;
|
|
48
|
+
routings: {
|
|
49
|
+
sourceSwap?: SwapQuote;
|
|
50
|
+
bridge: {
|
|
51
|
+
protocol: 'wormhole' | 'cctp';
|
|
52
|
+
token: string;
|
|
53
|
+
amount: string;
|
|
54
|
+
feeUSD: number;
|
|
55
|
+
estimatedTimeSeconds: number;
|
|
56
|
+
};
|
|
57
|
+
targetSwap?: SwapQuote;
|
|
58
|
+
};
|
|
59
|
+
totalFeeUSD: number;
|
|
60
|
+
estimatedTimeSeconds: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SwapResult {
|
|
64
|
+
txHash: string;
|
|
65
|
+
fromToken: string;
|
|
66
|
+
toToken: string;
|
|
67
|
+
fromAmount: string;
|
|
68
|
+
toAmount: string;
|
|
69
|
+
status: 'pending' | 'confirmed' | 'failed';
|
|
70
|
+
gasUsed?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DEXConfig {
|
|
74
|
+
/** Maximum slippage in basis points (default: 50 = 0.5%) */
|
|
75
|
+
maxSlippageBps: number;
|
|
76
|
+
/** Deadline for swap in seconds (default: 300 = 5 minutes) */
|
|
77
|
+
deadlineSeconds: number;
|
|
78
|
+
/** Preferred protocols in order of preference */
|
|
79
|
+
preferredProtocols: string[];
|
|
80
|
+
/** Chains enabled for swapping (Wormhole IDs) */
|
|
81
|
+
enabledChains: number[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface TokenInfo {
|
|
85
|
+
address: string;
|
|
86
|
+
symbol: string;
|
|
87
|
+
decimals: number;
|
|
88
|
+
chainId: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const DEFAULT_CONFIG: DEXConfig = {
|
|
92
|
+
maxSlippageBps: 50, // 0.5%
|
|
93
|
+
deadlineSeconds: 300, // 5 minutes
|
|
94
|
+
preferredProtocols: ['uniswap_v3', 'jupiter', 'curve', 'uniswap_v2'],
|
|
95
|
+
enabledChains: [1, 2, 5, 6, 23, 24, 30], // Sol, Eth, Poly, Avax, Arb, Op, Base
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Common token addresses by Wormhole Chain ID
|
|
99
|
+
export const COMMON_TOKENS: Record<number, Record<string, string>> = {
|
|
100
|
+
2: { // Ethereum
|
|
101
|
+
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
102
|
+
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
103
|
+
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
|
104
|
+
},
|
|
105
|
+
30: { // Base
|
|
106
|
+
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
107
|
+
WETH: '0x4200000000000000000000000000000000000006',
|
|
108
|
+
},
|
|
109
|
+
1: { // Solana
|
|
110
|
+
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // Native USDC
|
|
111
|
+
SOL: 'So11111111111111111111111111111111111111112',
|
|
112
|
+
},
|
|
113
|
+
23: { // Arbitrum
|
|
114
|
+
USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
115
|
+
WETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
116
|
+
},
|
|
117
|
+
24: { // Optimism
|
|
118
|
+
USDC: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607', // Bridged (Standard)
|
|
119
|
+
USDC_NATIVE: '0x0b2C639c533813f4Aa9D7837CAf992c92bdE5162', // Native
|
|
120
|
+
WETH: '0x4200000000000000000000000000000000000006',
|
|
121
|
+
},
|
|
122
|
+
5: { // Polygon
|
|
123
|
+
USDC: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // Bridged
|
|
124
|
+
USDC_NATIVE: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
125
|
+
WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Map chain IDs to CCTP Domain presence (Wormhole IDs)
|
|
130
|
+
export const CCTP_SUPPORTED_CHAINS = [1, 2, 5, 23, 24, 30, 21, 6]; // Sol, Eth, Poly, Arb, Op, Base, Sui, Avax
|
|
131
|
+
|
|
132
|
+
export class DEXAggregator {
|
|
133
|
+
private config: DEXConfig;
|
|
134
|
+
private quoteCache: Map<string, { quote: SwapQuote; expiresAt: number }> = new Map();
|
|
135
|
+
|
|
136
|
+
constructor(config: Partial<DEXConfig> = {}) {
|
|
137
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get best swap quote for a single chain.
|
|
142
|
+
*/
|
|
143
|
+
async getBestQuote(
|
|
144
|
+
chainId: number,
|
|
145
|
+
fromToken: string,
|
|
146
|
+
toToken: string,
|
|
147
|
+
amount: string
|
|
148
|
+
): Promise<SwapQuote | null> {
|
|
149
|
+
if (this.areAddressesEqual(chainId, fromToken, toToken)) return null; // No swap needed
|
|
150
|
+
|
|
151
|
+
const quotes = await this.getQuotes(chainId, fromToken, toToken, amount);
|
|
152
|
+
return quotes.length > 0 ? quotes[0] : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get swap quotes from multiple protocols.
|
|
157
|
+
*/
|
|
158
|
+
async getQuotes(
|
|
159
|
+
chainId: number,
|
|
160
|
+
fromToken: string,
|
|
161
|
+
toToken: string,
|
|
162
|
+
amount: string
|
|
163
|
+
): Promise<SwapQuote[]> {
|
|
164
|
+
// Allow enabled chains
|
|
165
|
+
if (!this.config.enabledChains.includes(chainId)) {
|
|
166
|
+
// throw new Error(`Chain ${chainId} not enabled for swapping`);
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Use cache if valid
|
|
171
|
+
const cacheKey = `${chainId}:${fromToken}:${toToken}:${amount}`;
|
|
172
|
+
const cached = this.quoteCache.get(cacheKey);
|
|
173
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
174
|
+
return [cached.quote];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// In a real implementation, we would query 1inch, ParaSwap, Jupiter (Solana), etc.
|
|
178
|
+
// Here we simulate a quote request
|
|
179
|
+
const simulatedQuote = await this.simulateQuote(chainId, fromToken, toToken, amount);
|
|
180
|
+
if (simulatedQuote) {
|
|
181
|
+
this.quoteCache.set(cacheKey, {
|
|
182
|
+
quote: simulatedQuote,
|
|
183
|
+
expiresAt: Date.now() + 15000 // 15s cache
|
|
184
|
+
});
|
|
185
|
+
return [simulatedQuote];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find optimized cross-chain route including swaps.
|
|
193
|
+
* Supports CCTP and standard bridging.
|
|
194
|
+
*/
|
|
195
|
+
async findCrossChainRoute(
|
|
196
|
+
sourceChain: number,
|
|
197
|
+
sourceToken: string,
|
|
198
|
+
targetChain: number,
|
|
199
|
+
targetToken: string,
|
|
200
|
+
amount: string
|
|
201
|
+
): Promise<CrossChainQuote | null> {
|
|
202
|
+
// 1. Identify Bridge Token
|
|
203
|
+
// Ideally we check common bridge tokens: USDC, ETH, SOL
|
|
204
|
+
|
|
205
|
+
// Priority: USDC (for CCTP) > Native > Others
|
|
206
|
+
const isUSDCSource = this.isUSDC(sourceChain, sourceToken);
|
|
207
|
+
const isUSDCTarget = this.isUSDC(targetChain, targetToken);
|
|
208
|
+
|
|
209
|
+
// Default to USDC-First approach if available on that chain
|
|
210
|
+
let bridgeTokenSymbol = 'USDC';
|
|
211
|
+
if (!isUSDCSource && !this.getTokenAddress(sourceChain, 'USDC')) {
|
|
212
|
+
// Fallback to WETH if USDC not present
|
|
213
|
+
bridgeTokenSymbol = 'WETH';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const sourceBridgeToken = this.getTokenAddress(sourceChain, bridgeTokenSymbol) || sourceToken;
|
|
217
|
+
const targetBridgeToken = this.getTokenAddress(targetChain, bridgeTokenSymbol) || targetToken;
|
|
218
|
+
|
|
219
|
+
// Step 1: Source Swap (if needed)
|
|
220
|
+
let sourceSwap: SwapQuote | undefined;
|
|
221
|
+
let bridgeAmount = amount;
|
|
222
|
+
|
|
223
|
+
if (!this.areAddressesEqual(sourceChain, sourceToken, sourceBridgeToken)) {
|
|
224
|
+
const quote = await this.getBestQuote(sourceChain, sourceToken, sourceBridgeToken, amount);
|
|
225
|
+
if (!quote) return null; // Cannot swap to bridge token
|
|
226
|
+
sourceSwap = quote;
|
|
227
|
+
bridgeAmount = quote.toAmount;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Step 2: Bridge Logic
|
|
231
|
+
// If connecting to/from USDC and supported, use CCTP
|
|
232
|
+
const bridgeProtocol = (bridgeTokenSymbol === 'USDC' && this.supportsCCTP(sourceChain, targetChain))
|
|
233
|
+
? 'cctp' : 'wormhole';
|
|
234
|
+
|
|
235
|
+
// Estimate bridge fees & time
|
|
236
|
+
const bridgeFee = this.estimateBridgeCost(bridgeProtocol, sourceChain, targetChain);
|
|
237
|
+
const bridgeTime = this.estimateBridgeTime(bridgeProtocol, sourceChain, targetChain);
|
|
238
|
+
|
|
239
|
+
// CCTP is burn/mint (no value loss other than gas, handled in feeUSD)
|
|
240
|
+
const destBridgeAmount = bridgeAmount;
|
|
241
|
+
|
|
242
|
+
// Step 3: Target Swap (if needed)
|
|
243
|
+
let targetSwap: SwapQuote | undefined;
|
|
244
|
+
let finalAmount = destBridgeAmount;
|
|
245
|
+
|
|
246
|
+
if (!this.areAddressesEqual(targetChain, targetBridgeToken, targetToken)) {
|
|
247
|
+
const quote = await this.getBestQuote(targetChain, targetBridgeToken, targetToken, destBridgeAmount);
|
|
248
|
+
if (!quote) return null; // Cannot swap from bridge token
|
|
249
|
+
targetSwap = quote;
|
|
250
|
+
finalAmount = quote.toAmount;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const totalFeeUSD = (sourceSwap?.estimatedGasUSD || 0) +
|
|
254
|
+
bridgeFee +
|
|
255
|
+
(targetSwap?.estimatedGasUSD || 0);
|
|
256
|
+
|
|
257
|
+
const totalTime = (sourceSwap ? 15 : 0) + bridgeTime + (targetSwap ? 15 : 0);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
id: `route_${Date.now()}`,
|
|
261
|
+
sourceChain,
|
|
262
|
+
targetChain,
|
|
263
|
+
fromToken: sourceToken,
|
|
264
|
+
toToken: targetToken,
|
|
265
|
+
fromAmount: amount,
|
|
266
|
+
estimatedToAmount: finalAmount,
|
|
267
|
+
routings: {
|
|
268
|
+
sourceSwap,
|
|
269
|
+
bridge: {
|
|
270
|
+
protocol: bridgeProtocol,
|
|
271
|
+
token: bridgeTokenSymbol,
|
|
272
|
+
amount: bridgeAmount,
|
|
273
|
+
feeUSD: bridgeFee,
|
|
274
|
+
estimatedTimeSeconds: bridgeTime
|
|
275
|
+
},
|
|
276
|
+
targetSwap
|
|
277
|
+
},
|
|
278
|
+
totalFeeUSD,
|
|
279
|
+
estimatedTimeSeconds: totalTime
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check if CCTP is supported between chains.
|
|
285
|
+
*/
|
|
286
|
+
supportsCCTP(sourceChain: number, targetChain: number): boolean {
|
|
287
|
+
return CCTP_SUPPORTED_CHAINS.includes(sourceChain) &&
|
|
288
|
+
CCTP_SUPPORTED_CHAINS.includes(targetChain);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if token is USDC.
|
|
293
|
+
*/
|
|
294
|
+
isUSDC(chain: number, token: string): boolean {
|
|
295
|
+
const addr = this.getTokenAddress(chain, 'USDC') || this.getTokenAddress(chain, 'USDC_NATIVE');
|
|
296
|
+
return addr ? this.areAddressesEqual(chain, addr, token) : false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Execute a swap (Simulation).
|
|
301
|
+
*/
|
|
302
|
+
async executeSwap(
|
|
303
|
+
quote: SwapQuote,
|
|
304
|
+
signer: { signTransaction: (tx: unknown) => Promise<string> }
|
|
305
|
+
): Promise<SwapResult> {
|
|
306
|
+
if (Date.now() > quote.expiresAt) {
|
|
307
|
+
throw new Error('Quote expired');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Simulated result
|
|
311
|
+
const txHash = '0x' + Array.from({ length: 64 }, () =>
|
|
312
|
+
Math.floor(Math.random() * 16).toString(16)
|
|
313
|
+
).join('');
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
txHash,
|
|
317
|
+
fromToken: quote.fromToken,
|
|
318
|
+
toToken: quote.toToken,
|
|
319
|
+
fromAmount: quote.fromAmount,
|
|
320
|
+
toAmount: quote.toAmount,
|
|
321
|
+
status: 'confirmed',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get supported tokens for a chain.
|
|
327
|
+
*/
|
|
328
|
+
getSupportedTokens(chainId: number): string[] {
|
|
329
|
+
return Object.keys(COMMON_TOKENS[chainId] || {});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Check if a swap route exists.
|
|
334
|
+
*/
|
|
335
|
+
async hasRoute(
|
|
336
|
+
chainId: number,
|
|
337
|
+
fromToken: string,
|
|
338
|
+
toToken: string
|
|
339
|
+
): Promise<boolean> {
|
|
340
|
+
try {
|
|
341
|
+
const quote = await this.getBestQuote(chainId, fromToken, toToken, '1000000');
|
|
342
|
+
return quote !== null;
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get estimated output for a swap.
|
|
350
|
+
*/
|
|
351
|
+
async getEstimatedOutput(
|
|
352
|
+
chainId: number,
|
|
353
|
+
fromToken: string,
|
|
354
|
+
toToken: string,
|
|
355
|
+
amount: string
|
|
356
|
+
): Promise<{ output: string; priceImpact: number } | null> {
|
|
357
|
+
const quote = await this.getBestQuote(chainId, fromToken, toToken, amount);
|
|
358
|
+
if (!quote) return null;
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
output: quote.toAmount,
|
|
362
|
+
priceImpact: quote.priceImpact,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Clear quote cache.
|
|
368
|
+
*/
|
|
369
|
+
clearCache(): void {
|
|
370
|
+
this.quoteCache.clear();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Helper to find common bridge token address
|
|
374
|
+
getTokenAddress(chain: number, symbol: string): string | undefined {
|
|
375
|
+
return COMMON_TOKENS[chain]?.[symbol];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Private helper for address comparison handling case sensitivity
|
|
379
|
+
private areAddressesEqual(chain: number, a: string, b: string): boolean {
|
|
380
|
+
if (chain === 1 || chain === 21 || chain === 22) {
|
|
381
|
+
// Solana, Sui, Aptos are simple strings (Native addresses are typically Case Sensitive in Base58)
|
|
382
|
+
return a === b;
|
|
383
|
+
}
|
|
384
|
+
// EVM is case insensitive
|
|
385
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Simulation helpers
|
|
389
|
+
private async simulateQuote(chain: number, from: string, to: string, amount: string): Promise<SwapQuote | null> {
|
|
390
|
+
const amountBigInt = BigInt(amount);
|
|
391
|
+
const slippage = this.config.maxSlippageBps / 10000;
|
|
392
|
+
|
|
393
|
+
// Simulate price impact
|
|
394
|
+
// Cap at 3%
|
|
395
|
+
const priceImpact = Math.min(Number(amountBigInt) / 1e12, 0.03);
|
|
396
|
+
|
|
397
|
+
const outputMultiplier = from === to ? 1 : 0.997; // 0.3% fee
|
|
398
|
+
const toAmount = BigInt(Math.floor(Number(amountBigInt) * outputMultiplier * (1 - priceImpact)));
|
|
399
|
+
const toAmountMin = BigInt(Math.floor(Number(toAmount) * (1 - slippage)));
|
|
400
|
+
|
|
401
|
+
const protocol = (chain === 1) ? 'jupiter' : 'uniswap_v3';
|
|
402
|
+
|
|
403
|
+
const estimatedGasUSD = (chain === 1) ? 0.001 : 0.5;
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
id: `sim_quote_${Date.now()}`,
|
|
407
|
+
protocol,
|
|
408
|
+
fromToken: from,
|
|
409
|
+
toToken: to,
|
|
410
|
+
fromAmount: amount,
|
|
411
|
+
toAmount: toAmount.toString(),
|
|
412
|
+
toAmountMin: toAmountMin.toString(),
|
|
413
|
+
priceImpact,
|
|
414
|
+
estimatedGasUSD,
|
|
415
|
+
route: [{
|
|
416
|
+
protocol,
|
|
417
|
+
poolAddress: '0x' + '0'.repeat(40),
|
|
418
|
+
fromToken: from,
|
|
419
|
+
toToken: to,
|
|
420
|
+
fee: 3000
|
|
421
|
+
}],
|
|
422
|
+
expiresAt: Date.now() + 60000
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private estimateBridgeCost(protocol: string, source: number, dest: number): number {
|
|
427
|
+
if (protocol === 'cctp') return 0.2; // CCTP is cheap
|
|
428
|
+
return 1.5; // Wormhole Standard
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private estimateBridgeTime(protocol: string, source: number, dest: number): number {
|
|
432
|
+
if (protocol === 'cctp') return 600; // ~10-20 mins
|
|
433
|
+
return 120; // Wormhole Standard
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Singleton instance
|
|
438
|
+
let defaultAggregator: DEXAggregator | null = null;
|
|
439
|
+
export function getDEXAggregator(): DEXAggregator {
|
|
440
|
+
if (!defaultAggregator) {
|
|
441
|
+
defaultAggregator = new DEXAggregator();
|
|
442
|
+
}
|
|
443
|
+
return defaultAggregator;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export function createDEXAggregator(config?: Partial<DEXConfig>): DEXAggregator {
|
|
447
|
+
return new DEXAggregator(config);
|
|
448
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module FeeEstimator
|
|
4
|
+
* @description
|
|
5
|
+
* Cross-chain fee estimation service.
|
|
6
|
+
*
|
|
7
|
+
* Accurately calculating the cost of a cross-chain transaction is complex because it involves:
|
|
8
|
+
* - Source chain gas fees.
|
|
9
|
+
* - Wormhole/Relayer fees (in source token).
|
|
10
|
+
* - Target chain redemption fees.
|
|
11
|
+
*
|
|
12
|
+
* This module connects to the Veridex Relayer API to get real-time fee quotes.
|
|
13
|
+
*/
|
|
14
|
+
import { createRelayerClient, RelayerClient } from '@veridex/sdk';
|
|
15
|
+
|
|
16
|
+
export class FeeEstimator {
|
|
17
|
+
private relayerClient?: RelayerClient;
|
|
18
|
+
|
|
19
|
+
constructor(relayerUrl?: string, relayerApiKey?: string) {
|
|
20
|
+
if (relayerUrl) {
|
|
21
|
+
this.relayerClient = createRelayerClient({
|
|
22
|
+
baseUrl: relayerUrl,
|
|
23
|
+
apiKey: relayerApiKey
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async estimateFee(sourceChain: number, targetChain: number): Promise<number> {
|
|
29
|
+
if (sourceChain === targetChain) return 0.01;
|
|
30
|
+
|
|
31
|
+
if (this.relayerClient) {
|
|
32
|
+
try {
|
|
33
|
+
const quote = await this.relayerClient.getFeeQuote(sourceChain, targetChain);
|
|
34
|
+
// Convert bigint fee to USD (simplified)
|
|
35
|
+
return Number(quote.feeInSourceToken) / 1e18;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Failed to get real fee quote', e);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 0.50; // Fallback $0.50 for cross-chain
|
|
42
|
+
}
|
|
43
|
+
}
|