Merge branch 'master' into jour-master

# Conflicts:
#	buy.ts
This commit is contained in:
Filip Dunder
2024-03-24 18:25:50 +01:00
9 changed files with 78 additions and 59 deletions

View File

@ -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.
@ -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.
@ -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)

90
buy.ts
View File

@ -24,67 +24,52 @@ import {
KeyedAccountInfo,
TransactionMessage,
VersionedTransaction,
Commitment,
} from '@solana/web3.js';
import { getTokenAccounts, RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, OPENBOOK_PROGRAM_ID, createPoolKeys } from './liquidity';
import { retrieveEnvVariable } from './utils';
import { logger } from './utils';
import { getMinimalMarketV3, MinimalMarketLayoutV3 } from './market';
import { MintLayout } from './types';
import pino from 'pino';
import bs58 from 'bs58';
import BN from 'bn.js';
import * as fs from 'fs';
import * as path from 'path';
import BN from 'bn.js';
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);
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,
MIN_POOL_SIZE,
} from './constants';
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<string> = new Set<string>();
let existingOpenBookMarkets: Set<string> = new Set<string>();
let existingTokenAccounts: Map<string, MinimalTokenAccountData> = new Map<string, MinimalTokenAccountData>();
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 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));
const MIN_POOL_SIZE = new BN(retrieveEnvVariable('MIN_POOL_SIZE', logger));
let snipeList: string[] = [];
@ -92,13 +77,10 @@ async function init(): Promise<void> {
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;
@ -126,7 +108,7 @@ async function init(): Promise<void> {
);
// 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(), <MinimalTokenAccountData>{
@ -237,7 +219,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);
}
@ -257,7 +239,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,
@ -277,7 +259,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(
@ -286,14 +268,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`,
);
@ -353,7 +335,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,
@ -368,7 +350,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(
@ -377,7 +359,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);
@ -390,7 +372,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`,
);
@ -442,7 +424,7 @@ const runListener = async () => {
const _ = processRaydiumPool(updatedAccountInfo.accountId, poolState);
}
},
commitment,
COMMITMENT_LEVEL,
[
{ dataSize: LIQUIDITY_STATE_LAYOUT_V4.span },
{
@ -476,7 +458,7 @@ const runListener = async () => {
const _ = processOpenBookMarket(updatedAccountInfo);
}
},
commitment,
COMMITMENT_LEVEL,
[
{ dataSize: MARKET_STATE_LAYOUT_V3.span },
{
@ -500,7 +482,7 @@ const runListener = async () => {
const _ = sell(updatedAccountInfo.accountId, accountData.mint, accountData.amount);
},
commitment,
COMMITMENT_LEVEL,
[
{
dataSize: 165,

17
constants/constants.ts Normal file
View File

@ -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);

1
constants/index.ts Normal file
View File

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

View File

@ -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",

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -1 +1,2 @@
export * from './utils';
export * from './logger';

17
utils/logger.ts Normal file
View File

@ -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,
);