feat: add warp tx executor

This commit is contained in:
Filip Dunder
2024-04-15 22:58:36 +02:00
parent 0c5786ca1c
commit 25bb1a696c
9 changed files with 122 additions and 36 deletions

View File

@ -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
View File

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

View File

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

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View 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 };
}
}