@deserialize/multi-vm-wallet 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constant.js +24 -6
- package/dist/constant.js.map +1 -1
- package/dist/evm/evm.d.ts +3 -1
- package/dist/evm/evm.js +154 -9
- package/dist/evm/evm.js.map +1 -1
- package/dist/evm/script.d.ts +24 -0
- package/dist/evm/script.js +111 -0
- package/dist/evm/script.js.map +1 -0
- package/dist/evm/utils.d.ts +13 -0
- package/dist/evm/utils.js +59 -6
- package/dist/evm/utils.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/dist/test.js +47 -20
- package/dist/test.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/package.json +6 -3
- package/utils/constant.ts +25 -6
- package/utils/evm/evm.ts +242 -9
- package/utils/evm/script.ts +160 -0
- package/utils/evm/utils.ts +72 -7
- package/utils/index.ts +3 -1
- package/utils/test.ts +56 -16
- package/utils/todo.txt +7 -0
- package/utils/utils.ts +3 -0
package/utils/evm/evm.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { EVMDeriveChildPrivateKey } from "../bip32";
|
|
|
8
8
|
import { ChainWallet } from "../IChainWallet";
|
|
9
9
|
import { Balance, ChainWalletConfig, NFTInfo, TokenInfo, TransactionResult } from "../types";
|
|
10
10
|
import { VM } from "../vm";
|
|
11
|
-
import { ethers, JsonRpcProvider, Wallet } from "ethers";
|
|
11
|
+
import { ethers, JsonRpcProvider, Wallet, formatUnits } from "ethers";
|
|
12
12
|
import BN from "bn.js";
|
|
13
13
|
import {
|
|
14
14
|
getNativeBalance,
|
|
@@ -22,13 +22,51 @@ import {
|
|
|
22
22
|
prepareSwapParams,
|
|
23
23
|
formatAmountToWei,
|
|
24
24
|
isChainSupportedByKyber,
|
|
25
|
+
isChainSupportedByDebonk,
|
|
26
|
+
// normalizeTokenAddressForDebonk,
|
|
27
|
+
convertSlippageForDebonk,
|
|
25
28
|
TransactionParams,
|
|
26
29
|
approveToken,
|
|
27
30
|
executeContractMethod,
|
|
28
|
-
getTokenInfo
|
|
31
|
+
getTokenInfo,
|
|
32
|
+
DESERIALIZED_SUPPORTED_CHAINS
|
|
29
33
|
} from "./utils";
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
interface DebonkQuoteResponse {
|
|
36
|
+
tokenA: string;
|
|
37
|
+
tokenB: string;
|
|
38
|
+
amountIn: string;
|
|
39
|
+
amountOut: string;
|
|
40
|
+
tokenPrice: string;
|
|
41
|
+
routePlan: Array<{
|
|
42
|
+
tokenA: string;
|
|
43
|
+
tokenB: string;
|
|
44
|
+
dexId: string;
|
|
45
|
+
poolAddress: string;
|
|
46
|
+
aToB: boolean;
|
|
47
|
+
fee: number;
|
|
48
|
+
}>;
|
|
49
|
+
dexId: string;
|
|
50
|
+
dexFactory: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface DebonkSwapResponse {
|
|
54
|
+
transactions: Array<{
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
data: string;
|
|
58
|
+
value: string;
|
|
59
|
+
gasLimit?: string;
|
|
60
|
+
gasPrice?: string;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface DebonkSwapResult {
|
|
65
|
+
success: boolean;
|
|
66
|
+
hash: string;
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
32
70
|
|
|
33
71
|
export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
34
72
|
derivationPath = "m/44'/60'/0'/0/"; // Default EVM derivation path
|
|
@@ -153,6 +191,200 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
153
191
|
slippage: `${slippage / 100}%`
|
|
154
192
|
});
|
|
155
193
|
|
|
194
|
+
// Check if this is a 0G chain that should use Debonk
|
|
195
|
+
if (isChainSupportedByDebonk(chainId)) {
|
|
196
|
+
console.log('Using Debonk API for 0G chain swap');
|
|
197
|
+
return await this.performDebonkSwap(tokenIn, tokenOut, amount, slippage, recipient, deadline);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Otherwise use Kyber (existing flow)
|
|
201
|
+
console.log('Using KyberSwap for non-0G chain swap');
|
|
202
|
+
return await this.performKyberSwap(tokenIn, tokenOut, amount, slippage, recipient, deadline);
|
|
203
|
+
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('Swap failed:', error);
|
|
206
|
+
throw new Error(`Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async performDebonkSwap(
|
|
211
|
+
tokenIn: TokenInfo,
|
|
212
|
+
tokenOut: TokenInfo,
|
|
213
|
+
amount: number,
|
|
214
|
+
slippage: number = 50, // bps (e.g., 50 = 0.5%)
|
|
215
|
+
recipient?: string,
|
|
216
|
+
deadline?: number
|
|
217
|
+
): Promise<DebonkSwapResult> {
|
|
218
|
+
try {
|
|
219
|
+
const BASE_URL = 'https://evm-api.deserialize.xyz';
|
|
220
|
+
const tokenInAddress = tokenIn.address;
|
|
221
|
+
const tokenOutAddress = tokenOut.address;
|
|
222
|
+
|
|
223
|
+
// Convert amount to wei (multiply by 10^18 for 18 decimal tokens)
|
|
224
|
+
const amountInWei = (amount * Math.pow(10, tokenIn.decimals || 18)).toString();
|
|
225
|
+
console.log("amountInWei", amountInWei)
|
|
226
|
+
// Convert slippage from bps to percentage (e.g., 50 bps -> 0.5%)
|
|
227
|
+
const slippagePercentage = slippage / 100;
|
|
228
|
+
|
|
229
|
+
console.log("Debonk API swap params:", {
|
|
230
|
+
tokenA: tokenInAddress,
|
|
231
|
+
tokenB: tokenOutAddress,
|
|
232
|
+
amountIn: amount,
|
|
233
|
+
amountInWei: amountInWei,
|
|
234
|
+
slippage: slippagePercentage,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Step 1: Get quote from API
|
|
238
|
+
console.log("Getting quote from Debonk API...");
|
|
239
|
+
|
|
240
|
+
const quotePayload = {
|
|
241
|
+
tokenA: tokenInAddress,
|
|
242
|
+
tokenB: tokenOutAddress,
|
|
243
|
+
amountIn: amountInWei, // Use wei amount
|
|
244
|
+
dexId: "ZERO_G"
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
console.log("Quote request payload:", quotePayload);
|
|
248
|
+
|
|
249
|
+
const quoteResponse = await fetch(`${BASE_URL}/quote`, {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
headers: {
|
|
252
|
+
'Content-Type': 'application/json',
|
|
253
|
+
},
|
|
254
|
+
body: JSON.stringify(quotePayload)
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!quoteResponse.ok) {
|
|
258
|
+
const errorText = await quoteResponse.text();
|
|
259
|
+
console.error("Quote API error response:", errorText);
|
|
260
|
+
return this.fail(`Quote API failed: ${quoteResponse.status} ${errorText}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const quote: DebonkQuoteResponse = await quoteResponse.json();
|
|
264
|
+
console.log("Quote received:", JSON.stringify(quote, null, 2));
|
|
265
|
+
|
|
266
|
+
// Step 2: Fix the quote dexId for swap API (it expects "ALL")
|
|
267
|
+
const modifiedQuote = {
|
|
268
|
+
...quote,
|
|
269
|
+
dexId: "ALL" // Change from "ZERO_G" to "ALL" as required by swap API
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
console.log("Modified quote for swap API:", JSON.stringify(modifiedQuote, null, 2));
|
|
273
|
+
|
|
274
|
+
// Step 3: Get wallet address
|
|
275
|
+
const walletAddress = await this.getWallet().getAddress();
|
|
276
|
+
console.log("Wallet address:", walletAddress);
|
|
277
|
+
|
|
278
|
+
// Step 4: Call swap API with modified quote
|
|
279
|
+
console.log("Calling swap API...");
|
|
280
|
+
|
|
281
|
+
const swapPayload = {
|
|
282
|
+
publicKey: walletAddress,
|
|
283
|
+
slippage: slippagePercentage,
|
|
284
|
+
quote: modifiedQuote
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
console.log("Swap request payload:", JSON.stringify(swapPayload, null, 2));
|
|
288
|
+
|
|
289
|
+
const swapResponse = await fetch(`${BASE_URL}/swap`, {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
headers: {
|
|
292
|
+
'Content-Type': 'application/json',
|
|
293
|
+
},
|
|
294
|
+
body: JSON.stringify(swapPayload)
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (!swapResponse.ok) {
|
|
298
|
+
const errorText = await swapResponse.text();
|
|
299
|
+
console.error("Swap API error response:", errorText);
|
|
300
|
+
return this.fail(`Swap API failed: ${swapResponse.status} ${errorText}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const swapData: DebonkSwapResponse = await swapResponse.json();
|
|
304
|
+
console.log("Swap API response:", JSON.stringify(swapData, null, 2));
|
|
305
|
+
|
|
306
|
+
const wallet = this.getWallet();
|
|
307
|
+
let lastTxHash = '';
|
|
308
|
+
|
|
309
|
+
// Step 5: Execute each transaction sequentially
|
|
310
|
+
console.log(`Executing ${swapData.transactions.length} transactions...`);
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < swapData.transactions.length; i++) {
|
|
313
|
+
const transaction = swapData.transactions[i];
|
|
314
|
+
console.log(`Executing transaction ${i + 1} of ${swapData.transactions.length}`);
|
|
315
|
+
|
|
316
|
+
// Prepare transaction object
|
|
317
|
+
const txRequest = {
|
|
318
|
+
to: transaction.to,
|
|
319
|
+
data: transaction.data,
|
|
320
|
+
value: transaction.value,
|
|
321
|
+
// gasPrice: 70000000000,
|
|
322
|
+
// gasLimit: 70000, // Increase significantly
|
|
323
|
+
};
|
|
324
|
+
console.log("Prepared transaction request:", txRequest);
|
|
325
|
+
console.log(`Transaction ${i + 1} request:`, txRequest);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const txResponse = await wallet.sendTransaction(txRequest);
|
|
329
|
+
console.log(`Transaction ${i + 1} sent:`, txResponse.hash);
|
|
330
|
+
|
|
331
|
+
// Wait for confirmation
|
|
332
|
+
const receipt = await txResponse.wait();
|
|
333
|
+
|
|
334
|
+
if (!receipt) {
|
|
335
|
+
return this.fail(`Transaction ${i + 1} failed - no receipt received`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const txHash = receipt.hash || txResponse.hash;
|
|
339
|
+
lastTxHash = txHash;
|
|
340
|
+
|
|
341
|
+
console.log(`Transaction ${i + 1} confirmed:`, {
|
|
342
|
+
hash: txHash,
|
|
343
|
+
blockNumber: receipt.blockNumber,
|
|
344
|
+
gasUsed: receipt.gasUsed?.toString()
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
} catch (txError: any) {
|
|
348
|
+
console.error(`Transaction ${i + 1} failed:`, txError);
|
|
349
|
+
return this.fail(`Transaction ${i + 1} failed: ${txError?.message ?? String(txError)}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log("All transactions completed successfully");
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
hash: lastTxHash // Return the hash of the last transaction
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
} catch (error: any) {
|
|
361
|
+
console.error("Debonk API swap error:", error);
|
|
362
|
+
return this.fail(`Debonk API swap failed: ${error?.message ?? String(error)}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Helper method for EVMChainWallet class
|
|
367
|
+
private fail(message: string): DebonkSwapResult {
|
|
368
|
+
return {
|
|
369
|
+
success: false,
|
|
370
|
+
hash: "",
|
|
371
|
+
error: message
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
private async performKyberSwap(
|
|
377
|
+
tokenIn: TokenInfo,
|
|
378
|
+
tokenOut: TokenInfo,
|
|
379
|
+
amount: number,
|
|
380
|
+
slippage: number = 50,
|
|
381
|
+
recipient?: string,
|
|
382
|
+
deadline?: number
|
|
383
|
+
): Promise<TransactionResult> {
|
|
384
|
+
try {
|
|
385
|
+
const wallet = this.getWallet();
|
|
386
|
+
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
387
|
+
|
|
156
388
|
if (!isChainSupportedByKyber(chainId)) {
|
|
157
389
|
throw new Error(`Chain ${chainId} is not supported by KyberSwap`);
|
|
158
390
|
}
|
|
@@ -179,7 +411,7 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
179
411
|
isNativeOut
|
|
180
412
|
);
|
|
181
413
|
|
|
182
|
-
console.log('
|
|
414
|
+
console.log('Kyber swap parameters:', {
|
|
183
415
|
tokenInAddress,
|
|
184
416
|
tokenOutAddress,
|
|
185
417
|
formattedAmountIn,
|
|
@@ -212,7 +444,7 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
212
444
|
clientId: 'EVMChainWallet'
|
|
213
445
|
});
|
|
214
446
|
|
|
215
|
-
console.log('
|
|
447
|
+
console.log('Kyber swap transaction prepared:', {
|
|
216
448
|
to: swapTx.to,
|
|
217
449
|
dataLength: swapTx.data?.length || 0,
|
|
218
450
|
gasLimit: swapTx.gasLimit?.toString(),
|
|
@@ -255,20 +487,20 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
255
487
|
);
|
|
256
488
|
|
|
257
489
|
if (result.success) {
|
|
258
|
-
console.log('
|
|
490
|
+
console.log(' Kyber swap completed successfully:', {
|
|
259
491
|
hash: result.hash,
|
|
260
492
|
gasUsed: result.gasUsed?.toString(),
|
|
261
493
|
blockNumber: result.blockNumber
|
|
262
494
|
});
|
|
263
495
|
} else {
|
|
264
|
-
console.log('
|
|
496
|
+
console.log(' Kyber swap failed:', result);
|
|
265
497
|
}
|
|
266
498
|
|
|
267
499
|
return result;
|
|
268
500
|
|
|
269
501
|
} catch (error) {
|
|
270
|
-
console.error('
|
|
271
|
-
throw new Error(`
|
|
502
|
+
console.error('Kyber swap failed:', error);
|
|
503
|
+
throw new Error(`Kyber swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
272
504
|
}
|
|
273
505
|
}
|
|
274
506
|
|
|
@@ -282,6 +514,7 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
282
514
|
gasEstimate: string;
|
|
283
515
|
route: string[];
|
|
284
516
|
}> {
|
|
517
|
+
|
|
285
518
|
try {
|
|
286
519
|
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
287
520
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { ethers } from 'ethers';
|
|
4
|
+
|
|
5
|
+
// Simplified ABI for Uniswap V3 Router
|
|
6
|
+
const ROUTER_ABI = [
|
|
7
|
+
'function exactInputSingle((address tokenIn, address tokenIn, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)',
|
|
8
|
+
'function exactOutputSingle((address tokenIn, address tokenIn, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountIn)'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// ERC20 ABI (simplified)
|
|
12
|
+
const ERC20_ABI = [
|
|
13
|
+
'function approve(address spender, uint256 amount) external returns (bool)',
|
|
14
|
+
'function allowance(address owner, address spender) external view returns (uint256)',
|
|
15
|
+
'function balanceOf(address account) external view returns (uint256)'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export interface SwapParams {
|
|
19
|
+
privateKey: string;
|
|
20
|
+
tokenIn: string;
|
|
21
|
+
tokenOut: string;
|
|
22
|
+
tokenInDecimal:number;
|
|
23
|
+
amountIn: string;
|
|
24
|
+
adminAddress: string;
|
|
25
|
+
routerAddress: string;
|
|
26
|
+
minAmountOut:string;
|
|
27
|
+
rpcUrl: string;
|
|
28
|
+
feeTier?: number; // 500 = 0.05%, 3000 = 0.3%, 10000 = 1%
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const quote = async (quoterAddress:string,rpc:string,tokenIn:string, tokenOut:string, amountIn:string, fee:number,sqrtPriceLimitX96="0") => {
|
|
32
|
+
const provider = new ethers.JsonRpcProvider(rpc);
|
|
33
|
+
|
|
34
|
+
const viewABI = [
|
|
35
|
+
{
|
|
36
|
+
inputs: [
|
|
37
|
+
{ internalType: "address", name: "tokenIn", type: "address" },
|
|
38
|
+
{ internalType: "address", name: "tokenOut", type: "address" },
|
|
39
|
+
{ internalType: "uint24", name: "fee", type: "uint24" },
|
|
40
|
+
{ internalType: "uint256", name: "amountIn", type: "uint256" },
|
|
41
|
+
{ internalType: "uint160", name: "sqrtPriceLimitX96", type: "uint160" },
|
|
42
|
+
],
|
|
43
|
+
name: "quoteExactInputSingle",
|
|
44
|
+
outputs: [{ internalType: "uint256", name: "amountOut", type: "uint256" }],
|
|
45
|
+
stateMutability: "view",
|
|
46
|
+
type: "function",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const quoter = new ethers.Contract(quoterAddress, viewABI, provider);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const amountOut = await quoter.quoteExactInputSingle(
|
|
54
|
+
tokenIn,
|
|
55
|
+
tokenOut,
|
|
56
|
+
fee,
|
|
57
|
+
amountIn,
|
|
58
|
+
sqrtPriceLimitX96
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
console.log("Amount Out:", amountOut.toString());
|
|
62
|
+
return amountOut.toString();
|
|
63
|
+
} catch (err:any) {
|
|
64
|
+
console.error("Quote failed:", err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export const quickSwapSetup = async (params: SwapParams) => {
|
|
70
|
+
const {
|
|
71
|
+
privateKey,
|
|
72
|
+
tokenIn,
|
|
73
|
+
tokenOut,
|
|
74
|
+
amountIn,
|
|
75
|
+
tokenInDecimal,
|
|
76
|
+
adminAddress,
|
|
77
|
+
routerAddress,
|
|
78
|
+
rpcUrl,
|
|
79
|
+
minAmountOut,
|
|
80
|
+
|
|
81
|
+
feeTier = 100 // 0.01% fee tier
|
|
82
|
+
} = params;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Setup provider and wallet
|
|
86
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
87
|
+
const wallet = new ethers.Wallet(privateKey, provider);
|
|
88
|
+
|
|
89
|
+
// Create contract instances
|
|
90
|
+
const router = new ethers.Contract(routerAddress, ROUTER_ABI, wallet);
|
|
91
|
+
const tokenInContract = new ethers.Contract(tokenIn, ERC20_ABI, wallet);
|
|
92
|
+
|
|
93
|
+
// Convert amount to wei
|
|
94
|
+
const amountInWei = ethers.parseUnits(amountIn, tokenInDecimal); // Assuming 18 decimals
|
|
95
|
+
|
|
96
|
+
// Check and approve token allowance
|
|
97
|
+
const allowance = await tokenInContract.allowance(wallet.address, routerAddress);
|
|
98
|
+
|
|
99
|
+
if (allowance < amountInWei) {
|
|
100
|
+
console.log('Approving tokens...');
|
|
101
|
+
const approveTx = await tokenInContract.approve(routerAddress, amountInWei);
|
|
102
|
+
await approveTx.wait();
|
|
103
|
+
console.log('Approval confirmed');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Calculate amount with fee (0.01% to admin)
|
|
107
|
+
const feeAmount = amountInWei * BigInt(1) / BigInt(10000); // 0.01%
|
|
108
|
+
const amountAfterFee = amountInWei - feeAmount;
|
|
109
|
+
|
|
110
|
+
// Transfer fee to admin
|
|
111
|
+
if (feeAmount > 0) {
|
|
112
|
+
console.log(`Transferring ${ethers.formatUnits(feeAmount, tokenIn)} fee to admin`);
|
|
113
|
+
const feeTransferTx = await tokenInContract.transfer(adminAddress, feeAmount);
|
|
114
|
+
await feeTransferTx.wait();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Set deadline (10 minutes from now)
|
|
118
|
+
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
119
|
+
|
|
120
|
+
// Prepare swap parameters
|
|
121
|
+
const swapParams = {
|
|
122
|
+
tokenIn: tokenIn,
|
|
123
|
+
tokenOut: tokenOut,
|
|
124
|
+
fee: feeTier,
|
|
125
|
+
recipient: wallet.address,
|
|
126
|
+
deadline: deadline,
|
|
127
|
+
amountIn: amountAfterFee,
|
|
128
|
+
amountOutMinimum: minAmountOut,
|
|
129
|
+
sqrtPriceLimitX96: 0
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
console.log('Executing swap...');
|
|
133
|
+
|
|
134
|
+
// Execute swap
|
|
135
|
+
const swapTx = await router.exactInputSingle(swapParams);
|
|
136
|
+
const receipt = await swapTx.wait();
|
|
137
|
+
|
|
138
|
+
console.log('Swap successful!');
|
|
139
|
+
console.log('Transaction hash:', receipt.hash);
|
|
140
|
+
|
|
141
|
+
// Check final balances
|
|
142
|
+
const tokenOutContract = new ethers.Contract(tokenOut, ERC20_ABI, wallet);
|
|
143
|
+
const finalBalance = await tokenOutContract.balanceOf(wallet.address);
|
|
144
|
+
|
|
145
|
+
console.log(`Final ${tokenOut} balance:`, ethers.formatUnits(finalBalance, 18));
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
txHash: receipt.hash,
|
|
150
|
+
amountOut: finalBalance.toString()
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Swap failed:', error);
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
package/utils/evm/utils.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { Balance, TokenInfo } from '../types'
|
|
3
2
|
import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt } from 'ethers'
|
|
4
3
|
import BN from 'bn.js'
|
|
@@ -24,6 +23,7 @@ interface TransactionResult {
|
|
|
24
23
|
effectiveGasPrice?: bigint
|
|
25
24
|
blockNumber?: number
|
|
26
25
|
confirmations: number
|
|
26
|
+
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface SwapParams {
|
|
@@ -114,6 +114,10 @@ const KYBER_SUPPORTED_CHAINS: { [key: string]: string } = {
|
|
|
114
114
|
'59144': 'linea'
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
export const DESERIALIZED_SUPPORTED_CHAINS: { [key: string]: string } = {
|
|
118
|
+
'16661': '0gMainnet',
|
|
119
|
+
};
|
|
120
|
+
|
|
117
121
|
interface KyberSwapParams {
|
|
118
122
|
chainId: string;
|
|
119
123
|
tokenIn: string;
|
|
@@ -143,13 +147,18 @@ const ERC20_ABI = [
|
|
|
143
147
|
]
|
|
144
148
|
|
|
145
149
|
export const getNativeBalance = async (address: string, provider: JsonRpcProvider): Promise<Balance> => {
|
|
146
|
-
const balance = await provider.getBalance(address)
|
|
150
|
+
const balance = await provider.getBalance(address); // returns BigInt in ethers v6
|
|
151
|
+
|
|
152
|
+
const dem = 10n ** 18n;
|
|
153
|
+
|
|
154
|
+
// ⚠️ BigInt division truncates, so avoid dividing BigInt directly
|
|
155
|
+
const formatted = Number(balance) / Number(dem);
|
|
147
156
|
|
|
148
157
|
return {
|
|
149
|
-
balance: new BN(balance),
|
|
150
|
-
formatted
|
|
158
|
+
balance: new BN(balance.toString()), // raw wei as BN
|
|
159
|
+
formatted, // e.g. 0.1
|
|
151
160
|
decimal: 18
|
|
152
|
-
}
|
|
161
|
+
};
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
export const getTokenInfo = async (
|
|
@@ -755,6 +764,9 @@ export async function performSwap(params: {
|
|
|
755
764
|
chargeFeeBy?: 'currency_in' | 'currency_out';
|
|
756
765
|
clientId?: string;
|
|
757
766
|
}): Promise<TransactionParams> {
|
|
767
|
+
if (!KYBER_SUPPORTED_CHAINS[params.chainId]) {
|
|
768
|
+
throw new Error(`KyberSwap does not support chain ID: ${params.chainId}`);
|
|
769
|
+
}
|
|
758
770
|
try {
|
|
759
771
|
console.log('Starting KyberSwap aggregation...', {
|
|
760
772
|
tokenIn: params.tokenIn,
|
|
@@ -762,7 +774,9 @@ export async function performSwap(params: {
|
|
|
762
774
|
amountIn: params.amountIn,
|
|
763
775
|
chainId: params.chainId
|
|
764
776
|
});
|
|
777
|
+
|
|
765
778
|
console.log('Fetching best swap route across all DEXs...');
|
|
779
|
+
|
|
766
780
|
const routeResponse = await getKyberSwapRoute({
|
|
767
781
|
chainId: params.chainId,
|
|
768
782
|
tokenIn: params.tokenIn,
|
|
@@ -775,12 +789,20 @@ export async function performSwap(params: {
|
|
|
775
789
|
clientId: params.clientId || 'MyWalletApp'
|
|
776
790
|
});
|
|
777
791
|
|
|
792
|
+
// Debug: Log the full response to understand structure
|
|
793
|
+
console.log('Full KyberSwap route response:', JSON.stringify(routeResponse, null, 2));
|
|
794
|
+
|
|
778
795
|
if (!routeResponse.data || !routeResponse.data.routeSummary) {
|
|
779
796
|
throw new Error('No valid route found for the swap');
|
|
780
797
|
}
|
|
781
798
|
|
|
782
799
|
const { routeSummary, routerAddress } = routeResponse.data;
|
|
783
800
|
|
|
801
|
+
// Debug: Log what we actually received
|
|
802
|
+
console.log('routeSummary keys:', Object.keys(routeSummary));
|
|
803
|
+
console.log('routeSummary.swaps exists:', !!routeSummary.swaps);
|
|
804
|
+
|
|
805
|
+
// Safe logging that handles undefined swaps
|
|
784
806
|
console.log('✅ Best route found:', {
|
|
785
807
|
tokenIn: routeSummary.tokenIn,
|
|
786
808
|
tokenOut: routeSummary.tokenOut,
|
|
@@ -788,10 +810,16 @@ export async function performSwap(params: {
|
|
|
788
810
|
amountOut: routeSummary.amountOut,
|
|
789
811
|
gasEstimate: routeSummary.gas,
|
|
790
812
|
routerAddress,
|
|
791
|
-
|
|
813
|
+
// Only try to access swaps if it exists
|
|
814
|
+
swapsCount: routeSummary.swaps ? routeSummary.swaps.length : 0,
|
|
815
|
+
// Only extract exchange names if swaps exists and has the expected structure
|
|
816
|
+
dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
|
|
817
|
+
? routeSummary.swaps.map(swap => swap?.exchange || 'unknown').filter(Boolean)
|
|
818
|
+
: ['unknown']
|
|
792
819
|
});
|
|
793
820
|
|
|
794
|
-
console.log('
|
|
821
|
+
console.log('Building executable transaction...');
|
|
822
|
+
|
|
795
823
|
const buildResponse = await buildKyberSwapTransaction(
|
|
796
824
|
params.chainId,
|
|
797
825
|
routeSummary,
|
|
@@ -802,6 +830,9 @@ export async function performSwap(params: {
|
|
|
802
830
|
params.clientId || 'MyWalletApp'
|
|
803
831
|
);
|
|
804
832
|
|
|
833
|
+
// Debug: Log build response
|
|
834
|
+
console.log('Build response:', JSON.stringify(buildResponse, null, 2));
|
|
835
|
+
|
|
805
836
|
if (!buildResponse.data || !buildResponse.data.data) {
|
|
806
837
|
throw new Error('Failed to build transaction data');
|
|
807
838
|
}
|
|
@@ -826,6 +857,16 @@ export async function performSwap(params: {
|
|
|
826
857
|
|
|
827
858
|
} catch (error) {
|
|
828
859
|
console.error('❌ KyberSwap aggregation failed:', error);
|
|
860
|
+
|
|
861
|
+
// More detailed error logging
|
|
862
|
+
if (error instanceof Error) {
|
|
863
|
+
console.error('Error details:', {
|
|
864
|
+
message: error.message,
|
|
865
|
+
stack: error.stack,
|
|
866
|
+
name: error.name
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
829
870
|
throw new Error(`Swap preparation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
830
871
|
}
|
|
831
872
|
}
|
|
@@ -838,6 +879,10 @@ export function isChainSupportedByKyber(chainId: string): boolean {
|
|
|
838
879
|
return chainId in KYBER_SUPPORTED_CHAINS;
|
|
839
880
|
}
|
|
840
881
|
|
|
882
|
+
export function isChainSupportedByDebonk(chainId: string): boolean {
|
|
883
|
+
return chainId in DESERIALIZED_SUPPORTED_CHAINS;
|
|
884
|
+
}
|
|
885
|
+
|
|
841
886
|
export function getNativeTokenAddress(): string {
|
|
842
887
|
return '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
843
888
|
}
|
|
@@ -878,4 +923,24 @@ export function prepareSwapParams(
|
|
|
878
923
|
tokenOutAddress,
|
|
879
924
|
formattedAmountIn
|
|
880
925
|
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Normalize token address for Debonk
|
|
930
|
+
* Converts 'native' or variations to the standard 0xEeeee... address
|
|
931
|
+
*/
|
|
932
|
+
// export function normalizeTokenAddressForDebonk(tokenAddress: string): string {
|
|
933
|
+
// if (tokenAddress === 'native' ||
|
|
934
|
+
// tokenAddress.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') {
|
|
935
|
+
// return getNativeTokenAddress();
|
|
936
|
+
// }
|
|
937
|
+
// return tokenAddress;
|
|
938
|
+
// }
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Convert slippage from basis points to percentage for Debonk
|
|
942
|
+
* Input: 50 (0.5% in bps) -> Output: 0.5 (percentage)
|
|
943
|
+
*/
|
|
944
|
+
export function convertSlippageForDebonk(slippageBps: number): number {
|
|
945
|
+
return slippageBps / 100;
|
|
881
946
|
}
|