Forráskód Böngészése

feat: add support for wsol swaps

Filip Dunder 1 éve
szülő
commit
636ef3e515
10 módosított fájl, 242 hozzáadás és 117 törlés
  1. 5 3
      .env.copy
  2. 34 7
      README.md
  3. 139 62
      buy.ts
  4. 0 6
      common/constants.ts
  5. 0 1
      common/index.ts
  6. 54 7
      liquidity/liquidity.ts
  7. 10 5
      market/market.ts
  8. BIN
      output.png
  9. 0 26
      utils/utils.ts
  10. BIN
      wsol.png

+ 5 - 3
.env.copy

@@ -1,4 +1,6 @@
 PRIVATE_KEY=
 PRIVATE_KEY=
-RPC_ENDPOINT=
-RPC_WEBSOCKET_ENDPOINT=
-```
+RPC_ENDPOINT=https://api.mainnet-beta.solana.com
+RPC_WEBSOCKET_ENDPOINT=wss://api.mainnet-beta.solana.com
+QUOTE_MINT=WSOL
+QUOTE_AMOUNT=0.1
+COMMITMENT_LEVEL=finalized

+ 34 - 7
README.md

@@ -1,17 +1,44 @@
 # Solana Sniper Bot
 # Solana Sniper Bot
-Proof of concept - 2023-04-20
-
 This code is written as proof of concept for demonstrating how we can buy new tokens immediately after liquidity pool is created.
 This code is written as proof of concept for demonstrating how we can buy new tokens immediately after liquidity pool is created.
 
 
-Script listens to new raydium USDC pools and buys token for a fixed amount in USDC.
-Depending on speed of RPC node, the purchase usually happens before token is available on Raydium for swapping.
+Script listens to new raydium USDC/SOL pools and buys token for a fixed amount in USDC/SOL.
+Depending on speed of RPC node, the purchase usually happens before token is available on Raydium UI for swapping.
 
 
 # Setup
 # Setup
 In order to run the script you need to:
 In order to run the script you need to:
 - Create a new empty Solana wallet
 - Create a new empty Solana wallet
 - Transfer some SOL to it.
 - Transfer some SOL to it.
-- Convert some SOL to USDC.
-  - We need USDC because the script is buying USDC pairs.
-- Set your PRIVATE_KEY, RPC_ENDPOINT and RPC_WEBSOCKET_ENDPOINT in the .env file (remove the .copy from the file name when done)
+- Convert some SOL to USDC or WSOL.
+  - You need USDC or WSOL depending on configuration set below.
+- Set your 
+  - PRIVATE_KEY (your wallet private key)
+  - RPC_ENDPOINT (https endpoint like helius/quicknode)
+  - RPC_WEBSOCKET_ENDPOINT (websocket endpoint like helius/quicknode)
+  - QUOTE_MINT (which pools to look at, USDC or WSOL)
+  - QUOTE_AMOUNT (amount used to buy each new token)
+  - COMMITMENT_LEVEL
+
+  in the .env file (remove the .copy from the file name when done). 
+  Make sure to replace default values.
+
 - Install dependencies by typing: `npm install`
 - Install dependencies by typing: `npm install`
 - Run the script by typing: `npm run buy` in terminal
 - Run the script by typing: `npm run buy` in terminal
+
+You should see following output:
+![output](output.png)
+
+# Support
+
+## Unsupported RPC node
+- If you see following error in your log file:
+`Error: 410 Gone:  {"jsonrpc":"2.0","error":{"code": 410, "message":"The RPC call or parameters have been disabled."}, "id": "986f3599-b2b7-47c4-b951-074c19842bad" }`
+it means your RPC node doesn't support methods needed to execute script.
+  - FIX: Change your RPC node. You can use Helius or Quicknode.
+
+
+- If you see following error in your log file:
+`Error: No SOL token account found in wallet: `
+it means that wallet you provided doesn't have USDC/WSOL token account.
+  - FIX: Go to dex and swap some SOL to USDC/WSOL. For example when you swap sol to wsol you should see it in wallet as shown below:
+![wsol](wsol.png)
+

+ 139 - 62
buy.ts

@@ -1,10 +1,19 @@
 import {
 import {
   Liquidity,
   Liquidity,
   LIQUIDITY_STATE_LAYOUT_V4,
   LIQUIDITY_STATE_LAYOUT_V4,
+  LiquidityPoolKeys,
   LiquidityStateV4,
   LiquidityStateV4,
   MARKET_STATE_LAYOUT_V2,
   MARKET_STATE_LAYOUT_V2,
+  MARKET_STATE_LAYOUT_V3,
+  MarketStateV3,
+  Token,
+  TokenAmount,
 } from '@raydium-io/raydium-sdk';
 } from '@raydium-io/raydium-sdk';
-import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
+import {
+  createAssociatedTokenAccountIdempotentInstruction,
+  getAssociatedTokenAddressSync,
+  TOKEN_PROGRAM_ID,
+} from '@solana/spl-token';
 import {
 import {
   Keypair,
   Keypair,
   Connection,
   Connection,
@@ -13,17 +22,17 @@ import {
   KeyedAccountInfo,
   KeyedAccountInfo,
   TransactionMessage,
   TransactionMessage,
   VersionedTransaction,
   VersionedTransaction,
+  Commitment,
 } from '@solana/web3.js';
 } from '@solana/web3.js';
 import {
 import {
   getAllAccountsV4,
   getAllAccountsV4,
   getTokenAccounts,
   getTokenAccounts,
-  getAccountPoolKeysFromAccountDataV4,
   RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
   RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
   OPENBOOK_PROGRAM_ID,
   OPENBOOK_PROGRAM_ID,
+  createPoolKeys,
 } from './liquidity';
 } from './liquidity';
-import { retry, retrieveEnvVariable } from './utils';
-import { USDC_AMOUNT, USDC_TOKEN_ID } from './common';
-import { getAllMarketsV3 } from './market';
+import { retrieveEnvVariable } from './utils';
+import { getAllMarketsV3, MinimalMarketLayoutV3 } from './market';
 import pino from 'pino';
 import pino from 'pino';
 import bs58 from 'bs58';
 import bs58 from 'bs58';
 
 
@@ -71,6 +80,9 @@ const solanaConnection = new Connection(RPC_ENDPOINT, {
 export type MinimalTokenAccountData = {
 export type MinimalTokenAccountData = {
   mint: PublicKey;
   mint: PublicKey;
   address: PublicKey;
   address: PublicKey;
+  ata: PublicKey;
+  poolKeys?: LiquidityPoolKeys;
+  market?: MinimalMarketLayoutV3;
 };
 };
 
 
 let existingLiquidityPools: Set<string> = new Set<string>();
 let existingLiquidityPools: Set<string> = new Set<string>();
@@ -81,36 +93,95 @@ let existingTokenAccounts: Map<string, MinimalTokenAccountData> = new Map<
 >();
 >();
 
 
 let wallet: Keypair;
 let wallet: Keypair;
-let usdcTokenKey: PublicKey;
-const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger);
+let quoteToken: Token;
+let quoteTokenAssociatedAddress: PublicKey;
+let quoteAmount: TokenAmount;
+let commitment: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment;
 
 
 async function init(): Promise<void> {
 async function init(): Promise<void> {
+  // get wallet
+  const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger);
   wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
   wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
-  logger.info(`Wallet Address: ${wallet.publicKey.toString()}`);
-  const allLiquidityPools = await getAllAccountsV4(solanaConnection);
+  logger.info(`Wallet Address: ${wallet.publicKey}`);
+
+  // get quote mint and amount
+  const QUOTE_MINT = retrieveEnvVariable('QUOTE_MINT', logger);
+  const QUOTE_AMOUNT = retrieveEnvVariable('QUOTE_AMOUNT', logger);
+  switch (QUOTE_MINT) {
+    case 'WSOL': {
+      quoteToken = Token.WSOL;
+      quoteAmount = new TokenAmount(Token.WSOL, QUOTE_AMOUNT, false);
+      break;
+    }
+    case 'USDC': {
+      quoteToken = new Token(
+        TOKEN_PROGRAM_ID,
+        new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
+        6,
+        'USDC',
+        'USDC',
+      );
+      quoteAmount = new TokenAmount(quoteToken, QUOTE_AMOUNT, false);
+      break;
+    }
+    default: {
+      throw new Error(
+        `Unsupported quote mint "${QUOTE_MINT}". Supported values are USDC and WSOL`,
+      );
+    }
+  }
+
+  logger.info(
+    `Script will buy all new tokens using ${QUOTE_MINT}. Amount that will be used to buy each token is: ${quoteAmount.toFixed().toString()}`
+  );
+
+  // get all existing liquidity pools
+  const allLiquidityPools = await getAllAccountsV4(
+    solanaConnection,
+    quoteToken.mint,
+    commitment,
+  );
   existingLiquidityPools = new Set(
   existingLiquidityPools = new Set(
     allLiquidityPools.map((p) => p.id.toString()),
     allLiquidityPools.map((p) => p.id.toString()),
   );
   );
-  const allMarkets = await getAllMarketsV3(solanaConnection);
+
+  // get all open-book markets
+  const allMarkets = await getAllMarketsV3(solanaConnection, quoteToken.mint, commitment);
   existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
   existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
   const tokenAccounts = await getTokenAccounts(
   const tokenAccounts = await getTokenAccounts(
     solanaConnection,
     solanaConnection,
     wallet.publicKey,
     wallet.publicKey,
+    commitment,
   );
   );
-  logger.info(`Total USDC markets ${existingOpenBookMarkets.size}`);
-  logger.info(`Total USDC pools ${existingLiquidityPools.size}`);
-  tokenAccounts.forEach((ta) => {
+
+  logger.info(
+    `Total ${quoteToken.symbol} markets ${existingOpenBookMarkets.size}`,
+  );
+  logger.info(
+    `Total ${quoteToken.symbol} pools ${existingLiquidityPools.size}`,
+  );
+
+  // check existing wallet for associated token account of quote mint
+  for (const ta of tokenAccounts) {
     existingTokenAccounts.set(ta.accountInfo.mint.toString(), <
     existingTokenAccounts.set(ta.accountInfo.mint.toString(), <
       MinimalTokenAccountData
       MinimalTokenAccountData
     >{
     >{
       mint: ta.accountInfo.mint,
       mint: ta.accountInfo.mint,
       address: ta.pubkey,
       address: ta.pubkey,
     });
     });
-  });
-  const token = tokenAccounts.find(
-    (acc) => acc.accountInfo.mint.toString() === USDC_TOKEN_ID.toString(),
+  }
+
+  const tokenAccount = tokenAccounts.find(
+    (acc) => acc.accountInfo.mint.toString() === quoteToken.mint.toString(),
   )!;
   )!;
-  usdcTokenKey = token!.pubkey;
+
+  if (!tokenAccount) {
+    throw new Error(
+      `No ${quoteToken.symbol} token account found in wallet: ${wallet.publicKey}`,
+    );
+  }
+
+  quoteTokenAssociatedAddress = tokenAccount.pubkey;
 }
 }
 
 
 export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
 export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
@@ -128,92 +199,98 @@ export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
 export async function processOpenBookMarket(
 export async function processOpenBookMarket(
   updatedAccountInfo: KeyedAccountInfo,
   updatedAccountInfo: KeyedAccountInfo,
 ) {
 ) {
-  let accountData: any;
+  let accountData: MarketStateV3 | undefined;
   try {
   try {
-    accountData = MARKET_STATE_LAYOUT_V2.decode(
+    accountData = MARKET_STATE_LAYOUT_V3.decode(
       updatedAccountInfo.accountInfo.data,
       updatedAccountInfo.accountInfo.data,
     );
     );
 
 
-    // to be competitive, we create token account before buying the token...
+    // to be competitive, we collect market data before buying the token...
     if (existingTokenAccounts.has(accountData.baseMint.toString())) {
     if (existingTokenAccounts.has(accountData.baseMint.toString())) {
       return;
       return;
     }
     }
 
 
-    const destinationAccount = await getOrCreateAssociatedTokenAccount(
-      solanaConnection,
-      wallet,
+    const ata = getAssociatedTokenAddressSync(
       accountData.baseMint,
       accountData.baseMint,
       wallet.publicKey,
       wallet.publicKey,
     );
     );
     existingTokenAccounts.set(accountData.baseMint.toString(), <
     existingTokenAccounts.set(accountData.baseMint.toString(), <
       MinimalTokenAccountData
       MinimalTokenAccountData
     >{
     >{
-      address: destinationAccount.address,
-      mint: destinationAccount.mint,
+      address: ata,
+      mint: accountData.baseMint,
+      market: <MinimalMarketLayoutV3>{
+        bids: accountData.bids,
+        asks: accountData.asks,
+        eventQueue: accountData.eventQueue,
+      },
     });
     });
-    logger.info(
-      accountData,
-      `Created destination account: ${destinationAccount.address}`,
-    );
   } catch (e) {
   } catch (e) {
     logger.error({ ...accountData, error: e }, `Failed to process market`);
     logger.error({ ...accountData, error: e }, `Failed to process market`);
   }
   }
 }
 }
 
 
-async function buy(accountId: PublicKey, accountData: any): Promise<void> {
-  const [poolKeys, latestBlockhash] = await Promise.all([
-    getAccountPoolKeysFromAccountDataV4(
-      solanaConnection,
-      accountId,
-      accountData,
-    ),
-    solanaConnection.getLatestBlockhash({ commitment: 'processed' }),
-  ]);
-
-  const baseMint = poolKeys.baseMint.toString();
-  const tokenAccountOut =
-    existingTokenAccounts && existingTokenAccounts.get(baseMint)?.address;
+async function buy(
+  accountId: PublicKey,
+  accountData: LiquidityStateV4,
+): Promise<void> {
+  const tokenAccount = existingTokenAccounts.get(
+    accountData.baseMint.toString(),
+  );
 
 
-  if (!tokenAccountOut) {
-    logger.info(`No token account for ${baseMint}`);
+  if (!tokenAccount) {
     return;
     return;
   }
   }
+
+  tokenAccount.poolKeys = createPoolKeys(
+    accountId,
+    accountData,
+    tokenAccount.market!,
+  );
   const { innerTransaction, address } = Liquidity.makeSwapFixedInInstruction(
   const { innerTransaction, address } = Liquidity.makeSwapFixedInInstruction(
     {
     {
-      poolKeys,
+      poolKeys: tokenAccount.poolKeys,
       userKeys: {
       userKeys: {
-        tokenAccountIn: usdcTokenKey,
-        tokenAccountOut: tokenAccountOut,
+        tokenAccountIn: quoteTokenAssociatedAddress,
+        tokenAccountOut: tokenAccount.address,
         owner: wallet.publicKey,
         owner: wallet.publicKey,
       },
       },
-      amountIn: USDC_AMOUNT * 1000000,
+      amountIn: quoteAmount.raw,
       minAmountOut: 0,
       minAmountOut: 0,
     },
     },
-    poolKeys.version,
+    tokenAccount.poolKeys.version,
   );
   );
 
 
+  const latestBlockhash = await solanaConnection.getLatestBlockhash({
+    commitment: commitment,
+  });
   const messageV0 = new TransactionMessage({
   const messageV0 = new TransactionMessage({
     payerKey: wallet.publicKey,
     payerKey: wallet.publicKey,
     recentBlockhash: latestBlockhash.blockhash,
     recentBlockhash: latestBlockhash.blockhash,
     instructions: [
     instructions: [
       ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }),
       ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }),
       ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 30000 }),
       ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 30000 }),
+      createAssociatedTokenAccountIdempotentInstruction(
+        wallet.publicKey,
+        tokenAccount.address,
+        wallet.publicKey,
+        accountData.baseMint,
+      ),
       ...innerTransaction.instructions,
       ...innerTransaction.instructions,
     ],
     ],
   }).compileToV0Message();
   }).compileToV0Message();
   const transaction = new VersionedTransaction(messageV0);
   const transaction = new VersionedTransaction(messageV0);
   transaction.sign([wallet, ...innerTransaction.signers]);
   transaction.sign([wallet, ...innerTransaction.signers]);
-  const rawTransaction = transaction.serialize();
-  const signature = await retry(
-    () =>
-      solanaConnection.sendRawTransaction(rawTransaction, {
-        skipPreflight: true,
-      }),
-    { retryIntervalMs: 10, retries: 50 }, // TODO handle retries more efficiently
+  const signature = await solanaConnection.sendRawTransaction(
+    transaction.serialize(),
+    {
+      maxRetries: 5,
+      preflightCommitment: commitment,
+    },
   );
   );
   logger.info(
   logger.info(
     {
     {
-      ...accountData,
+      mint: accountData.baseMint,
       url: `https://solscan.io/tx/${signature}?cluster=${network}`,
       url: `https://solscan.io/tx/${signature}?cluster=${network}`,
     },
     },
     'Buy',
     'Buy',
