UNPKG

@symmetry-hq/liquidity-sdk

Version:

Exchange functionality using symmetry funds liquidity

189 lines (179 loc) 7.74 kB
import { BN, Program } from "@coral-xyz/anchor"; import { ComputeBudgetProgram, Connection, PublicKey, Transaction, TransactionInstruction, TransactionSignature } from "@solana/web3.js"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { FundsIDL } from "./fundsIDL"; import { TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync } from "@solana/spl-token"; import { applyBlockHashAndPartialSign, sendAndConfirmTransaction, prepareAccounts } from "./txHelpers"; import { RouteData, SWAP_FEE_ACCOUNT, TokenSettings } from "./types"; export async function generateSwapInstruction( program: Program<FundsIDL>, user: PublicKey, tokenListData: TokenSettings[], routeData: RouteData, fromTokenAccount: PublicKey, toTokenAccount: PublicKey, slippage: number = 0.5, ): Promise<TransactionInstruction> { let { fromTokenId, toTokenId, fromAmount, toAmount } = routeData; let minimumReceived = toAmount * (1 - slippage / 100); return await program.methods .liquidityProvision( new BN(fromTokenId), new BN(toTokenId), new BN( Math.floor( fromAmount * 10 ** tokenListData[fromTokenId].decimals ) ), new BN( Math.floor( minimumReceived * 10 ** tokenListData[toTokenId].decimals ) ), ) .accounts({ buyer: user, fundState: routeData.swapAccounts.fundState, pdaAccount: routeData.swapAccounts.authority, pdaFromTokenAccount: routeData.swapAccounts.source, buyerFromTokenAccount: fromTokenAccount, pdaToTokenAccount: routeData.swapAccounts.destination, buyerToTokenAccount: toTokenAccount, swapFeeAccount: getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, routeData.swapAccounts.fees.smfWallet ), hostFeeAccount: getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, routeData.swapAccounts.fees.hostWallet ), managerFeeAccount: getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, routeData.swapAccounts.fees.managerWallet ), tokenList: routeData.swapAccounts.tokenList, curveData: routeData.swapAccounts.curveData, tokenProgram: TOKEN_PROGRAM_ID }) .remainingAccounts(routeData.swapAccounts.remainingAccounts) .instruction(); } export async function executeSwap( program: Program<FundsIDL>, wallet: Wallet, connection: Connection, tokenListData: TokenSettings[], routeData: RouteData, userFromTokenAccount: PublicKey|undefined, userToTokenAccount: PublicKey|undefined, slippage: number = 0.5, ): Promise<any> { let {preTransaction, postTransaction, fromTokenAccount, toTokenAccount} = await prepareAccounts(wallet, connection, tokenListData, routeData, userFromTokenAccount, userToTokenAccount); let mainTransaction = new Transaction(); let instructions = []; let swapFeeAccount = getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, SWAP_FEE_ACCOUNT, ); const info2 = await program.provider.connection.getAccountInfo(swapFeeAccount, "confirmed"); if (!info2) { instructions.push( createAssociatedTokenAccountInstruction( wallet.publicKey, swapFeeAccount, SWAP_FEE_ACCOUNT, routeData.swapAccounts.fees.feeTokenMint, ) ); } let hostFeeAccount = getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, routeData.swapAccounts.fees.hostWallet, ); const info3 = await program.provider.connection.getAccountInfo(hostFeeAccount, "confirmed"); if (!info3 && routeData.swapAccounts.fees.hostWallet.toBase58() !== SWAP_FEE_ACCOUNT.toBase58() && routeData.swapAccounts.fees.hostWallet.toBase58() !== wallet.publicKey.toBase58()) { instructions.push( createAssociatedTokenAccountInstruction( wallet.publicKey, hostFeeAccount, routeData.swapAccounts.fees.hostWallet, routeData.swapAccounts.fees.feeTokenMint, ) ); } let managerFeeAccount = getAssociatedTokenAddressSync( routeData.swapAccounts.fees.feeTokenMint, routeData.swapAccounts.fees.managerWallet, ); const info4 = await program.provider.connection.getAccountInfo(managerFeeAccount, "confirmed"); if (!info4 && routeData.swapAccounts.fees.managerWallet.toBase58() !== SWAP_FEE_ACCOUNT.toBase58() && routeData.swapAccounts.fees.managerWallet.toBase58() !== routeData.swapAccounts.fees.hostWallet.toBase58() && routeData.swapAccounts.fees.managerWallet.toBase58() !== wallet.publicKey.toBase58()) { instructions.push( createAssociatedTokenAccountInstruction( wallet.publicKey, managerFeeAccount, routeData.swapAccounts.fees.managerWallet, routeData.swapAccounts.fees.feeTokenMint, ) ); } if (instructions.length > 0) { preTransaction.add(...instructions); } mainTransaction.add(await generateSwapInstruction( program, wallet.publicKey, tokenListData, routeData, fromTokenAccount, toTokenAccount, slippage )); if(preTransaction.instructions.length > 0) { preTransaction.add(ComputeBudgetProgram.setComputeUnitLimit({units: 1000000})); preTransaction.add(ComputeBudgetProgram.setComputeUnitPrice({microLamports: 20000})); } if(postTransaction.instructions.length > 0) { postTransaction.add(ComputeBudgetProgram.setComputeUnitLimit({units: 1000000})); postTransaction.add(ComputeBudgetProgram.setComputeUnitPrice({microLamports: 20000})); } mainTransaction.add(ComputeBudgetProgram.setComputeUnitLimit({units: 1000000})); mainTransaction.add(ComputeBudgetProgram.setComputeUnitPrice({microLamports: 20000})); const { txIndex, transactions } = await applyBlockHashAndPartialSign( connection, wallet, preTransaction, mainTransaction, postTransaction, [], [], ); const signedTxs = await wallet.signAllTransactions(transactions); let serialized: any = []; let err: any = {}; let txId; for (let i = 0; i < signedTxs.length; i++){ serialized.push(signedTxs[i].serialize()); for (let i = 0; i < serialized.length; i++) { const { signature, response } = await sendAndConfirmTransaction(connection, serialized[i]); let failed = false; if (!signature || !response || response == "Error" || response.meta.err) failed = true; if(failed && err.message == undefined){ if (i != txIndex) err.message = "Couldn't prepare accounts"; else err.message = "Swap failed"; err.txId = signature; } if(i == txIndex) txId = signature; } } if(err.message) throw new FundError(err.message, err.txId); return txId; } export class FundError extends Error { public txId: TransactionSignature; constructor (message: string, txId: TransactionSignature = "") { super(message); this.txId = txId; } }