From 742357b953bf0e6491e057bb0c96bc53842f730a Mon Sep 17 00:00:00 2001 From: Christopher Mael Date: Sat, 23 Mar 2024 17:08:04 +0100 Subject: [PATCH 1/7] move readme images to readme/ folder --- output.png => readme/output.png | Bin wsol.png => readme/wsol.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename output.png => readme/output.png (100%) rename wsol.png => readme/wsol.png (100%) diff --git a/output.png b/readme/output.png similarity index 100% rename from output.png rename to readme/output.png diff --git a/wsol.png b/readme/wsol.png similarity index 100% rename from wsol.png rename to readme/wsol.png From 193be74fcba188e9dec00487c69a4442ef40184e Mon Sep 17 00:00:00 2001 From: Christopher Mael Date: Sat, 23 Mar 2024 17:12:03 +0100 Subject: [PATCH 2/7] add tsc script for checking types --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 18af0e5..75ea9c1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "solana-sniper-bot", "author": "Filip Dundjer", "scripts": { - "buy": "ts-node buy.ts" + "buy": "ts-node buy.ts", + "tsc": "tsc --noEmit" }, "dependencies": { "@raydium-io/raydium-sdk": "^1.3.1-beta.47", From 63009f17df5524f6325d5d4cf7ad41465515d56f Mon Sep 17 00:00:00 2001 From: Christopher Mael Date: Sat, 23 Mar 2024 17:13:24 +0100 Subject: [PATCH 3/7] move logger to utils/logger --- buy.ts | 18 +----------------- utils/index.ts | 1 + utils/logger.ts | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 utils/logger.ts diff --git a/buy.ts b/buy.ts index fc9cccd..30e0ba8 100644 --- a/buy.ts +++ b/buy.ts @@ -27,29 +27,13 @@ import { Commitment, } from '@solana/web3.js'; import { getTokenAccounts, RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, OPENBOOK_PROGRAM_ID, createPoolKeys } from './liquidity'; -import { retrieveEnvVariable } from './utils'; +import { logger, retrieveEnvVariable } from './utils'; import { getMinimalMarketV3, MinimalMarketLayoutV3 } from './market'; import { MintLayout } from './types'; -import pino from 'pino'; import bs58 from 'bs58'; import * as fs from 'fs'; import * as path from 'path'; -const transport = pino.transport({ - target: 'pino-pretty', -}); - -export const logger = pino( - { - level: 'info', - redact: ['poolKeys'], - serializers: { - error: pino.stdSerializers.err, - }, - base: undefined, - }, - transport, -); const network = 'mainnet-beta'; const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger); diff --git a/utils/index.ts b/utils/index.ts index 04bca77..d90cdf8 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1 +1,2 @@ export * from './utils'; +export * from './logger'; \ No newline at end of file diff --git a/utils/logger.ts b/utils/logger.ts new file mode 100644 index 0000000..393068e --- /dev/null +++ b/utils/logger.ts @@ -0,0 +1,17 @@ +import pino from "pino"; + +const transport = pino.transport({ + target: 'pino-pretty', +}); + +export const logger = pino( + { + level: 'info', + redact: ['poolKeys'], + serializers: { + error: pino.stdSerializers.err, + }, + base: undefined, + }, + transport, +); From 9192b3dc12aa43f79773ff2e6c06ac294fd11578 Mon Sep 17 00:00:00 2001 From: Christopher Mael Date: Sat, 23 Mar 2024 17:19:36 +0100 Subject: [PATCH 4/7] separate constants out to constants/ --- buy.ts | 62 ++++++++++++++++++++---------------------- constants/constants.ts | 17 ++++++++++++ constants/index.ts | 1 + 3 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 constants/constants.ts create mode 100644 constants/index.ts diff --git a/buy.ts b/buy.ts index 30e0ba8..1eeff05 100644 --- a/buy.ts +++ b/buy.ts @@ -24,21 +24,30 @@ import { KeyedAccountInfo, TransactionMessage, VersionedTransaction, - Commitment, } from '@solana/web3.js'; import { getTokenAccounts, RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, OPENBOOK_PROGRAM_ID, createPoolKeys } from './liquidity'; -import { logger, retrieveEnvVariable } from './utils'; +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'; - - -const network = 'mainnet-beta'; -const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger); -const RPC_WEBSOCKET_ENDPOINT = retrieveEnvVariable('RPC_WEBSOCKET_ENDPOINT', logger); -const LOG_LEVEL = retrieveEnvVariable('LOG_LEVEL', logger); +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, +} from './constants'; const solanaConnection = new Connection(RPC_ENDPOINT, { wsEndpoint: RPC_WEBSOCKET_ENDPOINT, @@ -59,14 +68,6 @@ let wallet: Keypair; let quoteToken: Token; let quoteTokenAssociatedAddress: PublicKey; let quoteAmount: TokenAmount; -let commitment: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment; - -const CHECK_IF_MINT_IS_RENOUNCED = retrieveEnvVariable('CHECK_IF_MINT_IS_RENOUNCED', logger) === 'true'; -const USE_SNIPE_LIST = retrieveEnvVariable('USE_SNIPE_LIST', logger) === 'true'; -const SNIPE_LIST_REFRESH_INTERVAL = Number(retrieveEnvVariable('SNIPE_LIST_REFRESH_INTERVAL', logger)); -const AUTO_SELL = retrieveEnvVariable('AUTO_SELL', logger) === 'true'; -const MAX_SELL_RETRIES = Number(retrieveEnvVariable('MAX_SELL_RETRIES', logger)); -const AUTO_SELL_DELAY = Number(retrieveEnvVariable('AUTO_SELL_DELAY', logger)); let snipeList: string[] = []; @@ -74,13 +75,10 @@ async function init(): Promise { logger.level = LOG_LEVEL; // get wallet - const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger); wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); 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; @@ -108,7 +106,7 @@ async function init(): Promise { ); // check existing wallet for associated token account of quote mint - const tokenAccounts = await getTokenAccounts(solanaConnection, wallet.publicKey, commitment); + const tokenAccounts = await getTokenAccounts(solanaConnection, wallet.publicKey, COMMITMENT_LEVEL); for (const ta of tokenAccounts) { existingTokenAccounts.set(ta.accountInfo.mint.toString(), { @@ -198,7 +196,7 @@ async function buy(accountId: PublicKey, accountData: LiquidityStateV4): Promise if (!tokenAccount) { // it's possible that we didn't have time to fetch open book data - const market = await getMinimalMarketV3(solanaConnection, accountData.marketId, commitment); + const market = await getMinimalMarketV3(solanaConnection, accountData.marketId, COMMITMENT_LEVEL); tokenAccount = saveTokenAccount(accountData.baseMint, market); } @@ -218,7 +216,7 @@ async function buy(accountId: PublicKey, accountData: LiquidityStateV4): Promise ); const latestBlockhash = await solanaConnection.getLatestBlockhash({ - commitment: commitment, + commitment: COMMITMENT_LEVEL, }); const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, @@ -238,7 +236,7 @@ async function buy(accountId: PublicKey, accountData: LiquidityStateV4): Promise const transaction = new VersionedTransaction(messageV0); transaction.sign([wallet, ...innerTransaction.signers]); const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), { - preflightCommitment: commitment, + preflightCommitment: COMMITMENT_LEVEL, }); logger.info({ mint: accountData.baseMint, signature }, `Sent buy tx`); const confirmation = await solanaConnection.confirmTransaction( @@ -247,14 +245,14 @@ async function buy(accountId: PublicKey, accountData: LiquidityStateV4): Promise lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, blockhash: latestBlockhash.blockhash, }, - commitment, + COMMITMENT_LEVEL, ); if (!confirmation.value.err) { logger.info( { mint: accountData.baseMint, signature, - url: `https://solscan.io/tx/${signature}?cluster=${network}`, + url: `https://solscan.io/tx/${signature}?cluster=${NETWORK}`, }, `Confirmed buy tx`, ); @@ -314,7 +312,7 @@ async function sell(accountId: PublicKey, mint: PublicKey, amount: BigNumberish) ); const latestBlockhash = await solanaConnection.getLatestBlockhash({ - commitment: commitment, + commitment: COMMITMENT_LEVEL, }); const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, @@ -329,7 +327,7 @@ async function sell(accountId: PublicKey, mint: PublicKey, amount: BigNumberish) const transaction = new VersionedTransaction(messageV0); transaction.sign([wallet, ...innerTransaction.signers]); const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), { - preflightCommitment: commitment, + preflightCommitment: COMMITMENT_LEVEL, }); logger.info({ mint, signature }, `Sent sell tx`); const confirmation = await solanaConnection.confirmTransaction( @@ -338,7 +336,7 @@ async function sell(accountId: PublicKey, mint: PublicKey, amount: BigNumberish) lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, blockhash: latestBlockhash.blockhash, }, - commitment, + COMMITMENT_LEVEL, ); if (confirmation.value.err) { logger.debug(confirmation.value.err); @@ -351,7 +349,7 @@ async function sell(accountId: PublicKey, mint: PublicKey, amount: BigNumberish) dex: `https://dexscreener.com/solana/${mint}?maker=${wallet.publicKey}`, mint, signature, - url: `https://solscan.io/tx/${signature}?cluster=${network}`, + url: `https://solscan.io/tx/${signature}?cluster=${NETWORK}`, }, `Confirmed sell tx`, ); @@ -401,7 +399,7 @@ const runListener = async () => { const _ = processRaydiumPool(updatedAccountInfo.accountId, poolState); } }, - commitment, + COMMITMENT_LEVEL, [ { dataSize: LIQUIDITY_STATE_LAYOUT_V4.span }, { @@ -435,7 +433,7 @@ const runListener = async () => { const _ = processOpenBookMarket(updatedAccountInfo); } }, - commitment, + COMMITMENT_LEVEL, [ { dataSize: MARKET_STATE_LAYOUT_V3.span }, { @@ -459,7 +457,7 @@ const runListener = async () => { const _ = sell(updatedAccountInfo.accountId, accountData.mint, accountData.amount); }, - commitment, + COMMITMENT_LEVEL, [ { dataSize: 165, diff --git a/constants/constants.ts b/constants/constants.ts new file mode 100644 index 0000000..c0eceb3 --- /dev/null +++ b/constants/constants.ts @@ -0,0 +1,17 @@ +import { Commitment } from "@solana/web3.js"; +import { logger, retrieveEnvVariable } from "../utils"; + +export const NETWORK = 'mainnet-beta'; +export const COMMITMENT_LEVEL: Commitment = retrieveEnvVariable('COMMITMENT_LEVEL', logger) as Commitment; +export const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger); +export const RPC_WEBSOCKET_ENDPOINT = retrieveEnvVariable('RPC_WEBSOCKET_ENDPOINT', logger); +export const LOG_LEVEL = retrieveEnvVariable('LOG_LEVEL', logger); +export const CHECK_IF_MINT_IS_RENOUNCED = retrieveEnvVariable('CHECK_IF_MINT_IS_RENOUNCED', logger) === 'true'; +export const USE_SNIPE_LIST = retrieveEnvVariable('USE_SNIPE_LIST', logger) === 'true'; +export const SNIPE_LIST_REFRESH_INTERVAL = Number(retrieveEnvVariable('SNIPE_LIST_REFRESH_INTERVAL', logger)); +export const AUTO_SELL = retrieveEnvVariable('AUTO_SELL', logger) === 'true'; +export const MAX_SELL_RETRIES = Number(retrieveEnvVariable('MAX_SELL_RETRIES', logger)); +export const AUTO_SELL_DELAY = Number(retrieveEnvVariable('AUTO_SELL_DELAY', logger)); +export const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger); +export const QUOTE_MINT = retrieveEnvVariable('QUOTE_MINT', logger); +export const QUOTE_AMOUNT = retrieveEnvVariable('QUOTE_AMOUNT', logger); \ No newline at end of file diff --git a/constants/index.ts b/constants/index.ts new file mode 100644 index 0000000..e94e4b1 --- /dev/null +++ b/constants/index.ts @@ -0,0 +1 @@ +export * from './constants'; \ No newline at end of file From b168a6060c9e3833bc551ca239a9a9736504db71 Mon Sep 17 00:00:00 2001 From: Christopher Mael Date: Sat, 23 Mar 2024 17:30:05 +0100 Subject: [PATCH 5/7] make Sets and Map into consts --- buy.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buy.ts b/buy.ts index 1eeff05..4921e2d 100644 --- a/buy.ts +++ b/buy.ts @@ -53,16 +53,16 @@ const solanaConnection = new Connection(RPC_ENDPOINT, { wsEndpoint: RPC_WEBSOCKET_ENDPOINT, }); -export type MinimalTokenAccountData = { +export interface MinimalTokenAccountData { mint: PublicKey; address: PublicKey; poolKeys?: LiquidityPoolKeys; market?: MinimalMarketLayoutV3; }; -let existingLiquidityPools: Set = new Set(); -let existingOpenBookMarkets: Set = new Set(); -let existingTokenAccounts: Map = new Map(); +const existingLiquidityPools: Set = new Set(); +const existingOpenBookMarkets: Set = new Set(); +const existingTokenAccounts: Map = new Map(); let wallet: Keypair; let quoteToken: Token; From 65e4ee002a5de7e212fd172bd48893ed95f56d99 Mon Sep 17 00:00:00 2001 From: Filip Dunder Date: Sun, 24 Mar 2024 18:20:59 +0100 Subject: [PATCH 6/7] fix: readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d57ea0..7093f3b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ To run the script you need to: - Run the script by typing: `npm run buy` in terminal You should see the following output: -![output](output.png) +![output](readme/output.png) ## Snipe list By default, script buys each token which has a new liquidity pool created and open for trading. @@ -73,7 +73,7 @@ To collect more information on an issue, please change `LOG_LEVEL` to `debug`. 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) +![wsol](readme/wsol.png) ## Contact [![](https://img.shields.io/discord/1201826085655023616?color=5865F2&logo=Discord&style=flat-square)](https://discord.gg/xYUETCA2aP) From 1273fbb634b7e01de4c5740864592a227e14278a Mon Sep 17 00:00:00 2001 From: Filip Dunder Date: Sun, 24 Mar 2024 18:22:15 +0100 Subject: [PATCH 7/7] fix: var name --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7093f3b..ed6f26c 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ It will buy only when new pool is open for trading. If you want to buy token tha By default, auto sell is enabled. If you want to disable it, you need to: - Change variable `AUTO_SELL` to `false` - Update `MAX_SELL_RETRIES` to set the maximum number of retries for selling token -- Update `SELL_DELAY` to the number of milliseconds you want to wait before selling the token +- Update `AUTO_SELL_DELAY` to the number of milliseconds you want to wait before selling the token - This will sell the token after the specified delay. (+- RPC node speed) -If you set SELL_DELAY to 0, token will be sold immediately after it is bought. +If you set AUTO_SELL_DELAY to 0, token will be sold immediately after it is bought. There is no guarantee that the token will be sold at a profit or even sold at all. The developer is not responsible for any losses incurred by using this feature.