index.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // Boilerplate utils to bootstrap an orderbook for testing on a localnet.
  2. // not super relevant to the point of the example, though may be useful to
  3. // include into your own workspace for testing.
  4. //
  5. // TODO: Modernize all these apis. This is all quite clunky.
  6. const Token = require("@solana/spl-token").Token;
  7. const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID;
  8. const serum = require('@project-serum/serum');
  9. const {
  10. DexInstructions,
  11. TokenInstructions,
  12. MarketProxy,
  13. OpenOrders,
  14. OpenOrdersPda,
  15. MARKET_STATE_LAYOUT_V3,
  16. } = serum;
  17. const anchor = require("@project-serum/anchor");
  18. const BN = anchor.BN;
  19. const web3 = anchor.web3;
  20. const {
  21. SYSVAR_RENT_PUBKEY,
  22. COnnection,
  23. Account,
  24. Transaction,
  25. PublicKey,
  26. SystemProgram,
  27. } = web3;
  28. const serumCmn = require("@project-serum/common");
  29. const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
  30. const MARKET_MAKER = new Account();
  31. async function initMarket({
  32. provider,
  33. getAuthority,
  34. proxyProgramId,
  35. marketLoader,
  36. }) {
  37. // Setup mints with initial tokens owned by the provider.
  38. const decimals = 6;
  39. const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
  40. provider,
  41. new BN("1000000000000000000"),
  42. undefined,
  43. decimals
  44. );
  45. const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
  46. provider,
  47. new BN("1000000000000000000"),
  48. undefined,
  49. decimals
  50. );
  51. // Create a funded account to act as market maker.
  52. const amount = new BN("10000000000000").muln(10 ** decimals);
  53. const marketMaker = await fundAccount({
  54. provider,
  55. mints: [
  56. { god: GOD_A, mint: MINT_A, amount, decimals },
  57. { god: GOD_USDC, mint: USDC, amount, decimals },
  58. ],
  59. });
  60. // Setup A/USDC with resting orders.
  61. const asks = [
  62. [6.041, 7.8],
  63. [6.051, 72.3],
  64. [6.055, 5.4],
  65. [6.067, 15.7],
  66. [6.077, 390.0],
  67. [6.09, 24.0],
  68. [6.11, 36.3],
  69. [6.133, 300.0],
  70. [6.167, 687.8],
  71. ];
  72. const bids = [
  73. [6.004, 8.5],
  74. [5.995, 12.9],
  75. [5.987, 6.2],
  76. [5.978, 15.3],
  77. [5.965, 82.8],
  78. [5.961, 25.4],
  79. ];
  80. [MARKET_A_USDC, vaultSigner] = await setupMarket({
  81. baseMint: MINT_A,
  82. quoteMint: USDC,
  83. marketMaker: {
  84. account: marketMaker.account,
  85. baseToken: marketMaker.tokens[MINT_A.toString()],
  86. quoteToken: marketMaker.tokens[USDC.toString()],
  87. },
  88. bids,
  89. asks,
  90. provider,
  91. getAuthority,
  92. proxyProgramId,
  93. marketLoader,
  94. });
  95. return {
  96. marketA: MARKET_A_USDC,
  97. vaultSigner,
  98. marketMaker,
  99. mintA: MINT_A,
  100. usdc: USDC,
  101. godA: GOD_A,
  102. godUsdc: GOD_USDC,
  103. };
  104. }
  105. async function fundAccount({ provider, mints }) {
  106. const marketMaker = {
  107. tokens: {},
  108. account: MARKET_MAKER,
  109. };
  110. // Transfer lamports to market maker.
  111. await provider.send(
  112. (() => {
  113. const tx = new Transaction();
  114. tx.add(
  115. SystemProgram.transfer({
  116. fromPubkey: provider.wallet.publicKey,
  117. toPubkey: MARKET_MAKER.publicKey,
  118. lamports: 100000000000,
  119. })
  120. );
  121. return tx;
  122. })()
  123. );
  124. // Transfer SPL tokens to the market maker.
  125. for (let k = 0; k < mints.length; k += 1) {
  126. const { mint, god, amount, decimals } = mints[k];
  127. let MINT_A = mint;
  128. let GOD_A = god;
  129. // Setup token accounts owned by the market maker.
  130. const mintAClient = new Token(
  131. provider.connection,
  132. MINT_A,
  133. TOKEN_PROGRAM_ID,
  134. provider.wallet.payer // node only
  135. );
  136. const marketMakerTokenA = await mintAClient.createAccount(
  137. MARKET_MAKER.publicKey
  138. );
  139. await provider.send(
  140. (() => {
  141. const tx = new Transaction();
  142. tx.add(
  143. Token.createTransferCheckedInstruction(
  144. TOKEN_PROGRAM_ID,
  145. GOD_A,
  146. MINT_A,
  147. marketMakerTokenA,
  148. provider.wallet.publicKey,
  149. [],
  150. amount,
  151. decimals
  152. )
  153. );
  154. return tx;
  155. })()
  156. );
  157. marketMaker.tokens[mint.toString()] = marketMakerTokenA;
  158. }
  159. return marketMaker;
  160. }
  161. async function setupMarket({
  162. provider,
  163. marketMaker,
  164. baseMint,
  165. quoteMint,
  166. bids,
  167. asks,
  168. getAuthority,
  169. proxyProgramId,
  170. marketLoader,
  171. }) {
  172. const [marketAPublicKey, vaultOwner] = await listMarket({
  173. connection: provider.connection,
  174. wallet: provider.wallet,
  175. baseMint: baseMint,
  176. quoteMint: quoteMint,
  177. baseLotSize: 100000,
  178. quoteLotSize: 100,
  179. dexProgramId: DEX_PID,
  180. feeRateBps: 0,
  181. getAuthority,
  182. });
  183. const MARKET_A_USDC = await marketLoader(marketAPublicKey);
  184. return [MARKET_A_USDC, vaultOwner];
  185. }
  186. async function listMarket({
  187. connection,
  188. wallet,
  189. baseMint,
  190. quoteMint,
  191. baseLotSize,
  192. quoteLotSize,
  193. dexProgramId,
  194. feeRateBps,
  195. getAuthority,
  196. }) {
  197. const market = new Account();
  198. const requestQueue = new Account();
  199. const eventQueue = new Account();
  200. const bids = new Account();
  201. const asks = new Account();
  202. const baseVault = new Account();
  203. const quoteVault = new Account();
  204. const quoteDustThreshold = new BN(100);
  205. const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
  206. market.publicKey,
  207. dexProgramId
  208. );
  209. const tx1 = new Transaction();
  210. tx1.add(
  211. SystemProgram.createAccount({
  212. fromPubkey: wallet.publicKey,
  213. newAccountPubkey: baseVault.publicKey,
  214. lamports: await connection.getMinimumBalanceForRentExemption(165),
  215. space: 165,
  216. programId: TOKEN_PROGRAM_ID,
  217. }),
  218. SystemProgram.createAccount({
  219. fromPubkey: wallet.publicKey,
  220. newAccountPubkey: quoteVault.publicKey,
  221. lamports: await connection.getMinimumBalanceForRentExemption(165),
  222. space: 165,
  223. programId: TOKEN_PROGRAM_ID,
  224. }),
  225. TokenInstructions.initializeAccount({
  226. account: baseVault.publicKey,
  227. mint: baseMint,
  228. owner: vaultOwner,
  229. }),
  230. TokenInstructions.initializeAccount({
  231. account: quoteVault.publicKey,
  232. mint: quoteMint,
  233. owner: vaultOwner,
  234. })
  235. );
  236. const tx2 = new Transaction();
  237. tx2.add(
  238. SystemProgram.createAccount({
  239. fromPubkey: wallet.publicKey,
  240. newAccountPubkey: market.publicKey,
  241. lamports: await connection.getMinimumBalanceForRentExemption(
  242. MARKET_STATE_LAYOUT_V3.span
  243. ),
  244. space: MARKET_STATE_LAYOUT_V3.span,
  245. programId: dexProgramId,
  246. }),
  247. SystemProgram.createAccount({
  248. fromPubkey: wallet.publicKey,
  249. newAccountPubkey: requestQueue.publicKey,
  250. lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
  251. space: 5120 + 12,
  252. programId: dexProgramId,
  253. }),
  254. SystemProgram.createAccount({
  255. fromPubkey: wallet.publicKey,
  256. newAccountPubkey: eventQueue.publicKey,
  257. lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
  258. space: 262144 + 12,
  259. programId: dexProgramId,
  260. }),
  261. SystemProgram.createAccount({
  262. fromPubkey: wallet.publicKey,
  263. newAccountPubkey: bids.publicKey,
  264. lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
  265. space: 65536 + 12,
  266. programId: dexProgramId,
  267. }),
  268. SystemProgram.createAccount({
  269. fromPubkey: wallet.publicKey,
  270. newAccountPubkey: asks.publicKey,
  271. lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
  272. space: 65536 + 12,
  273. programId: dexProgramId,
  274. }),
  275. DexInstructions.initializeMarket({
  276. market: market.publicKey,
  277. requestQueue: requestQueue.publicKey,
  278. eventQueue: eventQueue.publicKey,
  279. bids: bids.publicKey,
  280. asks: asks.publicKey,
  281. baseVault: baseVault.publicKey,
  282. quoteVault: quoteVault.publicKey,
  283. baseMint,
  284. quoteMint,
  285. baseLotSize: new BN(baseLotSize),
  286. quoteLotSize: new BN(quoteLotSize),
  287. feeRateBps,
  288. vaultSignerNonce,
  289. quoteDustThreshold,
  290. programId: dexProgramId,
  291. authority: await getAuthority(market.publicKey),
  292. })
  293. );
  294. const transactions = [
  295. { transaction: tx1, signers: [baseVault, quoteVault] },
  296. {
  297. transaction: tx2,
  298. signers: [market, requestQueue, eventQueue, bids, asks],
  299. },
  300. ];
  301. for (let tx of transactions) {
  302. await anchor.getProvider().send(tx.transaction, tx.signers);
  303. }
  304. const acc = await connection.getAccountInfo(market.publicKey);
  305. return [market.publicKey, vaultOwner];
  306. }
  307. async function signTransactions({
  308. transactionsAndSigners,
  309. wallet,
  310. connection,
  311. }) {
  312. const blockhash = (await connection.getRecentBlockhash("max")).blockhash;
  313. transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
  314. transaction.recentBlockhash = blockhash;
  315. transaction.setSigners(
  316. wallet.publicKey,
  317. ...signers.map((s) => s.publicKey)
  318. );
  319. if (signers?.length > 0) {
  320. transaction.partialSign(...signers);
  321. }
  322. });
  323. return await wallet.signAllTransactions(
  324. transactionsAndSigners.map(({ transaction }) => transaction)
  325. );
  326. }
  327. async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
  328. const nonce = new BN(0);
  329. while (nonce.toNumber() < 255) {
  330. try {
  331. const vaultOwner = await PublicKey.createProgramAddress(
  332. [marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
  333. dexProgramId
  334. );
  335. return [vaultOwner, nonce];
  336. } catch (e) {
  337. nonce.iaddn(1);
  338. }
  339. }
  340. throw new Error("Unable to find nonce");
  341. }
  342. function sleep(ms) {
  343. return new Promise((resolve) => setTimeout(resolve, ms));
  344. }
  345. module.exports = {
  346. fundAccount,
  347. initMarket,
  348. setupMarket,
  349. DEX_PID,
  350. getVaultOwnerAndNonce,
  351. sleep,
  352. };