mirror of
https://github.com/fdundjer/solana-sniper-bot.git
synced 2025-11-09 20:12:06 +10:00
feat: add warp tx executor
This commit is contained in:
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
bot.ts
29
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: [
|
||||
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.config.unitPrice }),
|
||||
ComputeBudgetProgram.setComputeUnitLimit({ units: this.config.unitLimit }),
|
||||
...(this.isWarp
|
||||
? []
|
||||
: [
|
||||
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.config.unitPrice }),
|
||||
ComputeBudgetProgram.setComputeUnitLimit({ units: this.config.unitLimit }),
|
||||
]),
|
||||
...(direction === 'buy'
|
||||
? [
|
||||
createAssociatedTokenAccountIdempotentInstruction(
|
||||
@ -338,7 +339,7 @@ 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) {
|
||||
@ -374,11 +375,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;
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
40
index.ts
40
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(`Compute Unit limit: ${botConfig.unitLimit}`);
|
||||
logger.info(`Compute Unit price (micro lamports): ${botConfig.unitPrice}`);
|
||||
|
||||
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 }>;
|
||||
}
|
||||
|
||||
40
transactions/warp-transaction-executor.ts
Normal file
40
transactions/warp-transaction-executor.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { BlockhashWithExpiryBlockHeight, Keypair, VersionedTransaction } from '@solana/web3.js';
|
||||
import { TransactionExecutor } from './transaction-executor.interface';
|
||||
import { logger } from '../helpers';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import bs58 from 'bs58';
|
||||
|
||||
export class WarpTransactionExecutor implements TransactionExecutor {
|
||||
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 response = await axios.post<{ confirmed: boolean; signature: string }>(
|
||||
'https://tx.warp.id/transaction/execute',
|
||||
{
|
||||
transaction: bs58.encode(transaction.serialize()),
|
||||
payer: bs58.encode(payer.secretKey),
|
||||
fee: this.warpFee,
|
||||
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