From 25bb1a696c3bf779586434084994bc42b74a45f4 Mon Sep 17 00:00:00 2001 From: Filip Dunder Date: Mon, 15 Apr 2024 22:58:36 +0200 Subject: [PATCH] feat: add warp tx executor --- .env.copy | 11 +++-- bot.ts | 29 +++++++------- helpers/constants.ts | 2 + index.ts | 40 +++++++++++++++---- package-lock.json | 13 +++--- package.json | 1 + transactions/default-transaction-executor.ts | 13 ++++-- .../transaction-executor.interface.ts | 9 +++-- transactions/warp-transaction-executor.ts | 40 +++++++++++++++++++ 9 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 transactions/warp-transaction-executor.ts diff --git a/.env.copy b/.env.copy index c4be1c4..52e925b 100644 --- a/.env.copy +++ b/.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 diff --git a/bot.ts b/bot.ts index fa2c5d8..75791af 100644 --- a/bot.ts +++ b/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; } diff --git a/helpers/constants.ts b/helpers/constants.ts index c860a72..a60a8ee 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -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)); diff --git a/index.ts b/index.ts index d4f25aa..8859ac1 100644 --- a/index.ts +++ b/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 = { @@ -186,7 +212,7 @@ const runListener = async () => { await bot.sell(updatedAccountInfo.accountId, accountData); }); - printDetails(wallet, quoteToken, botConfig); + printDetails(wallet, quoteToken, bot); }; runListener(); diff --git a/package-lock.json b/package-lock.json index ccafdd8..dc75a61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" }, diff --git a/package.json b/package.json index 3008a18..a783d18 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/transactions/default-transaction-executor.ts b/transactions/default-transaction-executor.ts index fdb4311..66c222c 100644 --- a/transactions/default-transaction-executor.ts +++ b/transactions/default-transaction-executor.ts @@ -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); diff --git a/transactions/transaction-executor.interface.ts b/transactions/transaction-executor.interface.ts index 1871ae1..87ce465 100644 --- a/transactions/transaction-executor.interface.ts +++ b/transactions/transaction-executor.interface.ts @@ -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 }>; } diff --git a/transactions/warp-transaction-executor.ts b/transactions/warp-transaction-executor.ts new file mode 100644 index 0000000..2d81140 --- /dev/null +++ b/transactions/warp-transaction-executor.ts @@ -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 }; + } +}