123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- // Boilerplate utils to bootstrap an orderbook for testing on a localnet.
- // not super relevant to the point of the example, though may be useful to
- // include into your own workspace for testing.
- //
- // TODO: Modernize all these apis. This is all quite clunky.
- const Token = require("@solana/spl-token").Token;
- const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID;
- const serum = require('@project-serum/serum');
- const {
- DexInstructions,
- TokenInstructions,
- MarketProxy,
- OpenOrders,
- OpenOrdersPda,
- MARKET_STATE_LAYOUT_V3,
- } = serum;
- const anchor = require("@project-serum/anchor");
- const BN = anchor.BN;
- const web3 = anchor.web3;
- const {
- SYSVAR_RENT_PUBKEY,
- COnnection,
- Account,
- Transaction,
- PublicKey,
- SystemProgram,
- } = web3;
- const serumCmn = require("@project-serum/common");
- const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
- const MARKET_MAKER = new Account();
- async function initMarket({
- provider,
- getAuthority,
- proxyProgramId,
- marketLoader,
- }) {
- // Setup mints with initial tokens owned by the provider.
- const decimals = 6;
- const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
- provider,
- new BN("1000000000000000000"),
- undefined,
- decimals
- );
- const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
- provider,
- new BN("1000000000000000000"),
- undefined,
- decimals
- );
- // Create a funded account to act as market maker.
- const amount = new BN("10000000000000").muln(10 ** decimals);
- const marketMaker = await fundAccount({
- provider,
- mints: [
- { god: GOD_A, mint: MINT_A, amount, decimals },
- { god: GOD_USDC, mint: USDC, amount, decimals },
- ],
- });
- // Setup A/USDC with resting orders.
- const asks = [
- [6.041, 7.8],
- [6.051, 72.3],
- [6.055, 5.4],
- [6.067, 15.7],
- [6.077, 390.0],
- [6.09, 24.0],
- [6.11, 36.3],
- [6.133, 300.0],
- [6.167, 687.8],
- ];
- const bids = [
- [6.004, 8.5],
- [5.995, 12.9],
- [5.987, 6.2],
- [5.978, 15.3],
- [5.965, 82.8],
- [5.961, 25.4],
- ];
- [MARKET_A_USDC, vaultSigner] = await setupMarket({
- baseMint: MINT_A,
- quoteMint: USDC,
- marketMaker: {
- account: marketMaker.account,
- baseToken: marketMaker.tokens[MINT_A.toString()],
- quoteToken: marketMaker.tokens[USDC.toString()],
- },
- bids,
- asks,
- provider,
- getAuthority,
- proxyProgramId,
- marketLoader,
- });
- return {
- marketA: MARKET_A_USDC,
- vaultSigner,
- marketMaker,
- mintA: MINT_A,
- usdc: USDC,
- godA: GOD_A,
- godUsdc: GOD_USDC,
- };
- }
- async function fundAccount({ provider, mints }) {
- const marketMaker = {
- tokens: {},
- account: MARKET_MAKER,
- };
- // Transfer lamports to market maker.
- await provider.send(
- (() => {
- const tx = new Transaction();
- tx.add(
- SystemProgram.transfer({
- fromPubkey: provider.wallet.publicKey,
- toPubkey: MARKET_MAKER.publicKey,
- lamports: 100000000000,
- })
- );
- return tx;
- })()
- );
- // Transfer SPL tokens to the market maker.
- for (let k = 0; k < mints.length; k += 1) {
- const { mint, god, amount, decimals } = mints[k];
- let MINT_A = mint;
- let GOD_A = god;
- // Setup token accounts owned by the market maker.
- const mintAClient = new Token(
- provider.connection,
- MINT_A,
- TOKEN_PROGRAM_ID,
- provider.wallet.payer // node only
- );
- const marketMakerTokenA = await mintAClient.createAccount(
- MARKET_MAKER.publicKey
- );
- await provider.send(
- (() => {
- const tx = new Transaction();
- tx.add(
- Token.createTransferCheckedInstruction(
- TOKEN_PROGRAM_ID,
- GOD_A,
- MINT_A,
- marketMakerTokenA,
- provider.wallet.publicKey,
- [],
- amount,
- decimals
- )
- );
- return tx;
- })()
- );
- marketMaker.tokens[mint.toString()] = marketMakerTokenA;
- }
- return marketMaker;
- }
- async function setupMarket({
- provider,
- marketMaker,
- baseMint,
- quoteMint,
- bids,
- asks,
- getAuthority,
- proxyProgramId,
- marketLoader,
- }) {
- const [marketAPublicKey, vaultOwner] = await listMarket({
- connection: provider.connection,
- wallet: provider.wallet,
- baseMint: baseMint,
- quoteMint: quoteMint,
- baseLotSize: 100000,
- quoteLotSize: 100,
- dexProgramId: DEX_PID,
- feeRateBps: 0,
- getAuthority,
- });
- const MARKET_A_USDC = await marketLoader(marketAPublicKey);
- return [MARKET_A_USDC, vaultOwner];
- }
- async function listMarket({
- connection,
- wallet,
- baseMint,
- quoteMint,
- baseLotSize,
- quoteLotSize,
- dexProgramId,
- feeRateBps,
- getAuthority,
- }) {
- const market = new Account();
- const requestQueue = new Account();
- const eventQueue = new Account();
- const bids = new Account();
- const asks = new Account();
- const baseVault = new Account();
- const quoteVault = new Account();
- const quoteDustThreshold = new BN(100);
- const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
- market.publicKey,
- dexProgramId
- );
- const tx1 = new Transaction();
- tx1.add(
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: baseVault.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(165),
- space: 165,
- programId: TOKEN_PROGRAM_ID,
- }),
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: quoteVault.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(165),
- space: 165,
- programId: 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_STATE_LAYOUT_V3.span
- ),
- space: MARKET_STATE_LAYOUT_V3.span,
- programId: dexProgramId,
- }),
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: requestQueue.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
- space: 5120 + 12,
- programId: dexProgramId,
- }),
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: eventQueue.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
- space: 262144 + 12,
- programId: dexProgramId,
- }),
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: bids.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
- space: 65536 + 12,
- programId: dexProgramId,
- }),
- SystemProgram.createAccount({
- fromPubkey: wallet.publicKey,
- newAccountPubkey: asks.publicKey,
- lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
- space: 65536 + 12,
- programId: dexProgramId,
- }),
- 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 BN(baseLotSize),
- quoteLotSize: new BN(quoteLotSize),
- feeRateBps,
- vaultSignerNonce,
- quoteDustThreshold,
- programId: dexProgramId,
- authority: await getAuthority(market.publicKey),
- })
- );
- const transactions = [
- { transaction: tx1, signers: [baseVault, quoteVault] },
- {
- transaction: tx2,
- signers: [market, requestQueue, eventQueue, bids, asks],
- },
- ];
- for (let tx of transactions) {
- await anchor.getProvider().send(tx.transaction, tx.signers);
- }
- const acc = await connection.getAccountInfo(market.publicKey);
- return [market.publicKey, vaultOwner];
- }
- 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)
- );
- }
- async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
- const nonce = new BN(0);
- while (nonce.toNumber() < 255) {
- try {
- const vaultOwner = await PublicKey.createProgramAddress(
- [marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
- dexProgramId
- );
- return [vaultOwner, nonce];
- } catch (e) {
- nonce.iaddn(1);
- }
- }
- throw new Error("Unable to find nonce");
- }
- function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- module.exports = {
- fundAccount,
- initMarket,
- setupMarket,
- DEX_PID,
- getVaultOwnerAndNonce,
- sleep,
- };
|