| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 | // 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 TokenInstructions = require("@project-serum/serum").TokenInstructions;const Market = require("@project-serum/serum").Market;const DexInstructions = require("@project-serum/serum").DexInstructions;const web3 = require("@project-serum/anchor").web3;const Connection = web3.Connection;const BN = require("@project-serum/anchor").BN;const serumCmn = require("@project-serum/common");const Account = web3.Account;const Transaction = web3.Transaction;const PublicKey = web3.PublicKey;const SystemProgram = web3.SystemProgram;const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");async function setupTwoMarkets({ provider }) {  // Setup mints with initial tokens owned by the provider.  const decimals = 6;  const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(    provider,    new BN(1000000000000000),    undefined,    decimals  );  const [MINT_B, GOD_B] = await serumCmn.createMintAndVault(    provider,    new BN(1000000000000000),    undefined,    decimals  );  const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(    provider,    new BN(1000000000000000),    undefined,    decimals  );  // Create a funded account to act as market maker.  const amount = 100000 * 10 ** decimals;  const marketMaker = await fundAccount({    provider,    mints: [      { god: GOD_A, mint: MINT_A, amount, decimals },      { god: GOD_B, mint: MINT_B, amount, decimals },      { god: GOD_USDC, mint: USDC, amount, decimals },    ],  });  // Setup A/USDC and B/USDC markets 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 = 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,  });  MARKET_B_USDC = await setupMarket({    baseMint: MINT_B,    quoteMint: USDC,    marketMaker: {      account: marketMaker.account,      baseToken: marketMaker.tokens[MINT_B.toString()],      quoteToken: marketMaker.tokens[USDC.toString()],    },    bids,    asks,    provider,  });  return {    marketA: MARKET_A_USDC,    marketB: MARKET_B_USDC,    marketMaker,    mintA: MINT_A,    mintB: MINT_B,    usdc: USDC,    godA: GOD_A,    godB: GOD_B,    godUsdc: GOD_USDC,  };}// Creates everything needed for an orderbook to be running//// * Mints for both the base and quote currencies.// * Lists the market.// * Provides resting orders on the market.//// Returns a client that can be used to interact with the market// (and some other data, e.g., the mints and market maker account).async function initOrderbook({ provider, bids, asks }) {  if (!bids || !asks) {    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],    ];    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],    ];  }  // Create base and quote currency mints.  const decimals = 6;  const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(    provider,    new BN(1000000000000000),    undefined,    decimals  );  const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(    provider,    new BN(1000000000000000),    undefined,    decimals  );  // Create a funded account to act as market maker.  const amount = 100000 * 10 ** decimals;  const marketMaker = await fundAccount({    provider,    mints: [      { god: GOD_A, mint: MINT_A, amount, decimals },      { god: GOD_USDC, mint: USDC, amount, decimals },    ],  });  marketClient = 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,  });  return {    marketClient,    baseMint: MINT_A,    quoteMint: USDC,    marketMaker,  };}async function fundAccount({ provider, mints }) {  const MARKET_MAKER = new Account();  const marketMaker = {    tokens: {},    account: MARKET_MAKER,  };  // Transfer lamports to market maker.  await provider.sendAndConfirm(    (() => {      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.sendAndConfirm(      (() => {        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,}) {  const marketAPublicKey = await listMarket({    connection: provider.connection,    wallet: provider.wallet,    baseMint: baseMint,    quoteMint: quoteMint,    baseLotSize: 100000,    quoteLotSize: 100,    dexProgramId: DEX_PID,    feeRateBps: 0,  });  const MARKET_A_USDC = await Market.load(    provider.connection,    marketAPublicKey,    { commitment: "processed" },    DEX_PID  );  for (let k = 0; k < asks.length; k += 1) {    let ask = asks[k];    const { transaction, signers } =      await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {        owner: marketMaker.account,        payer: marketMaker.baseToken,        side: "sell",        price: ask[0],        size: ask[1],        orderType: "postOnly",        clientId: undefined,        openOrdersAddressKey: undefined,        openOrdersAccount: undefined,        feeDiscountPubkey: null,        selfTradeBehavior: "abortTransaction",      });    await provider.sendAndConfirm(      transaction,      signers.concat(marketMaker.account)    );  }  for (let k = 0; k < bids.length; k += 1) {    let bid = bids[k];    const { transaction, signers } =      await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {        owner: marketMaker.account,        payer: marketMaker.quoteToken,        side: "buy",        price: bid[0],        size: bid[1],        orderType: "postOnly",        clientId: undefined,        openOrdersAddressKey: undefined,        openOrdersAccount: undefined,        feeDiscountPubkey: null,        selfTradeBehavior: "abortTransaction",      });    await provider.sendAndConfirm(      transaction,      signers.concat(marketMaker.account)    );  }  return MARKET_A_USDC;}async function listMarket({  connection,  wallet,  baseMint,  quoteMint,  baseLotSize,  quoteLotSize,  dexProgramId,  feeRateBps,}) {  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.getLayout(dexProgramId).span      ),      space: Market.getLayout(dexProgramId).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,    })  );  const signedTransactions = await signTransactions({    transactionsAndSigners: [      { transaction: tx1, signers: [baseVault, quoteVault] },      {        transaction: tx2,        signers: [market, requestQueue, eventQueue, bids, asks],      },    ],    wallet,    connection,  });  for (let signedTransaction of signedTransactions) {    await sendAndConfirmRawTransaction(      connection,      signedTransaction.serialize()    );  }  const acc = await connection.getAccountInfo(market.publicKey);  return market.publicKey;}async function signTransactions({  transactionsAndSigners,  wallet,  connection,}) {  const blockhash = (await connection.getRecentBlockhash("finalized"))    .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 sendAndConfirmRawTransaction(  connection,  raw,  commitment = "processed") {  let tx = await connection.sendRawTransaction(raw, {    skipPreflight: true,  });  return await connection.confirmTransaction(tx, commitment);}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");}module.exports = {  fundAccount,  setupMarket,  initOrderbook,  setupTwoMarkets,  DEX_PID,  getVaultOwnerAndNonce,};
 |