Browse Source

Merge branch 'refs/heads/social-filter' into fd-master

Filip Dunder 1 year ago
parent
commit
44a2006105

+ 7 - 5
.env.copy

@@ -11,13 +11,13 @@ LOG_LEVEL=trace
 ONE_TOKEN_AT_A_TIME=true
 ONE_TOKEN_AT_A_TIME=true
 PRE_LOAD_EXISTING_MARKETS=false
 PRE_LOAD_EXISTING_MARKETS=false
 CACHE_NEW_MARKETS=false
 CACHE_NEW_MARKETS=false
-# default or warp
+# default or warp or jito
 TRANSACTION_EXECUTOR=default
 TRANSACTION_EXECUTOR=default
-# if using default executor fee below will be applied
+# if using default executor, fee below will be applied
 COMPUTE_UNIT_LIMIT=101337
 COMPUTE_UNIT_LIMIT=101337
 COMPUTE_UNIT_PRICE=421197
 COMPUTE_UNIT_PRICE=421197
-# if using warp executor fee below will be applied
-WARP_FEE=0.006
+# if using warp or jito executor, fee below will be applied
+CUSTOM_FEE=0.006
 
 
 # Buy
 # Buy
 QUOTE_MINT=WSOL
 QUOTE_MINT=WSOL
@@ -42,8 +42,10 @@ SNIPE_LIST_REFRESH_INTERVAL=30000
 FILTER_CHECK_DURATION=60000
 FILTER_CHECK_DURATION=60000
 FILTER_CHECK_INTERVAL=2000
 FILTER_CHECK_INTERVAL=2000
 CONSECUTIVE_FILTER_MATCHES=3
 CONSECUTIVE_FILTER_MATCHES=3
+CHECK_IF_MUTABLE=false
+CHECK_IF_SOCIALS=true
 CHECK_IF_MINT_IS_RENOUNCED=true
 CHECK_IF_MINT_IS_RENOUNCED=true
-CHECK_IF_FREEZABLE=true
+CHECK_IF_FREEZABLE=false
 CHECK_IF_BURNED=true
 CHECK_IF_BURNED=true
 MIN_POOL_SIZE=5
 MIN_POOL_SIZE=5
 MAX_POOL_SIZE=50
 MAX_POOL_SIZE=50

+ 27 - 9
README.md

@@ -1,12 +1,14 @@
-
 # Solana Trading Bot (Beta)
 # Solana Trading Bot (Beta)
-The Solana Trading Bot is a software tool designed to automate the buying and selling of tokens on the Solana blockchain. 
-It is configured to execute trades based on predefined parameters and strategies set by the user. 
+
+The Solana Trading Bot is a software tool designed to automate the buying and selling of tokens on the Solana blockchain.
+It is configured to execute trades based on predefined parameters and strategies set by the user.
 
 
 The bot can monitor market conditions in real-time, such as pool burn, mint renounced and other factors, and it will execute trades when these conditions are fulfilled.
 The bot can monitor market conditions in real-time, such as pool burn, mint renounced and other factors, and it will execute trades when these conditions are fulfilled.
 
 
 ## Setup
 ## Setup
+
 To run the script you need to:
 To run the script you need to:
+
 - Create a new empty Solana wallet
 - Create a new empty Solana wallet
 - Transfer some SOL to it.
 - Transfer some SOL to it.
 - Convert some SOL to USDC or WSOL.
 - Convert some SOL to USDC or WSOL.
@@ -22,14 +24,17 @@ You should see the following output:
 ### Configuration
 ### Configuration
 
 
 #### Wallet
 #### Wallet
+
 - `PRIVATE_KEY` - Your wallet's private key.
 - `PRIVATE_KEY` - Your wallet's private key.
 
 
 #### Connection
 #### Connection
+
 - `RPC_ENDPOINT` - HTTPS RPC endpoint for interacting with the Solana network.
 - `RPC_ENDPOINT` - HTTPS RPC endpoint for interacting with the Solana network.
 - `RPC_WEBSOCKET_ENDPOINT` - WebSocket RPC endpoint for real-time updates from the Solana network.
 - `RPC_WEBSOCKET_ENDPOINT` - WebSocket RPC endpoint for real-time updates from the Solana network.
 - `COMMITMENT_LEVEL`- The commitment level of transactions (e.g., "finalized" for the highest level of security).
 - `COMMITMENT_LEVEL`- The commitment level of transactions (e.g., "finalized" for the highest level of security).
 
 
 #### Bot
 #### Bot
+
 - `LOG_LEVEL` - Set logging level, e.g., `info`, `debug`, `trace`, etc.
 - `LOG_LEVEL` - Set logging level, e.g., `info`, `debug`, `trace`, etc.
 - `ONE_TOKEN_AT_A_TIME` - Set to `true` to process buying one token at a time.
 - `ONE_TOKEN_AT_A_TIME` - Set to `true` to process buying one token at a time.
 - `COMPUTE_UNIT_LIMIT` - Compute limit used to calculate fees.
 - `COMPUTE_UNIT_LIMIT` - Compute limit used to calculate fees.
@@ -38,13 +43,14 @@ You should see the following output:
   - This option should not be used with public RPC.
   - This option should not be used with public RPC.
 - `CACHE_NEW_MARKETS` - Set to `true` to cache new markets.
 - `CACHE_NEW_MARKETS` - Set to `true` to cache new markets.
   - This option should not be used with public RPC.
   - This option should not be used with public RPC.
