@deserialize/multi-vm-wallet 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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/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
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
import { Account } from "@solana/spl-token";
|
|
2
2
|
import { Connection, Keypair, PublicKey, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
|
|
3
3
|
import { TokenInfo } from "../types";
|
|
4
|
+
export interface JupiterQuoteResponse {
|
|
5
|
+
inputMint: string;
|
|
6
|
+
inAmount: string;
|
|
7
|
+
outputMint: string;
|
|
8
|
+
outAmount: string;
|
|
9
|
+
otherAmountThreshold: string;
|
|
10
|
+
swapMode: string;
|
|
11
|
+
slippageBps: number;
|
|
12
|
+
platformFee: null | any;
|
|
13
|
+
priceImpactPct: string;
|
|
14
|
+
routePlan: RoutePlan[];
|
|
15
|
+
contextSlot: number;
|
|
16
|
+
timeTaken: number;
|
|
17
|
+
}
|
|
18
|
+
interface RoutePlan {
|
|
19
|
+
swapInfo: SwapInfo;
|
|
20
|
+
percent: number;
|
|
21
|
+
}
|
|
22
|
+
interface SwapInfo {
|
|
23
|
+
ammKey: string;
|
|
24
|
+
label: string;
|
|
25
|
+
inputMint: string;
|
|
26
|
+
outputMint: string;
|
|
27
|
+
inAmount: string;
|
|
28
|
+
outAmount: string;
|
|
29
|
+
feeAmount: string;
|
|
30
|
+
feeMint: string;
|
|
31
|
+
}
|
|
32
|
+
interface JupiterSwapResponse {
|
|
33
|
+
swapTransaction: string;
|
|
34
|
+
lastValidBlockHeight: number;
|
|
35
|
+
prioritizationFeeLamports: number;
|
|
36
|
+
}
|
|
37
|
+
interface SwapParams {
|
|
38
|
+
fromToken: PublicKey;
|
|
39
|
+
toToken: PublicKey;
|
|
40
|
+
amount: number;
|
|
41
|
+
slippageBps?: number;
|
|
42
|
+
userPublicKey: PublicKey;
|
|
43
|
+
}
|
|
44
|
+
interface SwapResult {
|
|
45
|
+
success: boolean;
|
|
46
|
+
hash?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
inputAmount?: string;
|
|
49
|
+
outputAmount?: string;
|
|
50
|
+
priceImpact?: string;
|
|
51
|
+
}
|
|
4
52
|
export declare const createV0Transaction: (connection: Connection, inX: TransactionInstruction[], signers: Keypair[], payerPubKey: PublicKey, blockHash?: string) => Promise<VersionedTransaction>;
|
|
5
53
|
export declare const createAtaAndIx: (token: PublicKey, ownerPublicKey: PublicKey, tokenProgramId: PublicKey, connection: Connection) => Promise<{
|
|
6
54
|
AtaTokenIx: any;
|
|
@@ -24,3 +72,14 @@ export declare const getTransferNativeTransaction: (from: Keypair, to: PublicKey
|
|
|
24
72
|
export declare const getTransferTokenInx: (from: PublicKey, to: PublicKey, token: TokenInfo, amount: number, connection: Connection) => Promise<TransactionInstruction[]>;
|
|
25
73
|
export declare const getTransferTokenTransaction: (from: Keypair, to: PublicKey, token: TokenInfo, amount: number, connection: Connection) => Promise<VersionedTransaction>;
|
|
26
74
|
export declare const signAndSendTransaction: (transaction: VersionedTransaction, connection: Connection, signers: Keypair[]) => Promise<string>;
|
|
75
|
+
export declare const getJupiterQuote: (inputMint: string, outputMint: string, amount: number, slippageBps?: number) => Promise<JupiterQuoteResponse>;
|
|
76
|
+
export declare const buildJupiterSwapTransaction: (quote: JupiterQuoteResponse, userPublicKey: string, prioritizationFeeLamports?: number) => Promise<JupiterSwapResponse>;
|
|
77
|
+
export declare const executeJupiterSwap: (swapParams: SwapParams, connection: Connection, payer: Keypair) => Promise<SwapResult>;
|
|
78
|
+
export declare const uiAmountToBaseUnits: (uiAmount: number, decimals: number) => number;
|
|
79
|
+
export declare const baseUnitsToUiAmount: (baseAmount: string | number, decimals: number) => number;
|
|
80
|
+
export declare const getJupiterTokenList: () => Promise<any[]>;
|
|
81
|
+
export declare const validateJupiterTokens: (inputMint: string, outputMint: string) => Promise<{
|
|
82
|
+
valid: boolean;
|
|
83
|
+
message?: string;
|
|
84
|
+
}>;
|
|
85
|
+
export {};
|
package/dist/utils/svm/utils.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
//we will write all the svm utils function here
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.signAndSendTransaction = exports.getTransferTokenTransaction = exports.getTransferTokenInx = exports.getTransferNativeTransaction = exports.getTransferNativeInx = exports.getTokenAccountAccount = exports.getTokenBalance = exports.getSvmNativeBalance = exports.getProgramIdOfToken = exports.getSureAssociatedTokenAddressAndAccount = exports.createAtaAndIx = exports.createV0Transaction = void 0;
|
|
4
|
+
exports.validateJupiterTokens = exports.getJupiterTokenList = exports.baseUnitsToUiAmount = exports.uiAmountToBaseUnits = exports.executeJupiterSwap = exports.buildJupiterSwapTransaction = exports.getJupiterQuote = exports.signAndSendTransaction = exports.getTransferTokenTransaction = exports.getTransferTokenInx = exports.getTransferNativeTransaction = exports.getTransferNativeInx = exports.getTokenAccountAccount = exports.getTokenBalance = exports.getSvmNativeBalance = exports.getProgramIdOfToken = exports.getSureAssociatedTokenAddressAndAccount = exports.createAtaAndIx = exports.createV0Transaction = void 0;
|
|
5
5
|
const spl_token_1 = require("@solana/spl-token");
|
|
6
6
|
const web3_js_1 = require("@solana/web3.js");
|
|
7
7
|
const transactionSender_1 = require("./transactionSender");
|
|
8
8
|
const bn_js_1 = require("bn.js");
|
|
9
|
+
const JUPITER_BASE_URL = 'https://lite-api.jup.ag';
|
|
9
10
|
const createV0Transaction = async (connection, inX, signers, payerPubKey, blockHash) => {
|
|
10
11
|
const blockhash = blockHash || (await connection.getLatestBlockhash()).blockhash;
|
|
11
12
|
const message = new web3_js_1.TransactionMessage({
|
|
@@ -71,7 +72,7 @@ const getProgramIdOfToken = async (owner, token, connection) => {
|
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
74
|
exports.getProgramIdOfToken = getProgramIdOfToken;
|
|
74
|
-
//get native balance
|
|
75
|
+
//get native balance
|
|
75
76
|
const getSvmNativeBalance = async (address, connection) => {
|
|
76
77
|
const balance = await connection.getBalance(address);
|
|
77
78
|
return { balance: new bn_js_1.BN(balance), formatted: balance / web3_js_1.LAMPORTS_PER_SOL, decimal: 9 };
|
|
@@ -159,3 +160,145 @@ const signAndSendTransaction = async (transaction, connection, signers) => {
|
|
|
159
160
|
return res.transaction.signatures[0];
|
|
160
161
|
};
|
|
161
162
|
exports.signAndSendTransaction = signAndSendTransaction;
|
|
163
|
+
//swap
|
|
164
|
+
//you will. use jupiter for this
|
|
165
|
+
const getJupiterQuote = async (inputMint, outputMint, amount, slippageBps = 50) => {
|
|
166
|
+
const params = new URLSearchParams({
|
|
167
|
+
inputMint,
|
|
168
|
+
outputMint,
|
|
169
|
+
amount: amount.toString(),
|
|
170
|
+
slippageBps: slippageBps.toString(),
|
|
171
|
+
onlyDirectRoutes: 'false',
|
|
172
|
+
asLegacyTransaction: 'false'
|
|
173
|
+
});
|
|
174
|
+
const response = await fetch(`${JUPITER_BASE_URL}/swap/v1/quote?${params}`);
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const error = await response.json();
|
|
177
|
+
throw new Error(`Jupiter quote failed: ${error.message || response.statusText}`);
|
|
178
|
+
}
|
|
179
|
+
return await response.json();
|
|
180
|
+
};
|
|
181
|
+
exports.getJupiterQuote = getJupiterQuote;
|
|
182
|
+
const buildJupiterSwapTransaction = async (quote, userPublicKey, prioritizationFeeLamports) => {
|
|
183
|
+
const body = {
|
|
184
|
+
quoteResponse: quote,
|
|
185
|
+
userPublicKey,
|
|
186
|
+
wrapAndUnwrapSol: true,
|
|
187
|
+
useSharedAccounts: true,
|
|
188
|
+
feeAccount: undefined,
|
|
189
|
+
trackingAccount: undefined,
|
|
190
|
+
computeUnitPriceMicroLamports: undefined,
|
|
191
|
+
prioritizationFeeLamports: prioritizationFeeLamports || 1000,
|
|
192
|
+
asLegacyTransaction: false,
|
|
193
|
+
useTokenLedger: false,
|
|
194
|
+
destinationTokenAccount: undefined,
|
|
195
|
+
dynamicComputeUnitLimit: true,
|
|
196
|
+
skipUserAccountsRpcCalls: false
|
|
197
|
+
};
|
|
198
|
+
const response = await fetch(`${JUPITER_BASE_URL}/swap/v1/swap`, {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: {
|
|
201
|
+
'Content-Type': 'application/json',
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify(body),
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
const error = await response.json();
|
|
207
|
+
throw new Error(`Jupiter swap transaction build failed: ${error.message || response.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
return await response.json();
|
|
210
|
+
};
|
|
211
|
+
exports.buildJupiterSwapTransaction = buildJupiterSwapTransaction;
|
|
212
|
+
const executeJupiterSwap = async (swapParams, connection, payer) => {
|
|
213
|
+
try {
|
|
214
|
+
console.log('Getting Jupiter quote...');
|
|
215
|
+
const quote = await (0, exports.getJupiterQuote)(swapParams.fromToken.toString(), swapParams.toToken.toString(), swapParams.amount, swapParams.slippageBps);
|
|
216
|
+
console.log('Quote received:', {
|
|
217
|
+
inputAmount: quote.inAmount,
|
|
218
|
+
outputAmount: quote.outAmount,
|
|
219
|
+
priceImpact: quote.priceImpactPct
|
|
220
|
+
});
|
|
221
|
+
console.log('Building swap transaction...');
|
|
222
|
+
const swapResponse = await (0, exports.buildJupiterSwapTransaction)(quote, swapParams.userPublicKey.toString());
|
|
223
|
+
console.log('Deserializing transaction...');
|
|
224
|
+
const swapTransactionBuf = Buffer.from(swapResponse.swapTransaction, 'base64');
|
|
225
|
+
const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
|
|
226
|
+
console.log('Signing transaction...');
|
|
227
|
+
transaction.sign([payer]);
|
|
228
|
+
console.log('Sending transaction...');
|
|
229
|
+
const blockhash = await connection.getLatestBlockhash();
|
|
230
|
+
const signature = await (0, transactionSender_1.transactionSenderAndConfirmationWaiter)({
|
|
231
|
+
connection,
|
|
232
|
+
serializedTransaction: Buffer.from(transaction.serialize()),
|
|
233
|
+
blockhashWithExpiryBlockHeight: {
|
|
234
|
+
blockhash: blockhash.blockhash,
|
|
235
|
+
lastValidBlockHeight: blockhash.lastValidBlockHeight
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
if (!signature) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: 'Transaction failed to confirm'
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
console.log('Swap successful! Signature:', signature.transaction.signatures[0]);
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
hash: signature.transaction.signatures[0],
|
|
248
|
+
inputAmount: quote.inAmount,
|
|
249
|
+
outputAmount: quote.outAmount,
|
|
250
|
+
priceImpact: quote.priceImpactPct
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error('Jupiter swap failed:', error);
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
exports.executeJupiterSwap = executeJupiterSwap;
|
|
262
|
+
const uiAmountToBaseUnits = (uiAmount, decimals) => {
|
|
263
|
+
return Math.floor(uiAmount * Math.pow(10, decimals));
|
|
264
|
+
};
|
|
265
|
+
exports.uiAmountToBaseUnits = uiAmountToBaseUnits;
|
|
266
|
+
const baseUnitsToUiAmount = (baseAmount, decimals) => {
|
|
267
|
+
return Number(baseAmount) / Math.pow(10, decimals);
|
|
268
|
+
};
|
|
269
|
+
exports.baseUnitsToUiAmount = baseUnitsToUiAmount;
|
|
270
|
+
const getJupiterTokenList = async () => {
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(`${JUPITER_BASE_URL}/tokens/v1/mints/tradable`);
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
throw new Error(`Failed to fetch token list: ${response.statusText}`);
|
|
275
|
+
}
|
|
276
|
+
return await response.json();
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error('Failed to fetch Jupiter token list:', error);
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
exports.getJupiterTokenList = getJupiterTokenList;
|
|
284
|
+
const validateJupiterTokens = async (inputMint, outputMint) => {
|
|
285
|
+
try {
|
|
286
|
+
const tokenList = await (0, exports.getJupiterTokenList)();
|
|
287
|
+
const inputSupported = tokenList.includes(inputMint);
|
|
288
|
+
const outputSupported = tokenList.includes(outputMint);
|
|
289
|
+
if (!inputSupported && !outputSupported) {
|
|
290
|
+
return { valid: false, message: 'Both input and output tokens are not supported' };
|
|
291
|
+
}
|
|
292
|
+
if (!inputSupported) {
|
|
293
|
+
return { valid: false, message: 'Input token is not supported' };
|
|
294
|
+
}
|
|
295
|
+
if (!outputSupported) {
|
|
296
|
+
return { valid: false, message: 'Output token is not supported' };
|
|
297
|
+
}
|
|
298
|
+
return { valid: true };
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
return { valid: false, message: 'Failed to validate tokens' };
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
exports.validateJupiterTokens = validateJupiterTokens;
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deserialize/multi-vm-wallet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"devDependencies": {
|
|
5
5
|
"@types/bn.js": "^5.2.0",
|
|
6
|
-
"@types/node": "^24.1
|
|
7
|
-
"@types/promise-retry": "^1.1.6"
|
|
6
|
+
"@types/node": "^24.2.1",
|
|
7
|
+
"@types/promise-retry": "^1.1.6",
|
|
8
|
+
"ts-node-dev": "^2.0.0",
|
|
9
|
+
"typescript": "^5.9.2"
|
|
8
10
|
},
|
|
9
11
|
"dependencies": {
|
|
10
12
|
"@solana/spl-token": "^0.4.13",
|
|
@@ -22,7 +24,8 @@
|
|
|
22
24
|
"types": "dist/index.d.ts",
|
|
23
25
|
"scripts": {
|
|
24
26
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
25
|
-
"build": "
|
|
27
|
+
"build": "tsc -p tsconfig.json",
|
|
28
|
+
"dev": "ts-node-dev --respawn --transpile-only utils/index.ts",
|
|
26
29
|
"publish:sdk": "npm run build && npm publish --access=public"
|
|
27
30
|
},
|
|
28
31
|
"repository": {
|
package/utils/IChainWallet.ts
CHANGED
|
@@ -20,6 +20,8 @@ export abstract class ChainWallet<AddressType, PrivateKeyType, ConnectionType> {
|
|
|
20
20
|
abstract getTokenBalance(tokenAddress: AddressType): Promise<Balance>;
|
|
21
21
|
abstract transferNative(to: AddressType, amount: number): Promise<TransactionResult>;
|
|
22
22
|
abstract transferToken(tokenAddress: TokenInfo, to: AddressType, amount: number): Promise<TransactionResult>;
|
|
23
|
+
abstract swap (tokenAddress: TokenInfo, to: AddressType, amount: number, slippage?: number): Promise<TransactionResult>;
|
|
24
|
+
|
|
23
25
|
// abstract transferNFT(contractAddress: AddressType, tokenId: string, to: string): Promise<TransactionResult>;
|
|
24
26
|
// abstract getTokenInfo(tokenAddress: string): Promise<TokenInfo>;
|
|
25
27
|
// abstract getNFTInfo(contractAddress: string, tokenId: string): Promise<NFTInfo>;
|
package/utils/evm/evm.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
*
|
|
3
3
|
* @param phrase this is the pass phrase for this vm
|
|
4
4
|
* this is a class that will be responsible for creating several evm wallets
|
|
5
5
|
*/
|
|
@@ -9,12 +9,24 @@ import { ChainWallet } from "../IChainWallet";
|
|
|
9
9
|
import { Balance, ChainWalletConfig, NFTInfo, TokenInfo, TransactionResult } from "../types";
|
|
10
10
|
import { VM } from "../vm";
|
|
11
11
|
import { ethers, JsonRpcProvider, Wallet } from "ethers";
|
|
12
|
-
import
|
|
12
|
+
import BN from "bn.js";
|
|
13
|
+
import {
|
|
14
|
+
getNativeBalance,
|
|
15
|
+
getTokenBalance,
|
|
16
|
+
sendERC20Token,
|
|
17
|
+
sendNativeToken,
|
|
18
|
+
performSwap,
|
|
19
|
+
checkAndApprove,
|
|
20
|
+
signSendAndConfirm,
|
|
21
|
+
getNativeTokenAddress,
|
|
22
|
+
prepareSwapParams,
|
|
23
|
+
formatAmountToWei,
|
|
24
|
+
isChainSupportedByKyber,
|
|
25
|
+
TransactionParams
|
|
26
|
+
} from "./utils";
|
|
13
27
|
|
|
14
28
|
export const createEvmVmPrivateKey = (phrase: string) => { }
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
|
|
18
30
|
export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
19
31
|
derivationPath = "m/44'/60'/0'/0/"; // Default EVM derivation path
|
|
20
32
|
|
|
@@ -43,7 +55,6 @@ export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider> {
|
|
46
|
-
|
|
47
58
|
constructor(config: ChainWalletConfig, privateKey: string, index: number) {
|
|
48
59
|
super(config, privateKey, index);
|
|
49
60
|
const wallet = new Wallet(privateKey);
|
|
@@ -51,9 +62,11 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
51
62
|
this.privateKey = privateKey;
|
|
52
63
|
this.connection = new JsonRpcProvider(config.rpcUrl)
|
|
53
64
|
}
|
|
65
|
+
|
|
54
66
|
getWallet(): Wallet {
|
|
55
|
-
return new Wallet(this.privateKey);
|
|
67
|
+
return new Wallet(this.privateKey, this.connection);
|
|
56
68
|
}
|
|
69
|
+
|
|
57
70
|
generateAddress(): string {
|
|
58
71
|
return this.address;
|
|
59
72
|
}
|
|
@@ -80,5 +93,212 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
80
93
|
return await sendERC20Token(wallet, tokenAddress.address, to, amount.toString(), undefined, this.config.confirmationNo || 5)
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
|
|
96
|
+
// Updated swap method signature to match base class so created another method to use it inside swap
|
|
97
|
+
async swap(
|
|
98
|
+
tokenAddress: TokenInfo,
|
|
99
|
+
to: string,
|
|
100
|
+
amount: number,
|
|
101
|
+
slippage: number = 50
|
|
102
|
+
): Promise<TransactionResult> {
|
|
103
|
+
if (amount <= 0) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
hash: "",
|
|
107
|
+
error: "Amount must be greater than 0"
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const tokenOut: TokenInfo = {
|
|
111
|
+
address: to,
|
|
112
|
+
name: '',
|
|
113
|
+
symbol: '',
|
|
114
|
+
decimals: 18
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return await this.performCompleteSwap(tokenAddress, tokenOut, amount, slippage);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async performCompleteSwap(
|
|
121
|
+
tokenIn: TokenInfo,
|
|
122
|
+
tokenOut: TokenInfo,
|
|
123
|
+
amount: number,
|
|
124
|
+
slippage: number = 50,
|
|
125
|
+
recipient?: string,
|
|
126
|
+
deadline?: number
|
|
127
|
+
): Promise<TransactionResult> {
|
|
128
|
+
try {
|
|
129
|
+
const wallet = this.getWallet();
|
|
130
|
+
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
131
|
+
|
|
132
|
+
console.log(` Starting swap on chain ${chainId}:`, {
|
|
133
|
+
from: tokenIn.symbol || tokenIn.address,
|
|
134
|
+
to: tokenOut.symbol || tokenOut.address,
|
|
135
|
+
amount: amount,
|
|
136
|
+
slippage: `${slippage / 100}%`
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!isChainSupportedByKyber(chainId)) {
|
|
140
|
+
throw new Error(`Chain ${chainId} is not supported by KyberSwap`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const isNativeIn = tokenIn.address === 'native' ||
|
|
144
|
+
tokenIn.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
145
|
+
const isNativeOut = tokenOut.address === 'native' ||
|
|
146
|
+
tokenOut.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
147
|
+
|
|
148
|
+
let tokenInDecimals = 18;
|
|
149
|
+
if (!isNativeIn && tokenIn.decimals) {
|
|
150
|
+
tokenInDecimals = tokenIn.decimals;
|
|
151
|
+
} else if (!isNativeIn) {
|
|
152
|
+
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
153
|
+
tokenInDecimals = tokenBalance.decimal;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { tokenInAddress, tokenOutAddress, formattedAmountIn } = prepareSwapParams(
|
|
157
|
+
tokenIn.address,
|
|
158
|
+
tokenOut.address,
|
|
159
|
+
amount.toString(),
|
|
160
|
+
tokenInDecimals,
|
|
161
|
+
isNativeIn,
|
|
162
|
+
isNativeOut
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
console.log('Swap parameters:', {
|
|
166
|
+
tokenInAddress,
|
|
167
|
+
tokenOutAddress,
|
|
168
|
+
formattedAmountIn,
|
|
169
|
+
tokenInDecimals
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (isNativeIn) {
|
|
173
|
+
const nativeBalance = await this.getNativeBalance();
|
|
174
|
+
const requiredAmount = new BN(formattedAmountIn);
|
|
175
|
+
if (nativeBalance.balance.lt(requiredAmount)) {
|
|
176
|
+
throw new Error(`Insufficient native balance. Required: ${amount}, Available: ${nativeBalance.formatted}`);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
180
|
+
const requiredAmount = new BN(formattedAmountIn);
|
|
181
|
+
if (tokenBalance.balance.lt(requiredAmount)) {
|
|
182
|
+
throw new Error(`Insufficient token balance. Required: ${amount}, Available: ${tokenBalance.formatted}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
84
185
|
|
|
186
|
+
const swapTx = await performSwap({
|
|
187
|
+
chainId,
|
|
188
|
+
tokenIn: tokenInAddress,
|
|
189
|
+
tokenOut: tokenOutAddress,
|
|
190
|
+
amountIn: formattedAmountIn,
|
|
191
|
+
sender: this.address,
|
|
192
|
+
recipient: recipient || this.address,
|
|
193
|
+
slippageTolerance: slippage,
|
|
194
|
+
deadline: deadline ? Math.floor(Date.now() / 1000) + deadline : Math.floor(Date.now() / 1000) + 1200, // 20 minutes default
|
|
195
|
+
clientId: 'EVMChainWallet'
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
console.log('Swap transaction prepared:', {
|
|
199
|
+
to: swapTx.to,
|
|
200
|
+
dataLength: swapTx.data?.length || 0,
|
|
201
|
+
gasLimit: swapTx.gasLimit?.toString(),
|
|
202
|
+
value: swapTx.value?.toString()
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!isNativeIn) {
|
|
206
|
+
console.log('Checking token approval...');
|
|
207
|
+
const approvalResult = await checkAndApprove(
|
|
208
|
+
wallet,
|
|
209
|
+
tokenIn.address,
|
|
210
|
+
swapTx.to,
|
|
211
|
+
formattedAmountIn,
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
this.config.confirmationNo || 1
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (approvalResult.approvalNeeded && approvalResult.approvalResult) {
|
|
218
|
+
if (!approvalResult.approvalResult.success) {
|
|
219
|
+
throw new Error('Token approval failed');
|
|
220
|
+
}
|
|
221
|
+
console.log('Token approval successful');
|
|
222
|
+
} else if (approvalResult.approvalNeeded) {
|
|
223
|
+
throw new Error('Token approval was needed but failed');
|
|
224
|
+
} else {
|
|
225
|
+
console.log('Token approval not needed - sufficient allowance');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = await signSendAndConfirm(
|
|
230
|
+
wallet,
|
|
231
|
+
{
|
|
232
|
+
to: swapTx.to,
|
|
233
|
+
data: swapTx.data,
|
|
234
|
+
value: swapTx.value || '0',
|
|
235
|
+
gasLimit: swapTx.gasLimit
|
|
236
|
+
},
|
|
237
|
+
this.config.confirmationNo || 1,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (result.success) {
|
|
241
|
+
console.log(' Swap completed successfully:', {
|
|
242
|
+
hash: result.hash,
|
|
243
|
+
gasUsed: result.gasUsed?.toString(),
|
|
244
|
+
blockNumber: result.blockNumber
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
console.log(' Swap failed:', result);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('Swap failed:', error);
|
|
254
|
+
throw new Error(`Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async getSwapQuote(
|
|
259
|
+
tokenIn: TokenInfo,
|
|
260
|
+
tokenOut: TokenInfo,
|
|
261
|
+
amount: number
|
|
262
|
+
): Promise<{
|
|
263
|
+
amountOut: string;
|
|
264
|
+
priceImpact: string;
|
|
265
|
+
gasEstimate: string;
|
|
266
|
+
route: string[];
|
|
267
|
+
}> {
|
|
268
|
+
try {
|
|
269
|
+
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
270
|
+
|
|
271
|
+
if (!isChainSupportedByKyber(chainId)) {
|
|
272
|
+
throw new Error(`Chain ${chainId} is not supported by KyberSwap`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const isNativeIn = tokenIn.address === 'native' ||
|
|
276
|
+
tokenIn.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
277
|
+
const isNativeOut = tokenOut.address === 'native' ||
|
|
278
|
+
tokenOut.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
279
|
+
|
|
280
|
+
let tokenInDecimals = 18;
|
|
281
|
+
if (!isNativeIn && tokenIn.decimals) {
|
|
282
|
+
tokenInDecimals = tokenIn.decimals;
|
|
283
|
+
} else if (!isNativeIn) {
|
|
284
|
+
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
285
|
+
tokenInDecimals = tokenBalance.decimal;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const { tokenInAddress, tokenOutAddress, formattedAmountIn } = prepareSwapParams(
|
|
289
|
+
tokenIn.address,
|
|
290
|
+
tokenOut.address,
|
|
291
|
+
amount.toString(),
|
|
292
|
+
tokenInDecimals,
|
|
293
|
+
isNativeIn,
|
|
294
|
+
isNativeOut
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
throw new Error("Quote functionality requires direct API integration - use the swap method for full execution");
|
|
298
|
+
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error('Error getting swap quote:', error);
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|