@clonegod/ttd-bsc-send-tx 1.0.2 → 2.0.2

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.
@@ -22,9 +22,7 @@ class _48ClubTrade {
22
22
  }
23
23
  sendPrivateTransactionWith48SP(signedTx) {
24
24
  return __awaiter(this, void 0, void 0, function* () {
25
- console.log(`[48Club-HTTP] sendPrivateTransaction: tx type=${typeof signedTx}, length=${signedTx === null || signedTx === void 0 ? void 0 : signedTx.length}, prefix=${signedTx === null || signedTx === void 0 ? void 0 : signedTx.substring(0, 10)}`);
26
25
  const spSign = yield this.get48SPSignature([signedTx]);
27
- console.log(`[48Club-HTTP] spSign type=${typeof spSign}, length=${spSign === null || spSign === void 0 ? void 0 : spSign.length}, value=${spSign === null || spSign === void 0 ? void 0 : spSign.substring(0, 20)}...`);
28
26
  const response = yield axios_1.default.post(this.rpcUrl, {
29
27
  jsonrpc: "2.0", id: 1,
30
28
  method: "eth_sendPrivateTransaction",
@@ -40,10 +38,8 @@ class _48ClubTrade {
40
38
  }
41
39
  sendBundle(params) {
42
40
  return __awaiter(this, void 0, void 0, function* () {
43
- console.log(`[48Club-HTTP] sendBundle: txs count=${params.txs.length}, lengths=${params.txs.map(t => t === null || t === void 0 ? void 0 : t.length).join(',')}`);
44
41
  const currentTimestamp = Math.floor(Date.now() / 1000);
45
42
  const spSign = yield this.get48SPSignature(params.txs);
46
- console.log(`[48Club-HTTP] bundle spSign type=${typeof spSign}, length=${spSign === null || spSign === void 0 ? void 0 : spSign.length}`);
47
43
  const response = yield axios_1.default.post(this.rpcUrl, {
48
44
  jsonrpc: "2.0", id: 1,
49
45
  method: "eth_sendBundle",
@@ -8,7 +8,7 @@ const get_48club_sp_private_key = () => {
8
8
  return _48club_sp_wallet_private_key;
9
9
  }
10
10
  try {
11
- const wallet = (0, ttd_core_1.load_wallet)(process.env._48CLUB_SP_WALLET_ID || 'TTD-PAYMENT');
11
+ const wallet = (0, ttd_core_1.load_wallet)(process.env._48CLUB_SP_WALLET_ID || 'TTD-PAYMENT', false);
12
12
  console.log('Load 48club SP wallet success, wallet address:', wallet.public_key);
13
13
  _48club_sp_wallet_private_key = wallet.private_key;
14
14
  return _48club_sp_wallet_private_key;
@@ -9,36 +9,17 @@ const concat = ((_b = _ethers.utils) === null || _b === void 0 ? void 0 : _b.con
9
9
  const recoverAddress = ((_c = _ethers.utils) === null || _c === void 0 ? void 0 : _c.recoverAddress) || _ethers.recoverAddress;
10
10
  class SoulPointSignature {
11
11
  static generate48SPSignature(privateKey, txs) {
12
- var _a, _b, _c, _d, _e;
13
- console.log(`[48SP] ethers version: ${ethers_1.ethers.version}`);
14
- console.log(`[48SP] keccak256 source: ${((_a = _ethers.utils) === null || _a === void 0 ? void 0 : _a.keccak256) ? 'v5(utils)' : 'v6(top-level)'}`);
15
- console.log(`[48SP] txs count: ${txs.length}, lengths: ${txs.map(t => t.length).join(',')}`);
16
12
  const wallet = new ethers_1.ethers.Wallet(privateKey);
17
- console.log(`[48SP] wallet._signingKey: ${typeof wallet._signingKey}, wallet.signingKey: ${typeof wallet.signingKey}`);
18
- try {
19
- const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256(tx) : tx);
20
- console.log(`[48SP] txHashes: ${txHashes.map(h => h.substring(0, 20) + '...').join(', ')}`);
21
- const concatenatedHashes = concat(txHashes);
22
- console.log(`[48SP] concatenated type: ${typeof concatenatedHashes}, isUint8Array: ${concatenatedHashes instanceof Uint8Array}`);
23
- const messageHash = keccak256(concatenatedHashes);
24
- console.log(`[48SP] messageHash: ${messageHash}`);
25
- if (typeof wallet._signingKey === 'function') {
26
- console.log(`[48SP] using v5 signing path`);
27
- const signature = wallet._signingKey().signDigest(messageHash);
28
- console.log(`[48SP] signature.compact: ${(_b = signature.compact) === null || _b === void 0 ? void 0 : _b.substring(0, 20)}..., length: ${(_c = signature.compact) === null || _c === void 0 ? void 0 : _c.length}`);
29
- return signature.compact;
30
- }
31
- else {
32
- console.log(`[48SP] using v6 signing path`);
33
- const signature = wallet.signingKey.sign(messageHash);
34
- console.log(`[48SP] signature.compactSerialized: ${(_d = signature.compactSerialized) === null || _d === void 0 ? void 0 : _d.substring(0, 20)}..., length: ${(_e = signature.compactSerialized) === null || _e === void 0 ? void 0 : _e.length}`);
35
- return signature.compactSerialized;
36
- }
13
+ const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256(tx) : tx);
14
+ const concatenatedHashes = concat(txHashes);
15
+ const messageHash = keccak256(concatenatedHashes);
16
+ if (typeof wallet._signingKey === 'function') {
17
+ const signature = wallet._signingKey().signDigest(messageHash);
18
+ return signature.compact;
37
19
  }
38
- catch (err) {
39
- console.error(`[48SP] ERROR in generate48SPSignature: ${err.message}`);
40
- console.error(`[48SP] stack: ${err.stack}`);
41
- throw err;
20
+ else {
21
+ const signature = wallet.signingKey.sign(messageHash);
22
+ return signature.compactSerialized;
42
23
  }
43
24
  }
44
25
  static verify48SPSignature(signature, txs, expectedSignerAddress) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-send-tx",
3
- "version": "1.0.2",
3
+ "version": "2.0.2",
4
4
  "description": "BSC 交易发送模块(HTTP直发 + WS bundle 转发)",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -5,11 +5,11 @@ echo "$@"
5
5
  echo
6
6
 
7
7
  # args
8
- app_id="ttd-bsc-send-tx-ws"
8
+ app_id="ttd-bsc-send-tx"
9
9
  app_ns="BSC"
10
10
 
11
11
  # sync code
12
- git pull
12
+ # git pull
13
13
  yarn install
14
14
  npm run build
15
15
 
@@ -1,64 +0,0 @@
1
- import axios from 'axios';
2
- import { SoulPointSignature } from './sp_signature';
3
- import { get_48club_sp_private_key } from './member';
4
-
5
- /**
6
- * 48Club HTTP API
7
- * https://docs.48.club/puissant-builder
8
- */
9
- export class _48ClubTrade {
10
- private rpcUrl: string;
11
-
12
- constructor() {
13
- this.rpcUrl = process.env._48CLUB_RPC_URL || 'https://puissant-bsc.48.club';
14
- }
15
-
16
- async sendPrivateTransactionWith48SP(signedTx: string): Promise<string> {
17
- console.log(`[48Club-HTTP] sendPrivateTransaction: tx type=${typeof signedTx}, length=${signedTx?.length}, prefix=${signedTx?.substring(0, 10)}`);
18
- const spSign = await this.get48SPSignature([signedTx]);
19
- console.log(`[48Club-HTTP] spSign type=${typeof spSign}, length=${spSign?.length}, value=${spSign?.substring(0, 20)}...`);
20
-
21
- const response = await axios.post(this.rpcUrl, {
22
- jsonrpc: "2.0", id: 1,
23
- method: "eth_sendPrivateTransaction",
24
- params: [{ tx: signedTx, preferences: { '48spSign': spSign } }]
25
- }, {
26
- headers: { 'Content-Type': 'application/json' }
27
- });
28
-
29
- if (response.data.error) {
30
- throw new Error(`48club private tx: ${response.data.error.message}`);
31
- }
32
- return response.data.result;
33
- }
34
-
35
- async sendBundle(params: { txs: string[] }): Promise<string> {
36
- console.log(`[48Club-HTTP] sendBundle: txs count=${params.txs.length}, lengths=${params.txs.map(t => t?.length).join(',')}`);
37
- const currentTimestamp = Math.floor(Date.now() / 1000);
38
- const spSign = await this.get48SPSignature(params.txs);
39
- console.log(`[48Club-HTTP] bundle spSign type=${typeof spSign}, length=${spSign?.length}`);
40
-
41
- const response = await axios.post(this.rpcUrl, {
42
- jsonrpc: "2.0", id: 1,
43
- method: "eth_sendBundle",
44
- params: [{
45
- txs: params.txs,
46
- maxTimestamp: currentTimestamp + 2,
47
- '48spSign': spSign
48
- }]
49
- }, {
50
- headers: { 'Content-Type': 'application/json' }
51
- });
52
-
53
- if (response.data.error) {
54
- throw new Error(`48club bundle: ${response.data.error.message}`);
55
- }
56
- return response.data.result;
57
- }
58
-
59
- async get48SPSignature(txs: string[]): Promise<string> {
60
- const privateKey = get_48club_sp_private_key();
61
- if (!privateKey) throw new Error('48club SP private key not found');
62
- return SoulPointSignature.generate48SPSignature(privateKey, txs);
63
- }
64
- }
@@ -1,4 +0,0 @@
1
- export { _48ClubTrade } from './http';
2
- export { _48ClubWsSender } from './ws';
3
- export { SoulPointSignature } from './sp_signature';
4
- export { get_48club_sp_private_key } from './member';
@@ -1,19 +0,0 @@
1
- import { load_wallet } from '@clonegod/ttd-core';
2
-
3
- var _48club_sp_wallet_private_key: string | null = null;
4
-
5
- export const get_48club_sp_private_key = (): string | null => {
6
- if (_48club_sp_wallet_private_key) {
7
- return _48club_sp_wallet_private_key;
8
- }
9
-
10
- try {
11
- const wallet = load_wallet(process.env._48CLUB_SP_WALLET_ID || 'TTD-PAYMENT');
12
- console.log('Load 48club SP wallet success, wallet address:', wallet.public_key);
13
- _48club_sp_wallet_private_key = wallet.private_key;
14
- return _48club_sp_wallet_private_key;
15
- } catch (error: any) {
16
- console.error('Load 48club SP wallet failed:' + error.message);
17
- return null;
18
- }
19
- };
@@ -1,64 +0,0 @@
1
- import { ethers } from 'ethers';
2
-
3
- const _ethers = ethers as any;
4
-
5
- // ethers v5/v6 兼容
6
- const keccak256 = _ethers.utils?.keccak256 || _ethers.keccak256;
7
- const concat = _ethers.utils?.concat || _ethers.concat;
8
- const recoverAddress = _ethers.utils?.recoverAddress || _ethers.recoverAddress;
9
-
10
- /**
11
- * 48 SoulPoint 签名
12
- * https://docs.48.club/puissant-builder/48-soulpoint-benefits
13
- */
14
- export class SoulPointSignature {
15
- static generate48SPSignature(privateKey: string, txs: string[]): string {
16
- console.log(`[48SP] ethers version: ${(ethers as any).version}`);
17
- console.log(`[48SP] keccak256 source: ${_ethers.utils?.keccak256 ? 'v5(utils)' : 'v6(top-level)'}`);
18
- console.log(`[48SP] txs count: ${txs.length}, lengths: ${txs.map(t => t.length).join(',')}`);
19
-
20
- const wallet = new ethers.Wallet(privateKey);
21
- console.log(`[48SP] wallet._signingKey: ${typeof (wallet as any)._signingKey}, wallet.signingKey: ${typeof (wallet as any).signingKey}`);
22
-
23
- try {
24
- const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256(tx) : tx);
25
- console.log(`[48SP] txHashes: ${txHashes.map(h => h.substring(0, 20) + '...').join(', ')}`);
26
-
27
- const concatenatedHashes = concat(txHashes);
28
- console.log(`[48SP] concatenated type: ${typeof concatenatedHashes}, isUint8Array: ${concatenatedHashes instanceof Uint8Array}`);
29
-
30
- const messageHash = keccak256(concatenatedHashes);
31
- console.log(`[48SP] messageHash: ${messageHash}`);
32
-
33
- // v5: wallet._signingKey().signDigest(hash).compact
34
- // v6: wallet.signingKey.sign(hash).compactSerialized
35
- if (typeof (wallet as any)._signingKey === 'function') {
36
- console.log(`[48SP] using v5 signing path`);
37
- const signature = (wallet as any)._signingKey().signDigest(messageHash);
38
- console.log(`[48SP] signature.compact: ${signature.compact?.substring(0, 20)}..., length: ${signature.compact?.length}`);
39
- return signature.compact;
40
- } else {
41
- console.log(`[48SP] using v6 signing path`);
42
- const signature = (wallet as any).signingKey.sign(messageHash);
43
- console.log(`[48SP] signature.compactSerialized: ${signature.compactSerialized?.substring(0, 20)}..., length: ${signature.compactSerialized?.length}`);
44
- return signature.compactSerialized;
45
- }
46
- } catch (err: any) {
47
- console.error(`[48SP] ERROR in generate48SPSignature: ${err.message}`);
48
- console.error(`[48SP] stack: ${err.stack}`);
49
- throw err;
50
- }
51
- }
52
-
53
- static verify48SPSignature(signature: string, txs: string[], expectedSignerAddress: string): boolean {
54
- try {
55
- const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256(tx) : tx);
56
- const concatenatedHashes = concat(txHashes);
57
- const messageHash = keccak256(concatenatedHashes);
58
- const recoveredAddr = recoverAddress(messageHash, signature);
59
- return recoveredAddr.toLowerCase() === expectedSignerAddress.toLowerCase();
60
- } catch {
61
- return false;
62
- }
63
- }
64
- }
package/src/_48club/ws.ts DELETED
@@ -1,47 +0,0 @@
1
- import { log_info, log_warn, WebSocketClient } from '@clonegod/ttd-core';
2
- import { BscSendTxRequestType } from '../types';
3
-
4
- /**
5
- * 48Club WebSocket 发送器(长连接发送 bundle 到 builder)
6
- */
7
- export class _48ClubWsSender {
8
- private wsClient: WebSocketClient | null = null;
9
- private wsUrl: string;
10
-
11
- constructor() {
12
- this.wsUrl = process.env._48CLUB_WS_URL || 'wss://puissant-builder.48.club/';
13
- }
14
-
15
- connect(): void {
16
- if (this.wsClient?.isConnected()) return;
17
- if (this.wsClient) { this.wsClient.disconnect(); this.wsClient = null; }
18
-
19
- this.wsClient = new WebSocketClient(this.wsUrl, { reconnectInterval: 1000 });
20
- this.wsClient.onOpen(() => log_info(`[48club-ws] connected`));
21
- this.wsClient.onMessage((msg: any) => log_info(`[48club-ws] response:`, msg));
22
- this.wsClient.connect();
23
- }
24
-
25
- isConnected(): boolean {
26
- return this.wsClient?.isConnected() || false;
27
- }
28
-
29
- async sendTransaction(request: BscSendTxRequestType): Promise<void> {
30
- if (!this.isConnected()) { log_warn('[48club-ws] not connected'); return; }
31
- if (!request.tipTx) { log_warn('[48club-ws] tipTx required for bundle'); return; }
32
-
33
- const currentTimestamp = Math.floor(Date.now() / 1000);
34
- const bundleParams: any = {
35
- txs: [request.mainTx, request.tipTx],
36
- maxTimestamp: currentTimestamp + 2,
37
- };
38
- if (request.blockNumber > 0) bundleParams.maxBlockNumber = request.blockNumber + 2;
39
- if (request._48spSign) bundleParams['48spSign'] = request._48spSign;
40
-
41
- this.wsClient!.send(JSON.stringify({
42
- jsonrpc: '2.0', id: '1',
43
- method: 'eth_sendBundle',
44
- params: [bundleParams]
45
- }));
46
- }
47
- }
@@ -1,57 +0,0 @@
1
- import { log_error } from '@clonegod/ttd-core';
2
- import axios from 'axios';
3
-
4
- /**
5
- * BlockRazor HTTP API
6
- * https://blockrazor.gitbook.io/blockrazor/bsc/block-builder
7
- */
8
- export class BlockRazorTrade {
9
- private rpcUrl: string;
10
- private authToken: string;
11
-
12
- constructor() {
13
- this.rpcUrl = process.env.BLOCKRAZOR_RPC_URL || 'https://rpc.blockrazor.builders';
14
- this.authToken = process.env.BLOCKRAZOR_AUTH_TOKEN || '';
15
- }
16
-
17
- async sendPrivateTransaction(signedTx: string): Promise<string> {
18
- try {
19
- const response = await axios.post(this.rpcUrl, {
20
- jsonrpc: "2.0", id: "1",
21
- method: "eth_sendPrivateTransaction",
22
- params: [signedTx]
23
- }, {
24
- headers: { 'Content-Type': 'application/json', 'Authorization': this.authToken }
25
- });
26
-
27
- if (response.data.error) {
28
- throw new Error(`${response.data.error.code} - ${response.data.error.message}`);
29
- }
30
- return response.data.result;
31
- } catch (error: any) {
32
- log_error('blockrazor sendPrivateTransaction failed', error);
33
- throw error;
34
- }
35
- }
36
-
37
- async sendBundle(transactions: string[]): Promise<string> {
38
- try {
39
- const currentTimestamp = Math.floor(Date.now() / 1000);
40
- const response = await axios.post(this.rpcUrl, {
41
- jsonrpc: "2.0", id: "1",
42
- method: "eth_sendBundle",
43
- params: [{ txs: transactions, maxTimestamp: currentTimestamp + 5 }]
44
- }, {
45
- headers: { 'Content-Type': 'application/json', 'Authorization': this.authToken }
46
- });
47
-
48
- if (response.data.error) {
49
- throw new Error(`Bundle: ${response.data.error.message}`);
50
- }
51
- return response.data.result;
52
- } catch (error: any) {
53
- log_error('blockrazor sendBundle failed', error);
54
- throw error;
55
- }
56
- }
57
- }
@@ -1 +0,0 @@
1
- export { BloXRouteWsSender } from './ws';
@@ -1,53 +0,0 @@
1
- import { log_info, log_warn, WebSocketClient } from '@clonegod/ttd-core';
2
- import { BscSendTxRequestType } from '../types';
3
-
4
- /**
5
- * BloXRoute WebSocket 发送器(长连接发送 bundle 到 builder)
6
- * https://docs.bloxroute.com/bsc-and-eth/apis/transaction-bundles
7
- */
8
- export class BloXRouteWsSender {
9
- private wsClient: WebSocketClient | null = null;
10
- private wsUrl: string;
11
- private authToken: string;
12
-
13
- constructor() {
14
- this.wsUrl = process.env.BLOXROUTE_WS_URL || 'wss://api.blxrbdn.com/ws';
15
- this.authToken = process.env.BLOX_AUTH_KEY || '';
16
- }
17
-
18
- connect(): void {
19
- if (this.wsClient?.isConnected()) return;
20
- if (this.wsClient) { this.wsClient.disconnect(); this.wsClient = null; }
21
-
22
- const headers: { [key: string]: string } = {};
23
- if (this.authToken) headers['Authorization'] = this.authToken;
24
-
25
- this.wsClient = new WebSocketClient(this.wsUrl, { headers, reconnectInterval: 1000 });
26
- this.wsClient.onOpen(() => log_info(`[bloxroute-ws] connected`));
27
- this.wsClient.onMessage((msg: any) => log_info(`[bloxroute-ws] response:`, msg));
28
- this.wsClient.connect();
29
- }
30
-
31
- isConnected(): boolean {
32
- return this.wsClient?.isConnected() || false;
33
- }
34
-
35
- async sendTransaction(request: BscSendTxRequestType): Promise<void> {
36
- if (!this.isConnected()) { log_warn('[bloxroute-ws] not connected'); return; }
37
- if (!request.tipTx) { log_warn('[bloxroute-ws] tipTx required for bundle'); return; }
38
-
39
- const targetBlockNumber = request.blockNumber + 4;
40
-
41
- this.wsClient!.send(JSON.stringify({
42
- id: "1",
43
- method: "blxr_submit_bundle",
44
- params: {
45
- transaction: [request.mainTx, request.tipTx],
46
- blockchain_network: "BSC-Mainnet",
47
- block_number: `0x${targetBlockNumber.toString(16)}`,
48
- max_timestamp: Math.floor(Date.now() / 1000) + 2,
49
- mev_builders: { "all": "" }
50
- }
51
- }));
52
- }
53
- }
package/src/constants.ts DELETED
@@ -1,5 +0,0 @@
1
- export enum BSC_EOA_ADDRESS {
2
- BLOCKRAZOR = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20',
3
- _48CLUB = '0x4848489f0b2BEdd788c696e2D79b6b69D7484848',
4
- BLXR = '0x74c5F8C6ffe41AD4789602BDB9a48E6Cad623520',
5
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { TransactionSender } from './transaction_sender';
2
- export { BSC_EOA_ADDRESS } from './constants';
package/src/rpc/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import { log_warn } from '@clonegod/ttd-core';
2
-
3
- export class BscMainnetRpc {
4
- private url: string;
5
- private headers: Headers;
6
-
7
- constructor(rpc_endpoint: string) {
8
- this.url = rpc_endpoint || process.env.BSC_RPC_ENDPOINT || '';
9
- this.headers = new Headers();
10
- this.headers.append("Content-Type", "application/json");
11
- }
12
-
13
- async eth_sendRawTransaction(signedTx: string): Promise<string> {
14
- setTimeout(async () => {
15
- console.log(`Sending transaction to ${this.url}`);
16
-
17
- const raw = JSON.stringify({
18
- jsonrpc: "2.0",
19
- method: "eth_sendRawTransaction",
20
- params: [signedTx],
21
- id: 1
22
- });
23
-
24
- const requestOptions = {
25
- method: 'POST',
26
- headers: this.headers,
27
- body: raw,
28
- redirect: 'follow' as const
29
- };
30
-
31
- const response = await fetch(this.url, requestOptions);
32
- const result = await response.json();
33
-
34
- if (result.error) {
35
- log_warn(result.error.message);
36
- }
37
- return result.result;
38
- }, 0);
39
-
40
- return '';
41
- }
42
- }
@@ -1,123 +0,0 @@
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
- }
@@ -1,8 +0,0 @@
1
- export interface BscSendTxRequestType {
2
- builder: '48club' | 'bloxroute'
3
- pair: string
4
- mainTx: string
5
- tipTx: string
6
- _48spSign: string
7
- blockNumber: number
8
- }
package/src/ws_client.ts DELETED
@@ -1,22 +0,0 @@
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 DELETED
@@ -1,104 +0,0 @@
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 DELETED
@@ -1,25 +0,0 @@
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
- }