feat: add support for buying only specified tokens

This commit is contained in:
Filip Dunder
2024-02-03 10:12:42 +01:00
parent 61531d06a7
commit 9e1b2da0f1
4 changed files with 84 additions and 17 deletions

76
buy.ts
View File

@ -35,6 +35,8 @@ import { retrieveEnvVariable } from './utils';
import { getAllMarketsV3, MinimalMarketLayoutV3 } from './market';
import pino from 'pino';
import bs58 from 'bs58';
import * as fs from 'fs';
import * as path from 'path';
const transport = pino.transport({
targets: [
@ -80,7 +82,6 @@ const solanaConnection = new Connection(RPC_ENDPOINT, {
export type MinimalTokenAccountData = {
mint: PublicKey;
address: PublicKey;
ata: PublicKey;
poolKeys?: LiquidityPoolKeys;
market?: MinimalMarketLayoutV3;
};
@ -96,7 +97,16 @@ let wallet: Keypair;
let quoteToken: Token;
let quoteTokenAssociatedAddress: PublicKey;
let quoteAmount: TokenAmount;
let commitment: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment;
let commitment: Commitment = retrieveEnvVariable(
'COMMITMENT_LEVEL',
logger,
) as Commitment;
const USE_SNIPE_LIST = Boolean(retrieveEnvVariable('USE_SNIPE_LIST', logger));
const SNIPE_LIST_REFRESH_INTERVAL = Number(
retrieveEnvVariable('SNIPE_LIST_REFRESH_INTERVAL', logger),
);
let snipeList: string[] = [];
async function init(): Promise<void> {
// get wallet
@ -132,7 +142,7 @@ async function init(): Promise<void> {
}
logger.info(
`Script will buy all new tokens using ${QUOTE_MINT}. Amount that will be used to buy each token is: ${quoteAmount.toFixed().toString()}`
`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
@ -146,13 +156,12 @@ async function init(): Promise<void> {
);
// get all open-book markets
const allMarkets = await getAllMarketsV3(solanaConnection, quoteToken.mint, commitment);
existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
const tokenAccounts = await getTokenAccounts(
const allMarkets = await getAllMarketsV3(
solanaConnection,
wallet.publicKey,
quoteToken.mint,
commitment,
);
existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
logger.info(
`Total ${quoteToken.symbol} markets ${existingOpenBookMarkets.size}`,
@ -162,6 +171,12 @@ async function init(): Promise<void> {
);
// check existing wallet for associated token account of quote mint
const tokenAccounts = await getTokenAccounts(
solanaConnection,
wallet.publicKey,
commitment,
);
for (const ta of tokenAccounts) {
existingTokenAccounts.set(ta.accountInfo.mint.toString(), <
MinimalTokenAccountData
@ -182,6 +197,9 @@ async function init(): Promise<void> {
}
quoteTokenAssociatedAddress = tokenAccount.pubkey;
// load tokens to snipe
loadSnipeList();
}
export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
@ -190,6 +208,11 @@ export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
accountData = LIQUIDITY_STATE_LAYOUT_V4.decode(
updatedAccountInfo.accountInfo.data,
);
if (!shouldBuy(accountData.baseMint.toString())) {
return;
}
await buy(updatedAccountInfo.accountId, accountData);
} catch (e) {
logger.error({ ...accountData, error: e }, `Failed to process pool`);
@ -297,16 +320,36 @@ async function buy(
);
}
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 {
return USE_SNIPE_LIST ? snipeList.includes(key) : true;
}
const runListener = async () => {
await init();
const raydiumSubscriptionId = solanaConnection.onProgramAccountChange(
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
async (updatedAccountInfo) => {
const existing = existingLiquidityPools.has(
updatedAccountInfo.accountId.toString(),
);
const key = updatedAccountInfo.accountId.toString();
const existing = existingLiquidityPools.has(key);
if (!existing) {
existingLiquidityPools.add(updatedAccountInfo.accountId.toString());
existingLiquidityPools.add(key);
const _ = processRaydiumPool(updatedAccountInfo);
}
},
@ -337,11 +380,10 @@ const runListener = async () => {
const openBookSubscriptionId = solanaConnection.onProgramAccountChange(
OPENBOOK_PROGRAM_ID,
async (updatedAccountInfo) => {
const existing = existingOpenBookMarkets.has(
updatedAccountInfo.accountId.toString(),
);
const key = updatedAccountInfo.accountId.toString();
const existing = existingOpenBookMarkets.has(key);
if (!existing) {
existingOpenBookMarkets.add(updatedAccountInfo.accountId.toString());
existingOpenBookMarkets.add(key);
const _ = processOpenBookMarket(updatedAccountInfo);
}
},
@ -359,6 +401,10 @@ const runListener = async () => {
logger.info(`Listening for raydium changes: ${raydiumSubscriptionId}`);
logger.info(`Listening for open book changes: ${openBookSubscriptionId}`);
if (USE_SNIPE_LIST) {
setInterval(loadSnipeList, SNIPE_LIST_REFRESH_INTERVAL);
}
};
runListener();