UNPKG

@unloc-xyz/unloc-sdk

Version:
352 lines 14.6 kB
import { Keypair, PublicKey, SystemProgram, Transaction } 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"; export async function getAssociatedPoolKeys({ programId, serumProgramId, marketId, baseMint, quoteMint, }) { 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, mint, transaction, conn) { 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, wallet, transaction, signers = []) { const txid = await wallet.sendTransaction(transaction, connection, { signers, skipPreflight: true, preflightCommitment: "confirmed" }); return txid; } export function getBigNumber(num) { return num === undefined || num === null ? 0 : parseFloat(num.toString()); } export async function getMarket(conn, marketAddress, serumProgramId) { try { const marketAddressPubKey = new PublicKey(marketAddress); const market = await Market.load(conn, marketAddressPubKey, undefined, new PublicKey(serumProgramId)); return market; } catch (error) { console.log("get market err: ", error); throw error; } } export class Market extends MarketSerum { baseVault = null; quoteVault = null; requestQueue = null; eventQueue = null; bids = null; asks = null; baseLotSize = 0; quoteLotSize = 0; // private _decoded: any quoteMint = null; baseMint = null; vaultSignerNonce = null; static async load(connection, address, options = {}, programId) { 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, mint) { const { data } = throwIfNull(await connection.getAccountInfo(mint), 'mint not found'); const { decimals } = SPL_MINT_LAYOUT.decode(data); return decimals; } function throwIfNull(value, message = 'account not found') { if (value === null) { throw new Error(message); } return value; } export async function getFilteredTokenAccountsByOwner(connection, programId, mint) { // @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, txid) { let txidSuccessFlag = 0; await conn.onSignature(txid, function (_signatureResult, _context) { 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) { const connection = provider.connection; const wallet = provider.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 }) { 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 async function getVaultOwnerAndNonce(marketId, dexProgramId) { 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, }) { 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, }) { const rawTransaction = signedTransaction.serialize(); const startTime = getUnixTs(); const txid = 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)); } //# sourceMappingURL=buyback-utils.js.map