mirror of
https://github.com/fdundjer/solana-sniper-bot.git
synced 2025-11-10 04:22:05 +10:00
11
.env.copy
11
.env.copy
@ -9,10 +9,15 @@ COMMITMENT_LEVEL=confirmed
|
||||
# Bot
|
||||
LOG_LEVEL=debug
|
||||
ONE_TOKEN_AT_A_TIME=true
|
||||
COMPUTE_UNIT_LIMIT=421197
|
||||
COMPUTE_UNIT_PRICE=101337
|
||||
PRE_LOAD_EXISTING_MARKETS=false
|
||||
CACHE_NEW_MARKETS=false
|
||||
# default or warp
|
||||
TRANSACTION_EXECUTOR=default
|
||||
# if using default executor fee below will be applied
|
||||
COMPUTE_UNIT_LIMIT=421197
|
||||
COMPUTE_UNIT_PRICE=101337
|
||||
# if using warp executor fee below will be applied
|
||||
WARP_FEE=0.006
|
||||
|
||||
# Buy
|
||||
QUOTE_MINT=WSOL
|
||||
@ -27,7 +32,7 @@ MAX_SELL_RETRIES=10
|
||||
AUTO_SELL_DELAY=0
|
||||
PRICE_CHECK_INTERVAL=2000
|
||||
PRICE_CHECK_DURATION=60000
|
||||
TAKE_PROFIT=25
|
||||
TAKE_PROFIT=20
|
||||
STOP_LOSS=15
|
||||
SELL_SLIPPAGE=5
|
||||
|
||||
|
||||
29
README.md
29
README.md
@ -40,6 +40,11 @@ You should see the following output:
|
||||
- This option should not be used with public RPC.
|
||||
- `CACHE_NEW_MARKETS` - Set to `true` to cache new markets.
|
||||
- This option should not be used with public RPC.
|
||||
- `TRANSACTION_EXECUTOR` - Set to `warp` to use warp infrastructure for executing transactions
|
||||
- For more details checkout [warp](#warp-transactions-beta) section
|
||||
- `WARP_FEE` - If using warp executor this value will be used for transaction fees instead of `COMPUTE_UNIT_LIMIT` and `COMPUTE_UNIT_LIMIT`
|
||||
- Minimum value is 0.0001 SOL, but we recommend using 0.006 SOL or above
|
||||
- On top of this fee, minimal solana network fee will be applied
|
||||
|
||||
#### Buy
|
||||
- `QUOTE_MINT` - Amount used to buy each new token.
|
||||
@ -66,12 +71,30 @@ You should see the following output:
|
||||
- Pool must not exist before the script starts.
|
||||
- `SNIPE_LIST_REFRESH_INTERVAL` - Interval in milliseconds to refresh the snipe list.
|
||||
- `CHECK_IF_MINT_IS_RENOUNCED` - Set to `true` to buy tokens only if their mint is renounced.
|
||||
- `CHECK_IF_BURNED` - Set to `true` to buy tokens only if their liqudity pool is burned.
|
||||
- `MIN_POOL_SIZE` - Bot will buy only if the pool size is greater than the specified amount.
|
||||
- `CHECK_IF_BURNED` - Set to `true` to buy tokens only if their liquidity pool is burned.
|
||||
- `MIN_POOL_SIZE` - Bot will buy only if the pool size is greater than or equal the specified amount.
|
||||
- Set `0` to disable.
|
||||
- `MAX_POOL_SIZE` - Bot will buy only if the pool size is less than the specified amount.
|
||||
- `MAX_POOL_SIZE` - Bot will buy only if the pool size is less than or equal the specified amount.
|
||||
- Set `0` to disable.
|
||||
|
||||
## Warp transactions (beta)
|
||||
In case you experience a lot of failed transactions or transaction performance is too slow, you can try using `warp` for executing transactions.
|
||||
Warp is hosted service that executes transactions using integrations with third party providers.
|
||||
|
||||
Using warp for transactions supports the team behind this project.
|
||||
|
||||
### Security
|
||||
When using warp, transaction is sent to the hosted service.
|
||||
**Payload that is being sent will NOT contain your wallet private key**. Fee transaction is signed on your machine.
|
||||
Each request is processed by hosted service and sent to third party provider.
|
||||
**We don't store your transactions, nor we store your private key.**
|
||||
|
||||
Note: Warp transactions are disabled by default.
|
||||
|
||||
### Fees
|
||||
When using warp for transactions, fee is distributed between developers of warp and third party providers.
|
||||
In case TX fails, no fee will be taken from your account.
|
||||
|
||||
## Common issues
|
||||
If you have an error which is not listed here, please create a new issue in this repository.
|
||||
To collect more information on an issue, please change `LOG_LEVEL` to `debug`.
|
||||
|
||||
32
bot.ts
32
bot.ts
@ -14,20 +14,14 @@ import {
|
||||
RawAccount,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from '@solana/spl-token';
|
||||
import {
|
||||
Liquidity,
|
||||
LiquidityPoolKeysV4,
|
||||
LiquidityStateV4,
|
||||
Percent,
|
||||
Token,
|
||||
TokenAmount,
|
||||
} from '@raydium-io/raydium-sdk';
|
||||
import { Liquidity, LiquidityPoolKeysV4, LiquidityStateV4, Percent, Token, TokenAmount } from '@raydium-io/raydium-sdk';
|
||||
import { MarketCache, PoolCache, SnipeListCache } from './cache';
|
||||
import { PoolFilters } from './filters';
|
||||
import { TransactionExecutor } from './transactions';
|
||||
import { createPoolKeys, logger, NETWORK, sleep } from './helpers';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import BN from 'bn.js';
|
||||
import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
|
||||
|
||||
export interface BotConfig {
|
||||
wallet: Keypair;
|
||||
@ -64,14 +58,17 @@ export class Bot {
|
||||
// one token at the time
|
||||
private readonly mutex: Mutex;
|
||||
private sellExecutionCount = 0;
|
||||
public readonly isWarp: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly connection: Connection,
|
||||
private readonly marketStorage: MarketCache,
|
||||
private readonly poolStorage: PoolCache,
|
||||
private readonly txExecutor: TransactionExecutor,
|
||||
private readonly config: BotConfig,
|
||||
readonly config: BotConfig,
|
||||
) {
|
||||
this.isWarp = txExecutor instanceof WarpTransactionExecutor;
|
||||
|
||||
this.mutex = new Mutex();
|
||||
this.poolFilters = new PoolFilters(connection, {
|
||||
quoteToken: this.config.quoteToken,
|
||||
@ -318,8 +315,12 @@ export class Bot {
|
||||
payerKey: wallet.publicKey,
|
||||
recentBlockhash: latestBlockhash.blockhash,
|
||||
instructions: [
|
||||
...(this.isWarp
|
||||
? []
|
||||
: [
|
||||
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.config.unitPrice }),
|
||||
ComputeBudgetProgram.setComputeUnitLimit({ units: this.config.unitLimit }),
|
||||
]),
|
||||
...(direction === 'buy'
|
||||
? [
|
||||
createAssociatedTokenAccountIdempotentInstruction(
|
||||
@ -338,10 +339,15 @@ export class Bot {
|
||||
const transaction = new VersionedTransaction(messageV0);
|
||||
transaction.sign([wallet, ...innerTransaction.signers]);
|
||||
|
||||
return this.txExecutor.executeAndConfirm(transaction, latestBlockhash);
|
||||
return this.txExecutor.executeAndConfirm(transaction, wallet, latestBlockhash);
|
||||
}
|
||||
|
||||
private async priceMatch(amountIn: TokenAmount, poolKeys: LiquidityPoolKeysV4) {
|
||||
if (this.config.priceCheckDuration === 0 || this.config.priceCheckInterval === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timesToCheck = this.config.priceCheckDuration / this.config.priceCheckInterval;
|
||||
const profitFraction = this.config.quoteAmount.mul(this.config.takeProfit).numerator.div(new BN(100));
|
||||
const profitAmount = new TokenAmount(this.config.quoteToken, profitFraction, true);
|
||||
const takeProfit = this.config.quoteAmount.add(profitAmount);
|
||||
@ -350,8 +356,6 @@ export class Bot {
|
||||
const lossAmount = new TokenAmount(this.config.quoteToken, lossFraction, true);
|
||||
const stopLoss = this.config.quoteAmount.subtract(lossAmount);
|
||||
const slippage = new Percent(this.config.sellSlippage, 100);
|
||||
|
||||
const timesToCheck = this.config.priceCheckDuration / this.config.priceCheckInterval;
|
||||
let timesChecked = 0;
|
||||
|
||||
do {
|
||||
@ -374,11 +378,11 @@ export class Bot {
|
||||
`Take profit: ${takeProfit.toFixed()} | Stop loss: ${stopLoss.toFixed()} | Current: ${amountOut.toFixed()}`,
|
||||
);
|
||||
|
||||
if (amountOut.lt(stopLoss)){
|
||||
if (amountOut.lt(stopLoss)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (amountOut.gt(takeProfit)){
|
||||
if (amountOut.gt(takeProfit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ export class PoolSizeFilter implements Filter {
|
||||
let inRange = true;
|
||||
|
||||
if (!this.maxPoolSize?.isZero()) {
|
||||
inRange = poolSize.lt(this.maxPoolSize);
|
||||
inRange = poolSize.raw.lte(this.maxPoolSize.raw);
|
||||
|
||||
if (!inRange) {
|
||||
return { ok: false, message: `PoolSize -> Pool size ${poolSize.toFixed()} > ${this.maxPoolSize.toFixed()}` };
|
||||
@ -26,7 +26,7 @@ export class PoolSizeFilter implements Filter {
|
||||
}
|
||||
|
||||
if (!this.minPoolSize?.isZero()) {
|
||||
inRange = poolSize.gt(this.minPoolSize);
|
||||
inRange = poolSize.raw.gte(this.minPoolSize.raw);
|
||||
|
||||
if (!inRange) {
|
||||
return { ok: false, message: `PoolSize -> Pool size ${poolSize.toFixed()} < ${this.minPoolSize.toFixed()}` };
|
||||
|
||||
@ -30,6 +30,8 @@ export const COMPUTE_UNIT_LIMIT = Number(retrieveEnvVariable('COMPUTE_UNIT_LIMIT
|
||||
export const COMPUTE_UNIT_PRICE = Number(retrieveEnvVariable('COMPUTE_UNIT_PRICE', logger));
|
||||
export const PRE_LOAD_EXISTING_MARKETS = retrieveEnvVariable('PRE_LOAD_EXISTING_MARKETS', logger) === 'true';
|
||||
export const CACHE_NEW_MARKETS = retrieveEnvVariable('CACHE_NEW_MARKETS', logger) === 'true';
|
||||
export const TRANSACTION_EXECUTOR = retrieveEnvVariable('TRANSACTION_EXECUTOR', logger);
|
||||
export const WARP_FEE = retrieveEnvVariable('WARP_FEE', logger);
|
||||
|
||||
// Buy
|
||||
export const AUTO_BUY_DELAY = Number(retrieveEnvVariable('AUTO_BUY_DELAY', logger));
|
||||
|
||||
36
index.ts
36
index.ts
@ -4,7 +4,7 @@ import { Connection, KeyedAccountInfo, Keypair } from '@solana/web3.js';
|
||||
import { LIQUIDITY_STATE_LAYOUT_V4, MARKET_STATE_LAYOUT_V3, Token, TokenAmount } from '@raydium-io/raydium-sdk';
|
||||
import { AccountLayout, getAssociatedTokenAddressSync } from '@solana/spl-token';
|
||||
import { Bot, BotConfig } from './bot';
|
||||
import { DefaultTransactionExecutor } from './transactions';
|
||||
import { DefaultTransactionExecutor, TransactionExecutor } from './transactions';
|
||||
import {
|
||||
getToken,
|
||||
getWallet,
|
||||
@ -36,16 +36,20 @@ import {
|
||||
BUY_SLIPPAGE,
|
||||
SELL_SLIPPAGE,
|
||||
PRICE_CHECK_DURATION,
|
||||
PRICE_CHECK_INTERVAL, SNIPE_LIST_REFRESH_INTERVAL,
|
||||
PRICE_CHECK_INTERVAL,
|
||||
SNIPE_LIST_REFRESH_INTERVAL,
|
||||
TRANSACTION_EXECUTOR,
|
||||
WARP_FEE,
|
||||
} from './helpers';
|
||||
import { version } from './package.json';
|
||||
import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
|
||||
|
||||
const connection = new Connection(RPC_ENDPOINT, {
|
||||
wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
|
||||
commitment: COMMITMENT_LEVEL,
|
||||
});
|
||||
|
||||
function printDetails(wallet: Keypair, quoteToken: Token, botConfig: BotConfig) {
|
||||
function printDetails(wallet: Keypair, quoteToken: Token, bot: Bot) {
|
||||
logger.info(`
|
||||
.. :-===++++-
|
||||
.-==+++++++- =+++++++++-
|
||||
@ -64,12 +68,22 @@ function printDetails(wallet: Keypair, quoteToken: Token, botConfig: BotConfig)
|
||||
Version: ${version}
|
||||
`);
|
||||
|
||||
const botConfig = bot.config;
|
||||
|
||||
logger.info('------- CONFIGURATION START -------');
|
||||
logger.info(`Wallet: ${wallet.publicKey.toString()}`);
|
||||
|
||||
logger.info('- Bot -');
|
||||
|
||||
logger.info(`Using warp: ${bot.isWarp}`);
|
||||
if (bot.isWarp) {
|
||||
logger.info(`Warp fee: ${WARP_FEE}`);
|
||||
}
|
||||
else {
|
||||
logger.info(`Compute Unit limit: ${botConfig.unitLimit}`);
|
||||
logger.info(`Compute Unit price (micro lamports): ${botConfig.unitPrice}`);
|
||||
}
|
||||
|
||||
logger.info(`Single token at the time: ${botConfig.oneTokenAtATime}`);
|
||||
logger.info(`Pre load existing markets: ${PRE_LOAD_EXISTING_MARKETS}`);
|
||||
logger.info(`Cache new markets: ${CACHE_NEW_MARKETS}`);
|
||||
@ -111,7 +125,19 @@ const runListener = async () => {
|
||||
|
||||
const marketCache = new MarketCache(connection);
|
||||
const poolCache = new PoolCache();
|
||||
const txExecutor = new DefaultTransactionExecutor(connection);
|
||||
let txExecutor: TransactionExecutor;
|
||||
|
||||
switch (TRANSACTION_EXECUTOR) {
|
||||
case 'warp': {
|
||||
txExecutor = new WarpTransactionExecutor(WARP_FEE);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
txExecutor = new DefaultTransactionExecutor(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const wallet = getWallet(PRIVATE_KEY.trim());
|
||||
const quoteToken = getToken(QUOTE_MINT);
|
||||
const botConfig = <BotConfig>{
|
||||
@ -186,7 +212,7 @@ const runListener = async () => {
|
||||
await bot.sell(updatedAccountInfo.accountId, accountData);
|
||||
});
|
||||
|
||||
printDetails(wallet, quoteToken, botConfig);
|
||||
printDetails(wallet, quoteToken, bot);
|
||||
};
|
||||
|
||||
runListener();
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@solana/spl-token": "^0.4.0",
|
||||
"@solana/web3.js": "^1.89.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.6.8",
|
||||
"bigint-buffer": "^1.1.5",
|
||||
"bip39": "^3.1.0",
|
||||
"bn.js": "^5.2.1",
|
||||
@ -396,10 +397,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.7",
|
||||
"license": "MIT",
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
|
||||
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -724,14 +726,15 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"@solana/spl-token": "^0.4.0",
|
||||
"@solana/web3.js": "^1.89.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.6.8",
|
||||
"bigint-buffer": "^1.1.5",
|
||||
"bip39": "^3.1.0",
|
||||
"bn.js": "^5.2.1",
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { BlockhashWithExpiryBlockHeight, Connection, Transaction, VersionedTransaction } from '@solana/web3.js';
|
||||
import {
|
||||
BlockhashWithExpiryBlockHeight,
|
||||
Connection,
|
||||
Keypair,
|
||||
Transaction,
|
||||
VersionedTransaction,
|
||||
} from '@solana/web3.js';
|
||||
import { TransactionExecutor } from './transaction-executor.interface';
|
||||
import { logger } from '../helpers';
|
||||
|
||||
@ -6,9 +12,10 @@ export class DefaultTransactionExecutor implements TransactionExecutor {
|
||||
constructor(private readonly connection: Connection) {}
|
||||
|
||||
public async executeAndConfirm(
|
||||
transaction: Transaction | VersionedTransaction,
|
||||
transaction: VersionedTransaction,
|
||||
payer: Keypair,
|
||||
latestBlockhash: BlockhashWithExpiryBlockHeight,
|
||||
): Promise<{ confirmed: boolean; signature: string }> {
|
||||
): Promise<{ confirmed: boolean; signature?: string }> {
|
||||
logger.debug('Executing transaction...');
|
||||
const signature = await this.execute(transaction);
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { BlockhashWithExpiryBlockHeight, Transaction, VersionedTransaction } from '@solana/web3.js';
|
||||
import { BlockhashWithExpiryBlockHeight, Keypair, MessageV0, Signer, VersionedTransaction } from '@solana/web3.js';
|
||||
|
||||
export interface TransactionExecutor {
|
||||
executeAndConfirm(
|
||||
transaction: Transaction | VersionedTransaction,
|
||||
latestBlockhash: BlockhashWithExpiryBlockHeight,
|
||||
): Promise<{ confirmed: boolean; signature: string }>;
|
||||
transaction: VersionedTransaction,
|
||||
payer: Keypair,
|
||||
latestBlockHash: BlockhashWithExpiryBlockHeight,
|
||||
): Promise<{ confirmed: boolean; signature?: string }>;
|
||||
}
|
||||
|
||||
64
transactions/warp-transaction-executor.ts
Normal file
64
transactions/warp-transaction-executor.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {
|
||||
BlockhashWithExpiryBlockHeight,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from '@solana/web3.js';
|
||||
import { TransactionExecutor } from './transaction-executor.interface';
|
||||
import { logger } from '../helpers';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import bs58 from 'bs58';
|
||||
import { Currency, CurrencyAmount } from '@raydium-io/raydium-sdk';
|
||||
|
||||
export class WarpTransactionExecutor implements TransactionExecutor {
|
||||
private readonly warpFeeWallet = new PublicKey('WARPzUMPnycu9eeCZ95rcAUxorqpBqHndfV3ZP5FSyS');
|
||||
|
||||
constructor(private readonly warpFee: string) {}
|
||||
|
||||
public async executeAndConfirm(
|
||||
transaction: VersionedTransaction,
|
||||
payer: Keypair,
|
||||
latestBlockhash: BlockhashWithExpiryBlockHeight,
|
||||
): Promise<{ confirmed: boolean; signature?: string }> {
|
||||
logger.debug('Executing transaction...');
|
||||
|
||||
try {
|
||||
const fee = new CurrencyAmount(Currency.SOL, this.warpFee, false).raw.toNumber();
|
||||
const warpFeeMessage = new TransactionMessage({
|
||||
payerKey: payer.publicKey,
|
||||
recentBlockhash: latestBlockhash.blockhash,
|
||||
instructions: [
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: payer.publicKey,
|
||||
toPubkey: this.warpFeeWallet,
|
||||
lamports: fee,
|
||||
}),
|
||||
],
|
||||
}).compileToV0Message();
|
||||
|
||||
const warpFeeTx = new VersionedTransaction(warpFeeMessage);
|
||||
warpFeeTx.sign([payer]);
|
||||
|
||||
const response = await axios.post<{ confirmed: boolean; signature: string }>(
|
||||
'https://tx.warp.id/transaction/execute',
|
||||
{
|
||||
transactions: [bs58.encode(warpFeeTx.serialize()), bs58.encode(transaction.serialize())],
|
||||
latestBlockhash,
|
||||
},
|
||||
{
|
||||
timeout: 100000,
|
||||
},
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
logger.trace({ error: error.response?.data }, 'Failed to execute warp transaction');
|
||||
}
|
||||
}
|
||||
|
||||
return { confirmed: false };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user