UNPKG

@unloc-xyz/unloc-sdk

Version:
522 lines (484 loc) 18.2 kB
import { Account, Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionSignature } from '@solana/web3.js' import {ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID} from "@solana/spl-token"; import * as anchor from "@project-serum/anchor"; import {DexInstructions, Market as MarketSerum, TokenInstructions,} from '@project-serum/serum'; import {Liquidity, Market as raydiumSerum, Spl, SPL_MINT_LAYOUT} from "@raydium-io/raydium-sdk"; import {LiquidityAssociatedPoolKeys} from "@raydium-io/raydium-sdk/src/liquidity" export async function getAssociatedPoolKeys({ programId, serumProgramId, marketId, baseMint, quoteMint, }: { programId: PublicKey; serumProgramId: PublicKey; marketId: PublicKey; baseMint: PublicKey; quoteMint: PublicKey; }): Promise<LiquidityAssociatedPoolKeys> { const id = await Liquidity.getAssociatedId({programId, marketId}); const lpMint = await Liquidity.getAssociatedLpMint({programId, marketId}); const {publicKey: authority, nonce} = await Liquidity.getAssociatedAuthority({programId}); const baseVault = await Liquidity.getAssociatedBaseVault({programId, marketId}); const quoteVault = await Liquidity.getAssociatedQuoteVault({programId, marketId}); const lpVault = await Liquidity.getAssociatedLpVault({programId, marketId}); const openOrders = await Liquidity.getAssociatedOpenOrders({programId, marketId}); const targetOrders = await Liquidity.getAssociatedTargetOrders({programId, marketId}); const withdrawQueue = await Liquidity.getAssociatedWithdrawQueue({programId, marketId}); const {publicKey: marketAuthority} = await raydiumSerum.getAssociatedAuthority({ programId: serumProgramId, marketId, }); return { // base id, baseMint, quoteMint, lpMint, // version version: 4, programId, // keys authority, nonce, baseVault, quoteVault, lpVault, openOrders, targetOrders, withdrawQueue, // market version marketVersion: 4, marketProgramId: serumProgramId, // market keys marketId, marketAuthority, }; } export async function createAssociatedTokenAccountIfNotExist( owner: PublicKey, mint: PublicKey, transaction: Transaction, conn: any, ) { const associatedAccount = await Spl.getAssociatedTokenAccount({mint, owner}) const payer = owner const associatedAccountInfo = await conn.getAccountInfo(associatedAccount) if (!associatedAccountInfo) { transaction.add(Spl.makeCreateAssociatedTokenAccountInstruction({ mint, associatedAccount, owner, payer }) ); } return associatedAccount } export async function sendTransaction( connection: Connection, wallet: any, transaction: Transaction, signers: Array<Account> = [] ) { const txid: TransactionSignature = await wallet.sendTransaction(transaction, connection, { signers, skipPreflight: true, preflightCommitment: "confirmed" }) return txid } export function getBigNumber(num: any) { return num === undefined || num === null ? 0 : parseFloat(num.toString()) } export async function getMarket(conn: any, marketAddress: string, serumProgramId: string): Promise<Market> { try { const marketAddressPubKey = new PublicKey(marketAddress) const market = await Market.load(conn, marketAddressPubKey, undefined, new PublicKey(serumProgramId)) return market } catch (error: any) { console.log("get market err: ", error) throw error; } } export class Market extends MarketSerum { public baseVault: PublicKey | null = null public quoteVault: PublicKey | null = null public requestQueue: PublicKey | null = null public eventQueue: PublicKey | null = null public bids: PublicKey | null = null public asks: PublicKey | null = null public baseLotSize: number = 0 public quoteLotSize: number = 0 // private _decoded: any public quoteMint: PublicKey | null = null public baseMint: PublicKey | null = null public vaultSignerNonce: Number | null = null static async load(connection: Connection, address: PublicKey, options: any = {}, programId: PublicKey) { const {owner, data} = throwIfNull(await connection.getAccountInfo(address), 'Market not found') if (!owner.equals(programId)) { throw new Error('Address not owned by program: ' + owner.toBase58()) } const decoded = this.getLayout(programId).decode(data) if (!decoded.accountFlags.initialized || !decoded.accountFlags.market || !decoded.ownAddress.equals(address)) { throw new Error('Invalid market') } const [baseMintDecimals, quoteMintDecimals] = await Promise.all([ getMintDecimals(connection, decoded.baseMint), getMintDecimals(connection, decoded.quoteMint) ]) const market = new Market(decoded, baseMintDecimals, quoteMintDecimals, options, programId) // market._decoded = decoded market.baseLotSize = decoded.baseLotSize market.quoteLotSize = decoded.quoteLotSize market.baseVault = decoded.baseVault market.quoteVault = decoded.quoteVault market.requestQueue = decoded.requestQueue market.eventQueue = decoded.eventQueue market.bids = decoded.bids market.asks = decoded.asks market.quoteMint = decoded.quoteMint market.baseMint = decoded.baseMint market.vaultSignerNonce = decoded.vaultSignerNonce return market } } export async function getMintDecimals(connection: Connection, mint: PublicKey): Promise<number> { const {data} = throwIfNull(await connection.getAccountInfo(mint), 'mint not found') const {decimals} = SPL_MINT_LAYOUT.decode(data) return decimals } function throwIfNull<T>(value: T | null, message = 'account not found'): T { if (value === null) { throw new Error(message) } return value } export async function getFilteredTokenAccountsByOwner( connection: Connection, programId: PublicKey, mint: PublicKey ): Promise<{ context: {}; value: [] }> { // @ts-ignore const resp = await connection._rpcRequest('getTokenAccountsByOwner', [ programId.toBase58(), { mint: mint.toBase58() }, { encoding: 'jsonParsed' } ]) if (resp.error) { throw new Error(resp.error.message) } return resp.result } export async function checkTxid(conn: Connection, txid: any) { let txidSuccessFlag = 0 await conn.onSignature(txid, function (_signatureResult: any, _context: any) { if (_signatureResult.err) { txidSuccessFlag = -1 } else { txidSuccessFlag = 1 } }) const timeAwait = new Date().getTime() let outOfWhile = false while (!outOfWhile) { console.log('wait txid:', txid, outOfWhile, txidSuccessFlag, (new Date().getTime() - timeAwait) / 1000) if (txidSuccessFlag !== 0) { outOfWhile = true } await new Promise((resolve) => setTimeout(resolve, 1000)) } if (txidSuccessFlag !== 1) { throw new Error('Transaction failed') } } export async function createMintPair(provider: anchor.AnchorProvider) { const connection = provider.connection const wallet = (provider as any).wallet const tokenA = await Token.createMint( connection, wallet.payer, wallet.publicKey, null, 9, TOKEN_PROGRAM_ID ); const tokenB = await Token.createMint( connection, wallet.payer, wallet.publicKey, null, 9, TOKEN_PROGRAM_ID ); const tokenAMintAddress = tokenA.publicKey const tokenBMintAddress = tokenB.publicKey const associatedTokenA = await Token.getAssociatedTokenAddress(ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, tokenAMintAddress, wallet.publicKey, true); const associatedTokenB = await Token.getAssociatedTokenAddress(ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, tokenBMintAddress, wallet.publicKey, true); const tx1 = new Transaction(); tx1.add( Token.createAssociatedTokenAccountInstruction(ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, tokenAMintAddress, associatedTokenA, wallet.publicKey, wallet.publicKey), Token.createAssociatedTokenAccountInstruction(ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, tokenBMintAddress, associatedTokenB, wallet.publicKey, wallet.publicKey), ); let txid = await provider.sendAndConfirm(tx1) sleep(5000) const tx2 = new Transaction(); tx2.add( Token.createMintToInstruction(TOKEN_PROGRAM_ID, tokenAMintAddress, associatedTokenA, wallet.publicKey, [wallet.payer], 100000000000000), Token.createMintToInstruction(TOKEN_PROGRAM_ID, tokenBMintAddress, associatedTokenB, wallet.publicKey, [wallet.payer], 200000000000000) ) txid = await provider.sendAndConfirm(tx2) console.log("create tokenA: ", tokenA.publicKey.toString(), " tokenB: ", tokenBMintAddress.toString(), "mint txid: ", txid) sleep(5000) return {tokenAMintAddress, tokenBMintAddress} } export async function createSerumMarket({ connection, wallet, baseMint, quoteMint, baseLotSize, quoteLotSize, dexProgram, market }: { connection: Connection; wallet: anchor.Wallet; baseMint: PublicKey; quoteMint: PublicKey; baseLotSize: number; quoteLotSize: number; dexProgram: PublicKey; market: Keypair; }): Promise<SerumMarketInfo> { const requestQueue = new Keypair(); const eventQueue = new Keypair(); const bids = new Keypair(); const asks = new Keypair(); const baseVault = new Keypair(); const quoteVault = new Keypair(); const feeRateBps = 0; const quoteDustThreshold = new anchor.BN(10); const {vaultOwner, vaultNonce} = await getVaultOwnerAndNonce(market.publicKey, dexProgram); const tx1 = new Transaction(); tx1.add( SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: baseVault.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(165), space: 165, programId: TokenInstructions.TOKEN_PROGRAM_ID, }), SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: quoteVault.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(165), space: 165, programId: TokenInstructions.TOKEN_PROGRAM_ID, }), TokenInstructions.initializeAccount({ account: baseVault.publicKey, mint: baseMint, owner: vaultOwner, }), TokenInstructions.initializeAccount({ account: quoteVault.publicKey, mint: quoteMint, owner: vaultOwner, }), ); const tx2 = new Transaction(); tx2.add( SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: market.publicKey, lamports: await connection.getMinimumBalanceForRentExemption( Market.getLayout(dexProgram).span, ), space: Market.getLayout(dexProgram).span, programId: dexProgram, }), SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: requestQueue.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12), space: 5120 + 12, programId: dexProgram, }), SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: eventQueue.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12), space: 262144 + 12, programId: dexProgram, }), SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: bids.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12), space: 65536 + 12, programId: dexProgram, }), SystemProgram.createAccount({ fromPubkey: wallet.publicKey, newAccountPubkey: asks.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12), space: 65536 + 12, programId: dexProgram, }), DexInstructions.initializeMarket({ market: market.publicKey, requestQueue: requestQueue.publicKey, eventQueue: eventQueue.publicKey, bids: bids.publicKey, asks: asks.publicKey, baseVault: baseVault.publicKey, quoteVault: quoteVault.publicKey, baseMint, quoteMint, baseLotSize: new anchor.BN(baseLotSize), quoteLotSize: new anchor.BN(quoteLotSize), feeRateBps, vaultSignerNonce: vaultNonce, quoteDustThreshold, programId: dexProgram, authority: undefined, }), ); const signedTransactions = await signTransactions({ transactionsAndSigners: [ {transaction: tx1, signers: [baseVault, quoteVault]}, { transaction: tx2, signers: [market, requestQueue, eventQueue, bids, asks], }, ], wallet: wallet, connection: connection, }); for (let signedTransaction of signedTransactions) { await sendSignedTransaction({ signedTransaction, connection: connection, }); } return { market: market.publicKey, requestQueue: requestQueue.publicKey, eventQueue: eventQueue.publicKey, bids: bids.publicKey, asks: asks.publicKey, baseVault: baseVault.publicKey, quoteVault: quoteVault.publicKey, baseMint, quoteMint, baseLotSize: new anchor.BN(baseLotSize), quoteLotSize: new anchor.BN(quoteLotSize), feeRateBps, vaultOwner, vaultSignerNonce: vaultNonce, quoteDustThreshold, programId: dexProgram, // authority: undefined, }; } export interface SerumMarketInfo { market: PublicKey; requestQueue: PublicKey; eventQueue: PublicKey; bids: PublicKey; asks: PublicKey; baseVault: PublicKey; quoteVault: PublicKey; baseMint: PublicKey; quoteMint: PublicKey; baseLotSize: anchor.BN; quoteLotSize: anchor.BN; feeRateBps: number; vaultOwner: PublicKey; vaultSignerNonce: anchor.BN; quoteDustThreshold: any; programId: any; } export async function getVaultOwnerAndNonce(marketId: PublicKey, dexProgramId: PublicKey) { const vaultNonce = new anchor.BN(0); while (true) { try { const vaultOwner = await PublicKey.createProgramAddress( [marketId.toBuffer(), vaultNonce.toArrayLike(Buffer, 'le', 8)], dexProgramId, ); return {vaultOwner, vaultNonce}; } catch (e) { vaultNonce.iaddn(1); } } } export async function signTransactions({ transactionsAndSigners, wallet, connection, }: { transactionsAndSigners: { transaction: Transaction; signers?: Array<Keypair>; }[]; wallet: anchor.Wallet; connection: Connection; }) { const blockhash = (await connection.getRecentBlockhash('max')).blockhash; transactionsAndSigners.forEach(({transaction, signers = []}) => { transaction.recentBlockhash = blockhash; transaction.setSigners( wallet.publicKey, ...signers.map((s) => s.publicKey), ); if (signers?.length > 0) { transaction.partialSign(...signers); } }); return await wallet.signAllTransactions( transactionsAndSigners.map(({transaction}) => transaction), ); } export async function sendSignedTransaction({ signedTransaction, connection, timeout = 15000, }: { signedTransaction: Transaction; connection: Connection; timeout?: number; }): Promise<string> { const rawTransaction = signedTransaction.serialize(); const startTime = getUnixTs(); const txid: TransactionSignature = await connection.sendRawTransaction( rawTransaction, { skipPreflight: true, }, ); console.log('Started awaiting confirmation for', txid); await sleep(timeout); console.log('Latency', txid, getUnixTs() - startTime); return txid; } export const getUnixTs = () => { return new Date().getTime() / 1000; }; export async function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }