@clonegod/ttd-bsc-common 3.0.10 → 3.0.11

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.
@@ -81,89 +81,93 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
81
81
  slippage: `${slippage_bps / 100}%`,
82
82
  });
83
83
  let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
84
- const callerHandle = yield this.callerManager.acquireCaller();
85
- const { wallet: caller, nonce: initialNonce } = callerHandle;
86
- try {
87
- let nonce = initialNonce;
88
- let nonce_from_error = -1;
89
- let txid = '';
90
- let i = 1;
91
- do {
92
- try {
93
- if (i > 1) {
94
- if (nonce_from_error >= 0) {
95
- nonce = nonce_from_error;
96
- nonce_from_error = -1;
97
- (0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
98
- }
99
- else {
100
- nonce = yield this.provider.getTransactionCount(caller.address, 'pending');
101
- (0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from chain: ${nonce}`, {}, order_trace_id);
102
- }
84
+ const { wallet: caller, nonce: initialNonce } = yield this.callerManager.acquireCaller();
85
+ let nonce = initialNonce;
86
+ let nonce_from_error = -1;
87
+ let txid = '';
88
+ let i = 1;
89
+ do {
90
+ try {
91
+ if (i > 1) {
92
+ if (nonce_from_error >= 0) {
93
+ nonce = nonce_from_error;
94
+ nonce_from_error = -1;
95
+ (0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
103
96
  }
104
- const { executorId, data } = this.encodeTradeData(context);
105
- const deadline = Math.floor(Date.now() / 1000) + 60;
106
- const vaultCalldata = this.vaultInterface.encodeFunctionData('delegatecallExecutorToTrade', [executorId, data, deadline]);
107
- const gasPriceGwei = this.getGasPriceGwei(context);
108
- const realGasPriceGwei = Math.min(Number(gasPriceGwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
109
- const tx = {
110
- to: this.tradeConfig.vaultAddress,
111
- data: vaultCalldata,
112
- gasLimit: this.chainConfig.gasOptions.gasLimit,
113
- gasPrice: ethers_1.ethers.utils.parseUnits(realGasPriceGwei, 'gwei'),
114
- nonce,
115
- chainId: this.chainConfig.chainId,
116
- value: 0,
117
- };
118
- const signedTx = yield caller.signTransaction(tx);
119
- txid = ethers_1.ethers.utils.keccak256(signedTx);
120
- (0, ttd_core_1.log_info)(`交易已签名`, { txid, nonce, caller: caller.address, gasPriceGwei: realGasPriceGwei }, order_trace_id);
121
- const tipNonce = nonce + 1;
122
- const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
123
- const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
124
- context.ui_tip_amount = new decimal_js_1.default(transfer_amount_gwei).div(Math.pow(10, 9)).toNumber();
125
- return yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller);
126
- });
127
- const only_bundle = order_msg.is_dex_maker;
128
- yield this.transactionSender.sendTransaction(signedTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
129
- (0, ttd_core_1.log_info)(`交易发送成功`, {
130
- pair, direction: isBuy ? 'BUY' : 'SELL',
131
- txid, attempt: i, caller: caller.address
132
- }, order_trace_id);
97
+ else {
98
+ nonce = yield this.provider.getTransactionCount(caller.address, 'pending');
99
+ (0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from chain: ${nonce}`, {}, order_trace_id);
100
+ }
101
+ }
102
+ const { executorId, data } = this.encodeTradeData(context);
103
+ const deadline = Math.floor(Date.now() / 1000) + 60;
104
+ const vaultCalldata = this.vaultInterface.encodeFunctionData('delegatecallExecutorToTrade', [executorId, data, deadline]);
105
+ const gasPriceGwei = this.getGasPriceGwei(context);
106
+ const realGasPriceGwei = Math.min(Number(gasPriceGwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
107
+ const tipNonce = nonce + 1;
108
+ const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
109
+ context.ui_tip_amount = new decimal_js_1.default(transfer_amount_gwei).div(Math.pow(10, 9)).toNumber();
110
+ const mainTx = {
111
+ to: this.tradeConfig.vaultAddress,
112
+ data: vaultCalldata,
113
+ gasLimit: this.chainConfig.gasOptions.gasLimit,
114
+ gasPrice: ethers_1.ethers.utils.parseUnits(realGasPriceGwei, 'gwei'),
115
+ nonce,
116
+ chainId: this.chainConfig.chainId,
117
+ value: 0,
118
+ };
119
+ const builderAddresses = [
120
+ ttd_bsc_send_tx_1.BSC_EOA_ADDRESS.BLOCKRAZOR,
121
+ ttd_bsc_send_tx_1.BSC_EOA_ADDRESS._48CLUB,
122
+ ttd_bsc_send_tx_1.BSC_EOA_ADDRESS.BLXR,
123
+ ];
124
+ const [signedMainTx, ...signedTips] = yield Promise.all([
125
+ caller.signTransaction(mainTx),
126
+ ...builderAddresses.map(addr => this.buildTipTransferTx(addr, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller)),
127
+ ]);
128
+ txid = ethers_1.ethers.utils.keccak256(signedMainTx);
129
+ const tipTxMap = new Map();
130
+ builderAddresses.forEach((addr, idx) => tipTxMap.set(addr, signedTips[idx]));
131
+ (0, ttd_core_1.log_info)(`交易已签名`, { txid, nonce, caller: caller.address, gasPriceGwei: realGasPriceGwei }, order_trace_id);
132
+ const only_bundle = order_msg.is_dex_maker;
133
+ const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
134
+ return tipTxMap.get(eoa_address) || (yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller));
135
+ });
136
+ yield this.transactionSender.sendTransaction(signedMainTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
137
+ (0, ttd_core_1.log_info)(`交易发送成功`, {
138
+ pair, direction: isBuy ? 'BUY' : 'SELL',
139
+ txid, attempt: i, caller: caller.address
140
+ }, order_trace_id);
141
+ return txid;
142
+ }
143
+ catch (error) {
144
+ const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
145
+ if (errorMessage.includes('rate limit')) {
146
+ (0, ttd_core_1.log_warn)(`Rate limit error, no retry! i=${i}`, {}, order_trace_id);
133
147
  return txid;
134
148
  }
135
- catch (error) {
136
- const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
137
- if (errorMessage.includes('rate limit')) {
138
- (0, ttd_core_1.log_warn)(`Rate limit error, no retry! i=${i}`, {}, order_trace_id);
139
- return txid;
140
- }
141
- if (errorMessage.includes('replacement transaction underpriced')) {
142
- (0, ttd_core_1.log_warn)(`Replacement tx underpriced, continue! i=${i}`, {}, order_trace_id);
143
- continue;
144
- }
145
- const isNonceError = errorMessage.includes('nonce');
146
- if (!isNonceError || i >= maxAttempts) {
147
- (0, ttd_core_1.log_warn)(`Non-nonce error or max retries, i=${i}, error: ${errorMessage}`, {}, order_trace_id);
148
- return txid;
149
- }
150
- (0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
151
- if (errorMessage.includes('nonce too')) {
152
- const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
153
- if (correctNonce !== null && correctNonce >= 0) {
154
- nonce_from_error = correctNonce;
155
- }
149
+ if (errorMessage.includes('replacement transaction underpriced')) {
150
+ (0, ttd_core_1.log_warn)(`Replacement tx underpriced, continue! i=${i}`, {}, order_trace_id);
151
+ continue;
152
+ }
153
+ const isNonceError = errorMessage.includes('nonce');
154
+ if (!isNonceError || i >= maxAttempts) {
155
+ (0, ttd_core_1.log_warn)(`Non-nonce error or max retries, i=${i}, error: ${errorMessage}`, {}, order_trace_id);
156
+ return txid;
157
+ }
158
+ (0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
159
+ if (errorMessage.includes('nonce too')) {
160
+ const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
161
+ if (correctNonce !== null && correctNonce >= 0) {
162
+ nonce_from_error = correctNonce;
156
163
  }
157
164
  }
158
- } while (++i <= maxAttempts);
159
- if (!txid) {
160
- throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
161
165
  }
162
- return txid;
163
- }
164
- finally {
165
- yield callerHandle.release();
166
+ } while (++i <= maxAttempts);
167
+ if (!txid) {
168
+ throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
166
169
  }
170
+ return txid;
167
171
  });
168
172
  }
169
173
  getGasPriceGwei(context) {
@@ -2,16 +2,12 @@ import { ethers } from "ethers";
2
2
  export interface CallerHandle {
3
3
  wallet: ethers.Wallet;
4
4
  nonce: number;
5
- release: () => Promise<void>;
6
5
  }
7
6
  export interface CallerManagerConfig {
8
7
  chainName: string;
9
8
  provider: ethers.providers.JsonRpcProvider;
10
9
  callerGroupIds: string[];
11
10
  chainId: number;
12
- lockExpireSeconds?: number;
13
- acquireTimeoutMs?: number;
14
- acquireRetryIntervalMs?: number;
15
11
  }
16
12
  export declare class CallerManager {
17
13
  private callers;
@@ -26,10 +22,8 @@ export declare class CallerManager {
26
22
  getCallerAddresses(): string[];
27
23
  private getNonceRedisKey;
28
24
  private getLastUsedRedisKey;
29
- private getLockKey;
30
25
  private getNonce;
31
26
  private setNonce;
32
- private advanceNonce;
33
27
  private getCallersSortedByLRU;
34
28
  private updateLastUsed;
35
29
  }
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.CallerManager = void 0;
13
- const ttd_core_1 = require("@clonegod/ttd-core");
13
+ const dist_1 = require("@clonegod/ttd-core/dist");
14
14
  const ethers_1 = require("ethers");
15
15
  const redis_1 = require("../redis");
16
16
  const CALLER_NONCE_KEY = 'caller:nonce';
@@ -19,29 +19,28 @@ const VAULT_CALLERS_KEY = 'vault:callers';
19
19
  class CallerManager {
20
20
  constructor(config) {
21
21
  this.callers = [];
22
- this.config = Object.assign({ lockExpireSeconds: 3, acquireTimeoutMs: 2000, acquireRetryIntervalMs: 50 }, config);
22
+ this.config = config;
23
23
  this.redis = new redis_1.SimpleRedisClient(`${config.chainName}:caller`);
24
24
  }
25
25
  init() {
26
26
  return __awaiter(this, void 0, void 0, function* () {
27
- const walletInfos = (0, ttd_core_1.load_wallet_multi)(this.config.callerGroupIds, false);
27
+ const walletInfos = (0, dist_1.load_wallet_multi)(this.config.callerGroupIds, false);
28
28
  const allWallets = walletInfos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.config.provider));
29
- (0, ttd_core_1.log_info)(`CallerManager: loaded ${allWallets.length} wallets from CALLER_GROUP_IDS`, allWallets.map(w => w.address));
29
+ (0, dist_1.log_info)(`CallerManager: loaded ${allWallets.length} wallets from CALLER_GROUP_IDS`, allWallets.map(w => w.address));
30
30
  const vaultCallersKey = `${this.config.chainName}:${VAULT_CALLERS_KEY}`;
31
31
  const allowedAddresses = yield this.redis.lrange(vaultCallersKey);
32
32
  if (allowedAddresses && allowedAddresses.length > 0) {
33
33
  const allowedSet = new Set(allowedAddresses.map(a => a.toLowerCase()));
34
34
  this.callers = allWallets.filter(w => allowedSet.has(w.address.toLowerCase()));
35
- const matched = this.callers.map(w => w.address);
36
- const skipped = allWallets.filter(w => !allowedSet.has(w.address.toLowerCase())).map(w => w.address);
37
- (0, ttd_core_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, matched);
35
+ (0, dist_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, this.callers.map(w => w.address));
36
+ const skipped = allWallets.filter(w => !allowedSet.has(w.address.toLowerCase()));
38
37
  if (skipped.length > 0) {
39
- (0, ttd_core_1.log_warn)(`CallerManager: skipped ${skipped.length} wallets not in Vault whitelist`, skipped);
38
+ (0, dist_1.log_warn)(`CallerManager: skipped ${skipped.length} wallets not in Vault whitelist`, skipped.map(w => w.address));
40
39
  }
41
40
  }
42
41
  else {
43
42
  this.callers = allWallets;
44
- (0, ttd_core_1.log_warn)(`CallerManager: Vault whitelist not found in Redis (${vaultCallersKey}), using all ${this.callers.length} loaded wallets`);
43
+ (0, dist_1.log_warn)(`CallerManager: Vault whitelist not found in Redis (${vaultCallersKey}), using all ${this.callers.length} loaded wallets`);
45
44
  }
46
45
  if (this.callers.length === 0) {
47
46
  throw new Error('CallerManager: no valid callers after whitelist matching');
@@ -50,45 +49,25 @@ class CallerManager {
50
49
  const address = caller.address.toLowerCase();
51
50
  const nonceKey = this.getNonceRedisKey();
52
51
  const existing = yield this.redis.hgetvalue(nonceKey, address);
53
- if (existing !== null && existing !== undefined) {
54
- (0, ttd_core_1.log_info)(`Caller ${caller.address} nonce from Redis: ${existing}`);
55
- return;
52
+ if (existing === null || existing === undefined) {
53
+ (0, dist_1.log_warn)(`Caller ${caller.address} nonce not found in Redis, stream-trade may not be running`);
54
+ }
55
+ else {
56
+ (0, dist_1.log_info)(`Caller ${caller.address} nonce=${existing} (from Redis)`);
56
57
  }
57
- const nonce = yield this.config.provider.getTransactionCount(caller.address, 'pending');
58
- yield this.redis.hsetValue(nonceKey, address, String(nonce), 24 * 60 * 60);
59
- (0, ttd_core_1.log_info)(`Caller ${caller.address} nonce from chain: ${nonce}`);
60
58
  })));
61
- (0, ttd_core_1.log_info)(`CallerManager initialized, ${this.callers.length} active callers`, this.callers.map(w => w.address));
59
+ (0, dist_1.log_info)(`CallerManager initialized, ${this.callers.length} active callers`, this.callers.map(w => w.address));
62
60
  });
63
61
  }
64
62
  acquireCaller() {
65
63
  return __awaiter(this, void 0, void 0, function* () {
66
64
  const startTime = Date.now();
67
- const timeout = this.config.acquireTimeoutMs;
68
- const retryInterval = this.config.acquireRetryIntervalMs;
69
- while (Date.now() - startTime < timeout) {
70
- const sortedCallers = yield this.getCallersSortedByLRU();
71
- for (const caller of sortedCallers) {
72
- const lockKey = this.getLockKey(caller.address);
73
- const lockValue = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
74
- const acquired = yield this.redis.acquireLock(lockKey, lockValue, this.config.lockExpireSeconds);
75
- if (!acquired)
76
- continue;
77
- const nonce = yield this.getNonce(caller.address);
78
- yield this.updateLastUsed(caller.address);
79
- (0, ttd_core_1.log_info)(`acquireCaller: ${caller.address}, nonce=${nonce}, took ${Date.now() - startTime}ms`);
80
- const address = caller.address;
81
- return {
82
- wallet: caller,
83
- nonce,
84
- release: () => __awaiter(this, void 0, void 0, function* () {
85
- yield this.redis.releaseLock(lockKey, lockValue);
86
- })
87
- };
88
- }
89
- yield (0, ttd_core_1.sleep)(retryInterval);
90
- }
91
- throw new Error(`acquireCaller failed: all ${this.callers.length} callers are busy, waited ${Date.now() - startTime}ms`);
65
+ const sortedCallers = yield this.getCallersSortedByLRU();
66
+ const caller = sortedCallers[0];
67
+ const nonce = yield this.getNonce(caller.address);
68
+ yield this.updateLastUsed(caller.address);
69
+ (0, dist_1.log_info)(`acquireCaller: ${caller.address}, nonce=${nonce}, took ${Date.now() - startTime}ms`);
70
+ return { wallet: caller, nonce };
92
71
  });
93
72
  }
94
73
  confirmNonce(address, confirmedNonce) {
@@ -96,7 +75,7 @@ class CallerManager {
96
75
  const current = yield this.getNonce(address);
97
76
  if (confirmedNonce > current) {
98
77
  yield this.setNonce(address, confirmedNonce);
99
- (0, ttd_core_1.log_info)(`confirmNonce: ${address}, ${current} → ${confirmedNonce}`);
78
+ (0, dist_1.log_info)(`confirmNonce: ${address}, ${current} → ${confirmedNonce}`);
100
79
  }
101
80
  });
102
81
  }
@@ -105,7 +84,7 @@ class CallerManager {
105
84
  const current = yield this.getNonce(address);
106
85
  if (nonce !== current) {
107
86
  yield this.setNonce(address, nonce);
108
- (0, ttd_core_1.log_info)(`forceSetNonce: ${address}, ${current} → ${nonce}`);
87
+ (0, dist_1.log_info)(`forceSetNonce: ${address}, ${current} → ${nonce}`);
109
88
  }
110
89
  });
111
90
  }
