@deserialize/multi-vm-wallet 1.0.0 → 1.0.12

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.
@@ -3,8 +3,9 @@ import { Balance } from '../types'
3
3
  import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt } from 'ethers'
4
4
  import BN from 'bn.js'
5
5
 
6
+ const KYBER_BASE_URL = 'https://aggregator-api.kyberswap.com';
6
7
 
7
- interface TransactionParams {
8
+ export interface TransactionParams {
8
9
  to: string
9
10
  value?: string | bigint // For native token transfers
10
11
  data?: string // For contract calls
@@ -25,6 +26,110 @@ interface TransactionResult {
25
26
  confirmations: number
26
27
  }
27
28
 
29
+ export interface SwapParams {
30
+ tokenIn: string;
31
+ tokenOut: string;
32
+ amountIn: string;
33
+ slippageTolerance?: number;
34
+ recipient?: string;
35
+ deadline?: number;
36
+ feeAmount?: string;
37
+ feeReceiver?: string;
38
+ isInBps?: boolean;
39
+ chargeFeeBy?: 'currency_in' | 'currency_out';
40
+ }
41
+
42
+ export interface KyberRoute {
43
+ tokenIn: string;
44
+ amountIn: string;
45
+ tokenOut: string;
46
+ amountOut: string;
47
+ gas: string;
48
+ gasPrice: string;
49
+ gasUsd: number;
50
+ amountOutUsd: string;
51
+ receivedUsd: string;
52
+ swaps: Array<{
53
+ pool: string;
54
+ tokenIn: string;
55
+ tokenOut: string;
56
+ swapAmount: string;
57
+ amountOut: string;
58
+ limitReturnAmount: string;
59
+ maxPrice: string;
60
+ exchange: string;
61
+ poolLength: number;
62
+ poolType: string;
63
+ }>;
64
+ tokens: {
65
+ [address: string]: {
66
+ address: string;
67
+ symbol: string;
68
+ name: string;
69
+ decimals: number;
70
+ price: number;
71
+ };
72
+ };
73
+ }
74
+
75
+ export interface KyberSwapResponse {
76
+ code: number;
77
+ message: string;
78
+ data: {
79
+ routeSummary: KyberRoute;
80
+ routerAddress: string;
81
+ };
82
+ }
83
+
84
+ export interface KyberBuildResponse {
85
+ code: number;
86
+ message: string;
87
+ data: {
88
+ amountIn: string;
89
+ amountInUsd: string;
90
+ amountOut: string;
91
+ amountOutUsd: string;
92
+ gas: string;
93
+ gasUsd: string;
94
+ outputChange: {
95
+ amount: string;
96
+ percent: number;
97
+ level: number;
98
+ };
99
+ data: string;
100
+ routerAddress: string;
101
+ };
102
+ }
103
+
104
+ const KYBER_SUPPORTED_CHAINS: { [key: string]: string } = {
105
+ '1': 'ethereum',
106
+ '137': 'polygon',
107
+ '56': 'bsc',
108
+ '43114': 'avalanche',
109
+ '250': 'fantom',
110
+ '42161': 'arbitrum',
111
+ '10': 'optimism',
112
+ '8453': 'base',
113
+ '324': 'zksync',
114
+ '59144': 'linea'
115
+ };
116
+
117
+ interface KyberSwapParams {
118
+ chainId: string;
119
+ tokenIn: string;
120
+ tokenOut: string;
121
+ amountIn: string;
122
+ slippageTolerance?: number; // in bips (e.g., 50 = 0.5%)
123
+ recipient?: string;
124
+ sender?: string;
125
+ deadline?: number; // Unix timestamp
126
+ feeAmount?: string;
127
+ feeReceiver?: string;
128
+ isInBps?: boolean;
129
+ chargeFeeBy?: 'currency_in' | 'currency_out';
130
+ clientId?: string;
131
+ }
132
+
28
133
  // ERC-20 ABI
29
134
  const ERC20_ABI = [
30
135
  "function balanceOf(address owner) view returns (uint256)",
@@ -502,3 +607,247 @@ export const safeApprove = async (
502
607
 
503
608
  //swaps
504
609
 
610
+
611
+
612
+ //kyber swap here
613
+ //docs -. https://docs.kyberswap.com/kyberswap-solutions/kyberswap-aggregator/developer-guides/execute-a-swap-with-the-aggregator-api
614
+ // the major constrain is that each function should return a transaction to sign, do not sign transaction or send transaction within util functions
615
+ // let the ChainWalletClass be the one to sign and send,
616
+ //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
617
+
618
+
619
+
620
+ export async function getKyberSwapRoute(params: KyberSwapParams): Promise<KyberSwapResponse> {
621
+ const chainName = KYBER_SUPPORTED_CHAINS[params.chainId];
622
+ if (!chainName) {
623
+ throw new Error(`Unsupported chain ID: ${params.chainId}`);
624
+ }
625
+
626
+ const queryParams = new URLSearchParams({
627
+ tokenIn: params.tokenIn,
628
+ tokenOut: params.tokenOut,
629
+ amountIn: params.amountIn,
630
+ });
631
+
632
+ if (params.feeAmount) queryParams.append('feeAmount', params.feeAmount);
633
+ if (params.feeReceiver) queryParams.append('feeReceiver', params.feeReceiver);
634
+ if (params.isInBps !== undefined) queryParams.append('isInBps', params.isInBps.toString());
635
+ if (params.chargeFeeBy) queryParams.append('chargeFeeBy', params.chargeFeeBy);
636
+
637
+ const url = `${KYBER_BASE_URL}/${chainName}/api/v1/routes?${queryParams}`;
638
+
639
+ const headers: { [key: string]: string } = {};
640
+ if (params.clientId) {
641
+ headers['x-client-id'] = params.clientId;
642
+ }
643
+
644
+ try {
645
+ const response = await fetch(url, { headers });
646
+ const data = await response.json();
647
+
648
+ if (!response.ok) {
649
+ throw new Error(`KyberSwap API error: ${data.message || response.statusText}`);
650
+ }
651
+
652
+ return data;
653
+ } catch (error) {
654
+ console.error('Error fetching KyberSwap route:', error);
655
+ throw error;
656
+ }
657
+ }
658
+
659
+
660
+ export async function buildKyberSwapTransaction(
661
+ chainId: string,
662
+ routeSummary: KyberRoute,
663
+ sender: string,
664
+ recipient: string,
665
+ slippageTolerance: number = 50,
666
+ deadline?: number,
667
+ clientId?: string
668
+ ): Promise<KyberBuildResponse> {
669
+ const chainName = KYBER_SUPPORTED_CHAINS[chainId];
670
+ if (!chainName) {
671
+ throw new Error(`Unsupported chain ID: ${chainId}`);
672
+ }
673
+
674
+ const url = `${KYBER_BASE_URL}/${chainName}/api/v1/route/build`;
675
+
676
+ const txDeadline = deadline || Math.floor(Date.now() / 1000) + 1200;
677
+
678
+ const body = {
679
+ routeSummary,
680
+ sender,
681
+ recipient,
682
+ slippageTolerance,
683
+ deadline: txDeadline,
684
+ source: clientId || 'MyWalletApp'
685
+ };
686
+
687
+ const headers: { [key: string]: string } = {
688
+ 'Content-Type': 'application/json',
689
+ };
690
+
691
+ if (clientId) {
692
+ headers['x-client-id'] = clientId;
693
+ }
694
+
695
+ try {
696
+ const response = await fetch(url, {
697
+ method: 'POST',
698
+ headers,
699
+ body: JSON.stringify(body)
700
+ });
701
+
702
+ const data = await response.json();
703
+
704
+ if (!response.ok) {
705
+ throw new Error(`KyberSwap build API error: ${data.message || response.statusText}`);
706
+ }
707
+
708
+ return data;
709
+ } catch (error) {
710
+ console.error('Error building KyberSwap transaction:', error);
711
+ throw error;
712
+ }
713
+ }
714
+
715
+ export async function performSwap(params: {
716
+ chainId: string;
717
+ tokenIn: string;
718
+ tokenOut: string;
719
+ amountIn: string;
720
+ sender: string;
721
+ recipient?: string;
722
+ slippageTolerance?: number;
723
+ deadline?: number;
724
+ feeAmount?: string;
725
+ feeReceiver?: string;
726
+ isInBps?: boolean;
727
+ chargeFeeBy?: 'currency_in' | 'currency_out';
728
+ clientId?: string;
729
+ }): Promise<TransactionParams> {
730
+ try {
731
+ console.log('Starting KyberSwap aggregation...', {
732
+ tokenIn: params.tokenIn,
733
+ tokenOut: params.tokenOut,
734
+ amountIn: params.amountIn,
735
+ chainId: params.chainId
736
+ });
737
+ console.log('Fetching best swap route across all DEXs...');
738
+ const routeResponse = await getKyberSwapRoute({
739
+ chainId: params.chainId,
740
+ tokenIn: params.tokenIn,
741
+ tokenOut: params.tokenOut,
742
+ amountIn: params.amountIn,
743
+ feeAmount: params.feeAmount,
744
+ feeReceiver: params.feeReceiver,
745
+ isInBps: params.isInBps,
746
+ chargeFeeBy: params.chargeFeeBy,
747
+ clientId: params.clientId || 'MyWalletApp'
748
+ });
749
+
750
+ if (!routeResponse.data || !routeResponse.data.routeSummary) {
751
+ throw new Error('No valid route found for the swap');
752
+ }
753
+
754
+ const { routeSummary, routerAddress } = routeResponse.data;
755
+
756
+ console.log('✅ Best route found:', {
757
+ tokenIn: routeSummary.tokenIn,
758
+ tokenOut: routeSummary.tokenOut,
759
+ amountIn: routeSummary.amountIn,
760
+ amountOut: routeSummary.amountOut,
761
+ gasEstimate: routeSummary.gas,
762
+ routerAddress,
763
+ dexSources: routeSummary.swaps.map(swap => swap.exchange)
764
+ });
765
+
766
+ console.log(' Building executable transaction...');
767
+ const buildResponse = await buildKyberSwapTransaction(
768
+ params.chainId,
769
+ routeSummary,
770
+ params.sender,
771
+ params.recipient || params.sender,
772
+ params.slippageTolerance || 50,
773
+ params.deadline,
774
+ params.clientId || 'MyWalletApp'
775
+ );
776
+
777
+ if (!buildResponse.data || !buildResponse.data.data) {
778
+ throw new Error('Failed to build transaction data');
779
+ }
780
+
781
+ const { data: encodedData, gas, routerAddress: finalRouterAddress } = buildResponse.data;
782
+
783
+ console.log('✅ Transaction built successfully:', {
784
+ to: finalRouterAddress,
785
+ dataLength: encodedData.length,
786
+ gasEstimate: gas,
787
+ expectedOutput: buildResponse.data.amountOut
788
+ });
789
+
790
+ return {
791
+ to: finalRouterAddress,
792
+ data: encodedData,
793
+ gasLimit: gas,
794
+ value: params.tokenIn.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
795
+ ? params.amountIn
796
+ : '0'
797
+ };
798
+
799
+ } catch (error) {
800
+ console.error('❌ KyberSwap aggregation failed:', error);
801
+ throw new Error(`Swap preparation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
802
+ }
803
+ }
804
+
805
+ export function getKyberSupportedChains(): { [key: string]: string } {
806
+ return { ...KYBER_SUPPORTED_CHAINS };
807
+ }
808
+
809
+ export function isChainSupportedByKyber(chainId: string): boolean {
810
+ return chainId in KYBER_SUPPORTED_CHAINS;
811
+ }
812
+
813
+ export function getNativeTokenAddress(): string {
814
+ return '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
815
+ }
816
+
817
+ export function formatAmountToWei(amount: string, decimals: number): string {
818
+ const amountBN = new BN(amount);
819
+ const multiplier = new BN(10).pow(new BN(decimals));
820
+ return amountBN.mul(multiplier).toString();
821
+ }
822
+
823
+ export function formatAmountFromWei(amountWei: string, decimals: number): string {
824
+ const amountBN = new BN(amountWei);
825
+ const divisor = new BN(10).pow(new BN(decimals));
826
+ return amountBN.div(divisor).toString();
827
+ }
828
+
829
+ export function prepareSwapParams(
830
+ tokenIn: string,
831
+ tokenOut: string,
832
+ amountIn: string,
833
+ tokenInDecimals: number,
834
+ isNativeIn: boolean = false,
835
+ isNativeOut: boolean = false
836
+ ): {
837
+ tokenInAddress: string;
838
+ tokenOutAddress: string;
839
+ formattedAmountIn: string;
840
+ } {
841
+ const tokenInAddress = isNativeIn ? getNativeTokenAddress() : tokenIn;
842
+ const tokenOutAddress = isNativeOut ? getNativeTokenAddress() : tokenOut;
843
+
844
+ const formattedAmountIn = amountIn.includes('.')
845
+ ? formatAmountToWei(amountIn, tokenInDecimals)
846
+ : amountIn;
847
+
848
+ return {
849
+ tokenInAddress,
850
+ tokenOutAddress,
851
+ formattedAmountIn
852
+ };
853
+ }
package/utils/index.ts CHANGED
@@ -3,4 +3,6 @@ export * from "./evm/evm"
3
3
  export * from "./types"
4
4
  export * from "./vm"
5
5
  export * from "./evm"
6
- export * from "./svm"
6
+ export * from "./svm"
7
+
8
+ export * from "./types"
package/utils/svm/svm.ts CHANGED
@@ -3,11 +3,23 @@ import { SVMDeriveChildPrivateKey } from "../bip32";
3
3
  import { VM } from "../vm";
4
4
  import { ChainWallet } from "../IChainWallet";
5
5
  import { Balance, ChainWalletConfig, TokenInfo, TransactionResult } from "../types";
6
- import { getSvmNativeBalance, getTokenBalance, getTransferNativeTransaction, getTransferTokenTransaction, signAndSendTransaction } from "./utils";
6
+ import {
7
+ getSvmNativeBalance,
8
+ getTokenBalance,
9
+ getTransferNativeTransaction,
10
+ getTransferTokenTransaction,
11
+ signAndSendTransaction,
12
+ getJupiterQuote,
13
+ buildJupiterSwapTransaction,
14
+ executeJupiterSwap,
15
+ uiAmountToBaseUnits,
16
+ validateJupiterTokens,
17
+ JupiterQuoteResponse
18
+ } from "./utils";
7
19
  import BN from "bn.js";
8
20
 
9
21
  export class SVMVM extends VM<PublicKey, Keypair, Connection> {
10
- derivationPath = "m/44'/501'/"; // Default EVM derivation path
22
+ derivationPath = "m/44'/501'/"; // Default SVM derivation path
11
23
 
12
24
  constructor(mnemonic: string) {
13
25
  super(mnemonic, "SVM");
@@ -68,8 +80,121 @@ export class SVMChainWallet extends ChainWallet<PublicKey, Keypair, Connection>
68
80
  const hash = await SVMVM.signAndSendTransaction(transaction, this.connection!, [this.privateKey]);
69
81
  return { success: true, hash }; // Placeholder
70
82
  }
71
- }
72
83
 
73
84
 
74
- //swaps jupiter swap here
85
+ async swap(fromToken: TokenInfo, toToken: PublicKey, amount: number, slippage: number = 50): Promise<TransactionResult> {
86
+ try {
87
+ if (amount <= 0) {
88
+ return {
89
+ success: false,
90
+ hash: "",
91
+ error: "Amount must be greater than 0"
92
+ };
93
+ }
94
+
95
+ if (slippage < 0 || slippage > 5000) {
96
+ return {
97
+ success: false,
98
+ hash: "",
99
+ error: "Slippage must be between 0 and 5000 basis points (0-50%)"
100
+ };
101
+ }
102
+
103
+ const fromTokenMint = new PublicKey(fromToken.address);
104
+ const toTokenMint = toToken;
105
+
106
+ const validation = await validateJupiterTokens(
107
+ fromTokenMint.toString(),
108
+ toTokenMint.toString()
109
+ );
110
+
111
+ if (!validation.valid) {
112
+ return {
113
+ success: false,
114
+ hash: "",
115
+ error: validation.message || "Token validation failed"
116
+ };
117
+ }
118
+
119
+ const baseAmount = uiAmountToBaseUnits(amount, fromToken.decimals);
120
+
121
+ const balance = await this.getTokenBalance(fromTokenMint);
122
+ if (balance.balance.lt(new BN(baseAmount))) {
123
+ return {
124
+ success: false,
125
+ hash: "",
126
+ error: "Insufficient balance for swap"
127
+ };
128
+ }
129
+
130
+ const swapResult = await executeJupiterSwap(
131
+ {
132
+ fromToken: fromTokenMint,
133
+ toToken: toTokenMint,
134
+ amount: baseAmount,
135
+ slippageBps: slippage,
136
+ userPublicKey: this.address
137
+ },
138
+ this.connection!,
139
+ this.privateKey
140
+ );
141
+
142
+ if (!swapResult.success) {
143
+ return {
144
+ success: false,
145
+ hash: "",
146
+ error: swapResult.error || "Swap failed"
147
+ };
148
+ }
75
149
 
150
+ return {
151
+ success: true,
152
+ hash: swapResult.hash || ""
153
+ };
154
+
155
+ } catch (error) {
156
+ console.error("Swap error:", error);
157
+ return {
158
+ success: false,
159
+ hash: "",
160
+ error: error instanceof Error ? error.message : "Unknown swap error occurred"
161
+ };
162
+ }
163
+ }
164
+
165
+ async getSwapQuote(fromToken: TokenInfo, toToken: PublicKey, amount: number, slippage: number = 50): Promise<{
166
+ success: boolean;
167
+ inputAmount?: string;
168
+ outputAmount?: string;
169
+ priceImpact?: string;
170
+ routePlan?: JupiterQuoteResponse['routePlan'];
171
+ slippageBps?: number;
172
+ error?: string;
173
+ }> {
174
+ try {
175
+ const fromTokenMint = new PublicKey(fromToken.address);
176
+ const baseAmount = uiAmountToBaseUnits(amount, fromToken.decimals);
177
+
178
+ const quote = await getJupiterQuote(
179
+ fromTokenMint.toString(),
180
+ toToken.toString(),
181
+ baseAmount,
182
+ slippage
183
+ );
184
+
185
+ return {
186
+ success: true,
187
+ inputAmount: quote.inAmount,
188
+ outputAmount: quote.outAmount,
189
+ priceImpact: quote.priceImpactPct,
190
+ routePlan: quote.routePlan,
191
+ slippageBps: quote.slippageBps
192
+ };
193
+ } catch (error) {
194
+ return {
195
+ success: false,
196
+ error: error instanceof Error ? error.message : "Failed to get swap quote"
197
+ };
198
+ }
199
+ }
200
+ }