|
@@ -1,555 +0,0 @@
|
|
|
-import {
|
|
|
- BigNumberish,
|
|
|
- Liquidity,
|
|
|
- LIQUIDITY_STATE_LAYOUT_V4,
|
|
|
- LiquidityPoolKeys,
|
|
|
- LiquidityStateV4,
|
|
|
- MARKET_STATE_LAYOUT_V3,
|
|
|
- MarketStateV3,
|
|
|
- Token,
|
|
|
- TokenAmount,
|
|
|
-} from '@raydium-io/raydium-sdk';
|
|
|
-import {
|
|
|
- AccountLayout,
|
|
|
- createAssociatedTokenAccountIdempotentInstruction,
|
|
|
- createCloseAccountInstruction,
|
|
|
- getAssociatedTokenAddressSync,
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
-} from '@solana/spl-token';
|
|
|
-import {
|
|
|
- Keypair,
|
|
|
- Connection,
|
|
|
- PublicKey,
|
|
|
- ComputeBudgetProgram,
|
|
|
- KeyedAccountInfo,
|
|
|
- TransactionMessage,
|
|
|
- VersionedTransaction,
|
|
|
-} from '@solana/web3.js';
|
|
|
-import { getTokenAccounts, RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, OPENBOOK_PROGRAM_ID, createPoolKeys } from './liquidity';
|
|
|
-import { logger } from './utils';
|
|
|
-import { getMinimalMarketV3, MinimalMarketLayoutV3 } from './market';
|
|
|
-import { MintLayout } from './types';
|
|
|
-import bs58 from 'bs58';
|
|
|
-import * as fs from 'fs';
|
|
|
-import * as path from 'path';
|
|
|
-import {
|
|
|
- AUTO_SELL,
|
|
|
- AUTO_SELL_DELAY,
|
|
|
- CHECK_IF_MINT_IS_RENOUNCED,
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- LOG_LEVEL,
|
|
|
- MAX_SELL_RETRIES,
|
|
|
- NETWORK,
|
|
|
- PRIVATE_KEY,
|
|
|
- QUOTE_AMOUNT,
|
|
|
- QUOTE_MINT,
|
|
|
- RPC_ENDPOINT,
|
|
|
- RPC_WEBSOCKET_ENDPOINT,
|
|
|
- SNIPE_LIST_REFRESH_INTERVAL,
|
|
|
- USE_SNIPE_LIST,
|
|
|
- MIN_POOL_SIZE,
|
|
|
- MAX_POOL_SIZE,
|
|
|
- ONE_TOKEN_AT_A_TIME,
|
|
|
-} from './constants';
|
|
|
-
|
|
|
-const solanaConnection = new Connection(RPC_ENDPOINT, {
|
|
|
- wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
|
|
|
-});
|
|
|
-
|
|
|
-export interface MinimalTokenAccountData {
|
|
|
- mint: PublicKey;
|
|
|
- address: PublicKey;
|
|
|
- poolKeys?: LiquidityPoolKeys;
|
|
|
- market?: MinimalMarketLayoutV3;
|
|
|
-}
|
|
|
-
|
|
|
-const existingLiquidityPools: Set<string> = new Set<string>();
|
|
|
-const existingOpenBookMarkets: Set<string> = new Set<string>();
|
|
|
-const existingTokenAccounts: Map<string, MinimalTokenAccountData> = new Map<string, MinimalTokenAccountData>();
|
|
|
-
|
|
|
-let wallet: Keypair;
|
|
|
-let quoteToken: Token;
|
|
|
-let quoteTokenAssociatedAddress: PublicKey;
|
|
|
-let quoteAmount: TokenAmount;
|
|
|
-let quoteMinPoolSizeAmount: TokenAmount;
|
|
|
-let quoteMaxPoolSizeAmount: TokenAmount;
|
|
|
-let processingToken: Boolean = false;
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-let snipeList: string[] = [];
|
|
|
-
|
|
|
-async function init(): Promise<void> {
|
|
|
- logger.level = LOG_LEVEL;
|
|
|
-
|
|
|
- // get wallet
|
|
|
- wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
|
|
|
- logger.info(`Wallet Address: ${wallet.publicKey}`);
|
|
|
-
|
|
|
- // get quote mint and amount
|
|
|
- switch (QUOTE_MINT) {
|
|
|
- case 'WSOL': {
|
|
|
- quoteToken = Token.WSOL;
|
|
|
- quoteAmount = new TokenAmount(Token.WSOL, QUOTE_AMOUNT, false);
|
|
|
- quoteMinPoolSizeAmount = new TokenAmount(quoteToken, MIN_POOL_SIZE, false);
|
|
|
- quoteMaxPoolSizeAmount = new TokenAmount(quoteToken, MAX_POOL_SIZE, false);
|
|
|
- break;
|
|
|
- }
|
|
|
- case 'USDC': {
|
|
|
- quoteToken = new Token(
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
- new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
|
|
|
- 6,
|
|
|
- 'USDC',
|
|
|
- 'USDC',
|
|
|
- );
|
|
|
- quoteAmount = new TokenAmount(quoteToken, QUOTE_AMOUNT, false);
|
|
|
- quoteMinPoolSizeAmount = new TokenAmount(quoteToken, MIN_POOL_SIZE, false);
|
|
|
- quoteMaxPoolSizeAmount = new TokenAmount(quoteToken, MAX_POOL_SIZE, false);
|
|
|
- break;
|
|
|
- }
|
|
|
- default: {
|
|
|
- throw new Error(`Unsupported quote mint "${QUOTE_MINT}". Supported values are USDC and WSOL`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- logger.info(`Snipe list: ${USE_SNIPE_LIST}`);
|
|
|
- logger.info(`Check mint renounced: ${CHECK_IF_MINT_IS_RENOUNCED}`);
|
|
|
- logger.info(
|
|
|
- `Min pool size: ${quoteMinPoolSizeAmount.isZero() ? 'false' : quoteMinPoolSizeAmount.toFixed()} ${quoteToken.symbol}`,
|
|
|
- );
|
|
|
- logger.info(
|
|
|
- `Max pool size: ${quoteMaxPoolSizeAmount.isZero() ? 'false' : quoteMaxPoolSizeAmount.toFixed()} ${quoteToken.symbol}`,
|
|
|
- );
|
|
|
- logger.info(`One token at a time: ${ONE_TOKEN_AT_A_TIME}`);
|
|
|
- logger.info(`Buy amount: ${quoteAmount.toFixed()} ${quoteToken.symbol}`);
|
|
|
- logger.info(`Auto sell: ${AUTO_SELL}`);
|
|
|
- logger.info(`Sell delay: ${AUTO_SELL_DELAY === 0 ? 'false' : AUTO_SELL_DELAY}`);
|
|
|
-
|
|
|
- // check existing wallet for associated token account of quote mint
|
|
|
- const tokenAccounts = await getTokenAccounts(solanaConnection, wallet.publicKey, COMMITMENT_LEVEL);
|
|
|
-
|
|
|
- for (const ta of tokenAccounts) {
|
|
|
- existingTokenAccounts.set(ta.accountInfo.mint.toString(), <MinimalTokenAccountData>{
|
|
|
- mint: ta.accountInfo.mint,
|
|
|
- address: ta.pubkey,
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- const tokenAccount = tokenAccounts.find((acc) => acc.accountInfo.mint.toString() === quoteToken.mint.toString())!;
|
|
|
-
|
|
|
- if (!tokenAccount) {
|
|
|
- throw new Error(`No ${quoteToken.symbol} token account found in wallet: ${wallet.publicKey}`);
|
|
|
- }
|
|
|
-
|
|
|
- quoteTokenAssociatedAddress = tokenAccount.pubkey;
|
|
|
-
|
|
|
- // load tokens to snipe
|
|
|
- loadSnipeList();
|
|
|
-}
|
|
|
-
|
|
|
-function saveTokenAccount(mint: PublicKey, accountData: MinimalMarketLayoutV3) {
|
|
|
- const ata = getAssociatedTokenAddressSync(mint, wallet.publicKey);
|
|
|
- const tokenAccount = <MinimalTokenAccountData>{
|
|
|
- address: ata,
|
|
|
- mint: mint,
|
|
|
- market: <MinimalMarketLayoutV3>{
|
|
|
- bids: accountData.bids,
|
|
|
- asks: accountData.asks,
|
|
|
- eventQueue: accountData.eventQueue,
|
|
|
- },
|
|
|
- };
|
|
|
- existingTokenAccounts.set(mint.toString(), tokenAccount);
|
|
|
- return tokenAccount;
|
|
|
-}
|
|
|
-
|
|
|
-export async function processRaydiumPool(id: PublicKey, poolState: LiquidityStateV4) {
|
|
|
- if (!shouldBuy(poolState.baseMint.toString())) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!quoteMinPoolSizeAmount.isZero()) {
|
|
|
- const poolSize = new TokenAmount(quoteToken, poolState.swapQuoteInAmount, true);
|
|
|
- logger.info(`Processing pool: ${id.toString()} with ${poolSize.toFixed()} ${quoteToken.symbol} in liquidity`);
|
|
|
-
|
|
|
- if (poolSize.lt(quoteMinPoolSizeAmount)) {
|
|
|
- logger.warn(
|
|
|
- {
|
|
|
- mint: poolState.baseMint,
|
|
|
- pooled: `${poolSize.toFixed()} ${quoteToken.symbol}`,
|
|
|
- },
|
|
|
- `Skipping pool, smaller than ${quoteMinPoolSizeAmount.toFixed()} ${quoteToken.symbol}`,
|
|
|
- `Swap quote in amount: ${poolSize.toFixed()}`,
|
|
|
- );
|
|
|
- logger.info(`-------------------🤖🔧------------------- \n`);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!quoteMaxPoolSizeAmount.isZero()) {
|
|
|
- const poolSize = new TokenAmount(quoteToken, poolState.swapQuoteInAmount, true);
|
|
|
-
|
|
|
- if (poolSize.gt(quoteMaxPoolSizeAmount)) {
|
|
|
- logger.warn(
|
|
|
- {
|
|
|
- mint: poolState.baseMint,
|
|
|
- pooled: `${poolSize.toFixed()} ${quoteToken.symbol}`,
|
|
|
- },
|
|
|
- `Skipping pool, bigger than ${quoteMaxPoolSizeAmount.toFixed()} ${quoteToken.symbol}`,
|
|
|
- `Swap quote in amount: ${poolSize.toFixed()}`,
|
|
|
- );
|
|
|
- logger.info(`-------------------🤖🔧------------------- \n`);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (CHECK_IF_MINT_IS_RENOUNCED) {
|
|
|
- const mintOption = await checkMintable(poolState.baseMint);
|
|
|
-
|
|
|
- if (mintOption !== true) {
|
|
|
- logger.warn({ mint: poolState.baseMint }, 'Skipping, owner can mint tokens!');
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- await buy(id, poolState);
|
|
|
-}
|
|
|
-
|
|
|
-export async function checkMintable(vault: PublicKey): Promise<boolean | undefined> {
|
|
|
- try {
|
|
|
- let { data } = (await solanaConnection.getAccountInfo(vault)) || {};
|
|
|
- if (!data) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const deserialize = MintLayout.decode(data);
|
|
|
- return deserialize.mintAuthorityOption === 0;
|
|
|
- } catch (e) {
|
|
|
- logger.debug(e);
|
|
|
- logger.error({ mint: vault }, `Failed to check if mint is renounced`);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export async function processOpenBookMarket(updatedAccountInfo: KeyedAccountInfo) {
|
|
|
- let accountData: MarketStateV3 | undefined;
|
|
|
- try {
|
|
|
- accountData = MARKET_STATE_LAYOUT_V3.decode(updatedAccountInfo.accountInfo.data);
|
|
|
-
|
|
|
- // to be competitive, we collect market data before buying the token...
|
|
|
- if (existingTokenAccounts.has(accountData.baseMint.toString())) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- saveTokenAccount(accountData.baseMint, accountData);
|
|
|
- } catch (e) {
|
|
|
- logger.debug(e);
|
|
|
- logger.error({ mint: accountData?.baseMint }, `Failed to process market`);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function buy(accountId: PublicKey, accountData: LiquidityStateV4): Promise<void> {
|
|
|
- try {
|
|
|
- let tokenAccount = existingTokenAccounts.get(accountData.baseMint.toString());
|
|
|
-
|
|
|
- if (!tokenAccount) {
|
|
|
- // it's possible that we didn't have time to fetch open book data
|
|
|
- const market = await getMinimalMarketV3(solanaConnection, accountData.marketId, COMMITMENT_LEVEL);
|
|
|
- tokenAccount = saveTokenAccount(accountData.baseMint, market);
|
|
|
- }
|
|
|
-
|
|
|
- tokenAccount.poolKeys = createPoolKeys(accountId, accountData, tokenAccount.market!);
|
|
|
- const { innerTransaction } = Liquidity.makeSwapFixedInInstruction(
|
|
|
- {
|
|
|
- poolKeys: tokenAccount.poolKeys,
|
|
|
- userKeys: {
|
|
|
- tokenAccountIn: quoteTokenAssociatedAddress,
|
|
|
- tokenAccountOut: tokenAccount.address,
|
|
|
- owner: wallet.publicKey,
|
|
|
- },
|
|
|
- amountIn: quoteAmount.raw,
|
|
|
- minAmountOut: 0,
|
|
|
- },
|
|
|
- tokenAccount.poolKeys.version,
|
|
|
- );
|
|
|
-
|
|
|
- const latestBlockhash = await solanaConnection.getLatestBlockhash({
|
|
|
- commitment: COMMITMENT_LEVEL,
|
|
|
- });
|
|
|
- const messageV0 = new TransactionMessage({
|
|
|
- payerKey: wallet.publicKey,
|
|
|
- recentBlockhash: latestBlockhash.blockhash,
|
|
|
- instructions: [
|
|
|
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 421197 }),
|
|
|
- ComputeBudgetProgram.setComputeUnitLimit({ units: 101337 }),
|
|
|
- createAssociatedTokenAccountIdempotentInstruction(
|
|
|
- wallet.publicKey,
|
|
|
- tokenAccount.address,
|
|
|
- wallet.publicKey,
|
|
|
- accountData.baseMint,
|
|
|
- ),
|
|
|
- ...innerTransaction.instructions,
|
|
|
- ],
|
|
|
- }).compileToV0Message();
|
|
|
- const transaction = new VersionedTransaction(messageV0);
|
|
|
- transaction.sign([wallet, ...innerTransaction.signers]);
|
|
|
- const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), {
|
|
|
- preflightCommitment: COMMITMENT_LEVEL,
|
|
|
- });
|
|
|
- logger.info({ mint: accountData.baseMint, signature }, `Sent buy tx`);
|
|
|
- processingToken = true;
|
|
|
-
|
|
|
- const confirmation = await solanaConnection.confirmTransaction(
|
|
|
- {
|
|
|
- signature,
|
|
|
- lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
|
- blockhash: latestBlockhash.blockhash,
|
|
|
- },
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- );
|
|
|
- if (!confirmation.value.err) {
|
|
|
- logger.info(`-------------------🟢------------------- `);
|
|
|
- logger.info(
|
|
|
- {
|
|
|
- mint: accountData.baseMint,
|
|
|
- signature,
|
|
|
- url: `https://solscan.io/tx/${signature}?cluster=${NETWORK}`,
|
|
|
- },
|
|
|
- `Confirmed buy tx`,
|
|
|
- );
|
|
|
- } else {
|
|
|
- logger.debug(confirmation.value.err);
|
|
|
- logger.info({ mint: accountData.baseMint, signature }, `Error confirming buy tx`);
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- logger.debug(e);
|
|
|
- processingToken = false;
|
|
|
- logger.error({ mint: accountData.baseMint }, `Failed to buy token`);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function sell(accountId: PublicKey, mint: PublicKey, amount: BigNumberish): Promise<void> {
|
|
|
- let sold = false;
|
|
|
- let retries = 0;
|
|
|
-
|
|
|
- if (AUTO_SELL_DELAY > 0) {
|
|
|
- await new Promise((resolve) => setTimeout(resolve, AUTO_SELL_DELAY));
|
|
|
- }
|
|
|
-
|
|
|
- do {
|
|
|
- try {
|
|
|
- const tokenAccount = existingTokenAccounts.get(mint.toString());
|
|
|
-
|
|
|
- if (!tokenAccount) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!tokenAccount.poolKeys) {
|
|
|
- logger.warn({ mint }, 'No pool keys found');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (amount === 0) {
|
|
|
- logger.info(
|
|
|
- {
|
|
|
- mint: tokenAccount.mint,
|
|
|
- },
|
|
|
- `Empty balance, can't sell`,
|
|
|
- );
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const { innerTransaction } = Liquidity.makeSwapFixedInInstruction(
|
|
|
- {
|
|
|
- poolKeys: tokenAccount.poolKeys!,
|
|
|
- userKeys: {
|
|
|
- tokenAccountOut: quoteTokenAssociatedAddress,
|
|
|
- tokenAccountIn: tokenAccount.address,
|
|
|
- owner: wallet.publicKey,
|
|
|
- },
|
|
|
- amountIn: amount,
|
|
|
- minAmountOut: 0,
|
|
|
- },
|
|
|
- tokenAccount.poolKeys!.version,
|
|
|
- );
|
|
|
-
|
|
|
- const latestBlockhash = await solanaConnection.getLatestBlockhash({
|
|
|
- commitment: COMMITMENT_LEVEL,
|
|
|
- });
|
|
|
- const messageV0 = new TransactionMessage({
|
|
|
- payerKey: wallet.publicKey,
|
|
|
- recentBlockhash: latestBlockhash.blockhash,
|
|
|
- instructions: [
|
|
|
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 421197 }),
|
|
|
- ComputeBudgetProgram.setComputeUnitLimit({ units: 101337 }),
|
|
|
- ...innerTransaction.instructions,
|
|
|
- createCloseAccountInstruction(tokenAccount.address, wallet.publicKey, wallet.publicKey),
|
|
|
- ],
|
|
|
- }).compileToV0Message();
|
|
|
- const transaction = new VersionedTransaction(messageV0);
|
|
|
- transaction.sign([wallet, ...innerTransaction.signers]);
|
|
|
- const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), {
|
|
|
- preflightCommitment: COMMITMENT_LEVEL,
|
|
|
- });
|
|
|
- logger.info({ mint, signature }, `Sent sell tx`);
|
|
|
- const confirmation = await solanaConnection.confirmTransaction(
|
|
|
- {
|
|
|
- signature,
|
|
|
- lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
|
- blockhash: latestBlockhash.blockhash,
|
|
|
- },
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- );
|
|
|
- if (confirmation.value.err) {
|
|
|
- logger.debug(confirmation.value.err);
|
|
|
- logger.info({ mint, signature }, `Error confirming sell tx`);
|
|
|
- continue;
|
|
|
- }
|
|
|
- logger.info(`-------------------🔴------------------- `);
|
|
|
- logger.info(
|
|
|
- {
|
|
|
- dex: `https://dexscreener.com/solana/${mint}?maker=${wallet.publicKey}`,
|
|
|
- mint,
|
|
|
- signature,
|
|
|
- url: `https://solscan.io/tx/${signature}?cluster=${NETWORK}`,
|
|
|
- },
|
|
|
- `Confirmed sell tx`,
|
|
|
- );
|
|
|
- sold = true;
|
|
|
- processingToken = false;
|
|
|
- } catch (e: any) {
|
|
|
- // wait for a bit before retrying
|
|
|
- await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
- retries++;
|
|
|
- logger.debug(e);
|
|
|
- logger.error({ mint }, `Failed to sell token, retry: ${retries}/${MAX_SELL_RETRIES}`);
|
|
|
- }
|
|
|
- } while (!sold && retries < MAX_SELL_RETRIES);
|
|
|
- processingToken = false;
|
|
|
-}
|
|
|
-
|
|
|
-function loadSnipeList() {
|
|
|
- if (!USE_SNIPE_LIST) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const count = snipeList.length;
|
|
|
- const data = fs.readFileSync(path.join(__dirname, 'snipe-list.txt'), 'utf-8');
|
|
|
- snipeList = data
|
|
|
- .split('\n')
|
|
|
- .map((a) => a.trim())
|
|
|
- .filter((a) => a);
|
|
|
-
|
|
|
- if (snipeList.length != count) {
|
|
|
- logger.info(`Loaded snipe list: ${snipeList.length}`);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function shouldBuy(key: string): boolean {
|
|
|
- logger.info(`-------------------🤖🔧------------------- `);
|
|
|
- logger.info(`Processing token: ${processingToken}`)
|
|
|
- return USE_SNIPE_LIST ? snipeList.includes(key) : ONE_TOKEN_AT_A_TIME ? !processingToken : true
|
|
|
-}
|
|
|
-
|
|
|
-const runListener = async () => {
|
|
|
- await init();
|
|
|
- const runTimestamp = Math.floor(new Date().getTime() / 1000);
|
|
|
- const raydiumSubscriptionId = solanaConnection.onProgramAccountChange(
|
|
|
- RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
|
|
- async (updatedAccountInfo) => {
|
|
|
- const key = updatedAccountInfo.accountId.toString();
|
|
|
- const poolState = LIQUIDITY_STATE_LAYOUT_V4.decode(updatedAccountInfo.accountInfo.data);
|
|
|
- const poolOpenTime = parseInt(poolState.poolOpenTime.toString());
|
|
|
- const existing = existingLiquidityPools.has(key);
|
|
|
-
|
|
|
- if (poolOpenTime > runTimestamp && !existing) {
|
|
|
- existingLiquidityPools.add(key);
|
|
|
- const _ = processRaydiumPool(updatedAccountInfo.accountId, poolState);
|
|
|
- }
|
|
|
- },
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- [
|
|
|
- { dataSize: LIQUIDITY_STATE_LAYOUT_V4.span },
|
|
|
- {
|
|
|
- memcmp: {
|
|
|
- offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'),
|
|
|
- bytes: quoteToken.mint.toBase58(),
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- memcmp: {
|
|
|
- offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('marketProgramId'),
|
|
|
- bytes: OPENBOOK_PROGRAM_ID.toBase58(),
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- memcmp: {
|
|
|
- offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('status'),
|
|
|
- bytes: bs58.encode([6, 0, 0, 0, 0, 0, 0, 0]),
|
|
|
- },
|
|
|
- },
|
|
|
- ],
|
|
|
- );
|
|
|
-
|
|
|
- const openBookSubscriptionId = solanaConnection.onProgramAccountChange(
|
|
|
- OPENBOOK_PROGRAM_ID,
|
|
|
- async (updatedAccountInfo) => {
|
|
|
- const key = updatedAccountInfo.accountId.toString();
|
|
|
- const existing = existingOpenBookMarkets.has(key);
|
|
|
- if (!existing) {
|
|
|
- existingOpenBookMarkets.add(key);
|
|
|
- const _ = processOpenBookMarket(updatedAccountInfo);
|
|
|
- }
|
|
|
- },
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- [
|
|
|
- { dataSize: MARKET_STATE_LAYOUT_V3.span },
|
|
|
- {
|
|
|
- memcmp: {
|
|
|
- offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'),
|
|
|
- bytes: quoteToken.mint.toBase58(),
|
|
|
- },
|
|
|
- },
|
|
|
- ],
|
|
|
- );
|
|
|
-
|
|
|
- if (AUTO_SELL) {
|
|
|
- const walletSubscriptionId = solanaConnection.onProgramAccountChange(
|
|
|
- TOKEN_PROGRAM_ID,
|
|
|
- async (updatedAccountInfo) => {
|
|
|
- const accountData = AccountLayout.decode(updatedAccountInfo.accountInfo!.data);
|
|
|
-
|
|
|
- if (updatedAccountInfo.accountId.equals(quoteTokenAssociatedAddress)) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const _ = sell(updatedAccountInfo.accountId, accountData.mint, accountData.amount);
|
|
|
- },
|
|
|
- COMMITMENT_LEVEL,
|
|
|
- [
|
|
|
- {
|
|
|
- dataSize: 165,
|
|
|
- },
|
|
|
- {
|
|
|
- memcmp: {
|
|
|
- offset: 32,
|
|
|
- bytes: wallet.publicKey.toBase58(),
|
|
|
- },
|
|
|
- },
|
|
|
- ],
|
|
|
- );
|
|
|
-
|
|
|
- logger.info(`Listening for wallet changes: ${walletSubscriptionId}`);
|
|
|
- }
|
|
|
-
|
|
|
- logger.info(`Listening for raydium changes: ${raydiumSubscriptionId}`);
|
|
|
- logger.info(`Listening for open book changes: ${openBookSubscriptionId}`);
|
|
|
-
|
|
|
- logger.info('------------------- 🚀 ---------------------');
|
|
|
- logger.info('Bot is running! Press CTRL + C to stop it.');
|
|
|
- logger.info('------------------- 🚀 ---------------------');
|
|
|
-
|
|
|
- if (USE_SNIPE_LIST) {
|
|
|
- setInterval(loadSnipeList, SNIPE_LIST_REFRESH_INTERVAL);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-runListener();
|