UNPKG

@kamino-finance/kliquidity-sdk

Version:

Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol

222 lines (197 loc) 6.95 kB
import { AccountRole, address, Address, IInstruction } from '@solana/kit'; import axios from 'axios'; import Decimal from 'decimal.js'; import { QuoteResponse, SwapInstructionsResponse, createJupiterApiClient, Instruction } from '@jup-ag/api'; const USDC_MINT = address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); export const DEFAULT_JUP_API_ENDPOINT = 'https://lite-api.jup.ag'; export const DEFAULT_JUP_SWAP_API = 'https://lite-api.jup.ag/swap/v1'; const jupiterSwapApi = createJupiterApiClient({ basePath: DEFAULT_JUP_SWAP_API, }); export type SwapTransactionsResponse = { setupTransaction: string | undefined; swapTransaction: string; cleanupTransaction: string | undefined; }; interface SwapIInstructionsResponse { tokenLedgerInstruction?: IInstruction; computeBudgetInstructions: Array<IInstruction>; setupInstructions: Array<IInstruction>; swapInstruction: IInstruction; cleanupInstruction?: IInstruction; addressLookupTableAddresses: Array<Address>; } export class JupService { // the amounts has to be in lamports static getBestRouteV6 = async ( userAddress: Address, amount: Decimal, inputMint: Address, outputMint: Address, slippageBps: number, asLegacyTransaction?: boolean, maxAccounts?: number, onlyDirectRoutes?: boolean ): Promise<SwapIInstructionsResponse> => { try { // https://lite-api.jup.ag/swap/v1/quote?inputMint=7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj&outputMint=mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So&amount=71101983&slippageBps=10&onlyDirectRoutes=false&asLegacyTransaction=false&maxAccounts=33 const res = await this.getBestRouteQuoteV6( amount, inputMint, outputMint, slippageBps, asLegacyTransaction, maxAccounts, onlyDirectRoutes ); const ixsResponse = await jupiterSwapApi.swapInstructionsPost({ swapRequest: { quoteResponse: res, userPublicKey: userAddress, wrapAndUnwrapSol: false, }, }); const swapIxs: SwapIInstructionsResponse = { tokenLedgerInstruction: ixsResponse.tokenLedgerInstruction ? transformResponseIx(ixsResponse.tokenLedgerInstruction) : undefined, computeBudgetInstructions: ixsResponse.computeBudgetInstructions.map((ix) => transformResponseIx(ix)), setupInstructions: ixsResponse.setupInstructions.map((ix) => transformResponseIx(ix)), swapInstruction: transformResponseIx(ixsResponse.swapInstruction), cleanupInstruction: ixsResponse.cleanupInstruction ? transformResponseIx(ixsResponse.cleanupInstruction) : undefined, addressLookupTableAddresses: ixsResponse.addressLookupTableAddresses.map((a) => address(a)), }; return swapIxs; } catch (error) { console.log('getBestRouteV6 error', error); throw error; } }; static getBestRouteQuoteV6 = async ( amount: Decimal, inputMint: Address, outputMint: Address, slippageBps: number, asLegacyTransaction?: boolean, maxAccounts?: number, onlyDirectRoutes?: boolean, jupEndpoint?: string ): Promise<QuoteResponse> => { try { const params = { inputMint, outputMint, amount: amount.floor().toNumber(), slippageBps, onlyDirectRoutes: onlyDirectRoutes, asLegacyTransaction, maxAccounts, }; const baseURL = jupEndpoint || DEFAULT_JUP_API_ENDPOINT; const res = await axios.get(`${baseURL}/swap/v1/quote`, { params }); return res.data as QuoteResponse; } catch (error) { console.log('getBestRouteQuoteV6 error', error); throw error; } }; static getSwapIxsFromQuote = async ( userAddress: Address, quote: QuoteResponse, wrapUnwrapSOL = true, asLegacyTransaction?: boolean ): Promise<SwapInstructionsResponse> => { try { return await jupiterSwapApi.swapInstructionsPost({ swapRequest: { quoteResponse: quote, userPublicKey: userAddress, wrapAndUnwrapSol: wrapUnwrapSOL, asLegacyTransaction: asLegacyTransaction, }, }); } catch (error) { console.log('getSwapTxFromQuote error', error); throw error; } }; static getPrice = async ( inputMint: Address | string, outputMint: Address | string, jupEndpoint?: string ): Promise<number> => { const params = { ids: inputMint.toString(), vsToken: outputMint.toString(), vsAmount: 1, }; // BONK token if (outputMint.toString() === 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263') { params.vsAmount = 100; } const baseURL = jupEndpoint || DEFAULT_JUP_API_ENDPOINT; const res = await axios.get(`${baseURL}/price/v2`, { params }); return res.data.data[inputMint.toString()].price; }; static getPrices = async ( inputMints: (Address | string)[], outputMint: Address | string, jupEndpoint?: string ): Promise<Map<Address, Decimal>> => { const mintsCommaSeparated = inputMints.map((mint) => mint.toString()).join(','); const params = { ids: mintsCommaSeparated, vsToken: outputMint.toString(), vsAmount: 1, }; // BONK token if (outputMint.toString() === 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263') { params.vsAmount = 100; } const baseURL = jupEndpoint || DEFAULT_JUP_API_ENDPOINT; const prices = new Map<Address, Decimal>(); try { const res = await axios.get(`${baseURL}/price/v2`, { params }); for (const mint of inputMints) { try { prices.set(address(mint), new Decimal(res.data.data[mint.toString()].price)); } catch (e) { prices.set(address(mint), new Decimal(0)); } } } catch (e) { // ignore } return prices; }; static getDollarPrices(inputMints: (Address | string)[], jupEndpoint?: string): Promise<Map<Address, Decimal>> { return this.getPrices(inputMints, USDC_MINT, jupEndpoint); } static getDollarPrice = async (inputMint: Address | string, jupEndpoint?: string): Promise<number> => { return this.getPrice(inputMint, USDC_MINT, jupEndpoint); }; } export function transformResponseIx(ix: Instruction): IInstruction { return { data: ix.data ? Buffer.from(ix.data, 'base64') : undefined, programAddress: address(ix.programId), accounts: ix.accounts.map((k) => ({ address: address(k.pubkey), role: getAccountRole({ isSigner: k.isSigner, isMut: k.isWritable }), })), }; } export function getAccountRole({ isSigner, isMut }: { isSigner: boolean; isMut: boolean }): AccountRole { if (isSigner && isMut) { return AccountRole.WRITABLE_SIGNER; } if (isSigner && !isMut) { return AccountRole.READONLY_SIGNER; } if (!isSigner && isMut) { return AccountRole.WRITABLE; } return AccountRole.READONLY; }