@@ -233,13 +310,13 @@ const runListener = async () => {
         const _ = processRaydiumPool(updatedAccountInfo);
         const _ = processRaydiumPool(updatedAccountInfo);
       }
       }
     },
     },
-    'processed',
+    commitment,
     [
     [
       { dataSize: LIQUIDITY_STATE_LAYOUT_V4.span },
       { dataSize: LIQUIDITY_STATE_LAYOUT_V4.span },
       {
       {
         memcmp: {
         memcmp: {
           offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'),
           offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'),
-          bytes: USDC_TOKEN_ID.toBase58(),
+          bytes: quoteToken.mint.toBase58(),
         },
         },
       },
       },
       {
       {
@@ -251,7 +328,7 @@ const runListener = async () => {
       {
       {
         memcmp: {
         memcmp: {
           offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('status'),
           offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('status'),
-          bytes: '14421D35quxec7'
+          bytes: bs58.encode([6, 0, 0, 0, 0, 0, 0, 0]),
         },
         },
       },
       },
     ],
     ],
@@ -268,13 +345,13 @@ const runListener = async () => {
         const _ = processOpenBookMarket(updatedAccountInfo);
         const _ = processOpenBookMarket(updatedAccountInfo);
       }
       }
     },
     },
-    'processed',
+    commitment,
     [
     [
       { dataSize: MARKET_STATE_LAYOUT_V2.span },
       { dataSize: MARKET_STATE_LAYOUT_V2.span },
       {
       {
         memcmp: {
         memcmp: {
           offset: MARKET_STATE_LAYOUT_V2.offsetOf('quoteMint'),
           offset: MARKET_STATE_LAYOUT_V2.offsetOf('quoteMint'),
-          bytes: USDC_TOKEN_ID.toBase58(),
+          bytes: quoteToken.mint.toBase58(),
         },
         },
       },
       },
     ],
     ],

