@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.
- package/dist/trade/index.d.ts +1 -0
- package/dist/trade/index.js +1 -0
- package/dist/trade/jito_tip_wallets.d.ts +21 -0
- package/dist/trade/jito_tip_wallets.js +121 -0
- package/dist/trade/send/helius.js +3 -5
- package/dist/trade/send/http_client.d.ts +2 -0
- package/dist/trade/send/http_client.js +41 -0
- package/dist/trade/send/jito.d.ts +1 -0
- package/dist/trade/send/jito.js +30 -5
- package/dist/trade/tx_builder.d.ts +7 -5
- package/dist/trade/tx_builder.js +5 -1
- package/package.json +1 -1
- package/src/trade/index.ts +2 -1
- package/src/trade/jito_tip_wallets.ts +143 -0
- package/src/trade/send/helius.ts +4 -2
- package/src/trade/send/http_client.ts +63 -0
- package/src/trade/send/jito.ts +42 -5
- package/src/trade/tx_builder.ts +11 -5
package/dist/trade/index.d.ts
CHANGED
package/dist/trade/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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,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>;
|
package/dist/trade/send/jito.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
18
|
+
buildTransactionForTipJito(context: TradeContext): Transaction;
|
|
17
19
|
}
|
package/dist/trade/tx_builder.js
CHANGED
|
@@ -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
|
|
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
package/src/trade/index.ts
CHANGED
|
@@ -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
|
+
|
package/src/trade/send/helius.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Transaction } from "@solana/web3.js"
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
+
|
package/src/trade/send/jito.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
package/src/trade/tx_builder.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
13
|
+
connection: Connection
|
|
14
|
+
keypair: Keypair
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
}
|