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

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.
@@ -1,6 +1,7 @@
1
1
  export declare class _48ClubTrade {
2
- private rpcUrl;
2
+ private client;
3
3
  constructor();
4
+ sendPrivateTransaction(signedTx: string): Promise<string>;
4
5
  sendPrivateTransactionWith48SP(signedTx: string): Promise<string>;
5
6
  sendBundle(params: {
6
7
  txs: string[];
@@ -14,21 +14,39 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports._48ClubTrade = void 0;
16
16
  const axios_1 = __importDefault(require("axios"));
17
+ const https_1 = __importDefault(require("https"));
17
18
  const sp_signature_1 = require("./sp_signature");
18
19
  const member_1 = require("./member");
19
20
  class _48ClubTrade {
20
21
  constructor() {
21
- this.rpcUrl = process.env._48CLUB_RPC_URL || 'https://puissant-bsc.48.club';
22
+ const rpcUrl = process.env._48CLUB_RPC_URL || 'https://puissant-builder.48.club/';
23
+ this.client = axios_1.default.create({
24
+ baseURL: rpcUrl,
25
+ headers: { 'Content-Type': 'application/json' },
26
+ httpsAgent: new https_1.default.Agent({ keepAlive: true, maxSockets: 10 }),
27
+ timeout: 5000,
28
+ });
29
+ }
30
+ sendPrivateTransaction(signedTx) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const response = yield this.client.post('', {
33
+ jsonrpc: "2.0", id: "1",
34
+ method: "eth_sendRawTransaction",
35
+ params: [signedTx]
36
+ });
37
+ if (response.data.error) {
38
+ throw new Error(`48club private tx: ${response.data.error.message}`);
39
+ }
40
+ return response.data.result;
41
+ });
22
42
  }
23
43
  sendPrivateTransactionWith48SP(signedTx) {
24
44
  return __awaiter(this, void 0, void 0, function* () {
25
- const spSign = yield this.get48SPSignature([signedTx]);
26
- const response = yield axios_1.default.post(this.rpcUrl, {
27
- jsonrpc: "2.0", id: 1,
28
- method: "eth_sendPrivateTransaction",
29
- params: [{ tx: signedTx, preferences: { '48spSign': spSign } }]
30
- }, {
31
- headers: { 'Content-Type': 'application/json' }
45
+ const spSignature = yield this.get48SPSignature([signedTx]);
46
+ const response = yield this.client.post('', {
47
+ jsonrpc: "2.0", id: "1",
48
+ method: "eth_sendPrivateTransactionWith48SP",
49
+ params: [signedTx, spSignature]
32
50
  });
33
51
  if (response.data.error) {
34
52
  throw new Error(`48club private tx: ${response.data.error.message}`);
@@ -39,17 +57,21 @@ class _48ClubTrade {
39
57
  sendBundle(params) {
40
58
  return __awaiter(this, void 0, void 0, function* () {
41
59
  const currentTimestamp = Math.floor(Date.now() / 1000);
42
- const spSign = yield this.get48SPSignature(params.txs);
43
- const response = yield axios_1.default.post(this.rpcUrl, {
44
- jsonrpc: "2.0", id: 1,
60
+ const requestParams = {
61
+ txs: params.txs,
62
+ maxTimestamp: currentTimestamp + 2,
63
+ };
64
+ try {
65
+ const spSignature = yield this.get48SPSignature(params.txs);
66
+ requestParams['48spSign'] = spSignature;
67
+ }
68
+ catch (error) {
69
+ console.warn(`[48Club] 生成 48SP 签名失败,将发送不带签名的 Bundle: ${error.message}`);
70
+ }
71
+ const response = yield this.client.post('', {
72
+ jsonrpc: "2.0", id: "1",
45
73
  method: "eth_sendBundle",
46
- params: [{
47
- txs: params.txs,
48
- maxTimestamp: currentTimestamp + 2,
49
- '48spSign': spSign
50
- }]
51
- }, {
52
- headers: { 'Content-Type': 'application/json' }
74
+ params: [requestParams]
53
75
  });
54
76
  if (response.data.error) {
55
77
  throw new Error(`48club bundle: ${response.data.error.message}`);
@@ -1,4 +1,6 @@
1
1
  export declare class SoulPointSignature {
2
2
  static generate48SPSignature(privateKey: string, txs: string[]): string;
3
+ private static signWithV5;
4
+ private static signWithV6;
3
5
  static verify48SPSignature(signature: string, txs: string[], expectedSignerAddress: string): boolean;
4
6
  }
@@ -4,30 +4,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.SoulPointSignature = void 0;
5
5
  const ethers_1 = require("ethers");
6
6
  const _ethers = ethers_1.ethers;
7
- const keccak256 = ((_a = _ethers.utils) === null || _a === void 0 ? void 0 : _a.keccak256) || _ethers.keccak256;
8
- const concat = ((_b = _ethers.utils) === null || _b === void 0 ? void 0 : _b.concat) || _ethers.concat;
9
- const recoverAddress = ((_c = _ethers.utils) === null || _c === void 0 ? void 0 : _c.recoverAddress) || _ethers.recoverAddress;
7
+ const keccak256Fn = ((_a = _ethers.utils) === null || _a === void 0 ? void 0 : _a.keccak256) || _ethers.keccak256;
8
+ const concatFn = ((_b = _ethers.utils) === null || _b === void 0 ? void 0 : _b.concat) || _ethers.concat;
9
+ const recoverAddressFn = ((_c = _ethers.utils) === null || _c === void 0 ? void 0 : _c.recoverAddress) || _ethers.recoverAddress;
10
10
  class SoulPointSignature {
11
11
  static generate48SPSignature(privateKey, txs) {
12
12
  const wallet = new ethers_1.ethers.Wallet(privateKey);
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;
13
+ try {
14
+ return this.signWithV5(wallet, txs);
15
+ }
16
+ catch (_a) {
17
+ return this.signWithV6(wallet, txs);
18
+ }
19
+ }
20
+ static signWithV5(wallet, txs) {
21
+ const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256Fn(tx) : tx);
22
+ const concatenatedHashes = concatFn(txHashes);
23
+ const messageHash = keccak256Fn(concatenatedHashes);
24
+ const signature = wallet._signingKey().signDigest(messageHash);
25
+ const r = signature.r.slice(2).padStart(64, '0');
26
+ const s = signature.s.slice(2).padStart(64, '0');
27
+ const v = signature.recoveryParam.toString(16).padStart(2, '0');
28
+ return '0x' + r + s + v;
29
+ }
30
+ static signWithV6(wallet, txs) {
31
+ const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256Fn(tx) : tx);
32
+ const concatenatedHashes = concatFn(txHashes);
33
+ const messageHash = keccak256Fn(concatenatedHashes);
34
+ const signingKey = wallet.signingKey;
35
+ const signature = signingKey.sign(messageHash);
36
+ const r = signature.r.slice(2).padStart(64, '0');
37
+ const s = signature.s.slice(2).padStart(64, '0');
38
+ let recoveryId;
39
+ if (signature.recoveryParam !== undefined) {
40
+ recoveryId = Number(signature.recoveryParam);
41
+ }
42
+ else if (signature.v !== undefined) {
43
+ const v = Number(signature.v);
44
+ recoveryId = v >= 27 ? v - 27 : v;
19
45
  }
20
46
  else {
21
- const signature = wallet.signingKey.sign(messageHash);
22
- return signature.compactSerialized;
47
+ recoveryId = 0;
23
48
  }
49
+ recoveryId = recoveryId % 2;
50
+ const v = recoveryId.toString(16).padStart(2, '0');
51
+ return '0x' + r + s + v;
24
52
  }
25
53
  static verify48SPSignature(signature, txs, expectedSignerAddress) {
26
54
  try {
27
- const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256(tx) : tx);
28
- const concatenatedHashes = concat(txHashes);
29
- const messageHash = keccak256(concatenatedHashes);
30
- const recoveredAddr = recoverAddress(messageHash, signature);
55
+ const txHashes = txs.map(tx => tx.startsWith('0x') ? keccak256Fn(tx) : tx);
56
+ const concatenatedHashes = concatFn(txHashes);
57
+ const messageHash = keccak256Fn(concatenatedHashes);
58
+ const recoveredAddr = recoverAddressFn(messageHash, signature);
31
59
  return recoveredAddr.toLowerCase() === expectedSignerAddress.toLowerCase();
32
60
  }
33
61
  catch (_a) {
@@ -1,6 +1,5 @@
1
1
  export declare class BlockRazorTrade {
2
- private rpcUrl;
3
- private authToken;
2
+ private client;
4
3
  constructor();
5
4
  sendPrivateTransaction(signedTx: string): Promise<string>;
6
5
  sendBundle(transactions: string[]): Promise<string>;
@@ -15,20 +15,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.BlockRazorTrade = void 0;
16
16
  const ttd_core_1 = require("@clonegod/ttd-core");
17
17
  const axios_1 = __importDefault(require("axios"));
18
+ const https_1 = __importDefault(require("https"));
18
19
  class BlockRazorTrade {
19
20
  constructor() {
20
- this.rpcUrl = process.env.BLOCKRAZOR_RPC_URL || 'https://rpc.blockrazor.builders';
21
- this.authToken = process.env.BLOCKRAZOR_AUTH_TOKEN || '';
21
+ const rpcUrl = process.env.BLOCKRAZOR_RPC_URL || 'https://rpc.blockrazor.builders';
22
+ const authToken = process.env.BLOCKRAZOR_AUTH_TOKEN || '';
23
+ this.client = axios_1.default.create({
24
+ baseURL: rpcUrl,
25
+ headers: { 'Content-Type': 'application/json', 'Authorization': authToken },
26
+ httpsAgent: new https_1.default.Agent({ keepAlive: true, maxSockets: 10 }),
27
+ timeout: 5000,
28
+ });
22
29
  }
23
30
  sendPrivateTransaction(signedTx) {
24
31
  return __awaiter(this, void 0, void 0, function* () {
25
32
  try {
26
- const response = yield axios_1.default.post(this.rpcUrl, {
33
+ const response = yield this.client.post('', {
27
34
  jsonrpc: "2.0", id: "1",
28
35
  method: "eth_sendPrivateTransaction",
29
36
  params: [signedTx]
30
- }, {
31
- headers: { 'Content-Type': 'application/json', 'Authorization': this.authToken }
32
37
  });
33
38
  if (response.data.error) {
34
39
  throw new Error(`${response.data.error.code} - ${response.data.error.message}`);
@@ -45,12 +50,10 @@ class BlockRazorTrade {
45
50
  return __awaiter(this, void 0, void 0, function* () {
46
51
  try {
47
52
  const currentTimestamp = Math.floor(Date.now() / 1000);
48
- const response = yield axios_1.default.post(this.rpcUrl, {
53
+ const response = yield this.client.post('', {
49
54
  jsonrpc: "2.0", id: "1",
50
55
  method: "eth_sendBundle",
51
56
  params: [{ txs: transactions, maxTimestamp: currentTimestamp + 5 }]
52
- }, {
53
- headers: { 'Content-Type': 'application/json', 'Authorization': this.authToken }
54
57
  });
55
58
  if (response.data.error) {
56
59
  throw new Error(`Bundle: ${response.data.error.message}`);
@@ -1,9 +1,13 @@
1
1
  import { AppConfig } from '@clonegod/ttd-core';
2
+ interface TraceWriter {
3
+ mark(name: string): void;
4
+ }
2
5
  export declare class TransactionSender {
3
6
  private rpc;
4
7
  private blockRazor;
5
8
  private _48Club;
6
9
  private sendBundleProxy;
7
10
  constructor(appConfig: AppConfig);
8
- sendTransaction(signedMainTx: string, eoa_tip_transaction: (eoa_address: string) => Promise<string>, order_trace_id: string, pair?: string, only_bundle?: boolean): Promise<string[]>;
11
+ sendTransaction(signedMainTx: string, tipTxMap: Map<string, string>, order_trace_id: string, pair?: string, only_bundle?: boolean, trace?: TraceWriter): Promise<string[]>;
9
12
  }
13
+ export {};
@@ -14,6 +14,8 @@ const ttd_core_1 = require("@clonegod/ttd-core");
14
14
  const rpc_1 = require("./rpc");
15
15
  const blockrazor_1 = require("./blockrazor");
16
16
  const _48club_1 = require("./_48club");
17
+ const sp_signature_1 = require("./_48club/sp_signature");
18
+ const member_1 = require("./_48club/member");
17
19
  const ws_client_1 = require("./ws_client");
18
20
  const constants_1 = require("./constants");
19
21
  class TransactionSender {
@@ -23,81 +25,90 @@ class TransactionSender {
23
25
  this._48Club = new _48club_1._48ClubTrade();
24
26
  this.sendBundleProxy = new ws_client_1.BscSendTxProxy();
25
27
  }
26
- sendTransaction(signedMainTx_1, eoa_tip_transaction_1, order_trace_id_1) {
27
- return __awaiter(this, arguments, void 0, function* (signedMainTx, eoa_tip_transaction, order_trace_id, pair = '', only_bundle = false) {
28
- const rpcProviders = [
28
+ sendTransaction(signedMainTx_1, tipTxMap_1, order_trace_id_1) {
29
+ return __awaiter(this, arguments, void 0, function* (signedMainTx, tipTxMap, order_trace_id, pair = '', only_bundle = false, trace) {
30
+ const results = [];
31
+ const errors = [];
32
+ if (process.env.SEND_TX_BLOX_BUNDLE_WS === 'true') {
33
+ try {
34
+ const tipTx = tipTxMap.get(constants_1.BSC_EOA_ADDRESS.BLXR);
35
+ this.sendBundleProxy.sendTransaction('bloxroute', pair, signedMainTx, tipTx);
36
+ results.push('sent to bloxroute ws');
37
+ }
38
+ catch (error) {
39
+ errors.push(`bloxroute_ws: ${error.message}`);
40
+ (0, ttd_core_1.log_info)(`Transaction send failed`, { provider: 'bloxroute_ws', order_trace_id, error: error.message });
41
+ }
42
+ }
43
+ if (process.env.SEND_TX_48CLUB_BUNDLE_WS === 'true') {
44
+ try {
45
+ const tipTx = tipTxMap.get(constants_1.BSC_EOA_ADDRESS._48CLUB);
46
+ let spSignature;
47
+ try {
48
+ const privateKey = (0, member_1.get_48club_sp_private_key)();
49
+ if (privateKey) {
50
+ spSignature = sp_signature_1.SoulPointSignature.generate48SPSignature(privateKey, [signedMainTx, tipTx]);
51
+ }
52
+ }
53
+ catch (_a) { }
54
+ this.sendBundleProxy.sendTransaction('48club', pair, signedMainTx, tipTx, spSignature);
55
+ results.push('sent to 48club ws');
56
+ }
57
+ catch (error) {
58
+ errors.push(`48club_ws: ${error.message}`);
59
+ (0, ttd_core_1.log_info)(`Transaction send failed`, { provider: '48club_ws', order_trace_id, error: error.message });
60
+ }
61
+ }
62
+ trace === null || trace === void 0 ? void 0 : trace.mark('ws_sent');
63
+ const httpProviders = [
29
64
  {
30
- name: 'Default RPC',
65
+ name: 'default_rpc',
31
66
  enable: process.env.SEND_TX_DEFAULT_RPC_PRIVATE === 'true',
32
67
  is_bundle: false,
33
68
  send: () => __awaiter(this, void 0, void 0, function* () { return this.rpc.eth_sendRawTransaction(signedMainTx); }),
34
69
  },
35
70
  {
36
- name: 'BlockRazor HTTP Private',
71
+ name: 'blockrazor_private',
37
72
  enable: process.env.SEND_TX_BLOCKRAZOR_PRIVATE === 'true',
38
73
  is_bundle: false,
39
74
  send: () => __awaiter(this, void 0, void 0, function* () { return this.blockRazor.sendPrivateTransaction(signedMainTx); }),
40
75
  },
41
76
  {
42
- name: 'BlockRazor HTTP Bundle',
77
+ name: 'blockrazor_bundle',
43
78
  enable: process.env.SEND_TX_BLOCKRAZOR_BUNDLE === 'true',
44
79
  is_bundle: true,
45
80
  send: () => __awaiter(this, void 0, void 0, function* () {
46
- const bundle = [signedMainTx, yield eoa_tip_transaction(constants_1.BSC_EOA_ADDRESS.BLOCKRAZOR)];
47
- return this.blockRazor.sendBundle(bundle);
81
+ const tipTx = tipTxMap.get(constants_1.BSC_EOA_ADDRESS.BLOCKRAZOR);
82
+ return this.blockRazor.sendBundle([signedMainTx, tipTx]);
48
83
  }),
49
84
  },
50
85
  {
51
- name: '48Club HTTP Private',
86
+ name: '48club_private',
52
87
  enable: process.env.SEND_TX_48CLUB_PRIVATE === 'true',
53
88
  is_bundle: false,
54
89
  send: () => __awaiter(this, void 0, void 0, function* () { return this._48Club.sendPrivateTransactionWith48SP(signedMainTx); }),
55
90
  },
56
91
  {
57
- name: '48Club HTTP Bundle',
92
+ name: '48club_bundle',
58
93
  enable: process.env.SEND_TX_48CLUB_BUNDLE === 'true',
59
94
  is_bundle: true,
60
95
  send: () => __awaiter(this, void 0, void 0, function* () {
61
- const bundle = [signedMainTx, yield eoa_tip_transaction(constants_1.BSC_EOA_ADDRESS._48CLUB)];
62
- return this._48Club.sendBundle({ txs: bundle });
63
- }),
64
- },
65
- {
66
- name: '48Club WS Bundle',
67
- enable: process.env.SEND_TX_48CLUB_BUNDLE_WS === 'true',
68
- is_bundle: true,
69
- send: () => __awaiter(this, void 0, void 0, function* () {
70
- const tipTx = yield eoa_tip_transaction(constants_1.BSC_EOA_ADDRESS._48CLUB);
71
- const spSignature = yield this._48Club.get48SPSignature([signedMainTx, tipTx]);
72
- this.sendBundleProxy.sendTransaction('48club', pair, signedMainTx, tipTx, spSignature);
73
- return 'sent to 48club ws';
74
- }),
75
- },
76
- {
77
- name: 'BloxRoute WS Bundle',
78
- enable: process.env.SEND_TX_BLOX_BUNDLE_WS === 'true',
79
- is_bundle: true,
80
- send: () => __awaiter(this, void 0, void 0, function* () {
81
- const tipTx = yield eoa_tip_transaction(constants_1.BSC_EOA_ADDRESS.BLXR);
82
- this.sendBundleProxy.sendTransaction('bloxroute', pair, signedMainTx, tipTx);
83
- return 'sent to bloxroute ws';
96
+ const tipTx = tipTxMap.get(constants_1.BSC_EOA_ADDRESS._48CLUB);
97
+ return this._48Club.sendBundle({ txs: [signedMainTx, tipTx] });
84
98
  }),
85
99
  },
86
100
  ];
87
- const results = [];
88
- const errors = [];
89
- yield Promise.all(rpcProviders
101
+ yield Promise.all(httpProviders
90
102
  .filter(p => p.enable)
91
103
  .filter(p => only_bundle ? p.is_bundle : true)
92
104
  .map((provider) => __awaiter(this, void 0, void 0, function* () {
93
105
  try {
94
106
  const txHash = yield provider.send();
95
107
  results.push(txHash);
96
- (0, ttd_core_1.log_info)(`Transaction sent`, { provider: provider.name, order_trace_id, txHash });
97
108
  }
98
109
  catch (error) {
99
110
  errors.push(`${provider.name}: ${error.message}`);
100
- (0, ttd_core_1.log_info)(`Transaction failed`, { provider: provider.name, order_trace_id, error: error.message });
111
+ (0, ttd_core_1.log_info)(`Transaction send failed`, { provider: provider.name, order_trace_id, error: error.message });
101
112
  }
102
113
  })));
103
114
  if (errors.some(e => e.includes('nonce'))) {
@@ -1,6 +1,9 @@
1
1
  export declare class BscSendTxProxy {
2
2
  private wsUrl;
3
3
  private ws;
4
+ private clientName;
5
+ private connected;
6
+ private pendingQueue;
4
7
  constructor();
5
8
  sendTransaction(builder: '48club' | 'bloxroute', pair: string, mainTx: string, tipTx: string, _48spSign?: string): void;
6
9
  }
package/dist/ws_client.js CHANGED
@@ -2,17 +2,42 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BscSendTxProxy = void 0;
4
4
  const ttd_core_1 = require("@clonegod/ttd-core");
5
+ const logger = (0, ttd_core_1.createLogger)(__filename);
5
6
  class BscSendTxProxy {
6
7
  constructor() {
8
+ this.connected = false;
9
+ this.pendingQueue = [];
7
10
  const host = process.env.SEND_TX_WS_HOST || '127.0.0.1';
8
11
  this.wsUrl = `ws://${host}:${ttd_core_1.SERVICE_PORT.SEND_TX_WS}/bsc/send_tx`;
12
+ this.clientName = process.env.APP_NAME
13
+ || process.env.name
14
+ || (process.env.pm_id ? `pm2-${process.env.pm_id}` : `pid-${process.pid}`);
9
15
  this.ws = new ttd_core_1.WebSocketClient(this.wsUrl);
10
- this.ws.onOpen(() => console.log('BscSendTxProxy connected:', this.wsUrl));
11
- this.ws.onMessage((message) => console.log('BscSendTxProxy response:', message));
16
+ this.ws.onOpen(() => {
17
+ this.connected = true;
18
+ this.ws.send(JSON.stringify({ type: 'hello', clientName: this.clientName }));
19
+ const flushed = this.pendingQueue.length;
20
+ if (flushed > 0) {
21
+ for (const msg of this.pendingQueue) {
22
+ this.ws.send(msg);
23
+ }
24
+ this.pendingQueue.length = 0;
25
+ }
26
+ logger.info(`BscSendTxProxy connected: ${this.wsUrl}, client=${this.clientName}, flushed=${flushed}`);
27
+ });
28
+ this.ws.onMessage((message) => logger.debug(`BscSendTxProxy response`, message));
12
29
  this.ws.connect();
13
30
  }
14
31
  sendTransaction(builder, pair, mainTx, tipTx, _48spSign = null) {
15
- this.ws.send(JSON.stringify({ builder, pair, mainTx, tipTx, _48spSign }));
32
+ const msg = JSON.stringify({ builder, pair, mainTx, tipTx, _48spSign });
33
+ if (this.connected) {
34
+ this.ws.send(msg);
35
+ }
36
+ else {
37
+ if (this.pendingQueue.length < 100) {
38
+ this.pendingQueue.push(msg);
39
+ }
40
+ }
16
41
  }
17
42
  }
18
43
  exports.BscSendTxProxy = BscSendTxProxy;
package/dist/ws_server.js CHANGED
@@ -18,6 +18,8 @@ class BscSendTxWebSocketServer {
18
18
  constructor(appConfig, serverPort = ttd_core_1.SERVICE_PORT.SEND_TX_WS) {
19
19
  this.wss = null;
20
20
  this.latestBlockNumber = 0;
21
+ this.clientNames = new WeakMap();
22
+ this.connectedCount = 0;
21
23
  this._48clubWsSender = null;
22
24
  this.bloxrouteWsSender = null;
23
25
  this.appConfig = appConfig;
@@ -45,7 +47,14 @@ class BscSendTxWebSocketServer {
45
47
  handleMessage(ws, message) {
46
48
  var _a, _b;
47
49
  try {
48
- const request = JSON.parse(message);
50
+ const parsed = JSON.parse(message);
51
+ if (parsed.type === 'hello') {
52
+ this.clientNames.set(ws, parsed.clientName || 'unknown');
53
+ this.connectedCount++;
54
+ (0, ttd_core_1.log_info)(`[WS_SERVER] client connected: ${parsed.clientName}, total=${this.connectedCount}`);
55
+ return;
56
+ }
57
+ const request = parsed;
49
58
  if (!request.mainTx || !request.builder || !request.tipTx) {
50
59
  ws.send(JSON.stringify({ error: 'Missing required fields: mainTx, builder, tipTx' }));
51
60
  return;
@@ -71,9 +80,12 @@ class BscSendTxWebSocketServer {
71
80
  return;
72
81
  this.wss = new ws_1.WebSocketServer({ port: this.serverPort });
73
82
  this.wss.on('connection', (ws) => {
74
- (0, ttd_core_1.log_info)(`[WS_SERVER] client connected`);
75
83
  ws.on('message', (msg) => this.handleMessage(ws, msg.toString()));
76
- ws.on('close', () => (0, ttd_core_1.log_info)(`[WS_SERVER] client disconnected`));
84
+ ws.on('close', () => {
85
+ const name = this.clientNames.get(ws) || 'unknown';
86
+ this.connectedCount = Math.max(0, this.connectedCount - 1);
87
+ (0, ttd_core_1.log_info)(`[WS_SERVER] client disconnected: ${name}, total=${this.connectedCount}`);
88
+ });
77
89
  ws.on('error', (err) => (0, ttd_core_1.log_error)(`[WS_SERVER] ws error`, err));
78
90
  });
79
91
  (0, ttd_core_1.log_info)(`[WS_SERVER] started on port ${this.serverPort}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-send-tx",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "BSC 交易发送模块(HTTP直发 + WS bundle 转发)",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "push": "npm run build && npm publish"
13
13
  },
14
14
  "dependencies": {
15
- "@clonegod/ttd-core": "3.0.8",
15
+ "@clonegod/ttd-core": "3.0.13",
16
16
  "axios": "^1.12.0",
17
17
  "dotenv": "^16.4.7",
18
18
  "ethers": "^5.8.0",