@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.
- package/README.md +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/IChainWallet.d.ts +1 -0
- package/dist/utils/evm/evm.d.ts +8 -0
- package/dist/utils/evm/evm.js +150 -1
- package/dist/utils/evm/utils.d.ts +116 -1
- package/dist/utils/evm/utils.js +191 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/svm/svm.d.ts +11 -0
- package/dist/utils/svm/svm.js +85 -2
- package/dist/utils/svm/utils.d.ts +59 -0
- package/dist/utils/svm/utils.js +145 -2
- package/package.json +7 -4
- package/utils/IChainWallet.ts +2 -0
- package/utils/evm/evm.ts +227 -7
- package/utils/evm/utils.ts +350 -1
- package/utils/index.ts +3 -1
- package/utils/svm/svm.ts +129 -4
- package/utils/svm/utils.ts +241 -2
- package/utils/svm/index.js +0 -17
- package/utils/svm/svm.js +0 -71
- package/utils/svm/transactionSender.js +0 -83
- package/utils/svm/utils.js +0 -161
package/utils/evm/utils.ts
CHANGED
|
@@ -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
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 {
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|