@@ -121,9 +100,6 @@ class CallerManager {
121
100
  getLastUsedRedisKey() {
122
101
  return `${this.config.chainName}:${CALLER_LAST_USED_KEY}`;
123
102
  }
124
- getLockKey(address) {
125
- return `${this.config.chainName}:lock:caller:${address.toLowerCase()}`;
126
- }
127
103
  getNonce(address) {
128
104
  return __awaiter(this, void 0, void 0, function* () {
129
105
  const nonceKey = this.getNonceRedisKey();
@@ -140,14 +116,6 @@ class CallerManager {
140
116
  yield this.redis.hsetValue(nonceKey, address.toLowerCase(), String(nonce), 24 * 60 * 60);
141
117
  });
142
118
  }
143
- advanceNonce(address, newNonce) {
144
- return __awaiter(this, void 0, void 0, function* () {
145
- const current = yield this.getNonce(address);
146
- if (newNonce > current) {
147
- yield this.setNonce(address, newNonce);
148
- }
149
- });
150
- }
151
119
  getCallersSortedByLRU() {
152
120
  return __awaiter(this, void 0, void 0, function* () {
153
121
  const lastUsedKey = this.getLastUsedRedisKey();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "3.0.10",
3
+ "version": "3.0.11",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "push": "npm run build && npm publish"
15
15
  },
16
16
  "dependencies": {
17
- "@clonegod/ttd-core": "3.0.5",
17
+ "@clonegod/ttd-core": "3.0.7",
18
18
  "@clonegod/ttd-bsc-send-tx": "1.0.0",
19
19
  "axios": "^1.12.0",
20
20
  "dotenv": "^16.4.7",