@clonegod/ttd-bsc-send-tx 1.0.0
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/README.md +40 -0
- package/dist/_48club/http.d.ts +9 -0
- package/dist/_48club/http.js +69 -0
- package/dist/_48club/index.d.ts +4 -0
- package/dist/_48club/index.js +11 -0
- package/dist/_48club/member.d.ts +1 -0
- package/dist/_48club/member.js +21 -0
- package/dist/_48club/sp_signature.d.ts +4 -0
- package/dist/_48club/sp_signature.js +27 -0
- package/dist/_48club/ws.d.ts +9 -0
- package/dist/_48club/ws.js +63 -0
- package/dist/blockrazor/index.d.ts +7 -0
- package/dist/blockrazor/index.js +67 -0
- package/dist/bloxroute/index.d.ts +1 -0
- package/dist/bloxroute/index.js +5 -0
- package/dist/bloxroute/ws.d.ts +10 -0
- package/dist/bloxroute/ws.js +65 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/rpc/index.d.ts +6 -0
- package/dist/rpc/index.js +47 -0
- package/dist/transaction_sender.d.ts +9 -0
- package/dist/transaction_sender.js +116 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +2 -0
- package/dist/ws_client.d.ts +6 -0
- package/dist/ws_client.js +18 -0
- package/dist/ws_server.d.ts +1 -0
- package/dist/ws_server.js +93 -0
- package/package.json +29 -0
- package/scripts/deploy.sh +29 -0
- package/scripts/start-ttd-bsc-send-tx-ws.sh +24 -0
- package/src/_48club/http.ts +60 -0
- package/src/_48club/index.ts +4 -0
- package/src/_48club/member.ts +19 -0
- package/src/_48club/sp_signature.ts +28 -0
- package/src/_48club/ws.ts +47 -0
- package/src/blockrazor/index.ts +57 -0
- package/src/bloxroute/index.ts +1 -0
- package/src/bloxroute/ws.ts +53 -0
- package/src/constants.ts +5 -0
- package/src/index.ts +2 -0
- package/src/rpc/index.ts +42 -0
- package/src/transaction_sender.ts +123 -0
- package/src/types/index.ts +8 -0
- package/src/ws_client.ts +22 -0
- package/src/ws_server.ts +104 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { AppConfig, log_info } from '@clonegod/ttd-core';
|
|
2
|
+
import { BscMainnetRpc } from "./rpc";
|
|
3
|
+
import { BlockRazorTrade } from "./blockrazor";
|
|
4
|
+
import { _48ClubTrade } from "./_48club";
|
|
5
|
+
import { BscSendTxProxy } from "./ws_client";
|
|
6
|
+
import { BSC_EOA_ADDRESS } from "./constants";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 交易发送编排器
|
|
10
|
+
* 管理所有发送通道(HTTP 直发 + WS bundle 转发),并行发送
|
|
11
|
+
*/
|
|
12
|
+
export class TransactionSender {
|
|
13
|
+
private rpc: BscMainnetRpc;
|
|
14
|
+
private blockRazor: BlockRazorTrade;
|
|
15
|
+
private _48Club: _48ClubTrade;
|
|
16
|
+
private sendBundleProxy: BscSendTxProxy;
|
|
17
|
+
|
|
18
|
+
constructor(appConfig: AppConfig) {
|
|
19
|
+
this.rpc = new BscMainnetRpc(appConfig.env_args.rpc_endpoint);
|
|
20
|
+
this.blockRazor = new BlockRazorTrade();
|
|
21
|
+
this._48Club = new _48ClubTrade();
|
|
22
|
+
this.sendBundleProxy = new BscSendTxProxy();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async sendTransaction(
|
|
26
|
+
signedMainTx: string,
|
|
27
|
+
eoa_tip_transaction: (eoa_address: string) => Promise<string>,
|
|
28
|
+
order_trace_id: string,
|
|
29
|
+
pair: string = '',
|
|
30
|
+
only_bundle: boolean = false
|
|
31
|
+
): Promise<string[]> {
|
|
32
|
+
const rpcProviders = [
|
|
33
|
+
{
|
|
34
|
+
name: 'Default RPC',
|
|
35
|
+
enable: process.env.SEND_TX_DEFAULT_RPC_PRIVATE === 'true',
|
|
36
|
+
is_bundle: false,
|
|
37
|
+
send: async () => this.rpc.eth_sendRawTransaction(signedMainTx),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'BlockRazor HTTP Private',
|
|
41
|
+
enable: process.env.SEND_TX_BLOCKRAZOR_PRIVATE === 'true',
|
|
42
|
+
is_bundle: false,
|
|
43
|
+
send: async () => this.blockRazor.sendPrivateTransaction(signedMainTx),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'BlockRazor HTTP Bundle',
|
|
47
|
+
enable: process.env.SEND_TX_BLOCKRAZOR_BUNDLE === 'true',
|
|
48
|
+
is_bundle: true,
|
|
49
|
+
send: async () => {
|
|
50
|
+
const bundle = [signedMainTx, await eoa_tip_transaction(BSC_EOA_ADDRESS.BLOCKRAZOR)];
|
|
51
|
+
return this.blockRazor.sendBundle(bundle);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: '48Club HTTP Private',
|
|
56
|
+
enable: process.env.SEND_TX_48CLUB_PRIVATE === 'true',
|
|
57
|
+
is_bundle: false,
|
|
58
|
+
send: async () => this._48Club.sendPrivateTransactionWith48SP(signedMainTx),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: '48Club HTTP Bundle',
|
|
62
|
+
enable: process.env.SEND_TX_48CLUB_BUNDLE === 'true',
|
|
63
|
+
is_bundle: true,
|
|
64
|
+
send: async () => {
|
|
65
|
+
const bundle = [signedMainTx, await eoa_tip_transaction(BSC_EOA_ADDRESS._48CLUB)];
|
|
66
|
+
return this._48Club.sendBundle({ txs: bundle });
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: '48Club WS Bundle',
|
|
71
|
+
enable: process.env.SEND_TX_48CLUB_BUNDLE_WS === 'true',
|
|
72
|
+
is_bundle: true,
|
|
73
|
+
send: async () => {
|
|
74
|
+
const tipTx = await eoa_tip_transaction(BSC_EOA_ADDRESS._48CLUB);
|
|
75
|
+
const spSignature = await this._48Club.get48SPSignature([signedMainTx, tipTx]);
|
|
76
|
+
this.sendBundleProxy.sendTransaction('48club', pair, signedMainTx, tipTx, spSignature);
|
|
77
|
+
return 'sent to 48club ws';
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'BloxRoute WS Bundle',
|
|
82
|
+
enable: process.env.SEND_TX_BLOX_BUNDLE_WS === 'true',
|
|
83
|
+
is_bundle: true,
|
|
84
|
+
send: async () => {
|
|
85
|
+
const tipTx = await eoa_tip_transaction(BSC_EOA_ADDRESS.BLXR);
|
|
86
|
+
this.sendBundleProxy.sendTransaction('bloxroute', pair, signedMainTx, tipTx);
|
|
87
|
+
return 'sent to bloxroute ws';
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const results: string[] = [];
|
|
93
|
+
const errors: string[] = [];
|
|
94
|
+
|
|
95
|
+
await Promise.all(
|
|
96
|
+
rpcProviders
|
|
97
|
+
.filter(p => p.enable)
|
|
98
|
+
.filter(p => only_bundle ? p.is_bundle : true)
|
|
99
|
+
.map(async (provider) => {
|
|
100
|
+
try {
|
|
101
|
+
const txHash = await provider.send();
|
|
102
|
+
results.push(txHash);
|
|
103
|
+
log_info(`Transaction sent`, { provider: provider.name, order_trace_id, txHash });
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
errors.push(`${provider.name}: ${error.message}`);
|
|
106
|
+
log_info(`Transaction failed`, { provider: provider.name, order_trace_id, error: error.message });
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (errors.some(e => e.includes('nonce'))) {
|
|
112
|
+
throw new Error(`Nonce error: ${JSON.stringify(errors)}`);
|
|
113
|
+
}
|
|
114
|
+
if (errors.some(e => e.includes('bundle already exist') || e.includes('already known'))) {
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
if (results.length === 0 || errors.length > 0) {
|
|
118
|
+
throw new Error(`Send failed: ${JSON.stringify(errors)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/ws_client.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { WebSocketClient, SERVICE_PORT } from '@clonegod/ttd-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WS Client — trade 进程通过此客户端连接 WS Server,提交 bundle 请求
|
|
5
|
+
*/
|
|
6
|
+
export class BscSendTxProxy {
|
|
7
|
+
private wsUrl: string;
|
|
8
|
+
private ws: WebSocketClient;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
const host = process.env.SEND_TX_WS_HOST || '127.0.0.1';
|
|
12
|
+
this.wsUrl = `ws://${host}:${SERVICE_PORT.SEND_TX_WS}/bsc/send_tx`;
|
|
13
|
+
this.ws = new WebSocketClient(this.wsUrl);
|
|
14
|
+
this.ws.onOpen(() => console.log('BscSendTxProxy connected:', this.wsUrl));
|
|
15
|
+
this.ws.onMessage((message: any) => console.log('BscSendTxProxy response:', message));
|
|
16
|
+
this.ws.connect();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
sendTransaction(builder: '48club' | 'bloxroute', pair: string, mainTx: string, tipTx: string, _48spSign: string = null as any) {
|
|
20
|
+
this.ws.send(JSON.stringify({ builder, pair, mainTx, tipTx, _48spSign }));
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/ws_server.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS Server — 接收 trade 进程提交的 bundle 请求,转发给 Builder
|
|
3
|
+
*
|
|
4
|
+
* 启动方式:node dist/ws_server.js
|
|
5
|
+
*/
|
|
6
|
+
require('dotenv').config();
|
|
7
|
+
|
|
8
|
+
import { AppConfig, CHAIN_ID, log_error, log_info, SERVICE_PORT } from '@clonegod/ttd-core';
|
|
9
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
10
|
+
import { BscSendTxRequestType } from './types';
|
|
11
|
+
import { _48ClubWsSender } from './_48club';
|
|
12
|
+
import { BloXRouteWsSender } from './bloxroute';
|
|
13
|
+
|
|
14
|
+
class BscSendTxWebSocketServer {
|
|
15
|
+
private wss: WebSocketServer | null = null;
|
|
16
|
+
private serverPort: number;
|
|
17
|
+
private appConfig: AppConfig;
|
|
18
|
+
private latestBlockNumber: number = 0;
|
|
19
|
+
|
|
20
|
+
private _48clubWsSender: _48ClubWsSender | null = null;
|
|
21
|
+
private bloxrouteWsSender: BloXRouteWsSender | null = null;
|
|
22
|
+
|
|
23
|
+
constructor(appConfig: AppConfig, serverPort: number = SERVICE_PORT.SEND_TX_WS) {
|
|
24
|
+
this.appConfig = appConfig;
|
|
25
|
+
this.serverPort = serverPort;
|
|
26
|
+
|
|
27
|
+
this.appConfig.arb_event_subscriber.subscribe_new_block(CHAIN_ID.BSC, (data: string) => {
|
|
28
|
+
try {
|
|
29
|
+
this.latestBlockNumber = JSON.parse(data).blockNumber;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
log_error('parse block update event failed', error);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.initSenders();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private initSenders(): void {
|
|
39
|
+
if (process.env.SEND_TX_48CLUB_WS === 'true') {
|
|
40
|
+
this._48clubWsSender = new _48ClubWsSender();
|
|
41
|
+
this._48clubWsSender.connect();
|
|
42
|
+
}
|
|
43
|
+
if (process.env.SEND_TX_BLOXROUTE_WS === 'true') {
|
|
44
|
+
this.bloxrouteWsSender = new BloXRouteWsSender();
|
|
45
|
+
this.bloxrouteWsSender.connect();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private handleMessage(ws: WebSocket, message: string): void {
|
|
50
|
+
try {
|
|
51
|
+
const request: BscSendTxRequestType = JSON.parse(message);
|
|
52
|
+
|
|
53
|
+
if (!request.mainTx || !request.builder || !request.tipTx) {
|
|
54
|
+
ws.send(JSON.stringify({ error: 'Missing required fields: mainTx, builder, tipTx' }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
request.blockNumber = this.latestBlockNumber;
|
|
59
|
+
log_info(`[WS_SERVER] recv bundle request`, { builder: request.builder, pair: request.pair });
|
|
60
|
+
|
|
61
|
+
switch (request.builder) {
|
|
62
|
+
case '48club':
|
|
63
|
+
this._48clubWsSender?.sendTransaction(request);
|
|
64
|
+
break;
|
|
65
|
+
case 'bloxroute':
|
|
66
|
+
this.bloxrouteWsSender?.sendTransaction(request);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
} catch (error: any) {
|
|
70
|
+
log_error(`[WS_SERVER] handle request failed`, error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async start(): Promise<void> {
|
|
75
|
+
if (this.wss) return;
|
|
76
|
+
|
|
77
|
+
this.wss = new WebSocketServer({ port: this.serverPort });
|
|
78
|
+
|
|
79
|
+
this.wss.on('connection', (ws: WebSocket) => {
|
|
80
|
+
log_info(`[WS_SERVER] client connected`);
|
|
81
|
+
ws.on('message', (msg: Buffer) => this.handleMessage(ws, msg.toString()));
|
|
82
|
+
ws.on('close', () => log_info(`[WS_SERVER] client disconnected`));
|
|
83
|
+
ws.on('error', (err: Error) => log_error(`[WS_SERVER] ws error`, err));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
log_info(`[WS_SERVER] started on port ${this.serverPort}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 启动入口
|
|
91
|
+
process.on('uncaughtException', (err) => { log_error(`[BSC_SEND_TX] uncaught exception`, err); process.exit(1); });
|
|
92
|
+
process.on('unhandledRejection', (reason) => { log_error(`[BSC_SEND_TX] unhandled rejection`, reason instanceof Error ? reason : new Error(String(reason))); process.exit(1); });
|
|
93
|
+
|
|
94
|
+
const main = async () => {
|
|
95
|
+
const appConfig = new AppConfig();
|
|
96
|
+
await appConfig.init();
|
|
97
|
+
await appConfig.subscribe_config_change();
|
|
98
|
+
|
|
99
|
+
const wsPort = SERVICE_PORT.SEND_TX_WS;
|
|
100
|
+
const server = new BscSendTxWebSocketServer(appConfig, wsPort);
|
|
101
|
+
await server.start();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
main().catch((err) => { log_error(`[BSC_SEND_TX] startup failed`, err); process.exit(1); });
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"typeRoots": [
|
|
4
|
+
"./node_modules/@types",
|
|
5
|
+
"./types",
|
|
6
|
+
],
|
|
7
|
+
"lib": [
|
|
8
|
+
"es2015",
|
|
9
|
+
"dom"
|
|
10
|
+
],
|
|
11
|
+
"module": "CommonJS",
|
|
12
|
+
"target": "es6",
|
|
13
|
+
"rootDirs": [
|
|
14
|
+
"src"
|
|
15
|
+
],
|
|
16
|
+
"outDir": "./dist",
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"esModuleInterop": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"removeComments": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["dist"]
|
|
25
|
+
}
|