@clonegod/ttd-bsc-common 3.0.31 → 3.0.33

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.
@@ -17,6 +17,7 @@ export declare class SimpleRedisClient {
17
17
  hgetvalue(key: string, field: string): Promise<any>;
18
18
  hkeys(key: string): Promise<any>;
19
19
  hgetall(key: string): Promise<any>;
20
+ hmget(key: string, fields: string[]): Promise<(string | null)[]>;
20
21
  lrange(key: string, start?: number, stop?: number): Promise<string[]>;
21
22
  del(key: string, field?: string): Promise<any>;
22
23
  }
@@ -136,6 +136,12 @@ class SimpleRedisClient {
136
136
  return yield redisClient.hGetAll(key);
137
137
  });
138
138
  }
139
+ hmget(key, fields) {
140
+ return __awaiter(this, void 0, void 0, function* () {
141
+ const redisClient = yield this.getRedisClient();
142
+ return yield redisClient.hmGet(key, fields);
143
+ });
144
+ }
139
145
  lrange(key_1) {
140
146
  return __awaiter(this, arguments, void 0, function* (key, start = 0, stop = -1) {
141
147
  const redisClient = yield this.getRedisClient();
@@ -1,4 +1,4 @@
1
1
  import { TradeTrace } from '../trade/trade_trace';
2
2
  export interface ITransactionSender {
3
- sendTransaction(signedMainTx: string, eoa_tip_transaction: (eoa_address: string) => Promise<string>, order_trace_id: string, pair?: string, only_bundle?: boolean, trace?: Pick<TradeTrace, 'mark' | 'markProvider'>): Promise<string[]>;
3
+ sendTransaction(signedMainTx: string, tipTxMap: Map<string, string>, order_trace_id: string, pair?: string, only_bundle?: boolean, trace?: Pick<TradeTrace, 'mark'>): Promise<string[]>;
4
4
  }
@@ -36,6 +36,6 @@ export declare abstract class AbstractDexTrade extends AbastrcatTrade {
36
36
  inputToken: any;
37
37
  outputToken: any;
38
38
  };
39
- protected calculateAmountOutMin(context: TradeContext, inputToken: any, outputToken: any): ethers.BigNumber;
39
+ protected calculateAmountOutMin(context: TradeContext, _inputToken: any, outputToken: any): ethers.BigNumber;
40
40
  private extractNonceFromErrorMsg;
41
41
  }
@@ -57,6 +57,7 @@ const base_tx_result_checker_1 = require("./check/base_tx_result_checker");
57
57
  const redis_1 = require("../redis");
58
58
  const trade_direction_1 = require("../utils/trade_direction");
59
59
  const ethers_compat_1 = require("../utils/ethers_compat");
60
+ const fast_signer_1 = require("../utils/fast_signer");
60
61
  const trade_trace_1 = require("./trade_trace");
61
62
  const decimal_js_1 = __importDefault(require("decimal.js"));
62
63
  function buildTradeConfig() {
@@ -93,6 +94,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
93
94
  init(transactionSender) {
94
95
  return __awaiter(this, void 0, void 0, function* () {
95
96
  var _a, _b;
97
+ (0, fast_signer_1.patchEthersV5Signer)();
96
98
  this.provider = new ethers_compat_1.ethersCompat.JsonRpcProvider(this.chainConfig.rpcEndpoint);
97
99
  if (transactionSender) {
98
100
  this.transactionSender = transactionSender;
@@ -125,6 +127,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
125
127
  const direction = isBuy ? 'BUY' : 'SELL';
126
128
  const { inputToken, outputToken } = this.determineInputOutputTokens(order_msg, pool_info);
127
129
  const amountOutMin = this.calculateAmountOutMin(context, inputToken, outputToken);
130
+ context._precomputed = { inputToken, outputToken, amountOutMin };
128
131
  const trace = new trade_trace_1.TradeTrace(order_trace_id, pair, direction);
129
132
  trace.set('input', `${amount} ${inputToken.symbol}`);
130
133
  trace.set('outputMin', `${ethers_compat_1.ethersCompat.formatUnits(amountOutMin, outputToken.decimals)} ${outputToken.symbol}`);
@@ -188,10 +191,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
188
191
  trace.set('gas', realGasPriceGwei);
189
192
  trace.set('tip', transfer_amount_gwei);
190
193
  const only_bundle = order_msg.is_dex_maker;
191
- const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
192
- return tipTxMap.get(eoa_address) || (yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller));
193
- });
194
- yield this.transactionSender.sendTransaction(signedMainTx, eoa_tip_transaction, order_trace_id, pair, only_bundle, trace);
194
+ yield this.transactionSender.sendTransaction(signedMainTx, tipTxMap, order_trace_id, pair, only_bundle, trace);
195
195
  trace.mark('sent');
196
196
  trace.flush();
197
197
  try {
@@ -276,7 +276,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
276
276
  const { inputToken, outputToken } = (0, trade_direction_1.resolveTradeDirection)(pool_info, order_msg.isBuy);
277
277
  return { inputToken, outputToken };
278
278
  }
279
- calculateAmountOutMin(context, inputToken, outputToken) {
279
+ calculateAmountOutMin(context, _inputToken, outputToken) {
280
280
  const { price_msg, slippage_bps, order_msg } = context;
281
281
  const { isBuy } = order_msg;
282
282
  const slippage = slippage_bps / 10000;
@@ -25,6 +25,4 @@ export declare class CallerManager {
25
25
  private getLastUsedRedisKey;
26
26
  private getNonce;
27
27
  private setNonce;
28
- private getCallersSortedByLRU;
29
- private updateLastUsed;
30
28
  }
@@ -71,10 +71,31 @@ class CallerManager {
71
71
  }
72
72
  acquireCaller() {
73
73
  return __awaiter(this, void 0, void 0, function* () {
74
- const sortedCallers = yield this.getCallersSortedByLRU();
75
- const caller = sortedCallers[0];
76
- const nonce = yield this.getNonce(caller.address);
77
- yield this.updateLastUsed(caller.address);
74
+ const addresses = this.callers.map(w => w.address.toLowerCase());
75
+ const lastUsedKey = this.getLastUsedRedisKey();
76
+ const nonceKey = this.getNonceRedisKey();
77
+ const [lastUsedData, nonceValues] = yield Promise.all([
78
+ this.redis.hgetall(lastUsedKey),
79
+ this.redis.hmget(nonceKey, addresses),
80
+ ]);
81
+ let selectedIdx = 0;
82
+ let minLastUsed = Number.MAX_SAFE_INTEGER;
83
+ for (let i = 0; i < this.callers.length; i++) {
84
+ const redisTs = parseInt((lastUsedData === null || lastUsedData === void 0 ? void 0 : lastUsedData[addresses[i]]) || '0', 10);
85
+ if (redisTs < minLastUsed) {
86
+ minLastUsed = redisTs;
87
+ selectedIdx = i;
88
+ }
89
+ }
90
+ const caller = this.callers[selectedIdx];
91
+ const callerAddr = addresses[selectedIdx];
92
+ const nonceStr = nonceValues[selectedIdx];
93
+ if (nonceStr === null || nonceStr === undefined) {
94
+ throw new Error(`Caller ${caller.address} nonce not found in Redis, stream-trade may not be running`);
95
+ }
96
+ const nonce = parseInt(nonceStr, 10);
97
+ this.redis.hsetValue(lastUsedKey, callerAddr, String(Date.now()), 24 * 60 * 60)
98
+ .catch(err => logger.warn(`updateLastUsed failed: ${err.message}`));
78
99
  return { wallet: caller, nonce };
79
100
  });
80
101
  }
@@ -124,30 +145,5 @@ class CallerManager {
124
145
  yield this.redis.hsetValue(nonceKey, address.toLowerCase(), String(nonce), 24 * 60 * 60);
125
146
  });
126
147
  }
127
- getCallersSortedByLRU() {
128
- return __awaiter(this, void 0, void 0, function* () {
129
- const lastUsedKey = this.getLastUsedRedisKey();
130
- const lastUsedData = yield this.redis.hgetall(lastUsedKey);
131
- const callersWithTime = this.callers.map(caller => {
132
- let lastUsedAt = 0;
133
- const data = lastUsedData === null || lastUsedData === void 0 ? void 0 : lastUsedData[caller.address.toLowerCase()];
134
- if (data) {
135
- try {
136
- lastUsedAt = parseInt(data, 10) || 0;
137
- }
138
- catch (_a) { }
139
- }
140
- return { caller, lastUsedAt };
141
- });
142
- callersWithTime.sort((a, b) => a.lastUsedAt - b.lastUsedAt);
143
- return callersWithTime.map(item => item.caller);
144
- });
145
- }
146
- updateLastUsed(address) {
147
- return __awaiter(this, void 0, void 0, function* () {
148
- const lastUsedKey = this.getLastUsedRedisKey();
149
- yield this.redis.hsetValue(lastUsedKey, address.toLowerCase(), String(Date.now()), 24 * 60 * 60);
150
- });
151
- }
152
148
  }
153
149
  exports.CallerManager = CallerManager;
@@ -10,6 +10,10 @@ export declare class TradeResultSubscriber {
10
10
  listen(txHash: string, callback: (receipt: any) => void, timeoutMs?: number): void;
11
11
  remove(txHash: string): void;
12
12
  sendNonceWatch(caller: string, txHash: string): void;
13
+ private cachedSubscriptions;
14
+ private cachedClientName;
15
+ private getSubscriptions;
16
+ private getClientName;
13
17
  private ensureConnected;
14
18
  }
15
19
  export declare abstract class BaseTxResultChecker extends AbstractTransactionResultCheck {
@@ -19,6 +19,8 @@ class TradeResultSubscriber {
19
19
  this.ws = null;
20
20
  this.connected = false;
21
21
  this.listeners = new Map();
22
+ this.cachedSubscriptions = null;
23
+ this.cachedClientName = null;
22
24
  }
23
25
  static getInstance() {
24
26
  if (!TradeResultSubscriber.instance) {
@@ -43,26 +45,43 @@ class TradeResultSubscriber {
43
45
  this.ws.send(JSON.stringify({ type: 'nonceWatch', caller, txHash }));
44
46
  }
45
47
  }
48
+ getSubscriptions() {
49
+ if (this.cachedSubscriptions)
50
+ return this.cachedSubscriptions;
51
+ const groupIds = (process.env.TRADE_GROUP_ID || process.env.VAULT_GROUP_ID || '').split(',').map(s => s.trim()).filter(Boolean);
52
+ const subs = [];
53
+ for (const gid of groupIds) {
54
+ const w = (0, ttd_core_1.load_wallet)(gid, true);
55
+ if (w.public_key) {
56
+ subs.push({ address: w.public_key, groupId: gid });
57
+ }
58
+ }
59
+ this.cachedSubscriptions = subs;
60
+ return subs;
61
+ }
62
+ getClientName() {
63
+ if (this.cachedClientName)
64
+ return this.cachedClientName;
65
+ this.cachedClientName = process.env.APP_NAME
66
+ || process.env.name
67
+ || (process.env.pm_id ? `pm2-${process.env.pm_id}` : null)
68
+ || `pid-${process.pid}`;
69
+ return this.cachedClientName;
70
+ }
46
71
  ensureConnected() {
47
- if (this.ws && this.connected)
72
+ if (this.ws)
48
73
  return;
49
74
  const host = process.env.STREAM_TRADE_WS_HOST || '127.0.0.1';
50
75
  const wsUrl = `ws://${host}:${ttd_core_1.SERVICE_PORT.STREAM_TRADE_WS}`;
51
76
  this.ws = new ttd_core_1.WebSocketClient(wsUrl);
52
- const clientName = process.env.APP_NAME
53
- || process.env.name
54
- || (process.env.pm_id ? `pm2-${process.env.pm_id}` : null)
55
- || `pid-${process.pid}`;
56
77
  this.ws.onOpen(() => {
57
78
  this.connected = true;
58
- const groupIds = (process.env.TRADE_GROUP_ID || process.env.VAULT_GROUP_ID || '').split(',').map(s => s.trim()).filter(Boolean);
59
- for (const gid of groupIds) {
60
- const w = (0, ttd_core_1.load_wallet)(gid, true);
61
- if (w.public_key) {
62
- this.ws.send(JSON.stringify({ address: w.public_key, groupId: gid, clientName }));
63
- }
79
+ const subs = this.getSubscriptions();
80
+ const clientName = this.getClientName();
81
+ for (const { address, groupId } of subs) {
82
+ this.ws.send(JSON.stringify({ address, groupId, clientName }));
64
83
  }
65
- logger.info(`TradeResultSubscriber connected: ${wsUrl}`);
84
+ logger.info(`TradeResultSubscriber connected: ${wsUrl}, subscribed=${subs.length}`);
66
85
  });
67
86
  this.ws.onMessage((msg) => {
68
87
  if (msg.type !== 'TradeResult' || !msg.data)
@@ -5,12 +5,11 @@ export declare class TradeTrace {
5
5
  private startTime;
6
6
  private marks;
7
7
  private data;
8
- private providers;
9
8
  private error;
10
9
  constructor(orderId: string, pair?: string, direction?: string);
11
10
  mark(name: string): void;
12
11
  set(key: string, value: any): void;
13
- markProvider(name: string, ok: boolean, error?: string): void;
14
12
  markError(stage: string, message: string): void;
13
+ private buildTimeline;
15
14
  flush(): void;
16
15
  }
@@ -10,7 +10,6 @@ class TradeTrace {
10
10
  this.direction = direction;
11
11
  this.marks = [];
12
12
  this.data = {};
13
- this.providers = [];
14
13
  this.error = null;
15
14
  this.startTime = Date.now();
16
15
  }
@@ -20,23 +19,28 @@ class TradeTrace {
20
19
  set(key, value) {
21
20
  this.data[key] = value;
22
21
  }
23
- markProvider(name, ok, error) {
24
- this.providers.push({ name, ok, elapsed: Date.now() - this.startTime, error });
25
- }
26
22
  markError(stage, message) {
27
23
  this.error = { stage, message };
28
24
  }
25
+ buildTimeline() {
26
+ if (this.marks.length === 0)
27
+ return 'recv';
28
+ let prev = 0;
29
+ const parts = this.marks.map(m => {
30
+ const delta = m.elapsed - prev;
31
+ prev = m.elapsed;
32
+ return `${m.name}(${delta}ms)`;
33
+ });
34
+ const total = this.marks[this.marks.length - 1].elapsed;
35
+ return `recv -> ${parts.join(' -> ')} = ${total}ms`;
36
+ }
29
37
  flush() {
30
- const total = Date.now() - this.startTime;
31
- const timeline = this.marks.map(m => `${m.name}=${m.elapsed}ms`).join(' | ');
32
- const providerStatus = this.providers
33
- .map(p => `${p.name}=${p.ok ? '✓' : '✗'}${p.ok ? '' : '(' + (p.error || 'err') + ')'}`)
34
- .join(' ');
38
+ const timeline = this.buildTimeline();
35
39
  if (this.error) {
36
- logger.info(`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction}`, Object.assign({ timeline, error_stage: this.error.stage, error: this.error.message, total_ms: total }, this.data));
40
+ logger.info(`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction}`, Object.assign({ timeline, error_stage: this.error.stage, error: this.error.message }, this.data));
37
41
  }
38
42
  else {
39
- logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, Object.assign({ timeline, providers: providerStatus, total_ms: total }, this.data));
43
+ logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, Object.assign({ timeline }, this.data));
40
44
  }
41
45
  }
42
46
  }
@@ -0,0 +1 @@
1
+ export declare function patchEthersV5Signer(): boolean;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.patchEthersV5Signer = patchEthersV5Signer;
4
+ let patched = false;
5
+ function patchEthersV5Signer() {
6
+ if (patched)
7
+ return true;
8
+ let secp;
9
+ try {
10
+ secp = require('secp256k1');
11
+ }
12
+ catch (err) {
13
+ console.warn('[fast_signer] secp256k1 native module not found, skipping patch', err);
14
+ return false;
15
+ }
16
+ let SigningKey;
17
+ let arrayify;
18
+ let hexZeroPad;
19
+ let splitSignature;
20
+ try {
21
+ const signingKeyModule = require('@ethersproject/signing-key');
22
+ const bytesModule = require('@ethersproject/bytes');
23
+ SigningKey = signingKeyModule.SigningKey;
24
+ arrayify = bytesModule.arrayify;
25
+ hexZeroPad = bytesModule.hexZeroPad;
26
+ splitSignature = bytesModule.splitSignature;
27
+ }
28
+ catch (err) {
29
+ console.warn('[fast_signer] @ethersproject/signing-key not found, skipping patch', err);
30
+ return false;
31
+ }
32
+ const originalSignDigest = SigningKey.prototype.signDigest;
33
+ SigningKey.prototype.signDigest = function (digest) {
34
+ const privKeyBytes = arrayify(this.privateKey);
35
+ const digestBytes = arrayify(digest);
36
+ if (digestBytes.length !== 32) {
37
+ return originalSignDigest.call(this, digest);
38
+ }
39
+ try {
40
+ const { signature, recid } = secp.ecdsaSign(digestBytes, privKeyBytes);
41
+ const r = hexZeroPad('0x' + Buffer.from(signature.slice(0, 32)).toString('hex'), 32);
42
+ const s = hexZeroPad('0x' + Buffer.from(signature.slice(32, 64)).toString('hex'), 32);
43
+ return splitSignature({ recoveryParam: recid, r, s });
44
+ }
45
+ catch (err) {
46
+ console.warn('[fast_signer] native sign failed, fallback to elliptic.js', err);
47
+ return originalSignDigest.call(this, digest);
48
+ }
49
+ };
50
+ patched = true;
51
+ console.log('[fast_signer] ethers v5 SigningKey patched with native secp256k1');
52
+ return true;
53
+ }
@@ -3,6 +3,7 @@ export * from './gas_helper';
3
3
  export * from './trade_direction';
4
4
  export * from './pool_filter';
5
5
  export * from './ethers_compat';
6
+ export * from './fast_signer';
6
7
  export declare const sleep: (ms: number) => Promise<void>;
7
8
  export declare const formatPrice: (price: number, precision?: number) => string;
8
9
  export declare const formatUnits: (value: any, decimals: number) => Decimal;
@@ -24,6 +24,7 @@ __exportStar(require("./gas_helper"), exports);
24
24
  __exportStar(require("./trade_direction"), exports);
25
25
  __exportStar(require("./pool_filter"), exports);
26
26
  __exportStar(require("./ethers_compat"), exports);
27
+ __exportStar(require("./fast_signer"), exports);
27
28
  const sleep = (ms) => {
28
29
  return new Promise(resolve => setTimeout(resolve, ms));
29
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "3.0.31",
3
+ "version": "3.0.33",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -14,14 +14,16 @@
14
14
  "push": "npm run build && npm publish"
15
15
  },
16
16
  "dependencies": {
17
- "@clonegod/ttd-core": "3.0.11",
17
+ "@clonegod/ttd-core": "3.0.12",
18
18
  "@clonegod/ttd-bsc-send-tx": "2.0.3",
19
19
  "axios": "^1.12.0",
20
20
  "dotenv": "^16.4.7",
21
- "ethers": "^5.8.0"
21
+ "ethers": "^5.8.0",
22
+ "secp256k1": "^5.0.1"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^22.14.0",
26
+ "@types/secp256k1": "^4.0.6",
25
27
  "typescript": "^5.8.2"
26
28
  },
27
29
  "overrides": {},