-- `TRANSACTION_EXECUTOR` - Set to `warp` to use warp infrastructure for executing transactions
+- `TRANSACTION_EXECUTOR` - Set to `warp` to use warp infrastructure for executing transactions, or set it to jito to use JSON-RPC jito executer
   - For more details checkout [warp](#warp-transactions-beta) section
   - 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 
+- `CUSTOM_FEE` - If using warp or jito executors 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
   - On top of this fee, minimal solana network fee will be applied
 
 
 #### Buy
 #### Buy
+
 - `QUOTE_MINT` - Which pools to snipe, USDC or WSOL.
 - `QUOTE_MINT` - Which pools to snipe, USDC or WSOL.
 - `QUOTE_AMOUNT` - Amount used to buy each new token.
 - `QUOTE_AMOUNT` - Amount used to buy each new token.
 - `AUTO_BUY_DELAY` - Delay in milliseconds before buying a token.
 - `AUTO_BUY_DELAY` - Delay in milliseconds before buying a token.
@@ -52,10 +58,11 @@ You should see the following output:
 - `BUY_SLIPPAGE` - Slippage %
 - `BUY_SLIPPAGE` - Slippage %
 
 
 #### Sell
 #### Sell
+
 - `AUTO_SELL` - Set to `true` to enable automatic selling of tokens.
 - `AUTO_SELL` - Set to `true` to enable automatic selling of tokens.
   - If you want to manually sell bought tokens, disable this option.
   - If you want to manually sell bought tokens, disable this option.
 - `MAX_SELL_RETRIES` - Maximum number of retries for selling a token.
 - `MAX_SELL_RETRIES` - Maximum number of retries for selling a token.
-- `AUTO_SELL_DELAY` -  Delay in milliseconds before auto-selling a token.
+- `AUTO_SELL_DELAY` - Delay in milliseconds before auto-selling a token.
 - `PRICE_CHECK_INTERVAL` - Interval in milliseconds for checking the take profit and stop loss conditions.
 - `PRICE_CHECK_INTERVAL` - Interval in milliseconds for checking the take profit and stop loss conditions.
   - Set to zero to disable take profit and stop loss.
   - Set to zero to disable take profit and stop loss.
 - `PRICE_CHECK_DURATION` - Time in milliseconds to wait for stop loss/take profit conditions.
 - `PRICE_CHECK_DURATION` - Time in milliseconds to wait for stop loss/take profit conditions.
@@ -68,6 +75,7 @@ You should see the following output:
 - `SELL_SLIPPAGE` - Slippage %.
 - `SELL_SLIPPAGE` - Slippage %.
 
 
 #### Snipe list
 #### Snipe list
+
 - `USE_SNIPE_LIST` - Set to `true` to enable buying only tokens listed in `snipe-list.txt`.
 - `USE_SNIPE_LIST` - Set to `true` to enable buying only tokens listed in `snipe-list.txt`.
   - Pool must not exist before the bot starts.
   - Pool must not exist before the bot starts.
   - If token can be traded before bot starts nothing will happen. Bot will not buy the token.
   - If token can be traded before bot starts nothing will happen. Bot will not buy the token.
@@ -77,6 +85,7 @@ You should see the following output:
 Note: When using snipe list filters below will be disabled.
 Note: When using snipe list filters below will be disabled.
 
 
 #### Filters
 #### Filters
+
 - `FILTER_CHECK_INTERVAL` - Interval in milliseconds for checking if pool match the filters.
 - `FILTER_CHECK_INTERVAL` - Interval in milliseconds for checking if pool match the filters.
   - Set to zero to disable filters.
   - Set to zero to disable filters.
 - `FILTER_CHECK_DURATION` - Time in milliseconds to wait for pool to match the filters.
 - `FILTER_CHECK_DURATION` - Time in milliseconds to wait for pool to match the filters.
@@ -84,6 +93,8 @@ Note: When using snipe list filters below will be disabled.
   - Set to zero to disable filters.
   - Set to zero to disable filters.
 - `CONSECUTIVE_FILTER_MATCHES` - How many times in a row pool needs to match the filters.
 - `CONSECUTIVE_FILTER_MATCHES` - How many times in a row pool needs to match the filters.
   - This is useful because when pool is burned (and rugged), other filters may not report the same behavior. eg. pool size may still have old value
   - This is useful because when pool is burned (and rugged), other filters may not report the same behavior. eg. pool size may still have old value
+- `CHECK_IF_MUTABLE` - Set to `true` to buy tokens only if their metadata are not mutable.
+- `CHECK_IF_SOCIALS` - Set to `true` to buy tokens only if they have at least 1 social.
 - `CHECK_IF_MINT_IS_RENOUNCED` - Set to `true` to buy tokens only if their mint is renounced.
 - `CHECK_IF_MINT_IS_RENOUNCED` - Set to `true` to buy tokens only if their mint is renounced.
 - `CHECK_IF_FREEZABLE` - Set to `true` to buy tokens only if they are not freezable.
 - `CHECK_IF_FREEZABLE` - Set to `true` to buy tokens only if they are not freezable.
 - `CHECK_IF_BURNED` - Set to `true` to buy tokens only if their liquidity pool is burned.
 - `CHECK_IF_BURNED` - Set to `true` to buy tokens only if their liquidity pool is burned.
@@ -93,12 +104,14 @@ Note: When using snipe list filters below will be disabled.
   - Set `0` to disable.
   - Set `0` to disable.
 
 
 ## Warp transactions (beta)
 ## 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.
 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.
 Warp is hosted service that executes transactions using integrations with third party providers.
 
 
 Using warp for transactions supports the team behind this project.
 Using warp for transactions supports the team behind this project.
 
 
 ### Security
 ### Security
+
 When using warp, transaction is sent to the hosted service.
 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.
 **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.
 Each request is processed by hosted service and sent to third party provider.
@@ -107,20 +120,24 @@ Each request is processed by hosted service and sent to third party provider.
 Note: Warp transactions are disabled by default.
 Note: Warp transactions are disabled by default.
 
 
 ### Fees
 ### Fees
+
 When using warp for transactions, fee is distributed between developers of warp and third party providers.
 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.
 In case TX fails, no fee will be taken from your account.
 
 
 ## Common issues
 ## Common issues
+
 If you have an error which is not listed here, please create a new issue in this repository.
 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`.
 To collect more information on an issue, please change `LOG_LEVEL` to `debug`.
 
 
 ### Unsupported RPC node
 ### Unsupported RPC node
+
 - If you see following error in your log file:  
 - If you see following error in your log file:  
   `Error: 410 Gone:  {"jsonrpc":"2.0","error":{"code": 410, "message":"The RPC call or parameters have been disabled."}, "id": "986f3599-b2b7-47c4-b951-074c19842bad" }`  
   `Error: 410 Gone:  {"jsonrpc":"2.0","error":{"code": 410, "message":"The RPC call or parameters have been disabled."}, "id": "986f3599-b2b7-47c4-b951-074c19842bad" }`  
   it means your RPC node doesn't support methods needed to execute script.
   it means your RPC node doesn't support methods needed to execute script.
   - FIX: Change your RPC node. You can use Helius or Quicknode.
   - FIX: Change your RPC node. You can use Helius or Quicknode.
 
 
 ### No token account
 ### No token account
+
 - If you see following error in your log file:  
 - If you see following error in your log file:  
   `Error: No SOL token account found in wallet: `  
   `Error: No SOL token account found in wallet: `  
   it means that wallet you provided doesn't have USDC/WSOL token account.
   it means that wallet you provided doesn't have USDC/WSOL token account.
@@ -129,10 +146,11 @@ To collect more information on an issue, please change `LOG_LEVEL` to `debug`.
 ![wsol](readme/wsol.png)
 ![wsol](readme/wsol.png)
 
 
 ## Contact
 ## Contact
+
 [![](https://img.shields.io/discord/1201826085655023616?color=5865F2&logo=Discord&style=flat-square)](https://discord.gg/xYUETCA2aP)
 [![](https://img.shields.io/discord/1201826085655023616?color=5865F2&logo=Discord&style=flat-square)](https://discord.gg/xYUETCA2aP)
 
 
 - If you want to leave a tip, you can send it to the following address:
 - If you want to leave a tip, you can send it to the following address:
-`7gm6BPQrSBaTAYaJheuRevBNXcmKsgbkfBCVSjBnt9aP`
+  `7gm6BPQrSBaTAYaJheuRevBNXcmKsgbkfBCVSjBnt9aP`
 
 
 - If you need custom features or assistance, feel free to contact the admin team on discord for dedicated support.
 - If you need custom features or assistance, feel free to contact the admin team on discord for dedicated support.
 
 
@@ -140,4 +158,4 @@ To collect more information on an issue, please change `LOG_LEVEL` to `debug`.
 
 
 The Solana Trading Bot is provided as is, for learning purposes.
 The Solana Trading Bot is provided as is, for learning purposes.
 Trading cryptocurrencies and tokens involves risk, and past performance is not indicative of future results.
 Trading cryptocurrencies and tokens involves risk, and past performance is not indicative of future results.
-The use of this bot is at your own risk, and we are not responsible for any losses incurred while using the bot.
+The use of this bot is at your own risk, and we are not responsible for any losses incurred while using the bot.

+ 4 - 1
bot.ts

@@ -22,6 +22,7 @@ import { createPoolKeys, logger, NETWORK, sleep } from './helpers';
 import { Mutex } from 'async-mutex';
 import { Mutex } from 'async-mutex';
 import BN from 'bn.js';
 import BN from 'bn.js';
 import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
 import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
+import { JitoTransactionExecutor } from './transactions/jito-rpc-transaction-executor';
 
 
 export interface BotConfig {
 export interface BotConfig {
   wallet: Keypair;
   wallet: Keypair;
@@ -63,6 +64,7 @@ export class Bot {
   private readonly mutex: Mutex;
   private readonly mutex: Mutex;
   private sellExecutionCount = 0;
   private sellExecutionCount = 0;
   public readonly isWarp: boolean = false;
   public readonly isWarp: boolean = false;
+  public readonly isJito: boolean = false;
 
 
   constructor(
   constructor(
     private readonly connection: Connection,
     private readonly connection: Connection,
@@ -72,6 +74,7 @@ export class Bot {
     readonly config: BotConfig,
     readonly config: BotConfig,
   ) {
   ) {
     this.isWarp = txExecutor instanceof WarpTransactionExecutor;
     this.isWarp = txExecutor instanceof WarpTransactionExecutor;
+    this.isJito = txExecutor instanceof JitoTransactionExecutor;
 
 
     this.mutex = new Mutex();
     this.mutex = new Mutex();
     this.poolFilters = new PoolFilters(connection, {
     this.poolFilters = new PoolFilters(connection, {
@@ -324,7 +327,7 @@ export class Bot {
       payerKey: wallet.publicKey,
       payerKey: wallet.publicKey,
       recentBlockhash: latestBlockhash.blockhash,
       recentBlockhash: latestBlockhash.blockhash,
       instructions: [
       instructions: [
-        ...(this.isWarp
+        ...(this.isWarp || this.isJito
           ? []
           ? []
           : [
           : [
               ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.config.unitPrice }),
               ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.config.unitPrice }),

+ 1 - 0
filters/index.ts

@@ -1,4 +1,5 @@
 export * from './burn.filter';
 export * from './burn.filter';
+export * from './mutable.filter';
 export * from './pool-filters';
 export * from './pool-filters';
 export * from './pool-size.filter';
 export * from './pool-size.filter';
 export * from './renounced.filter';
 export * from './renounced.filter';

+ 66 - 0
filters/mutable.filter.ts

@@ -0,0 +1,66 @@
+import { Filter, FilterResult } from './pool-filters';
+import { Connection } from '@solana/web3.js';
+import { LiquidityPoolKeysV4 } from '@raydium-io/raydium-sdk';
+import { getPdaMetadataKey } from '@raydium-io/raydium-sdk';
+import { MetadataAccountData, MetadataAccountDataArgs } from '@metaplex-foundation/mpl-token-metadata';
+import { Serializer } from '@metaplex-foundation/umi/serializers';
+import { logger } from '../helpers';
+
+export class MutableFilter implements Filter {
+  private readonly errorMessage: string[] = [];
+
+  constructor(
+    private readonly connection: Connection,
+    private readonly metadataSerializer: Serializer<MetadataAccountDataArgs, MetadataAccountData>,
+    private readonly checkMutable: boolean,
+    private readonly checkSocials: boolean,
+  ) {
+    if (this.checkMutable) {
+      this.errorMessage.push('mutable');
+    }
+
+    if (this.checkSocials) {
+      this.errorMessage.push('socials');
+    }
+  }
+
+  async execute(poolKeys: LiquidityPoolKeysV4): Promise<FilterResult> {
+    try {
+      const metadataPDA = getPdaMetadataKey(poolKeys.baseMint);
+      const metadataAccount = await this.connection.getAccountInfo(metadataPDA.publicKey, this.connection.commitment);
+
+      if (!metadataAccount?.data) {
+        return { ok: false, message: 'Mutable -> Failed to fetch account data' };
+      }
+
+      const deserialize = this.metadataSerializer.deserialize(metadataAccount.data);
+      const mutable = !this.checkMutable || deserialize[0].isMutable;
+      const hasSocials = !this.checkSocials || (await this.hasSocials(deserialize[0]));
+      const ok = !mutable && hasSocials;
+      const message: string[] = [];
+
+      if (mutable) {
+        message.push('metadata can be changed');
+      }
+
+      if (!hasSocials) {
+        message.push('has no socials');
+      }
+
+      return { ok: ok, message: ok ? undefined : `MutableSocials -> Token ${message.join(' and ')}` };
+    } catch (e) {
+      logger.error({ mint: poolKeys.baseMint }, `MutableSocials -> Failed to check ${this.errorMessage.join(' and ')}`);
+    }
+
+    return {
+      ok: false,
+      message: `MutableSocials -> Failed to check ${this.errorMessage.join(' and ')}`,
+    };
+  }
+
+  private async hasSocials(metadata: MetadataAccountData) {
+    const response = await fetch(metadata.uri);
+    const data = await response.json();
+    return Object.values(data?.extensions ?? {}).some((value: any) => value !== null && value.length > 0);
+  }
+}

+ 7 - 1
filters/pool-filters.ts

@@ -1,9 +1,11 @@
 import { Connection } from '@solana/web3.js';
 import { Connection } from '@solana/web3.js';
 import { LiquidityPoolKeysV4, Token, TokenAmount } from '@raydium-io/raydium-sdk';
 import { LiquidityPoolKeysV4, Token, TokenAmount } from '@raydium-io/raydium-sdk';
+import { getMetadataAccountDataSerializer } from '@metaplex-foundation/mpl-token-metadata';
 import { BurnFilter } from './burn.filter';
 import { BurnFilter } from './burn.filter';
+import { MutableFilter } from './mutable.filter';
 import { RenouncedFreezeFilter } from './renounced.filter';
 import { RenouncedFreezeFilter } from './renounced.filter';
 import { PoolSizeFilter } from './pool-size.filter';
 import { PoolSizeFilter } from './pool-size.filter';
-import { CHECK_IF_BURNED, CHECK_IF_FREEZABLE, CHECK_IF_MINT_IS_RENOUNCED, logger } from '../helpers';
+import { CHECK_IF_BURNED, CHECK_IF_FREEZABLE, CHECK_IF_MINT_IS_RENOUNCED, CHECK_IF_MUTABLE, CHECK_IF_SOCIALS, logger } from '../helpers';
 
 
 export interface Filter {
 export interface Filter {
   execute(poolKeysV4: LiquidityPoolKeysV4): Promise<FilterResult>;
   execute(poolKeysV4: LiquidityPoolKeysV4): Promise<FilterResult>;
@@ -35,6 +37,10 @@ export class PoolFilters {
       this.filters.push(new RenouncedFreezeFilter(connection, CHECK_IF_MINT_IS_RENOUNCED, CHECK_IF_FREEZABLE));
       this.filters.push(new RenouncedFreezeFilter(connection, CHECK_IF_MINT_IS_RENOUNCED, CHECK_IF_FREEZABLE));
     }
     }
 
 
+    if (CHECK_IF_MUTABLE || CHECK_IF_SOCIALS) {
+      this.filters.push(new MutableFilter(connection, getMetadataAccountDataSerializer(), CHECK_IF_MUTABLE, CHECK_IF_SOCIALS));
+    }
+
     if (!args.minPoolSize.isZero() || !args.maxPoolSize.isZero()) {
     if (!args.minPoolSize.isZero() || !args.maxPoolSize.isZero()) {
       this.filters.push(new PoolSizeFilter(connection, args.quoteToken, args.minPoolSize, args.maxPoolSize));
       this.filters.push(new PoolSizeFilter(connection, args.quoteToken, args.minPoolSize, args.maxPoolSize));
     }
     }

+ 32 - 6
filters/renounced.filter.ts

@@ -5,10 +5,23 @@ import { LiquidityPoolKeysV4 } from '@raydium-io/raydium-sdk';
 import { logger } from '../helpers';
 import { logger } from '../helpers';
 
 
 export class RenouncedFreezeFilter implements Filter {
 export class RenouncedFreezeFilter implements Filter {
-  constructor(private readonly connection: Connection, private readonly checkRenounced: boolean, private readonly checkFreezable: boolean) {}
+  private readonly errorMessage: string[] = [];
+
+  constructor(
+    private readonly connection: Connection,
+    private readonly checkRenounced: boolean,
+    private readonly checkFreezable: boolean,
+  ) {
+    if (this.checkRenounced) {
+      this.errorMessage.push('mint');
+    }
+
+    if (this.checkFreezable) {
+      this.errorMessage.push('freeze');
+    }
+  }
 
 
   async execute(poolKeys: LiquidityPoolKeysV4): Promise<FilterResult> {
   async execute(poolKeys: LiquidityPoolKeysV4): Promise<FilterResult> {
-    const errorMessage = [ this.checkRenounced ? 'mint' : undefined, this.checkFreezable ? 'freeze' : undefined ].filter((e) => e !== undefined);
     try {
     try {
       const accountInfo = await this.connection.getAccountInfo(poolKeys.baseMint, this.connection.commitment);
       const accountInfo = await this.connection.getAccountInfo(poolKeys.baseMint, this.connection.commitment);
       if (!accountInfo?.data) {
       if (!accountInfo?.data) {
@@ -18,15 +31,28 @@ export class RenouncedFreezeFilter implements Filter {
       const deserialize = MintLayout.decode(accountInfo.data);
       const deserialize = MintLayout.decode(accountInfo.data);
       const renounced = !this.checkRenounced || deserialize.mintAuthorityOption === 0;
       const renounced = !this.checkRenounced || deserialize.mintAuthorityOption === 0;
       const freezable = !this.checkFreezable || deserialize.freezeAuthorityOption !== 0;
       const freezable = !this.checkFreezable || deserialize.freezeAuthorityOption !== 0;
-
-      const message = [ renounced ? undefined : 'mint', !freezable ? undefined : 'freeze' ].filter((e) => e !== undefined);
       const ok = renounced && !freezable;
       const ok = renounced && !freezable;
+      const message: string[] = [];
+
+      if (!renounced) {
+        message.push('mint');
+      }
+
+      if (freezable) {
+        message.push('freeze');
+      }
 
 
       return { ok: ok, message: ok ? undefined : `RenouncedFreeze -> Creator can ${message.join(' and ')} tokens` };
       return { ok: ok, message: ok ? undefined : `RenouncedFreeze -> Creator can ${message.join(' and ')} tokens` };
     } catch (e) {
     } catch (e) {
-      logger.error({ mint: poolKeys.baseMint }, `RenouncedFreeze -> Failed to check if creator can ${errorMessage.join(' and ')} tokens`);
+      logger.error(
+        { mint: poolKeys.baseMint },
+        `RenouncedFreeze -> Failed to check if creator can ${this.errorMessage.join(' and ')} tokens`,
+      );
     }
     }
 
 
-    return { ok: false, message: `RenouncedFreeze -> Failed to check if creator can ${errorMessage.join(' and ')} tokens` };
+    return {
+      ok: false,
+      message: `RenouncedFreeze -> Failed to check if creator can ${this.errorMessage.join(' and ')} tokens`,
+    };
   }
   }
 }
 }

+ 3 - 1
helpers/constants.ts

@@ -31,7 +31,7 @@ export const COMPUTE_UNIT_PRICE = Number(retrieveEnvVariable('COMPUTE_UNIT_PRICE
 export const PRE_LOAD_EXISTING_MARKETS = retrieveEnvVariable('PRE_LOAD_EXISTING_MARKETS', logger) === 'true';
 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 CACHE_NEW_MARKETS = retrieveEnvVariable('CACHE_NEW_MARKETS', logger) === 'true';
 export const TRANSACTION_EXECUTOR = retrieveEnvVariable('TRANSACTION_EXECUTOR', logger);
 export const TRANSACTION_EXECUTOR = retrieveEnvVariable('TRANSACTION_EXECUTOR', logger);
-export const WARP_FEE = retrieveEnvVariable('WARP_FEE', logger);
+export const CUSTOM_FEE = retrieveEnvVariable('CUSTOM_FEE', logger);
 
 
 // Buy
 // Buy
 export const AUTO_BUY_DELAY = Number(retrieveEnvVariable('AUTO_BUY_DELAY', logger));
 export const AUTO_BUY_DELAY = Number(retrieveEnvVariable('AUTO_BUY_DELAY', logger));
@@ -54,6 +54,8 @@ export const SELL_SLIPPAGE = Number(retrieveEnvVariable('SELL_SLIPPAGE', logger)
 export const FILTER_CHECK_INTERVAL = Number(retrieveEnvVariable('FILTER_CHECK_INTERVAL', logger));
 export const FILTER_CHECK_INTERVAL = Number(retrieveEnvVariable('FILTER_CHECK_INTERVAL', logger));
 export const FILTER_CHECK_DURATION = Number(retrieveEnvVariable('FILTER_CHECK_DURATION', logger));
 export const FILTER_CHECK_DURATION = Number(retrieveEnvVariable('FILTER_CHECK_DURATION', logger));
 export const CONSECUTIVE_FILTER_MATCHES = Number(retrieveEnvVariable('CONSECUTIVE_FILTER_MATCHES', logger));
 export const CONSECUTIVE_FILTER_MATCHES = Number(retrieveEnvVariable('CONSECUTIVE_FILTER_MATCHES', logger));
+export const CHECK_IF_MUTABLE = retrieveEnvVariable('CHECK_IF_MUTABLE', logger) === 'true';
+export const CHECK_IF_SOCIALS = retrieveEnvVariable('CHECK_IF_SOCIALS', logger) === 'true';
 export const CHECK_IF_MINT_IS_RENOUNCED = retrieveEnvVariable('CHECK_IF_MINT_IS_RENOUNCED', logger) === 'true';
 export const CHECK_IF_MINT_IS_RENOUNCED = retrieveEnvVariable('CHECK_IF_MINT_IS_RENOUNCED', logger) === 'true';
 export const CHECK_IF_FREEZABLE = retrieveEnvVariable('CHECK_IF_FREEZABLE', logger) === 'true';
 export const CHECK_IF_FREEZABLE = retrieveEnvVariable('CHECK_IF_FREEZABLE', logger) === 'true';
 export const CHECK_IF_BURNED = retrieveEnvVariable('CHECK_IF_BURNED', logger) === 'true';
 export const CHECK_IF_BURNED = retrieveEnvVariable('CHECK_IF_BURNED', logger) === 'true';

+ 13 - 5
index.ts

@@ -14,6 +14,7 @@ import {
   RPC_WEBSOCKET_ENDPOINT,
   RPC_WEBSOCKET_ENDPOINT,
   PRE_LOAD_EXISTING_MARKETS,
   PRE_LOAD_EXISTING_MARKETS,
   LOG_LEVEL,
   LOG_LEVEL,
+  CHECK_IF_MUTABLE,
   CHECK_IF_MINT_IS_RENOUNCED,
   CHECK_IF_MINT_IS_RENOUNCED,
   CHECK_IF_FREEZABLE,
   CHECK_IF_FREEZABLE,
   CHECK_IF_BURNED,
   CHECK_IF_BURNED,
@@ -40,13 +41,14 @@ import {
   PRICE_CHECK_INTERVAL,
   PRICE_CHECK_INTERVAL,
   SNIPE_LIST_REFRESH_INTERVAL,
   SNIPE_LIST_REFRESH_INTERVAL,
   TRANSACTION_EXECUTOR,
   TRANSACTION_EXECUTOR,
-  WARP_FEE,
+  CUSTOM_FEE,
   FILTER_CHECK_INTERVAL,
   FILTER_CHECK_INTERVAL,
   FILTER_CHECK_DURATION,
   FILTER_CHECK_DURATION,
   CONSECUTIVE_FILTER_MATCHES,
   CONSECUTIVE_FILTER_MATCHES,
 } from './helpers';
 } from './helpers';
 import { version } from './package.json';
 import { version } from './package.json';
 import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
 import { WarpTransactionExecutor } from './transactions/warp-transaction-executor';
+import { JitoTransactionExecutor } from './transactions/jito-rpc-transaction-executor';
 
 
 const connection = new Connection(RPC_ENDPOINT, {
 const connection = new Connection(RPC_ENDPOINT, {
   wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
   wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
@@ -79,9 +81,11 @@ function printDetails(wallet: Keypair, quoteToken: Token, bot: Bot) {
 
 
   logger.info('- Bot -');
   logger.info('- Bot -');
 
 
-  logger.info(`Using warp: ${bot.isWarp}`);
-  if (bot.isWarp) {
-    logger.info(`Warp fee: ${WARP_FEE}`);
+  logger.info(
+    `Using ${TRANSACTION_EXECUTOR} executer: ${bot.isWarp || bot.isJito || (TRANSACTION_EXECUTOR === 'default' ? true : false)}`,
+  );
+  if (bot.isWarp || bot.isJito) {
+    logger.info(`${TRANSACTION_EXECUTOR} fee: ${CUSTOM_FEE}`);
   } else {
   } else {
     logger.info(`Compute Unit limit: ${botConfig.unitLimit}`);
     logger.info(`Compute Unit limit: ${botConfig.unitLimit}`);
     logger.info(`Compute Unit price (micro lamports): ${botConfig.unitPrice}`);
     logger.info(`Compute Unit price (micro lamports): ${botConfig.unitPrice}`);
@@ -143,7 +147,11 @@ const runListener = async () => {
 
 
   switch (TRANSACTION_EXECUTOR) {
   switch (TRANSACTION_EXECUTOR) {
     case 'warp': {
     case 'warp': {
-      txExecutor = new WarpTransactionExecutor(WARP_FEE);
+      txExecutor = new WarpTransactionExecutor(CUSTOM_FEE);
+      break;
+    }
+    case 'jito': {
+      txExecutor = new JitoTransactionExecutor(CUSTOM_FEE, connection);
       break;
       break;
     }
     }
     default: {
     default: {

+ 83 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "name": "warp-solana-bot",
       "name": "warp-solana-bot",
       "version": "2.0.1",
       "version": "2.0.1",
       "dependencies": {
       "dependencies": {
+        "@metaplex-foundation/mpl-token-metadata": "^3.2.1",
         "@raydium-io/raydium-sdk": "^1.3.1-beta.47",
         "@raydium-io/raydium-sdk": "^1.3.1-beta.47",
         "@solana/spl-token": "^0.4.0",
         "@solana/spl-token": "^0.4.0",
         "@solana/web3.js": "^1.89.1",
         "@solana/web3.js": "^1.89.1",
@@ -75,6 +76,88 @@
         "@jridgewell/sourcemap-codec": "^1.4.10"
         "@jridgewell/sourcemap-codec": "^1.4.10"
       }
       }
     },
     },
+    "node_modules/@metaplex-foundation/mpl-token-metadata": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-3.2.1.tgz",
+      "integrity": "sha512-26W1NhQwDWmLOg/pBRYut7x/vEs/5kFS2sWVEY5/X0f2jJOLhnd4NaZQcq+5u+XZsXvm1jq2AtrRGPNK43oqWQ==",
+      "dependencies": {
+        "@metaplex-foundation/mpl-toolbox": "^0.9.4"
+      },
+      "peerDependencies": {
+        "@metaplex-foundation/umi": ">= 0.8.2 < 1"
+      }
+    },
+    "node_modules/@metaplex-foundation/mpl-toolbox": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/mpl-toolbox/-/mpl-toolbox-0.9.4.tgz",
+      "integrity": "sha512-fd6JxfoLbj/MM8FG2x91KYVy1U6AjBQw4qjt7+Da3trzQaWnSaYHDcYRG/53xqfvZ9qofY1T2t53GXPlD87lnQ==",
+      "peerDependencies": {
+        "@metaplex-foundation/umi": ">= 0.8.2 < 1"
+      }
+    },
+    "node_modules/@metaplex-foundation/umi": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi/-/umi-0.9.1.tgz",
+      "integrity": "sha512-IhHoOvp4vfO/++YL+78+iVuLM53+FDwUOZDYgH6lx0jYXyQ27BeaieeR5i+q3A9dz4KxQo5Nzc5aCA1109QGCQ==",
+      "peer": true,
+      "dependencies": {
+        "@metaplex-foundation/umi-options": "^0.8.9",
+        "@metaplex-foundation/umi-public-keys": "^0.8.9",
+        "@metaplex-foundation/umi-serializers": "^0.9.0"
+      }
+    },
+    "node_modules/@metaplex-foundation/umi-options": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-options/-/umi-options-0.8.9.tgz",
+      "integrity": "sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A==",
+      "peer": true
+    },
+    "node_modules/@metaplex-foundation/umi-public-keys": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-public-keys/-/umi-public-keys-0.8.9.tgz",
+      "integrity": "sha512-CxMzN7dgVGOq9OcNCJe2casKUpJ3RmTVoOvDFyeoTQuK+vkZ1YSSahbqC1iGuHEtKTLSjtWjKvUU6O7zWFTw3Q==",
+      "peer": true,
+      "dependencies": {
+        "@metaplex-foundation/umi-serializers-encodings": "^0.8.9"
+      }
+    },
+    "node_modules/@metaplex-foundation/umi-serializers": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers/-/umi-serializers-0.9.0.tgz",
+      "integrity": "sha512-hAOW9Djl4w4ioKeR4erDZl5IG4iJdP0xA19ZomdaCbMhYAAmG/FEs5khh0uT2mq53/MnzWcXSUPoO8WBN4Q+Vg==",
+      "peer": true,
+      "dependencies": {
+        "@metaplex-foundation/umi-options": "^0.8.9",
+        "@metaplex-foundation/umi-public-keys": "^0.8.9",
+        "@metaplex-foundation/umi-serializers-core": "^0.8.9",
+        "@metaplex-foundation/umi-serializers-encodings": "^0.8.9",
+        "@metaplex-foundation/umi-serializers-numbers": "^0.8.9"
+      }
+    },
+    "node_modules/@metaplex-foundation/umi-serializers-core": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-core/-/umi-serializers-core-0.8.9.tgz",
+      "integrity": "sha512-WT82tkiYJ0Qmscp7uTj1Hz6aWQPETwaKLAENAUN5DeWghkuBKtuxyBKVvEOuoXerJSdhiAk0e8DWA4cxcTTQ/w==",
+      "peer": true
+    },
+    "node_modules/@metaplex-foundation/umi-serializers-encodings": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-encodings/-/umi-serializers-encodings-0.8.9.tgz",
+      "integrity": "sha512-N3VWLDTJ0bzzMKcJDL08U3FaqRmwlN79FyE4BHj6bbAaJ9LEHjDQ9RJijZyWqTm0jE7I750fU7Ow5EZL38Xi6Q==",
+      "peer": true,
+      "dependencies": {
+        "@metaplex-foundation/umi-serializers-core": "^0.8.9"
+      }
+    },
+    "node_modules/@metaplex-foundation/umi-serializers-numbers": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-numbers/-/umi-serializers-numbers-0.8.9.tgz",
+      "integrity": "sha512-NtBf1fnVNQJHFQjLFzRu2i9GGnigb9hOm/Gfrk628d0q0tRJB7BOM3bs5C61VAs7kJs4yd+pDNVAERJkknQ7Lg==",
+      "peer": true,
+      "dependencies": {
+        "@metaplex-foundation/umi-serializers-core": "^0.8.9"
+      }
+    },
     "node_modules/@noble/curves": {
     "node_modules/@noble/curves": {
       "version": "1.3.0",
       "version": "1.3.0",
       "license": "MIT",
       "license": "MIT",

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
     "tsc": "tsc --noEmit"
     "tsc": "tsc --noEmit"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@metaplex-foundation/mpl-token-metadata": "^3.2.1",
     "@raydium-io/raydium-sdk": "^1.3.1-beta.47",
     "@raydium-io/raydium-sdk": "^1.3.1-beta.47",
     "@solana/spl-token": "^0.4.0",
     "@solana/spl-token": "^0.4.0",
     "@solana/web3.js": "^1.89.1",
     "@solana/web3.js": "^1.89.1",

+ 131 - 0
transactions/jito-rpc-transaction-executor.ts

@@ -0,0 +1,131 @@
+import {
+  BlockhashWithExpiryBlockHeight,
+  Keypair,
+  PublicKey,
+  SystemProgram,
+  Connection,
+  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 JitoTransactionExecutor implements TransactionExecutor {
+  // https://jito-labs.gitbook.io/mev/searcher-resources/json-rpc-api-reference/bundles/gettipaccounts
+  private jitpTipAccounts = [
+    'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
+    'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
+    '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
+    '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
+    'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
+    'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
+    'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt',
+    'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
+  ];
+
+  private JitoFeeWallet: PublicKey;
+
+  constructor(
+    private readonly jitoFee: string,
+    private readonly connection: Connection,
+  ) {
+    this.JitoFeeWallet = this.getRandomValidatorKey();
+  }
+
+  private getRandomValidatorKey(): PublicKey {
+    const randomValidator = this.jitpTipAccounts[Math.floor(Math.random() * this.jitpTipAccounts.length)];
+    return new PublicKey(randomValidator);
+  }
+
+  public async executeAndConfirm(
+    transaction: VersionedTransaction,
+    payer: Keypair,
+    latestBlockhash: BlockhashWithExpiryBlockHeight,
+  ): Promise<{ confirmed: boolean; signature?: string }> {
+    logger.debug('Starting Jito transaction execution...');
+    this.JitoFeeWallet = this.getRandomValidatorKey(); // Update wallet key each execution
+    logger.trace(`Selected Jito fee wallet: ${this.JitoFeeWallet.toBase58()}`);
+
+    try {
+      const fee = new CurrencyAmount(Currency.SOL, this.jitoFee, false).raw.toNumber();
+      logger.trace(`Calculated fee: ${fee} lamports`);
+
+      const jitTipTxFeeMessage = new TransactionMessage({
+        payerKey: payer.publicKey,
+        recentBlockhash: latestBlockhash.blockhash,
+        instructions: [
+          SystemProgram.transfer({
+            fromPubkey: payer.publicKey,
+            toPubkey: this.JitoFeeWallet,
+            lamports: fee,
+          }),
+        ],
+      }).compileToV0Message();
+
+      const jitoFeeTx = new VersionedTransaction(jitTipTxFeeMessage);
+      jitoFeeTx.sign([payer]);
+
+      const jitoTxsignature = bs58.encode(jitoFeeTx.signatures[0]);
+
+      // Serialize the transactions once here
+      const serializedjitoFeeTx = bs58.encode(jitoFeeTx.serialize());
+      const serializedTransaction = bs58.encode(transaction.serialize());
+      const serializedTransactions = [serializedjitoFeeTx, serializedTransaction];
+
+      // https://jito-labs.gitbook.io/mev/searcher-resources/json-rpc-api-reference/url
+      const endpoints = [
+        'https://mainnet.block-engine.jito.wtf/api/v1/bundles',
+        'https://amsterdam.mainnet.block-engine.jito.wtf/api/v1/bundles',
+        'https://frankfurt.mainnet.block-engine.jito.wtf/api/v1/bundles',
+        'https://ny.mainnet.block-engine.jito.wtf/api/v1/bundles',
+        'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles',
+      ];
+
+      const requests = endpoints.map((url) =>
+        axios.post(url, {
+          jsonrpc: '2.0',
+          id: 1,
+          method: 'sendBundle',
+          params: [serializedTransactions],
+        }),
+      );
+
+      logger.trace('Sending transactions to endpoints...');
+      const results = await Promise.all(requests.map((p) => p.catch((e) => e)));
+
+      const successfulResults = results.filter((result) => !(result instanceof Error));
+
+      if (successfulResults.length > 0) {
+        logger.trace(`At least one successful response`);
+        logger.debug(`Confirming jito transaction...`);
+        return await this.confirm(jitoTxsignature, latestBlockhash);
+      } else {
+        logger.debug(`No successful responses received for jito`);
+      }
+
+      return { confirmed: false };
+    } catch (error) {
+      if (error instanceof AxiosError) {
+        logger.trace({ error: error.response?.data }, 'Failed to execute jito transaction');
+      }
+      logger.error('Error during transaction execution', error);
+      return { confirmed: false };
+    }
+  }
+
+  private async confirm(signature: string, latestBlockhash: BlockhashWithExpiryBlockHeight) {
+    const confirmation = await this.connection.confirmTransaction(
+      {
+        signature,
+        lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
+        blockhash: latestBlockhash.blockhash,
+      },
+      this.connection.commitment,
+    );
+
+    return { confirmed: !confirmation.value.err, signature };
+  }
+}