+ 0 - 6
common/constants.ts

@@ -1,6 +0,0 @@
-import { PublicKey } from "@solana/web3.js";
-
-export const USDC_AMOUNT = 0.1; // how much do we spend on each token
-export const USDC_TOKEN_ID = new PublicKey(
-  'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
-);

+ 0 - 1
common/index.ts

@@ -1 +0,0 @@
-export * from './constants';

+ 54 - 7
liquidity/liquidity.ts

@@ -12,7 +12,7 @@ import {
   LiquidityStateV4,
   LiquidityStateV4,
 } from '@raydium-io/raydium-sdk';
 } from '@raydium-io/raydium-sdk';
 import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
 import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
-import { USDC_TOKEN_ID } from '../common';
+import { MinimalMarketLayoutV3 } from '../market';
 
 
 export const RAYDIUM_LIQUIDITY_PROGRAM_ID_V4 = MAINNET_PROGRAM_ID.AmmV4;
 export const RAYDIUM_LIQUIDITY_PROGRAM_ID_V4 = MAINNET_PROGRAM_ID.AmmV4;
 export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET;
 export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET;
@@ -31,19 +31,21 @@ export type MinimalLiquidityAccountData = {
 
 
 export async function getAllAccountsV4(
 export async function getAllAccountsV4(
   connection: Connection,
   connection: Connection,
+  quoteMint: PublicKey,
+  commitment?: Commitment,
 ): Promise<MinimalLiquidityAccountData[]> {
 ): Promise<MinimalLiquidityAccountData[]> {
   const { span } = LIQUIDITY_STATE_LAYOUT_V4;
   const { span } = LIQUIDITY_STATE_LAYOUT_V4;
   const accounts = await connection.getProgramAccounts(
   const accounts = await connection.getProgramAccounts(
     RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
     RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
     {
     {
       dataSlice: { offset: 0, length: 0 },
       dataSlice: { offset: 0, length: 0 },
-      commitment: 'processed',
+      commitment: commitment,
       filters: [
       filters: [
         { dataSize: span },
         { dataSize: span },
         {
         {
           memcmp: {
           memcmp: {
             offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'),
             offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'),
-            bytes: USDC_TOKEN_ID.toBase58(),
+            bytes: quoteMint.toBase58(),
           },
           },
         },
         },
         {
         {
@@ -66,6 +68,46 @@ export async function getAllAccountsV4(
   );
   );
 }
 }
 
 
+export function createPoolKeys(
+  id: PublicKey,
+  accountData: LiquidityStateV4,
+  minimalMarketLayoutV3: MinimalMarketLayoutV3,
+): LiquidityPoolKeys {
+  return {
+    id,
+    baseMint: accountData.baseMint,
+    quoteMint: accountData.quoteMint,
+    lpMint: accountData.lpMint,
+    baseDecimals: accountData.baseDecimal.toNumber(),
+    quoteDecimals: accountData.quoteDecimal.toNumber(),
+    lpDecimals: 5,
+    version: 4,
+    programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
+    authority: Liquidity.getAssociatedAuthority({
+      programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
+    }).publicKey,
+    openOrders: accountData.openOrders,
+    targetOrders: accountData.targetOrders,
+    baseVault: accountData.baseVault,
+    quoteVault: accountData.quoteVault,
+    marketVersion: 3,
+    marketProgramId: accountData.marketProgramId,
+    marketId: accountData.marketId,
+    marketAuthority: Market.getAssociatedAuthority({
+      programId: accountData.marketProgramId,
+      marketId: accountData.marketId,
+    }).publicKey,
+    marketBaseVault: accountData.baseVault,
+    marketQuoteVault: accountData.quoteVault,
+    marketBids: minimalMarketLayoutV3.bids,
+    marketAsks: minimalMarketLayoutV3.asks,
+    marketEventQueue: minimalMarketLayoutV3.eventQueue,
+    withdrawQueue: accountData.withdrawQueue,
+    lpVault: accountData.lpVault,
+    lookupTableAccount: PublicKey.default,
+  };
+}
+
 export async function getAccountPoolKeysFromAccountDataV4(
 export async function getAccountPoolKeysFromAccountDataV4(
   connection: Connection,
   connection: Connection,
   id: PublicKey,
   id: PublicKey,
@@ -73,7 +115,7 @@ export async function getAccountPoolKeysFromAccountDataV4(
   commitment?: Commitment,
   commitment?: Commitment,
 ): Promise<LiquidityPoolKeys> {
 ): Promise<LiquidityPoolKeys> {
   const marketInfo = await connection.getAccountInfo(accountData.marketId, {
   const marketInfo = await connection.getAccountInfo(accountData.marketId, {
-    commitment: commitment ?? 'processed',
+    commitment: commitment,
     dataSlice: {
     dataSlice: {
       offset: 253, // eventQueue
       offset: 253, // eventQueue
       length: 32 * 3,
       length: 32 * 3,
@@ -122,10 +164,15 @@ export async function getAccountPoolKeysFromAccountDataV4(
 export async function getTokenAccounts(
 export async function getTokenAccounts(
   connection: Connection,
   connection: Connection,
   owner: PublicKey,
   owner: PublicKey,
+  commitment?: Commitment,
 ) {
 ) {
-  const tokenResp = await connection.getTokenAccountsByOwner(owner, {
-    programId: TOKEN_PROGRAM_ID,
-  });
+  const tokenResp = await connection.getTokenAccountsByOwner(
+    owner,
+    {
+      programId: TOKEN_PROGRAM_ID,
+    },
+    commitment,
+  );
 
 
   const accounts: TokenAccount[] = [];
   const accounts: TokenAccount[] = [];
   for (const { pubkey, account } of tokenResp.value) {
   for (const { pubkey, account } of tokenResp.value) {

+ 10 - 5
market/market.ts

@@ -1,31 +1,36 @@
-import { Connection, PublicKey } from '@solana/web3.js';
+import { Commitment, Connection, PublicKey } from '@solana/web3.js';
 import {
 import {
+  GetStructureSchema,
   MARKET_STATE_LAYOUT_V3,
   MARKET_STATE_LAYOUT_V3,
 } from '@raydium-io/raydium-sdk';
 } from '@raydium-io/raydium-sdk';
-import { USDC_TOKEN_ID } from '../common';
 import {
 import {
+  MINIMAL_MARKET_STATE_LAYOUT_V3,
   OPENBOOK_PROGRAM_ID,
   OPENBOOK_PROGRAM_ID,
-
 } from '../liquidity';
 } from '../liquidity';
 
 
 export type MinimalOpenBookAccountData = {
 export type MinimalOpenBookAccountData = {
   id: PublicKey;
   id: PublicKey;
   programId: PublicKey;
   programId: PublicKey;
 };
 };
+export type MinimalMarketStateLayoutV3 = typeof MINIMAL_MARKET_STATE_LAYOUT_V3;
+export type MinimalMarketLayoutV3 =
+  GetStructureSchema<MinimalMarketStateLayoutV3>;
 
 
 export async function getAllMarketsV3(
 export async function getAllMarketsV3(
   connection: Connection,
   connection: Connection,
+  quoteMint: PublicKey,
+  commitment?: Commitment,
 ): Promise<MinimalOpenBookAccountData[]> {
 ): Promise<MinimalOpenBookAccountData[]> {
   const { span } = MARKET_STATE_LAYOUT_V3;
   const { span } = MARKET_STATE_LAYOUT_V3;
   const accounts = await connection.getProgramAccounts(OPENBOOK_PROGRAM_ID, {
   const accounts = await connection.getProgramAccounts(OPENBOOK_PROGRAM_ID, {
     dataSlice: { offset: 0, length: 0 },
     dataSlice: { offset: 0, length: 0 },
-    commitment: 'processed',
+    commitment: commitment,
     filters: [
     filters: [
       { dataSize: span },
       { dataSize: span },
       {
       {
         memcmp: {
         memcmp: {
           offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'),
           offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'),
-          bytes: USDC_TOKEN_ID.toBase58(),
+          bytes: quoteMint.toBase58(),
         },
         },
       },
       },
     ],
     ],

BIN
output.png


+ 0 - 26
utils/utils.ts

@@ -2,32 +2,6 @@ import { Logger } from "pino";
 import dotenv from 'dotenv';
 import dotenv from 'dotenv';
 dotenv.config();
 dotenv.config();
 
 
-/**
- * Runs the function `fn`
- * and retries automatically if it fails.
- *
- * Tries max `1 + retries` times
- * with `retryIntervalMs` milliseconds between retries.
- *
- * From https://mtsknn.fi/blog/js-retry-on-fail/
- */
-export const retry = async <T>(
-  fn: () => Promise<T> | T,
-  { retries, retryIntervalMs }: { retries: number; retryIntervalMs: number },
-): Promise<T> => {
-  try {
-    return await fn();
-  } catch (error) {
-    if (retries <= 0) {
-      throw error;
-    }
-    await sleep(retryIntervalMs);
-    return retry(fn, { retries: retries - 1, retryIntervalMs });
-  }
-};
-
-export const sleep = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
-
 export const retrieveEnvVariable = (variableName: string, logger: Logger) => {
 export const retrieveEnvVariable = (variableName: string, logger: Logger) => {
     const variable = process.env[variableName] || '';
     const variable = process.env[variableName] || '';
     if (!variable) {
     if (!variable) {

BIN
wsol.png