|
| 1 | +import { Connection, PublicKey, Keypair, Transaction, VersionedTransaction, TransactionMessage } from '@solana/web3.js' |
| 2 | +import { |
| 3 | + Liquidity, |
| 4 | + LiquidityPoolKeys, |
| 5 | + jsonInfo2PoolKeys, |
| 6 | + LiquidityPoolJsonInfo, |
| 7 | + TokenAccount, |
| 8 | + Token, |
| 9 | + TokenAmount, |
| 10 | + TOKEN_PROGRAM_ID, |
| 11 | + Percent, |
| 12 | + SPL_ACCOUNT_LAYOUT, |
| 13 | +} from '@raydium-io/raydium-sdk' |
| 14 | +import { Wallet } from '@project-serum/anchor' |
| 15 | +import bs58 from 'bs58' |
| 16 | +import 'dotenv/config'; |
| 17 | +import { swapConfig } from './swapConfig'; // Import the configuration |
| 18 | + |
| 19 | + |
| 20 | +class RaydiumSwap { |
| 21 | + allPoolKeysJson: LiquidityPoolJsonInfo[] |
| 22 | + connection: Connection |
| 23 | + wallet: Wallet |
| 24 | + |
| 25 | + constructor(RPC_URL: string, WALLET_PRIVATE_KEY: string) { |
| 26 | + this.connection = new Connection(process.env.RPC_URL |
| 27 | + , { commitment: 'confirmed' }) |
| 28 | + this.wallet = new Wallet(Keypair.fromSecretKey(Uint8Array.from(bs58.decode(process.env.WALLET_PRIVATE_KEY))))//base58.decode(WALLET_PRIVATE_KEY))) |
| 29 | + } |
| 30 | + |
| 31 | + async loadPoolKeys() { |
| 32 | + const liquidityJsonResp = await fetch(swapConfig.liquidityFile); //https://pastebin.com/raw/CHPMmnDh |
| 33 | + if (!liquidityJsonResp.ok) return [] |
| 34 | + const liquidityJson = (await liquidityJsonResp.json()) as { official: any; unOfficial: any } |
| 35 | + const allPoolKeysJson = [...(liquidityJson?.official ?? []), ...(liquidityJson?.unOfficial ?? [])] |
| 36 | + |
| 37 | + this.allPoolKeysJson = allPoolKeysJson |
| 38 | + } |
| 39 | + |
| 40 | + findPoolInfoForTokens(mintA: string, mintB: string) { |
| 41 | + const poolData = this.allPoolKeysJson.find( |
| 42 | + (i) => (i.baseMint === mintA && i.quoteMint === mintB) || (i.baseMint === mintB && i.quoteMint === mintA) |
| 43 | + ) |
| 44 | + |
| 45 | + if (!poolData) return null |
| 46 | + |
| 47 | + return jsonInfo2PoolKeys(poolData) as LiquidityPoolKeys |
| 48 | + } |
| 49 | + |
| 50 | + async getOwnerTokenAccounts() { |
| 51 | + const walletTokenAccount = await this.connection.getTokenAccountsByOwner(this.wallet.publicKey, { |
| 52 | + programId: TOKEN_PROGRAM_ID, |
| 53 | + }) |
| 54 | + |
| 55 | + return walletTokenAccount.value.map((i) => ({ |
| 56 | + pubkey: i.pubkey, |
| 57 | + programId: i.account.owner, |
| 58 | + accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data), |
| 59 | + })) |
| 60 | + } |
| 61 | + |
| 62 | + async getSwapTransaction( |
| 63 | + toToken: string, |
| 64 | + // fromToken: string, |
| 65 | + amount: number, |
| 66 | + poolKeys: LiquidityPoolKeys, |
| 67 | + maxLamports: number = 100000, |
| 68 | + useVersionedTransaction = true, |
| 69 | + fixedSide: 'in' | 'out' = 'in' |
| 70 | + ): Promise<Transaction | VersionedTransaction> { |
| 71 | + const directionIn = poolKeys.quoteMint.toString() == toToken |
| 72 | + const { minAmountOut, amountIn } = await this.calcAmountOut(poolKeys, amount, directionIn) |
| 73 | + console.log({ minAmountOut, amountIn }); |
| 74 | + const userTokenAccounts = await this.getOwnerTokenAccounts() |
| 75 | + const swapTransaction = await Liquidity.makeSwapInstructionSimple({ |
| 76 | + connection: this.connection, |
| 77 | + makeTxVersion: useVersionedTransaction ? 0 : 1, |
| 78 | + poolKeys: { |
| 79 | + ...poolKeys, |
| 80 | + }, |
| 81 | + userKeys: { |
| 82 | + tokenAccounts: userTokenAccounts, |
| 83 | + owner: this.wallet.publicKey, |
| 84 | + }, |
| 85 | + amountIn: amountIn, |
| 86 | + amountOut: minAmountOut, |
| 87 | + fixedSide: fixedSide, |
| 88 | + config: { |
| 89 | + bypassAssociatedCheck: false, |
| 90 | + }, |
| 91 | + computeBudgetConfig: { |
| 92 | + microLamports: maxLamports, |
| 93 | + }, |
| 94 | + }) |
| 95 | + |
| 96 | + const recentBlockhashForSwap = await this.connection.getLatestBlockhash() |
| 97 | + const instructions = swapTransaction.innerTransactions[0].instructions.filter(Boolean) |
| 98 | + |
| 99 | + if (useVersionedTransaction) { |
| 100 | + const versionedTransaction = new VersionedTransaction( |
| 101 | + new TransactionMessage({ |
| 102 | + payerKey: this.wallet.publicKey, |
| 103 | + recentBlockhash: recentBlockhashForSwap.blockhash, |
| 104 | + instructions: instructions, |
| 105 | + }).compileToV0Message() |
| 106 | + ) |
| 107 | + |
| 108 | + versionedTransaction.sign([this.wallet.payer]) |
| 109 | + |
| 110 | + return versionedTransaction |
| 111 | + } |
| 112 | + |
| 113 | + const legacyTransaction = new Transaction({ |
| 114 | + blockhash: recentBlockhashForSwap.blockhash, |
| 115 | + lastValidBlockHeight: recentBlockhashForSwap.lastValidBlockHeight, |
| 116 | + feePayer: this.wallet.publicKey, |
| 117 | + }) |
| 118 | + |
| 119 | + legacyTransaction.add(...instructions) |
| 120 | + |
| 121 | + return legacyTransaction |
| 122 | + } |
| 123 | + |
| 124 | + async sendLegacyTransaction(tx: Transaction) { |
| 125 | + const txid = await this.connection.sendTransaction(tx, [this.wallet.payer], { |
| 126 | + skipPreflight: true, |
| 127 | + maxRetries: 2, |
| 128 | + }) |
| 129 | + |
| 130 | + return txid |
| 131 | + } |
| 132 | + |
| 133 | + async sendVersionedTransaction(tx: VersionedTransaction) { |
| 134 | + const txid = await this.connection.sendTransaction(tx, { |
| 135 | + skipPreflight: true, |
| 136 | + maxRetries: 2, |
| 137 | + }) |
| 138 | + |
| 139 | + return txid |
| 140 | + } |
| 141 | + |
| 142 | + async simulateLegacyTransaction(tx: Transaction) { |
| 143 | + const txid = await this.connection.simulateTransaction(tx, [this.wallet.payer]) |
| 144 | + |
| 145 | + return txid |
| 146 | + } |
| 147 | + |
| 148 | + async simulateVersionedTransaction(tx: VersionedTransaction) { |
| 149 | + const txid = await this.connection.simulateTransaction(tx) |
| 150 | + |
| 151 | + return txid |
| 152 | + } |
| 153 | + |
| 154 | + getTokenAccountByOwnerAndMint(mint: PublicKey) { |
| 155 | + return { |
| 156 | + programId: TOKEN_PROGRAM_ID, |
| 157 | + pubkey: PublicKey.default, |
| 158 | + accountInfo: { |
| 159 | + mint: mint, |
| 160 | + amount: 0, |
| 161 | + }, |
| 162 | + } as unknown as TokenAccount |
| 163 | + } |
| 164 | + |
| 165 | + async calcAmountOut(poolKeys: LiquidityPoolKeys, rawAmountIn: number, swapInDirection: boolean) { |
| 166 | + const poolInfo = await Liquidity.fetchInfo({ connection: this.connection, poolKeys }) |
| 167 | + |
| 168 | + let currencyInMint = poolKeys.baseMint |
| 169 | + let currencyInDecimals = poolInfo.baseDecimals |
| 170 | + let currencyOutMint = poolKeys.quoteMint |
| 171 | + let currencyOutDecimals = poolInfo.quoteDecimals |
| 172 | + |
| 173 | + if (!swapInDirection) { |
| 174 | + currencyInMint = poolKeys.quoteMint |
| 175 | + currencyInDecimals = poolInfo.quoteDecimals |
| 176 | + currencyOutMint = poolKeys.baseMint |
| 177 | + currencyOutDecimals = poolInfo.baseDecimals |
| 178 | + } |
| 179 | + |
| 180 | + const currencyIn = new Token(TOKEN_PROGRAM_ID, currencyInMint, currencyInDecimals) |
| 181 | + const amountIn = new TokenAmount(currencyIn, rawAmountIn, false) |
| 182 | + const currencyOut = new Token(TOKEN_PROGRAM_ID, currencyOutMint, currencyOutDecimals) |
| 183 | + const slippage = new Percent(5, 100) // 5% slippage |
| 184 | + |
| 185 | + const { amountOut, minAmountOut, currentPrice, executionPrice, priceImpact, fee } = Liquidity.computeAmountOut({ |
| 186 | + poolKeys, |
| 187 | + poolInfo, |
| 188 | + amountIn, |
| 189 | + currencyOut, |
| 190 | + slippage, |
| 191 | + }) |
| 192 | + |
| 193 | + return { |
| 194 | + amountIn, |
| 195 | + amountOut, |
| 196 | + minAmountOut, |
| 197 | + currentPrice, |
| 198 | + executionPrice, |
| 199 | + priceImpact, |
| 200 | + fee, |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +export default RaydiumSwap |
0 commit comments