@deserialize/multi-vm-wallet 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/IChainWallet.d.ts +2 -0
  2. package/dist/IChainWallet.js.map +1 -1
  3. package/dist/evm/evm.d.ts +14 -172
  4. package/dist/evm/evm.js +38 -504
  5. package/dist/evm/evm.js.map +1 -1
  6. package/dist/evm/transaction.utils.d.ts +3 -3
  7. package/dist/evm/utils.d.ts +115 -80
  8. package/dist/evm/utils.js +272 -497
  9. package/dist/evm/utils.js.map +1 -1
  10. package/dist/helpers/index.d.ts +1 -0
  11. package/dist/helpers/index.js +5 -0
  12. package/dist/helpers/index.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/price.d.ts +2 -0
  17. package/dist/price.js +33 -0
  18. package/dist/price.js.map +1 -0
  19. package/dist/price.types.d.ts +38 -0
  20. package/dist/price.types.js +4 -0
  21. package/dist/price.types.js.map +1 -0
  22. package/dist/savings/index.d.ts +0 -0
  23. package/dist/savings/index.js +25 -0
  24. package/dist/savings/index.js.map +1 -0
  25. package/dist/savings/saving-actions.d.ts +29 -0
  26. package/dist/savings/saving-actions.js +56 -0
  27. package/dist/savings/saving-actions.js.map +1 -0
  28. package/dist/savings/savings-manager.d.ts +1 -1
  29. package/dist/savings/savings-manager.js +3 -3
  30. package/dist/savings/savings-manager.js.map +1 -1
  31. package/dist/svm/svm.d.ts +2 -0
  32. package/dist/svm/svm.js +12 -0
  33. package/dist/svm/svm.js.map +1 -1
  34. package/dist/test.js +7 -7
  35. package/dist/test.js.map +1 -1
  36. package/dist/vm.js.map +1 -1
  37. package/package.json +1 -1
  38. package/utils/IChainWallet.ts +2 -0
  39. package/utils/evm/evm.ts +326 -681
  40. package/utils/evm/utils.ts +438 -662
  41. package/utils/helpers/index.ts +6 -0
  42. package/utils/index.ts +1 -0
  43. package/utils/price.ts +37 -0
  44. package/utils/price.types.ts +45 -0
  45. package/utils/savings/index.ts +28 -0
  46. package/utils/savings/saving-actions.ts +77 -0
  47. package/utils/savings/savings-manager.ts +1 -1
  48. package/utils/svm/svm.ts +16 -2
  49. package/utils/test.ts +13 -4
  50. package/utils/vm.ts +2 -1
@@ -4,8 +4,16 @@ import BN from 'bn.js'
4
4
  import { HelperAPI } from '../helpers';
5
5
  import BigNumber from 'bignumber.js';
6
6
 
