@clonegod/ttd-sol-common 2.0.22 → 2.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,3 +3,4 @@ export * from './tx_result_parse';
3
3
  export * from './SolanaTradeAppConfig';
4
4
  export * from './tx_builder';
5
5
  export * from './send';
6
+ export * from './jito_tip_wallets';
@@ -19,3 +19,4 @@ __exportStar(require("./tx_result_parse"), exports);
19
19
  __exportStar(require("./SolanaTradeAppConfig"), exports);
20
20
  __exportStar(require("./tx_builder"), exports);
21
21
  __exportStar(require("./send"), exports);
22
+ __exportStar(require("./jito_tip_wallets"), exports);
@@ -0,0 +1,21 @@
1
+ import { Keypair } from '@solana/web3.js';
2
+ export interface WalletConfig {
3
+ public_key: string;
4
+ private_key: string;
5
+ type: string;
6
+ keypair: Keypair;
7
+ }
8
+ export interface GroupConfig {
9
+ group_id: string;
10
+ tip_wallets: WalletConfig[];
11
+ }
12
+ export declare class JitoTipWalletManager {
13
+ private readonly jito_tip_wallet_filename;
14
+ private walletConfigs;
15
+ private walletIndexMap;
16
+ constructor();
17
+ private createKeypair;
18
+ private loadWalletConfigs;
19
+ private printWalletInfo;
20
+ getNextWallet(groupId: string): Keypair | null;
21
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.JitoTipWalletManager = void 0;
40
+ const web3_js_1 = require("@solana/web3.js");
41
+ const dist_1 = require("@clonegod/ttd-core/dist");
42
+ const bs58_1 = __importDefault(require("bs58"));
43
+ const fs = __importStar(require("fs"));
44
+ const os_1 = __importDefault(require("os"));
45
+ const path_1 = __importDefault(require("path"));
46
+ class JitoTipWalletManager {
47
+ constructor() {
48
+ this.jito_tip_wallet_filename = 'jito_tip_wallet.json';
49
+ this.walletConfigs = [];
50
+ this.walletIndexMap = new Map();
51
+ this.loadWalletConfigs();
52
+ }
53
+ createKeypair(privateKeyBase58) {
54
+ const privateKeyBytes = bs58_1.default.decode(privateKeyBase58);
55
+ return web3_js_1.Keypair.fromSecretKey(privateKeyBytes);
56
+ }
57
+ loadWalletConfigs() {
58
+ try {
59
+ let jito_tip_wallet_path = process.env.JITO_TIP_WALLET_PATH || '';
60
+ if (!jito_tip_wallet_path.startsWith('/')) {
61
+ jito_tip_wallet_path = path_1.default.join(os_1.default.homedir(), 'data', 'keypairs', this.jito_tip_wallet_filename);
62
+ }
63
+ if (!fs.existsSync(jito_tip_wallet_path)) {
64
+ (0, dist_1.log_warn)(`[JitoTipWalletManager] 配置文件${this.jito_tip_wallet_filename}不存在!路径: ${jito_tip_wallet_path}`);
65
+ this.walletConfigs = [];
66
+ return;
67
+ }
68
+ const configData = fs.readFileSync(jito_tip_wallet_path, 'utf-8');
69
+ this.walletConfigs = JSON.parse(configData);
70
+ this.walletConfigs.forEach(group => {
71
+ group.tip_wallets.forEach(wallet => {
72
+ try {
73
+ wallet.keypair = this.createKeypair(wallet.private_key);
74
+ }
75
+ catch (error) {
76
+ (0, dist_1.log_error)(`[JitoTipWalletManager] 生成钱包 KeyPair 失败 - public_key: ${wallet.public_key}`, error);
77
+ }
78
+ });
79
+ });
80
+ (0, dist_1.log_info)(`[JitoTipWalletManager] 成功加载配置文件: ${jito_tip_wallet_path}`);
81
+ this.printWalletInfo();
82
+ }
83
+ catch (error) {
84
+ (0, dist_1.log_error)('[JitoTipWalletManager] 加载钱包配置时出错:', error);
85
+ this.walletConfigs = [];
86
+ }
87
+ }
88
+ printWalletInfo() {
89
+ (0, dist_1.log_info)('\n=== Tip钱包配置信息 ===');
90
+ if (this.walletConfigs.length === 0) {
91
+ (0, dist_1.log_info)('没有找到任何钱包配置');
92
+ return;
93
+ }
94
+ this.walletConfigs.forEach(group => {
95
+ (0, dist_1.log_info)(`\n分组: ${group.group_id}`);
96
+ (0, dist_1.log_info)(`钱包数量: ${group.tip_wallets.length}`);
97
+ group.tip_wallets.forEach((wallet, index) => {
98
+ const keypairStatus = wallet.keypair ? '✓' : '✗';
99
+ (0, dist_1.log_info)(` ${index + 1}. ${wallet.public_key}${wallet.type ? ` (${wallet.type})` : ''} [KeyPair: ${keypairStatus}]`);
100
+ });
101
+ });
102
+ (0, dist_1.log_info)('\n===================\n');
103
+ }
104
+ getNextWallet(groupId) {
105
+ const group = this.walletConfigs.find(g => g.group_id === groupId);
106
+ if (!group) {
107
+ (0, dist_1.log_warn)(`[JitoTipWalletManager] 未找到group_id对应的钱包组: ${groupId}`);
108
+ return null;
109
+ }
110
+ if (group.tip_wallets.length === 0) {
111
+ (0, dist_1.log_warn)(`[JitoTipWalletManager] group_id: ${groupId} 没有配置钱包`);
112
+ return null;
113
+ }
114
+ let currentIndex = this.walletIndexMap.get(groupId) || 0;
115
+ const wallet = group.tip_wallets[currentIndex];
116
+ currentIndex = (currentIndex + 1) % group.tip_wallets.length;
117
+ this.walletIndexMap.set(groupId, currentIndex);
118
+ return wallet.keypair;
119
+ }
120
+ }
121
+ exports.JitoTipWalletManager = JitoTipWalletManager;
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.sendTxWithHelius = exports.HELIUS_TIP_ACCOUNTS = void 0;
7
- const axios_1 = __importDefault(require("axios"));
4
+ const http_client_1 = require("./http_client");
8
5
  exports.HELIUS_TIP_ACCOUNTS = [
9
6
  "4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE",
10
7
  "D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ",
@@ -22,7 +19,8 @@ const sendTxWithHelius = async (signedTransaction, swqos_only) => {
22
19
  if (swqos_only) {
23
20
  url = url + "?swqos_only=true";
24
21
  }
25
- const response = await axios_1.default.post(url, {
22
+ const client = (0, http_client_1.getHttpClient)(url);
23
+ const response = await client.post(url, {
26
24
  jsonrpc: '2.0',
27
25
  id: Date.now().toString(),
28
26
  method: 'sendTransaction',
@@ -0,0 +1,2 @@
1
+ import { AxiosInstance } from "axios";
2
+ export declare function getHttpClient(url: string): AxiosInstance;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getHttpClient = getHttpClient;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const https_1 = __importDefault(require("https"));
9
+ class HttpClientManager {
10
+ constructor() {
11
+ this.axiosInstances = new Map();
12
+ }
13
+ createAxiosInstance(baseURL) {
14
+ const httpsAgent = new https_1.default.Agent({
15
+ keepAlive: true,
16
+ keepAliveMsecs: 30000,
17
+ maxSockets: 50,
18
+ maxFreeSockets: 10,
19
+ timeout: 30000,
20
+ });
21
+ return axios_1.default.create({
22
+ baseURL,
23
+ httpsAgent,
24
+ timeout: 30000,
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ });
29
+ }
30
+ getInstance(url) {
31
+ const baseURL = url.split('?')[0];
32
+ if (!this.axiosInstances.has(baseURL)) {
33
+ this.axiosInstances.set(baseURL, this.createAxiosInstance(baseURL));
34
+ }
35
+ return this.axiosInstances.get(baseURL);
36
+ }
37
+ }
38
+ const httpClientManager = new HttpClientManager();
39
+ function getHttpClient(url) {
40
+ return httpClientManager.getInstance(url);
41
+ }
@@ -7,3 +7,4 @@ export declare class JitoUtils {
7
7
  }
8
8
  export declare const getJitoTipAccount: () => string;
9
9
  export declare const sendBundleWithJito: (signedTransactions: Transaction[]) => Promise<string>;
10
+ export declare const sendBundleWithJitoByMultiIps: (signedTransactions: Transaction[]) => Promise<string>;
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.sendBundleWithJito = exports.getJitoTipAccount = exports.JitoUtils = exports.JITO_TIP_ACCOUNTS = void 0;
6
+ exports.sendBundleWithJitoByMultiIps = exports.sendBundleWithJito = exports.getJitoTipAccount = exports.JitoUtils = exports.JITO_TIP_ACCOUNTS = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
+ const http_client_1 = require("./http_client");
8
9
  exports.JITO_TIP_ACCOUNTS = [
9
10
  "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
10
11
  "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
@@ -23,7 +24,8 @@ class JitoUtils {
23
24
  });
24
25
  }
25
26
  static async fetchJitoTipAccounts() {
26
- const response = await axios_1.default.post('https://mainnet.block-engine.jito.wtf/api/v1/getTipAccounts', {
27
+ const url = 'https://mainnet.block-engine.jito.wtf/api/v1/getTipAccounts';
28
+ const response = await axios_1.default.post(url, {
27
29
  jsonrpc: '2.0',
28
30
  id: 1,
29
31
  method: 'getTipAccounts',
@@ -36,7 +38,7 @@ exports.JitoUtils = JitoUtils;
36
38
  JitoUtils.jitoTipAccounts = [];
37
39
  const getJitoTipAccount = () => {
38
40
  let jito_tip_accounts = JitoUtils.jitoTipAccounts;
39
- if (JitoUtils.jitoTipAccounts.length === 0) {
41
+ if (jito_tip_accounts.length === 0) {
40
42
  jito_tip_accounts = exports.JITO_TIP_ACCOUNTS;
41
43
  }
42
44
  return jito_tip_accounts[Math.floor(Math.random() * jito_tip_accounts.length)];
@@ -45,7 +47,8 @@ exports.getJitoTipAccount = getJitoTipAccount;
45
47
  JitoUtils.init();
46
48
  const sendBundleWithJito = async (signedTransactions) => {
47
49
  let url = process.env.JITO_SEND_BUNDLE_URL || 'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles';
48
- const response = await axios_1.default.post(url, {
50
+ const client = (0, http_client_1.getHttpClient)(url);
51
+ const response = await client.post(url, {
49
52
  jsonrpc: '2.0',
50
53
  id: 1,
51
54
  method: 'sendBundle',
@@ -56,7 +59,29 @@ const sendBundleWithJito = async (signedTransactions) => {
56
59
  }
57
60
  ]
58
61
  });
59
- console.dir(response.data, { depth: null });
60
62
  return response.data.result;
61
63
  };
62
64
  exports.sendBundleWithJito = sendBundleWithJito;
65
+ const sendBundleWithJitoByMultiIps = async (signedTransactions) => {
66
+ let ips = (process.env.JITO_SEND_BUNDLE_PROXY_SERVERS || '127.0.0.1').split(',');
67
+ let urls = ips.map(ip => `http://${ip}:18888/solana/send_tx`);
68
+ const body = {
69
+ trace_id: '',
70
+ txid: Buffer.from(signedTransactions[0].serialize()).toString('base64'),
71
+ encoding: 'base64',
72
+ encoded_tx: signedTransactions.map(tx => Buffer.from(tx.serialize()).toString('base64')).join(','),
73
+ max_retry: 3
74
+ };
75
+ Promise.all(urls.map(async (url) => {
76
+ try {
77
+ const client = (0, http_client_1.getHttpClient)(url);
78
+ const response = await client.post(url, body);
79
+ return response.data.result;
80
+ }
81
+ catch (error) {
82
+ console.error(`sendBundleWithJitoByMultiIps error: ${url}`, error);
83
+ }
84
+ }));
85
+ return 'success';
86
+ };
87
+ exports.sendBundleWithJitoByMultiIps = sendBundleWithJitoByMultiIps;
@@ -1,17 +1,19 @@
1
1
  import { TradeContext } from "@clonegod/ttd-core/dist";
2
2
  import { Connection, Keypair, Transaction, TransactionInstruction } from "@solana/web3.js";
3
3
  import { SolanaTradeAppConfig } from "./SolanaTradeAppConfig";
4
+ import { JitoTipWalletManager } from "./jito_tip_wallets";
4
5
  export declare class SolTransactionBuilder {
5
6
  appConfig: SolanaTradeAppConfig;
6
- protected connection: Connection;
7
- protected keypair: Keypair;
8
- protected recentBlockhash: string;
9
- protected recentBlockheight: number;
7
+ connection: Connection;
8
+ keypair: Keypair;
9
+ recentBlockhash: string;
10
+ recentBlockheight: number;
11
+ jitoTipWalletManager: JitoTipWalletManager;
10
12
  constructor(appConfig: SolanaTradeAppConfig);
11
13
  init(): Promise<void>;
12
14
  private handleBlockUpdateEvent;
13
15
  private getPreInstructions;
14
16
  private getPostInstructions;
15
17
  buildTransactionForSwap(context: TradeContext, swapInstructions: TransactionInstruction[]): Transaction;
16
- buildTransactionForTipJito(context: TradeContext, tipPayer?: Keypair): Transaction;
18
+ buildTransactionForTipJito(context: TradeContext): Transaction;
17
19
  }
@@ -6,11 +6,13 @@ const web3_js_1 = require("@solana/web3.js");
6
6
  const common_1 = require("../common");
7
7
  const helius_1 = require("./send/helius");
8
8
  const jito_1 = require("./send/jito");
9
+ const jito_tip_wallets_1 = require("./jito_tip_wallets");
9
10
  class SolTransactionBuilder {
10
11
  constructor(appConfig) {
11
12
  this.appConfig = appConfig;
12
13
  this.connection = appConfig.connection;
13
14
  this.keypair = appConfig.keypair;
15
+ this.jitoTipWalletManager = new jito_tip_wallets_1.JitoTipWalletManager();
14
16
  this.init();
15
17
  }
16
18
  async init() {
@@ -62,8 +64,10 @@ class SolTransactionBuilder {
62
64
  swapTx.sign(this.keypair);
63
65
  return swapTx;
64
66
  }
65
- buildTransactionForTipJito(context, tipPayer = undefined) {
67
+ buildTransactionForTipJito(context) {
68
+ const groupId = context.trade_runtime.group.id;
66
69
  const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset;
70
+ let tipPayer = this.jitoTipWalletManager.getNextWallet(groupId);
67
71
  if (!tipPayer) {
68
72
  tipPayer = this.keypair;
69
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sol-common",
3
- "version": "2.0.22",
3
+ "version": "2.0.24",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
@@ -2,4 +2,5 @@ export * from './tx_result_check'
2
2
  export * from './tx_result_parse'
3
3
  export * from './SolanaTradeAppConfig'
4
4
  export * from './tx_builder'
5
- export * from './send'
5
+ export * from './send'
6
+ export * from './jito_tip_wallets'
@@ -0,0 +1,143 @@
1
+ import { Keypair } from '@solana/web3.js';
2
+ import { log_info, log_warn, log_error } from '@clonegod/ttd-core/dist';
3
+ import bs58 from 'bs58';
4
+ import * as fs from 'fs';
5
+ import os from 'os';
6
+ import path from 'path';
7
+
8
+ /**
9
+ * 钱包配置接口
10
+ */
11
+ export interface WalletConfig {
12
+ public_key: string;
13
+ private_key: string;
14
+ type: string; // main | sub
15
+ keypair: Keypair; // 添加 Keypair 字段
16
+ }
17
+
18
+ /**
19
+ * 分组配置接口
20
+ */
21
+ export interface GroupConfig {
22
+ group_id: string;
23
+ tip_wallets: WalletConfig[];
24
+ }
25
+
26
+ /**
27
+ * Jito Tip 钱包管理器
28
+ *
29
+ * 功能:
30
+ * 1. 从文件加载 tip wallets
31
+ * 2. 返回下一个钱包(轮询)
32
+ */
33
+ export class JitoTipWalletManager {
34
+ private readonly jito_tip_wallet_filename: string = 'jito_tip_wallet.json';
35
+ private walletConfigs: GroupConfig[] = [];
36
+ private walletIndexMap: Map<string, number> = new Map();
37
+
38
+ constructor() {
39
+ this.loadWalletConfigs();
40
+ }
41
+
42
+ /**
43
+ * 从私钥创建 Keypair
44
+ */
45
+ private createKeypair(privateKeyBase58: string): Keypair {
46
+ const privateKeyBytes = bs58.decode(privateKeyBase58);
47
+ return Keypair.fromSecretKey(privateKeyBytes);
48
+ }
49
+
50
+ /**
51
+ * 从文件加载钱包配置
52
+ */
53
+ private loadWalletConfigs(): void {
54
+ try {
55
+ // 从环境变量获取配置文件路径
56
+ let jito_tip_wallet_path = process.env.JITO_TIP_WALLET_PATH || '';
57
+ if (!jito_tip_wallet_path.startsWith('/')) {
58
+ jito_tip_wallet_path = path.join(os.homedir(), 'data', 'keypairs', this.jito_tip_wallet_filename);
59
+ }
60
+
61
+ if (!fs.existsSync(jito_tip_wallet_path)) {
62
+ log_warn(`[JitoTipWalletManager] 配置文件${this.jito_tip_wallet_filename}不存在!路径: ${jito_tip_wallet_path}`);
63
+ this.walletConfigs = [];
64
+ return;
65
+ }
66
+
67
+ const configData = fs.readFileSync(jito_tip_wallet_path, 'utf-8');
68
+ this.walletConfigs = JSON.parse(configData);
69
+
70
+ // 为每个钱包生成 KeyPair
71
+ this.walletConfigs.forEach(group => {
72
+ group.tip_wallets.forEach(wallet => {
73
+ try {
74
+ wallet.keypair = this.createKeypair(wallet.private_key);
75
+ } catch (error) {
76
+ log_error(`[JitoTipWalletManager] 生成钱包 KeyPair 失败 - public_key: ${wallet.public_key}`, error);
77
+ }
78
+ });
79
+ });
80
+
81
+ log_info(`[JitoTipWalletManager] 成功加载配置文件: ${jito_tip_wallet_path}`);
82
+
83
+ // 加载完成后打印钱包信息
84
+ this.printWalletInfo();
85
+ } catch (error) {
86
+ log_error('[JitoTipWalletManager] 加载钱包配置时出错:', error);
87
+ this.walletConfigs = [];
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 打印钱包信息(用于调试)
93
+ */
94
+ private printWalletInfo(): void {
95
+ log_info('\n=== Tip钱包配置信息 ===');
96
+ if (this.walletConfigs.length === 0) {
97
+ log_info('没有找到任何钱包配置');
98
+ return;
99
+ }
100
+
101
+ this.walletConfigs.forEach(group => {
102
+ log_info(`\n分组: ${group.group_id}`);
103
+ log_info(`钱包数量: ${group.tip_wallets.length}`);
104
+ group.tip_wallets.forEach((wallet, index) => {
105
+ const keypairStatus = wallet.keypair ? '✓' : '✗';
106
+ log_info(` ${index + 1}. ${wallet.public_key}${wallet.type ? ` (${wallet.type})` : ''} [KeyPair: ${keypairStatus}]`);
107
+ });
108
+ });
109
+ log_info('\n===================\n');
110
+ }
111
+
112
+ /**
113
+ * 获取下一个钱包(轮询)
114
+ * @param groupId 分组ID
115
+ * @returns 钱包配置,如果未找到则返回 null
116
+ */
117
+ public getNextWallet(groupId: string): Keypair | null {
118
+ const group = this.walletConfigs.find(g => g.group_id === groupId);
119
+ if (!group) {
120
+ log_warn(`[JitoTipWalletManager] 未找到group_id对应的钱包组: ${groupId}`);
121
+ return null;
122
+ }
123
+
124
+ if (group.tip_wallets.length === 0) {
125
+ log_warn(`[JitoTipWalletManager] group_id: ${groupId} 没有配置钱包`);
126
+ return null;
127
+ }
128
+
129
+ // 获取当前索引,如果不存在则初始化为0
130
+ let currentIndex = this.walletIndexMap.get(groupId) || 0;
131
+
132
+ // 获取下一个钱包
133
+ const wallet = group.tip_wallets[currentIndex];
134
+
135
+ // 更新索引,如果达到列表末尾则重置为0
136
+ currentIndex = (currentIndex + 1) % group.tip_wallets.length;
137
+ this.walletIndexMap.set(groupId, currentIndex);
138
+
139
+ return wallet.keypair;
140
+ }
141
+ }
142
+
143
+
@@ -1,5 +1,5 @@
1
1
  import { Transaction } from "@solana/web3.js"
2
- import axios from "axios"
2
+ import { getHttpClient } from "./http_client"
3
3
 
4
4
  export const HELIUS_TIP_ACCOUNTS = [
5
5
  "4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE",
@@ -24,7 +24,9 @@ export const sendTxWithHelius = async (signedTransaction: Transaction, swqos_onl
24
24
  url = url + "?swqos_only=true"
25
25
  }
26
26
 
27
- const response = await axios.post<{ result: string }>(url, {
27
+ // 使用共享的 axios 实例,复用连接池
28
+ const client = getHttpClient(url)
29
+ const response = await client.post<{ result: string }>(url, {
28
30
  jsonrpc: '2.0',
29
31
  id: Date.now().toString(),
30
32
  method: 'sendTransaction',
@@ -0,0 +1,63 @@
1
+ import axios, { AxiosInstance } from "axios"
2
+ import https from "https"
3
+
4
+ /**
5
+ * 共享的 HTTP 客户端管理器
6
+ *
7
+ * 提供统一的 axios 实例管理,配置 keep-alive 连接池
8
+ * 避免重复创建连接,提升性能
9
+ */
10
+ class HttpClientManager {
11
+ private axiosInstances: Map<string, AxiosInstance> = new Map()
12
+
13
+ /**
14
+ * 创建配置了 keep-alive 的 axios 实例
15
+ */
16
+ private createAxiosInstance(baseURL: string): AxiosInstance {
17
+ const httpsAgent = new https.Agent({
18
+ keepAlive: true, // 启用 keep-alive
19
+ keepAliveMsecs: 30000, // keep-alive 心跳间隔:30秒(TCP 层探测,用于检测连接是否断开)
20
+ maxSockets: 50, // 最大连接数
21
+ maxFreeSockets: 10, // 最大空闲连接数
22
+ timeout: 30000, // 连接超时
23
+ })
24
+
25
+ return axios.create({
26
+ baseURL,
27
+ httpsAgent,
28
+ timeout: 30000,
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ })
33
+ }
34
+
35
+ /**
36
+ * 获取或创建共享的 axios 实例
37
+ * @param url - 完整 URL(会自动提取 baseURL)
38
+ * @returns 共享的 axios 实例
39
+ */
40
+ getInstance(url: string): AxiosInstance {
41
+ // 提取基础 URL(不包含查询参数)
42
+ const baseURL = url.split('?')[0]
43
+
44
+ if (!this.axiosInstances.has(baseURL)) {
45
+ this.axiosInstances.set(baseURL, this.createAxiosInstance(baseURL))
46
+ }
47
+
48
+ return this.axiosInstances.get(baseURL)!
49
+ }
50
+ }
51
+
52
+ // 单例模式,全局共享
53
+ const httpClientManager = new HttpClientManager()
54
+
55
+ /**
56
+ * 获取共享的 axios 实例
57
+ * @param url - 完整 URL
58
+ * @returns 共享的 axios 实例(已配置 keep-alive)
59
+ */
60
+ export function getHttpClient(url: string): AxiosInstance {
61
+ return httpClientManager.getInstance(url)
62
+ }
63
+
@@ -1,5 +1,6 @@
1
1
  import { Transaction } from "@solana/web3.js";
2
2
  import axios from "axios";
3
+ import { getHttpClient } from "./http_client";
3
4
 
4
5
  export const JITO_TIP_ACCOUNTS = [
5
6
  "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
@@ -23,7 +24,8 @@ export class JitoUtils {
23
24
  }
24
25
 
25
26
  static async fetchJitoTipAccounts(): Promise<string[]> {
26
- const response = await axios.post<{ result: string[] }>('https://mainnet.block-engine.jito.wtf/api/v1/getTipAccounts', {
27
+ const url = 'https://mainnet.block-engine.jito.wtf/api/v1/getTipAccounts'
28
+ const response = await axios.post<{ result: string[] }>(url, {
27
29
  jsonrpc: '2.0',
28
30
  id: 1,
29
31
  method: 'getTipAccounts',
@@ -36,7 +38,7 @@ export class JitoUtils {
36
38
 
37
39
  export const getJitoTipAccount = (): string => {
38
40
  let jito_tip_accounts = JitoUtils.jitoTipAccounts
39
- if (JitoUtils.jitoTipAccounts.length === 0) {
41
+ if (jito_tip_accounts.length === 0) {
40
42
  jito_tip_accounts = JITO_TIP_ACCOUNTS
41
43
  }
42
44
  return jito_tip_accounts[Math.floor(Math.random() * jito_tip_accounts.length)];
@@ -49,7 +51,9 @@ JitoUtils.init()
49
51
  export const sendBundleWithJito = async (signedTransactions: Transaction[]): Promise<string> => {
50
52
  let url = process.env.JITO_SEND_BUNDLE_URL || 'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles'
51
53
 
52
- const response = await axios.post<{ result: string }>(url, {
54
+ // 使用共享的 axios 实例,复用连接池
55
+ const client = getHttpClient(url)
56
+ const response = await client.post<{ result: string }>(url, {
53
57
  jsonrpc: '2.0',
54
58
  id: 1,
55
59
  method: 'sendBundle',
@@ -60,8 +64,41 @@ export const sendBundleWithJito = async (signedTransactions: Transaction[]): Pro
60
64
  }
61
65
  ]
62
66
  })
63
- console.dir(response.data, { depth: null })
64
-
67
+
68
+ // {
69
+ // jsonrpc: '2.0',
70
+ // result: 'e62062b4af6963bb0f67fea429f97e4f4fee59851a4ef65154b9f49b5995b5be', // bundle id
71
+ // id: 1
72
+ // }
73
+ // console.dir(response.data, { depth: null })
74
+
65
75
  return response.data.result;
66
76
  }
67
77
 
78
+
79
+ export const sendBundleWithJitoByMultiIps = async (signedTransactions: Transaction[]): Promise<string> => {
80
+ let ips = (process.env.JITO_SEND_BUNDLE_PROXY_SERVERS || '127.0.0.1').split(',')
81
+ let urls = ips.map(ip => `http://${ip}:18888/solana/send_tx`)
82
+
83
+ const body = {
84
+ trace_id: '',
85
+ txid: Buffer.from(signedTransactions[0].serialize()).toString('base64'),
86
+ encoding: 'base64', // base64
87
+ encoded_tx: signedTransactions.map(tx => Buffer.from(tx.serialize()).toString('base64')).join(','), // main tx
88
+ max_retry: 3
89
+ }
90
+
91
+ Promise.all(urls.map(async (url) => {
92
+ try {
93
+ const client = getHttpClient(url)
94
+ const response = await client.post<{ result: string }>(url, body)
95
+ return response.data.result;
96
+ } catch (error) {
97
+ console.error(`sendBundleWithJitoByMultiIps error: ${url}`, error)
98
+ }
99
+ }))
100
+
101
+ return 'success';
102
+ }
103
+
104
+
@@ -5,20 +5,24 @@ import { SolanaBlockMetaUpdateEvent } from "../types";
5
5
  import { SolanaTradeAppConfig } from "./SolanaTradeAppConfig";
6
6
  import { HELIUS_TIP_ACCOUNTS } from "./send/helius";
7
7
  import { getJitoTipAccount } from "./send/jito";
8
+ import { JitoTipWalletManager } from "./jito_tip_wallets";
8
9
 
9
10
 
10
11
  export class SolTransactionBuilder {
11
12
  appConfig: SolanaTradeAppConfig
12
- protected connection: Connection
13
- protected keypair: Keypair
13
+ connection: Connection
14
+ keypair: Keypair
14
15
 
15
- protected recentBlockhash: string
16
- protected recentBlockheight: number
16
+ recentBlockhash: string
17
+ recentBlockheight: number
18
+
19
+ jitoTipWalletManager: JitoTipWalletManager
17
20
 
18
21
  constructor(appConfig: SolanaTradeAppConfig) {
19
22
  this.appConfig = appConfig
20
23
  this.connection = appConfig.connection
21
24
  this.keypair = appConfig.keypair
25
+ this.jitoTipWalletManager = new JitoTipWalletManager()
22
26
  this.init()
23
27
  }
24
28
 
@@ -112,9 +116,11 @@ export class SolTransactionBuilder {
112
116
  /**
113
117
  * build transaction for tip jito
114
118
  */
115
- buildTransactionForTipJito(context: TradeContext, tipPayer: Keypair=undefined): Transaction {
119
+ buildTransactionForTipJito(context: TradeContext): Transaction {
120
+ const groupId = context.trade_runtime.group.id
116
121
  const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset
117
122
 
123
+ let tipPayer = this.jitoTipWalletManager.getNextWallet(groupId)
118
124
  if(!tipPayer) {
119
125
  tipPayer = this.keypair
120
126
  }