7
- const KYBER_BASE_URL = 'https://aggregator-api.kyberswap.com';
8
-
7
+ import type { TransactionReceipt as EthersTransactionReceipt } from 'ethers'
8
+ import type { Chain, TransactionReceipt as ViemTransactionReceipt } from 'viem'
9
+ import {
10
+ PublicClient,
11
+ WalletClient,
12
+ Hex,
13
+ encodeFunctionData,
14
+ parseEther,
15
+ formatEther,
16
+ } from 'viem'
9
17
  export interface TransactionParams {
10
18
  to: string
11
19
  value?: string | bigint // For native token transfers
@@ -19,7 +27,8 @@ export interface TransactionParams {
19
27
 
20
28
  interface TransactionResult {
21
29
  hash: string
22
- receipt: TransactionReceipt | null
30
+ receipt: EthersTransactionReceipt | null
31
+ viemReceipt?: ViemTransactionReceipt
23
32
  success: boolean
24
33
  gasUsed?: bigint
25
34
  effectiveGasPrice?: bigint
@@ -137,508 +146,555 @@ interface KyberSwapParams {
137
146
  }
138
147
 
139
148
  // ERC-20 ABI
140
- const ERC20_ABI = [
141
- "function balanceOf(address owner) view returns (uint256)",
142
- "function decimals() view returns (uint8)",
143
- "function symbol() view returns (string)",
144
- "function name() view returns (string)",
145
- "function transfer(address to, uint256 amount) returns (bool)",
146
- "function transferFrom(address from, address to, uint256 amount) returns (bool)",
147
- "function approve(address spender, uint256 amount) returns (bool)",
148
- "function allowance(address owner, address spender) view returns (uint256)"
149
- ]
150
-
151
- export const getNativeBalance = async (address: string, provider: JsonRpcProvider): Promise<Balance> => {
152
- const balance = await provider.getBalance(address)
153
- const final = ethers.formatEther(balance)
154
149
 
150
+ export const ERC20_ABI = [
151
+ {
152
+ name: 'balanceOf',
153
+ type: 'function',
154
+ stateMutability: 'view',
155
+ inputs: [{ name: 'owner', type: 'address' }],
156
+ outputs: [{ type: 'uint256' }],
157
+ },
158
+ {
159
+ name: 'decimals',
160
+ type: 'function',
161
+ stateMutability: 'view',
162
+ inputs: [],
163
+ outputs: [{ type: 'uint8' }],
164
+ },
165
+ {
166
+ name: 'symbol',
167
+ type: 'function',
168
+ stateMutability: 'view',
169
+ inputs: [],
170
+ outputs: [{ type: 'string' }],
171
+ },
172
+ {
173
+ name: 'name',
174
+ type: 'function',
175
+ stateMutability: 'view',
176
+ inputs: [],
177
+ outputs: [{ type: 'string' }],
178
+ },
179
+ {
180
+ name: 'transfer',
181
+ type: 'function',
182
+ stateMutability: 'nonpayable',
183
+ inputs: [
184
+ { name: 'to', type: 'address' },
185
+ { name: 'amount', type: 'uint256' },
186
+ ],
187
+ outputs: [{ type: 'bool' }],
188
+ },
189
+ {
190
+ name: 'approve',
191
+ type: 'function',
192
+ stateMutability: 'nonpayable',
193
+ inputs: [
194
+ { name: 'spender', type: 'address' },
195
+ { name: 'amount', type: 'uint256' },
196
+ ],
197
+ outputs: [{ type: 'bool' }],
198
+ },
199
+ {
200
+ name: 'allowance',
201
+ type: 'function',
202
+ stateMutability: 'view',
203
+ inputs: [
204
+ { name: 'owner', type: 'address' },
205
+ { name: 'spender', type: 'address' },
206
+ ],
207
+ outputs: [{ type: 'uint256' }],
208
+ },
209
+ ] as const
210
+
211
+
212
+ export const fromChainToViemChain = (config: ChainWalletConfig): Chain => {
155
213
  return {
156
- balance: new BN(balance),
157
- formatted: Number(final),
158
- decimal: 18
214
+ rpcUrls: {
215
+ default: {
216
+ http: [config.rpcUrl]
217
+ }
218
+ },
219
+ id: config.chainId,
220
+ name: config.name,
221
+ nativeCurrency: {
222
+ name: config.nativeToken.name,
223
+ symbol: config.nativeToken.symbol,
224
+ decimals: config.nativeToken.decimals
225
+ },
226
+ blockExplorers: {
227
+ default: {
228
+ name: config.name + " Explorer",
229
+ url: config.explorerUrl,
230
+ apiUrl: config.explorerUrl
231
+ },
232
+ },
233
+ testnet: config.testnet || false
234
+
159
235
  }
160
- }
161
236
 
162
- export const getTokenInfo = async (
163
- tokenAddress: string,
164
- provider: JsonRpcProvider
165
- ): Promise<TokenInfo> => {
166
- try {
167
- // Create contract instance
168
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider)
169
237
 
238
+ }
239
+ export function viemReceiptToEthersReceipt(
240
+ receipt: ViemTransactionReceipt,
241
+ ): EthersTransactionReceipt {
242
+ return {
243
+ to: receipt.to ?? null,
244
+ from: receipt.from,
245
+ contractAddress: receipt.contractAddress ?? null,
246
+
247
+ transactionIndex: Number(receipt.transactionIndex),
248
+ gasUsed: receipt.gasUsed,
249
+ logsBloom: receipt.logsBloom,
250
+ blockHash: receipt.blockHash,
251
+ transactionHash: receipt.transactionHash,
252
+
253
+ logs: receipt.logs.map((log) => ({
254
+ address: log.address,
255
+ topics: log.topics,
256
+ data: log.data,
257
+ blockNumber: Number(log.blockNumber),
258
+ transactionHash: log.transactionHash,
259
+ transactionIndex: Number(log.transactionIndex),
260
+ blockHash: log.blockHash,
261
+ logIndex: Number(log.logIndex),
262
+ removed: false,
263
+ })),
264
+
265
+ blockNumber: Number(receipt.blockNumber),
266
+ confirmations: 0, // ethers usually fills this lazily
267
+ cumulativeGasUsed: receipt.cumulativeGasUsed,
268
+ effectiveGasPrice: receipt.effectiveGasPrice,
269
+
270
+ status: receipt.status === 'success' ? 1 : 0,
271
+ type: receipt.type,
272
+ } as unknown as EthersTransactionReceipt
273
+ }
274
+
275
+
276
+ export const getNativeBalance = async (
277
+ address: Hex,
278
+ client: PublicClient
279
+ ): Promise<Balance> => {
280
+ const balance = await client.getBalance({ address })
170
281
 
171
- // Get decimals to format the balance properly
172
- const [decimals, name, symbol] = await Promise.all([
173
- await tokenContract.decimals(),
174
- await tokenContract.name(),
175
- await tokenContract.symbol(),
282
+ return {
283
+ balance: new BN(balance.toString()),
284
+ formatted: Number(formatEther(balance)),
285
+ decimal: 18,
286
+ }
287
+ }
176
288
 
177
- ])
178
289
 
179
- return {
180
- name: name,
181
- symbol: symbol,
290
+ export const getTokenInfo = async (
291
+ tokenAddress: Hex,
292
+ client: PublicClient
293
+ ): Promise<TokenInfo> => {
294
+ const [decimals, name, symbol] = await Promise.all([
295
+ client.readContract({
182
296
  address: tokenAddress,
183
- decimals: decimals
184
- }
185
- } catch (error) {
186
- console.error('Error fetching token balance:', error)
187
- throw error
297
+ abi: ERC20_ABI,
298
+ functionName: 'decimals',
299
+ }),
300
+ client.readContract({
301
+ address: tokenAddress,
302
+ abi: ERC20_ABI,
303
+ functionName: 'name',
304
+ }),
305
+ client.readContract({
306
+ address: tokenAddress,
307
+ abi: ERC20_ABI,
308
+ functionName: 'symbol',
309
+ }),
310
+ ])
311
+
312
+ return {
313
+ name,
314
+ symbol,
315
+ address: tokenAddress,
316
+ decimals,
188
317
  }
189
318
  }
319
+
190
320
  export const getTokenBalance = async (
191
- tokenAddress: string,
192
- walletAddress: string,
193
- provider: JsonRpcProvider
321
+ tokenAddress: Hex,
322
+ walletAddress: Hex,
323
+ client: PublicClient
194
324
  ): Promise<Balance> => {
195
- try {
196
- // Create contract instance
197
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider)
198
-
199
- // Get balance (returns BigNumber)
200
- const balance: bigint = await tokenContract.balanceOf(walletAddress)
325
+ const [balance, decimals] = await Promise.all([
326
+ client.readContract({
327
+ address: tokenAddress,
328
+ abi: ERC20_ABI,
329
+ functionName: 'balanceOf',
330
+ args: [walletAddress],
331
+ }),
332
+ client.readContract({
333
+ address: tokenAddress,
334
+ abi: ERC20_ABI,
335
+ functionName: 'decimals',
336
+ }),
337
+ ])
201
338
 
202
- // Get decimals to format the balance properly
203
- const decimals = await tokenContract.decimals()
204
- // Format balance by dividing by 10^decimals
205
- const formattedBalance = balance / (10n ** BigInt(decimals))
339
+ const formatted = balance / 10n ** BigInt(decimals)
206
340
 
207
- return {
208
- balance: new BN(balance.toString()), // Raw balance as BigNumber
209
- formatted: Number(formattedBalance.toString()),
210
- decimal: decimals
211
- }
212
- } catch (error) {
213
- console.error('Error fetching token balance:', error)
214
- throw error
341
+ return {
342
+ balance: new BN(balance.toString()),
343
+ formatted: Number(formatted),
344
+ decimal: decimals,
215
345
  }
216
346
  }
217
347
 
348
+
218
349
  /**
219
350
  * Sign, send, and confirm any EVM transaction
220
351
  */
221
352
  export const signSendAndConfirm = async (
222
- wallet: Wallet, // Connected wallet (private key + provider)
223
- transactionParams: TransactionParams,
224
- confirmations: number = 1, // Number of confirmations to wait for
225
- timeout: number = 300000 // 5 minutes timeout
353
+ walletClient: WalletClient,
354
+ publicClient: PublicClient,
355
+ params: {
356
+ to: Hex
357
+ data?: Hex
358
+ value?: bigint
359
+ gas?: bigint
360
+ nonce?: number
361
+ maxFeePerGas?: bigint
362
+ maxPriorityFeePerGas?: bigint
363
+ },
364
+ confirmations = 1
226
365
  ): Promise<TransactionResult> => {
227
- try {
228
- // Prepare transaction object
229
- const tx: TransactionRequest = {
230
- to: transactionParams.to,
231
- value: transactionParams.value || 0,
232
- data: transactionParams.data || '0x',
233
- }
234
-
235
- // Set gas parameters
236
- if (transactionParams.gasLimit) {
237
- tx.gasLimit = transactionParams.gasLimit
238
- }
239
-
240
- // Handle gas pricing (EIP-1559 vs legacy)
241
- if (transactionParams.maxFeePerGas && transactionParams.maxPriorityFeePerGas) {
242
- // EIP-1559 transaction
243
- tx.maxFeePerGas = transactionParams.maxFeePerGas
244
- tx.maxPriorityFeePerGas = transactionParams.maxPriorityFeePerGas
245
- } else if (transactionParams.gasPrice) {
246
- // Legacy transaction
247
- tx.gasPrice = transactionParams.gasPrice
248
- }
249
-
250
- // Set nonce if provided
251
- if (transactionParams.nonce !== undefined) {
252
- tx.nonce = transactionParams.nonce
253
- }
254
-
255
- // Estimate gas if not provided
256
- if (!tx.gasLimit) {
257
- tx.gasLimit = await wallet.estimateGas(tx)
258
- }
259
366
 
260
- console.log('Sending transaction:', tx)
261
-
262
- // Sign and send transaction
263
- const txResponse: TransactionResponse = await wallet.sendTransaction(tx)
264
-
265
- console.log(`Transaction sent with hash: ${txResponse.hash}`)
266
-
267
- // Wait for confirmation with timeout
268
- const receipt = await Promise.race([
269
- txResponse.wait(confirmations),
270
- new Promise<null>((_, reject) =>
271
- setTimeout(() => reject(new Error('Transaction confirmation timeout')), timeout)
272
- )
273
- ])
274
-
275
- const result: TransactionResult = {
276
- hash: txResponse.hash,
277
- receipt: receipt,
278
- success: receipt?.status === 1,
279
- gasUsed: receipt?.gasUsed,
280
- effectiveGasPrice: receipt?.gasPrice,
281
- blockNumber: receipt?.blockNumber,
282
- confirmations: confirmations
283
- }
367
+ if (walletClient.account === undefined) {
368
+ throw new Error("wallet Client is not Initialized with an Account")
369
+ }
370
+ const hash = await walletClient.sendTransaction({
371
+ to: params.to,
372
+ data: params.data,
373
+ value: params.value,
374
+ gas: params.gas,
375
+ nonce: params.nonce,
376
+ maxFeePerGas: params.maxFeePerGas,
377
+ maxPriorityFeePerGas: params.maxPriorityFeePerGas,
378
+ account: walletClient.account,
379
+ chain: publicClient.chain
284
380
 
285
- console.log(`Transaction ${result.success ? 'successful' : 'failed'}:`, result)
381
+ })
286
382
 
287
- return result
383
+ const receipt = await publicClient.waitForTransactionReceipt({
384
+ hash,
385
+ confirmations,
386
+ })
288
387
 
289
- } catch (error) {
290
- console.error('Transaction failed:', error)
291
- throw error
388
+ return {
389
+ hash,
390
+ receipt: viemReceiptToEthersReceipt(receipt),
391
+ viemReceipt: receipt,
392
+ success: receipt.status === 'success',
393
+ gasUsed: receipt.gasUsed,
394
+ effectiveGasPrice: receipt.effectiveGasPrice,
395
+ blockNumber: Number(receipt.blockNumber.toString()),
396
+ confirmations,
292
397
  }
293
398
  }
294
399
 
400
+
295
401
  /**
296
402
  * Send native token (ETH, BNB, MATIC, etc.)
297
403
  */
298
404
  export const sendNativeToken = async (
299
- wallet: Wallet,
300
- to: string,
301
- amount: string | bigint, // Amount in wei
302
- gasLimit?: string | bigint,
303
- confirmations: number = 1
304
- ): Promise<TransactionResult> => {
305
- return await signSendAndConfirm(
306
- wallet,
307
- {
308
- to,
309
- value: (Number(amount) * 10 ** 18).toString(),
310
- gasLimit
311
- },
405
+ walletClient: WalletClient,
406
+ publicClient: PublicClient,
407
+ to: Hex,
408
+ amount: string | bigint,
409
+ confirmations = 1
410
+ ) => {
411
+ const value =
412
+ typeof amount === 'string' ? parseEther(amount) : amount
413
+
414
+ return signSendAndConfirm(
415
+ walletClient,
416
+ publicClient,
417
+ { to, value },
312
418
  confirmations
313
419
  )
314
420
  }
315
421
 
422
+
316
423
  /**
317
424
  * Send ERC-20 token
318
425
  */
319
426
  export const sendERC20Token = async (
320
- wallet: Wallet,
321
- tokenAddress: string,
322
- to: string,
323
- amount: string | bigint, // Amount in token's smallest unit
324
- gasLimit?: string | bigint,
325
- confirmations: number = 1
326
- ): Promise<TransactionResult> => {
327
- try {
328
- // Create contract instance
329
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet)
330
-
331
- // Encode transfer function call
332
- const data = tokenContract.interface.encodeFunctionData('transfer', [to, amount])
333
-
334
- return await signSendAndConfirm(
335
- wallet,
336
- {
337
- to: tokenAddress,
338
- data,
339
- gasLimit
340
- },
341
- confirmations
342
- )
343
- } catch (error) {
344
- console.error('ERC-20 transfer failed:', error)
345
- throw error
346
- }
347
- }
427
+ walletClient: WalletClient,
428
+ publicClient: PublicClient,
429
+ tokenAddress: Hex,
430
+ to: Hex,
431
+ amount: bigint,
432
+ confirmations = 1
433
+ ) => {
434
+ const data = encodeFunctionData({
435
+ abi: ERC20_ABI,
436
+ functionName: 'transfer',
437
+ args: [to, amount],
438
+ })
348
439
 
349
- /**
350
- * Execute any contract method
351
- */
352
- export const executeContractMethod = async (
353
- wallet: Wallet,
354
- contractAddress: string,
355
- abi: any[],
356
- methodName: string,
357
- methodParams: any[] = [],
358
- value?: string | bigint, // For payable methods
359
- gasLimit?: string | bigint,
360
- confirmations: number = 1
361
- ): Promise<TransactionResult> => {
362
- try {
363
- // Create contract instance
364
- const contract = new Contract(contractAddress, abi, wallet)
365
-
366
- // Encode method call
367
- const data = contract.interface.encodeFunctionData(methodName, methodParams)
368
-
369
- return await signSendAndConfirm(
370
- wallet,
371
- {
372
- to: contractAddress,
373
- data,
374
- value,
375
- gasLimit
376
- },
377
- confirmations
378
- )
379
- } catch (error) {
380
- console.error('Contract method execution failed:', error)
381
- throw error
382
- }
440
+ return signSendAndConfirm(
441
+ walletClient,
442
+ publicClient,
443
+ { to: tokenAddress, data },
444
+ confirmations
445
+ )
383
446
  }
384
447
 
448
+
449
+
450
+
385
451
  /**
386
452
  * Get current gas prices (both legacy and EIP-1559)
387
453
  */
388
- export const getGasPrices = async (provider: JsonRpcProvider) => {
389
- try {
390
- const feeData = await provider.getFeeData()
391
-
392
- return {
393
- // Legacy
394
- gasPrice: feeData.gasPrice,
395
- // EIP-1559
396
- maxFeePerGas: feeData.maxFeePerGas,
397
- maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
398
- }
399
- } catch (error) {
400
- console.error('Error fetching gas prices:', error)
401
- throw error
454
+ export const getGasPrices = async (client: PublicClient) => {
455
+ const fees = await client.estimateFeesPerGas()
456
+ return {
457
+ gasPrice: fees.gasPrice,
458
+ maxFeePerGas: fees.maxFeePerGas,
459
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
402
460
  }
403
461
  }
404
462
 
463
+
405
464
  /**
406
465
  * Estimate gas for a transaction
407
466
  */
408
467
  export const estimateGas = async (
409
- provider: JsonRpcProvider,
410
- transactionParams: TransactionParams
411
- ): Promise<bigint> => {
412
- try {
413
- const tx: TransactionRequest = {
414
- to: transactionParams.to,
415
- value: transactionParams.value || 0,
416
- data: transactionParams.data || '0x'
417
- }
418
-
419
- return await provider.estimateGas(tx)
420
- } catch (error) {
421
- console.error('Gas estimation failed:', error)
422
- throw error
468
+ client: PublicClient,
469
+ params: {
470
+ to: Hex
471
+ data?: Hex
472
+ value?: bigint
423
473
  }
474
+ ) => {
475
+ return client.estimateGas(params)
424
476
  }
425
477
 
426
478
 
427
- /**
428
- * Check ERC-20 token allowance
429
- */
430
479
  export const checkAllowance = async (
431
- tokenAddress: string,
432
- owner: string,
433
- spender: string,
434
- provider: JsonRpcProvider
435
- ): Promise<{
436
- allowance: bigint,
437
- formatted: string,
438
- decimals: number
439
- }> => {
440
- try {
441
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider)
442
-
443
- // Get allowance and decimals
444
- const [allowance, decimals] = await Promise.all([
445
- tokenContract.allowance(owner, spender),
446
- tokenContract.decimals()
447
- ])
448
-
449
- // Format allowance for display
450
- const formattedAllowance = allowance / (10n ** BigInt(decimals))
480
+ client: PublicClient,
481
+ tokenAddress: Hex,
482
+ owner: Hex,
483
+ spender: Hex,
484
+ ) => {
485
+ const [allowance, decimals] = await Promise.all([
486
+ client.readContract({
487
+ address: tokenAddress,
488
+ abi: ERC20_ABI,
489
+ functionName: 'allowance',
490
+ args: [owner, spender],
491
+ }),
492
+ client.readContract({
493
+ address: tokenAddress,
494
+ abi: ERC20_ABI,
495
+ functionName: 'decimals',
496
+ }),
497
+ ])
451
498
 
452
- return {
453
- allowance,
454
- formatted: formattedAllowance.toString(),
455
- decimals
456
- }
457
- } catch (error) {
458
- console.error('Error checking allowance:', error)
459
- throw error
499
+ return {
500
+ allowance,
501
+ formatted: (allowance / 10n ** BigInt(decimals)).toString(),
502
+ decimals,
460
503
  }
461
504
  }
462
505
 
506
+
463
507
  /**
464
508
  * Check if allowance is sufficient for a transaction
465
509
  */
466
510
  export const isAllowanceSufficient = async (
467
- tokenAddress: string,
468
- owner: string,
469
- spender: string,
511
+ publicClient: PublicClient,
512
+ tokenAddress: `0x${string}`,
513
+ owner: `0x${string}`,
514
+ spender: `0x${string}`,
470
515
  requiredAmount: string | bigint,
471
- provider: JsonRpcProvider
472
- ): Promise<boolean> => {
473
- try {
474
- const { allowance } = await checkAllowance(tokenAddress, owner, spender, provider)
475
- const required = typeof requiredAmount === 'string' ? BigInt(requiredAmount) : requiredAmount
516
+ ) => {
517
+ const { allowance } = await checkAllowance(
518
+ publicClient,
519
+ tokenAddress,
520
+ owner,
521
+ spender,
522
+ )
476
523
 
477
- return allowance >= required
478
- } catch (error) {
479
- console.error('Error checking allowance sufficiency:', error)
480
- throw error
481
- }
524
+ return allowance >= BigInt(requiredAmount)
482
525
  }
483
526
 
527
+
484
528
  /**
485
529
  * Approve ERC-20 token spending
486
530
  */
487
531
  export const approveToken = async (
488
- wallet: Wallet,
489
- tokenAddress: string,
490
- spender: string,
491
- amount: string | bigint, // Amount to approve, use MaxUint256 for unlimited
492
- gasLimit?: string | bigint,
493
- confirmations: number = 1
494
- ): Promise<TransactionResult> => {
495
- try {
496
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet)
497
-
498
- // Encode approve function call
499
- const data = tokenContract.interface.encodeFunctionData('approve', [spender, amount])
532
+ walletClient: WalletClient,
533
+ publicClient: PublicClient,
534
+ tokenAddress: Hex,
535
+ spender: Hex,
536
+ amount: bigint,
537
+ confirmations = 1
538
+ ) => {
539
+ const data = encodeFunctionData({
540
+ abi: ERC20_ABI,
541
+ functionName: 'approve',
542
+ args: [spender, amount],
543
+ })
500
544
 
501
- return await signSendAndConfirm(
502
- wallet,
503
- {
504
- to: tokenAddress,
505
- data,
506
- gasLimit
507
- },
508
- confirmations
509
- )
510
- } catch (error) {
511
- console.error('Token approval failed:', error)
512
- throw error
513
- }
545
+ return signSendAndConfirm(
546
+ walletClient,
547
+ publicClient,
548
+ { to: tokenAddress, data },
549
+ confirmations
550
+ )
514
551
  }
515
552
 
553
+ const MAX_UINT256 =
554
+ 2n ** 256n - 1n
555
+
516
556
  /**
517
557
  * Approve unlimited token spending (MaxUint256)
518
558
  */
519
559
  export const approveTokenUnlimited = async (
520
- wallet: Wallet,
521
- tokenAddress: string,
522
- spender: string,
523
- gasLimit?: string | bigint,
524
- confirmations: number = 1
525
- ): Promise<TransactionResult> => {
526
- // MaxUint256 = 2^256 - 1
527
- const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935"
528
-
529
- return await approveToken(wallet, tokenAddress, spender, MAX_UINT256, gasLimit, confirmations)
560
+ walletClient: WalletClient,
561
+ publicClient: PublicClient,
562
+ tokenAddress: `0x${string}`,
563
+ spender: `0x${string}`,
564
+ gas?: bigint,
565
+ confirmations = 1,
566
+ ) => {
567
+ return approveToken(
568
+ walletClient,
569
+ publicClient,
570
+ tokenAddress,
571
+ spender,
572
+ MAX_UINT256,
573
+ confirmations,
574
+ )
530
575
  }
531
576
 
532
577
  /**
533
578
  * Check allowance and approve if necessary
534
579
  */
535
580
  export const checkAndApprove = async (
536
- wallet: Wallet,
537
- tokenAddress: string,
538
- spender: string,
539
- requiredAmount: string | bigint,
540
- approvalAmount?: string | bigint, // If not provided, will approve exactly the required amount
541
- gasLimit?: string | bigint,
542
- confirmations: number = 1
581
+ walletClient: WalletClient,
582
+ publicClient: PublicClient,
583
+ tokenAddress: `0x${string}`,
584
+ spender: `0x${string}`,
585
+ requiredAmount: bigint,
586
+ approvalAmount?: bigint,
587
+ gas?: bigint,
588
+ confirmations = 1,
543
589
  ): Promise<{
544
- approvalNeeded: boolean,
545
- currentAllowance: bigint,
590
+ approvalNeeded: boolean
591
+ currentAllowance: bigint
546
592
  approvalResult?: TransactionResult
547
593
  }> => {
548
- try {
549
- const owner = await wallet.getAddress()
550
- const provider = wallet.provider as JsonRpcProvider
551
-
552
- // Check current allowance
553
- const { allowance } = await checkAllowance(tokenAddress, owner, spender, provider)
554
- const required = typeof requiredAmount === 'string' ? BigInt(requiredAmount) : requiredAmount
594
+ const owner = walletClient.account!.address
555
595
 
556
- const approvalNeeded = allowance < required
596
+ const allowance = await publicClient.readContract({
597
+ address: tokenAddress,
598
+ abi: ERC20_ABI,
599
+ functionName: 'allowance',
600
+ args: [owner, spender],
601
+ })
557
602
 
558
- if (!approvalNeeded) {
559
- return {
560
- approvalNeeded: false,
561
- currentAllowance: allowance
562
- }
603
+ if (allowance >= requiredAmount) {
604
+ return {
605
+ approvalNeeded: false,
606
+ currentAllowance: allowance,
563
607
  }
608
+ }
564
609
 
565
- console.log(`Approval needed. Current: ${allowance}, Required: ${required}`)
610
+ const amountToApprove = approvalAmount ?? requiredAmount
566
611
 
567
- // Determine approval amount
568
- const amountToApprove = approvalAmount || required
612
+ const approvalResult = await approveToken(
613
+ walletClient,
614
+ publicClient,
615
+ tokenAddress,
616
+ spender,
617
+ amountToApprove,
569
618
 
570
- // Execute approval
571
- const approvalResult = await approveToken(
572
- wallet,
573
- tokenAddress,
574
- spender,
575
- amountToApprove,
576
- gasLimit,
577
- confirmations
578
- )
619
+ confirmations,
620
+ )
579
621
 
580
- return {
581
- approvalNeeded: true,
582
- currentAllowance: allowance,
583
- approvalResult
584
- }
585
- } catch (error) {
586
- console.error('Check and approve failed:', error)
587
- throw error
622
+ return {
623
+ approvalNeeded: true,
624
+ currentAllowance: allowance,
625
+ approvalResult,
588
626
  }
589
627
  }
590
628
 
629
+
591
630
  /**
592
631
  * Reset token allowance to zero (security best practice before setting new allowance)
593
632
  */
594
633
  export const resetAllowance = async (
595
- wallet: Wallet,
596
- tokenAddress: string,
597
- spender: string,
598
- gasLimit?: string | bigint,
599
- confirmations: number = 1
600
- ): Promise<TransactionResult> => {
601
- return await approveToken(wallet, tokenAddress, spender, "0", gasLimit, confirmations)
634
+ walletClient: WalletClient,
635
+ publicClient: PublicClient,
636
+ tokenAddress: `0x${string}`,
637
+ spender: `0x${string}`,
638
+ gas?: bigint,
639
+ confirmations = 1,
640
+ ) => {
641
+ return approveToken(
642
+ walletClient,
643
+ publicClient,
644
+ tokenAddress,
645
+ spender,
646
+ 0n,
647
+ confirmations,
648
+ )
602
649
  }
603
650
 
651
+
604
652
  /**
605
653
  * Safe approve: Reset to zero first, then approve the desired amount
606
654
  * (Some tokens like USDT require this)
607
655
  */
608
656
  export const safeApprove = async (
609
- wallet: Wallet,
610
- tokenAddress: string,
611
- spender: string,
612
- amount: string | bigint,
613
- gasLimit?: string | bigint,
614
- confirmations: number = 1
657
+ walletClient: WalletClient,
658
+ publicClient: PublicClient,
659
+ tokenAddress: `0x${string}`,
660
+ spender: `0x${string}`,
661
+ amount: bigint,
662
+ gas?: bigint,
663
+ confirmations = 1,
615
664
  ): Promise<{
616
- resetResult: TransactionResult,
665
+ resetResult: TransactionResult
617
666
  approveResult: TransactionResult
618
667
  }> => {
619
- try {
620
- console.log('Performing safe approve: reset then approve')
668
+ const resetResult = await resetAllowance(
669
+ walletClient,
670
+ publicClient,
671
+ tokenAddress,
672
+ spender,
673
+ gas,
674
+ confirmations,
675
+ )
621
676
 
622
- // First reset to zero
623
- const resetResult = await resetAllowance(wallet, tokenAddress, spender, gasLimit, confirmations)
677
+ if (!resetResult.success) {
678
+ throw new Error('Failed to reset allowance')
679
+ }
624
680
 
625
- if (!resetResult.success) {
626
- throw new Error('Failed to reset allowance to zero')
627
- }
681
+ const approveResult = await approveToken(
682
+ walletClient,
683
+ publicClient,
684
+ tokenAddress,
685
+ spender,
686
+ amount,
628
687
 
629
- // Then approve the desired amount
630
- const approveResult = await approveToken(wallet, tokenAddress, spender, amount, gasLimit, confirmations)
688
+ confirmations,
689
+ )
631
690
 
632
- return {
633
- resetResult,
634
- approveResult
635
- }
636
- } catch (error) {
637
- console.error('Safe approve failed:', error)
638
- throw error
691
+ return {
692
+ resetResult,
693
+ approveResult,
639
694
  }
640
695
  }
641
696
 
697
+
642
698
  export const discoverTokens = async (wallet: string, chain: ChainWalletConfig): Promise<UserTokenBalance<string>[]> => {
643
699
  const balances = await HelperAPI.getUserToken(wallet, chain.vmType ?? "EVM", chain.chainId)
644
700
 
@@ -676,287 +732,7 @@ export function calcTokenAmount(value: number | string | BigNumber, decimals?: n
676
732
  const divisor = new BigNumber(10).pow(decimals ?? 0);
677
733
  return new BigNumber(String(value)).div(divisor);
678
734
  }
679
- //swaps
680
-
681
-
682
-
683
- //kyber swap here
684
- //docs -. https://docs.kyberswap.com/kyberswap-solutions/kyberswap-aggregator/developer-guides/execute-a-swap-with-the-aggregator-api
685
- // the major constrain is that each function should return a transaction to sign, do not sign transaction or send transaction within util functions
686
- // let the ChainWalletClass be the one to sign and send,
687
- //so in you chainWallet.swap, you can have the futil swap function to get the transaction then another function to sign and send and confirm the transaction
688
-
689
-
690
-
691
- export async function getKyberSwapRoute(params: KyberSwapParams): Promise<KyberSwapResponse> {
692
- const chainName = KYBER_SUPPORTED_CHAINS[params.chainId];
693
- if (!chainName) {
694
- throw new Error(`Unsupported chain ID: ${params.chainId}`);
695
- }
696
-
697
- const queryParams = new URLSearchParams({
698
- tokenIn: params.tokenIn,
699
- tokenOut: params.tokenOut,
700
- amountIn: params.amountIn,
701
- });
702
-
703
- if (params.feeAmount) queryParams.append('feeAmount', params.feeAmount);
704
- if (params.feeReceiver) queryParams.append('feeReceiver', params.feeReceiver);
705
- if (params.isInBps !== undefined) queryParams.append('isInBps', params.isInBps.toString());
706
- if (params.chargeFeeBy) queryParams.append('chargeFeeBy', params.chargeFeeBy);
707
-
708
- const url = `${KYBER_BASE_URL}/${chainName}/api/v1/routes?${queryParams}`;
709
-
710
- const headers: { [key: string]: string } = {};
711
- if (params.clientId) {
712
- headers['x-client-id'] = params.clientId;
713
- }
714
-
715
- try {
716
- const response = await fetch(url, { headers });
717
- const data = await response.json();
718
-
719
- if (!response.ok) {
720
- throw new Error(`KyberSwap API error: ${data.message || response.statusText}`);
721
- }
722
-
723
- return data;
724
- } catch (error) {
725
- console.error('Error fetching KyberSwap route:', error);
726
- throw error;
727
- }
728
- }
729
-
730
-
731
- export async function buildKyberSwapTransaction(
732
- chainId: string,
733
- routeSummary: KyberRoute,
734
- sender: string,
735
- recipient: string,
736
- slippageTolerance: number = 50,
737
- deadline?: number,
738
- clientId?: string
739
- ): Promise<KyberBuildResponse> {
740
- const chainName = KYBER_SUPPORTED_CHAINS[chainId];
741
- if (!chainName) {
742
- throw new Error(`Unsupported chain ID: ${chainId}`);
743
- }
744
-
745
- const url = `${KYBER_BASE_URL}/${chainName}/api/v1/route/build`;
746
-
747
- const txDeadline = deadline || Math.floor(Date.now() / 1000) + 1200;
748
-
749
- const body = {
750
- routeSummary,
751
- sender,
752
- recipient,
753
- slippageTolerance,
754
- deadline: txDeadline,
755
- source: clientId || 'MyWalletApp'
756
- };
757
-
758
- const headers: { [key: string]: string } = {
759
- 'Content-Type': 'application/json',
760
- };
761
-
762
- if (clientId) {
763
- headers['x-client-id'] = clientId;
764
- }
765
735
 
766
- try {
767
- const response = await fetch(url, {
768
- method: 'POST',
769
- headers,
770
- body: JSON.stringify(body)
771
- });
772
-
773
- const data = await response.json();
774
-
775
- if (!response.ok) {
776
- throw new Error(`KyberSwap build API error: ${data.message || response.statusText}`);
777
- }
778
-
779
- return data;
780
- } catch (error) {
781
- console.error('Error building KyberSwap transaction:', error);
782
- throw error;
783
- }
784
- }
785
-
786
- export async function performSwap(params: {
787
- chainId: string;
788
- tokenIn: string;
789
- tokenOut: string;
790
- amountIn: string;
791
- sender: string;
792
- recipient?: string;
793
- slippageTolerance?: number;
794
- deadline?: number;
795
- feeAmount?: string;
796
- feeReceiver?: string;
797
- isInBps?: boolean;
798
- chargeFeeBy?: 'currency_in' | 'currency_out';
799
- clientId?: string;
800
- }): Promise<TransactionParams> {
801
- if (!KYBER_SUPPORTED_CHAINS[params.chainId]) {
802
- throw new Error(`KyberSwap does not support chain ID: ${params.chainId}`);
803
- }
804
- try {
805
- console.log('Starting KyberSwap aggregation...', {
806
- tokenIn: params.tokenIn,
807
- tokenOut: params.tokenOut,
808
- amountIn: params.amountIn,
809
- chainId: params.chainId
810
- });
811
-
812
- console.log('Fetching best swap route across all DEXs...');
813
-
814
- const routeResponse = await getKyberSwapRoute({
815
- chainId: params.chainId,
816
- tokenIn: params.tokenIn,
817
- tokenOut: params.tokenOut,
818
- amountIn: params.amountIn,
819
- feeAmount: params.feeAmount,
820
- feeReceiver: params.feeReceiver,
821
- isInBps: params.isInBps,
822
- chargeFeeBy: params.chargeFeeBy,
823
- clientId: params.clientId || 'MyWalletApp'
824
- });
825
-
826
- // Debug: Log the full response to understand structure
827
- console.log('Full KyberSwap route response:', JSON.stringify(routeResponse, null, 2));
828
-
829
- if (!routeResponse.data || !routeResponse.data.routeSummary) {
830
- throw new Error('No valid route found for the swap');
831
- }
832
-
833
- const { routeSummary, routerAddress } = routeResponse.data;
834
-
835
- // Debug: Log what we actually received
836
- console.log('routeSummary keys:', Object.keys(routeSummary));
837
- console.log('routeSummary.swaps exists:', !!routeSummary.swaps);
838
-
839
- // Safe logging that handles undefined swaps
840
- console.log('✅ Best route found:', {
841
- tokenIn: routeSummary.tokenIn,
842
- tokenOut: routeSummary.tokenOut,
843
- amountIn: routeSummary.amountIn,
844
- amountOut: routeSummary.amountOut,
845
- gasEstimate: routeSummary.gas,
846
- routerAddress,
847
- // Only try to access swaps if it exists
848
- swapsCount: routeSummary.swaps ? routeSummary.swaps.length : 0,
849
- // Only extract exchange names if swaps exists and has the expected structure
850
- dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
851
- ? routeSummary.swaps.map(swap => swap?.exchange || 'unknown').filter(Boolean)
852
- : ['unknown']
853
- });
854
-
855
- console.log('Building executable transaction...');
856
-
857
- const buildResponse = await buildKyberSwapTransaction(
858
- params.chainId,
859
- routeSummary,
860
- params.sender,
861
- params.recipient || params.sender,
862
- params.slippageTolerance || 50,
863
- params.deadline,
864
- params.clientId || 'MyWalletApp'
865
- );
866
-
867
- // Debug: Log build response
868
- console.log('Build response:', JSON.stringify(buildResponse, null, 2));
869
-
870
- if (!buildResponse.data || !buildResponse.data.data) {
871
- throw new Error('Failed to build transaction data');
872
- }
873
-
874
- const { data: encodedData, gas, routerAddress: finalRouterAddress } = buildResponse.data;
875
-
876
- console.log('✅ Transaction built successfully:', {
877
- to: finalRouterAddress,
878
- dataLength: encodedData.length,
879
- gasEstimate: gas,
880
- expectedOutput: buildResponse.data.amountOut
881
- });
882
-
883
- return {
884
- to: finalRouterAddress,
885
- data: encodedData,
886
- gasLimit: gas,
887
- value: params.tokenIn.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
888
- ? params.amountIn
889
- : '0'
890
- };
891
-
892
- } catch (error) {
893
- console.error('❌ KyberSwap aggregation failed:', error);
894
-
895
- // More detailed error logging
896
- if (error instanceof Error) {
897
- console.error('Error details:', {
898
- message: error.message,
899
- stack: error.stack,
900
- name: error.name
901
- });
902
- }
903
-
904
- throw new Error(`Swap preparation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
905
- }
906
- }
907
-
908
- export function getKyberSupportedChains(): { [key: string]: string } {
909
- return { ...KYBER_SUPPORTED_CHAINS };
910
- }
911
-
912
- export function isChainSupportedByKyber(chainId: string): boolean {
913
- return chainId in KYBER_SUPPORTED_CHAINS;
914
- }
915
-
916
- export function isChainSupportedByDebonk(chainId: string): boolean {
917
- return chainId in DESERIALIZED_SUPPORTED_CHAINS;
918
- }
919
-
920
- export function getNativeTokenAddress(): string {
921
- return '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
922
- }
923
-
924
- export function formatAmountToWei(amount: string, decimals: number): string {
925
- return parseUnits(amount, decimals).toString();
926
- }
927
- export function formatAmountFromWei(amountWei: string, decimals: number): string {
928
- return formatUnits(amountWei, decimals);
929
- }
930
-
931
- export function prepareSwapParams(
932
- tokenIn: string,
933
- tokenOut: string,
934
- amountIn: string,
935
- tokenInDecimals: number,
936
- isNativeIn: boolean = false,
937
- isNativeOut: boolean = false
938
- ): {
939
- tokenInAddress: string;
940
- tokenOutAddress: string;
941
- formattedAmountIn: string;
942
- } {
943
- const tokenInAddress = isNativeIn ? getNativeTokenAddress() : tokenIn;
944
- const tokenOutAddress = isNativeOut ? getNativeTokenAddress() : tokenOut;
945
-
946
- const formattedAmountIn = amountIn.includes('.')
947
- ? formatAmountToWei(amountIn, tokenInDecimals)
948
- : amountIn;
949
-
950
- return {
951
- tokenInAddress,
952
- tokenOutAddress,
953
- formattedAmountIn
954
- };
955
- }
956
-
957
- export function convertSlippageForDebonk(slippageBps: number): number {
958
- return slippageBps / 100;
959
- }
960
736
 
961
737
  export const transformEVMNFTToUnified = (nft: EVMNFT): NFT => {
962
738
  // Extract image URL from various sources