@clonegod/ttd-bsc-common 3.0.10 → 3.0.12

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.
@@ -0,0 +1,12 @@
1
+ import { ClmmTickCache } from './clmm_tick_cache';
2
+ export declare class CachedTickDataProvider {
3
+ private tickCache;
4
+ private poolAddress;
5
+ private tickSpacing;
6
+ constructor(tickCache: ClmmTickCache, poolAddress: string, tickSpacing: number);
7
+ getTick(tick: number): Promise<{
8
+ liquidityNet: string;
9
+ liquidityGross: string;
10
+ }>;
11
+ nextInitializedTickWithinOneWord(tick: number, lte: boolean, tickSpacing: number): Promise<[number, boolean]>;
12
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CachedTickDataProvider = void 0;
13
+ const ttd_core_1 = require("@clonegod/ttd-core");
14
+ const MIN_TICK = -887272;
15
+ const MAX_TICK = 887272;
16
+ class CachedTickDataProvider {
17
+ constructor(tickCache, poolAddress, tickSpacing) {
18
+ this.tickCache = tickCache;
19
+ this.poolAddress = poolAddress;
20
+ this.tickSpacing = tickSpacing;
21
+ }
22
+ getTick(tick) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ const tickInfo = this.tickCache.getTickInfo(this.poolAddress, tick);
25
+ if (tickInfo) {
26
+ return {
27
+ liquidityNet: tickInfo.liquidityNet.toString(),
28
+ liquidityGross: tickInfo.liquidityGross.toString(),
29
+ };
30
+ }
31
+ (0, ttd_core_1.log_warn)(`[CachedTickDataProvider] tick ${tick} not in cache for pool ${this.poolAddress}`);
32
+ return {
33
+ liquidityNet: '0',
34
+ liquidityGross: '0',
35
+ };
36
+ });
37
+ }
38
+ nextInitializedTickWithinOneWord(tick, lte, tickSpacing) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ const zeroForOne = !lte;
41
+ const result = this.tickCache.getNextInitializedTickWithinOneWord(this.poolAddress, tick, tickSpacing, zeroForOne);
42
+ if (result)
43
+ return result;
44
+ const compressed = Math.floor(tick / tickSpacing);
45
+ if (lte) {
46
+ const wordPos = compressed >> 8;
47
+ const minimum = (wordPos << 8) * tickSpacing;
48
+ return [Math.max(minimum, MIN_TICK), false];
49
+ }
50
+ else {
51
+ const wordPos = (compressed + 1) >> 8;
52
+ const maximum = (((wordPos + 1) << 8) - 1) * tickSpacing;
53
+ return [Math.min(maximum, MAX_TICK), false];
54
+ }
55
+ });
56
+ }
57
+ }
58
+ exports.CachedTickDataProvider = CachedTickDataProvider;
@@ -35,6 +35,7 @@ export declare class ClmmTickCache {
35
35
  private getBitmapIndex;
36
36
  private calcBitmapIndexes;
37
37
  private isInCachedRange;
38
+ getNextInitializedTickWithinOneWord(poolAddress: string, tick: number, tickSpacing: number, zeroForOne: boolean): [number, boolean] | null;
38
39
  clearPool(poolAddress: string): void;
39
40
  destroy(): void;
40
41
  }
@@ -20,7 +20,7 @@ class ClmmTickCache {
20
20
  this.loader = loader;
21
21
  this.config = {
22
22
  neighboringWords: (_a = config.neighboringWords) !== null && _a !== void 0 ? _a : 2,
23
- refreshInterval: (_b = config.refreshInterval) !== null && _b !== void 0 ? _b : 30000,
23
+ refreshInterval: (_b = config.refreshInterval) !== null && _b !== void 0 ? _b : 60000,
24
24
  minUpdateInterval: (_c = config.minUpdateInterval) !== null && _c !== void 0 ? _c : 3000,
25
25
  };
26
26
  }
@@ -201,6 +201,32 @@ class ClmmTickCache {
201
201
  const bitmapIndex = this.getBitmapIndex(tickIndex, state.tickSpacing);
202
202
  return state.bitmapIndexes.includes(bitmapIndex);
203
203
  }
204
+ getNextInitializedTickWithinOneWord(poolAddress, tick, tickSpacing, zeroForOne) {
205
+ const state = this.pools.get(poolAddress);
206
+ if (!state)
207
+ return null;
208
+ const compressed = Math.floor(tick / tickSpacing);
209
+ const wordPos = compressed >> 8;
210
+ const wordStartTick = (wordPos << 8) * tickSpacing;
211
+ const wordEndTick = ((wordPos + 1) << 8) * tickSpacing;
212
+ const ticksInWord = Array.from(state.ticks.keys())
213
+ .filter(t => t >= wordStartTick && t < wordEndTick)
214
+ .sort((a, b) => a - b);
215
+ if (ticksInWord.length === 0)
216
+ return null;
217
+ if (zeroForOne) {
218
+ const nextTick = ticksInWord.find(t => t > tick);
219
+ if (nextTick !== undefined)
220
+ return [nextTick, true];
221
+ }
222
+ else {
223
+ for (let i = ticksInWord.length - 1; i >= 0; i--) {
224
+ if (ticksInWord[i] < tick)
225
+ return [ticksInWord[i], true];
226
+ }
227
+ }
228
+ return null;
229
+ }
204
230
  clearPool(poolAddress) {
205
231
  this.pools.delete(poolAddress);
206
232
  const timer = this.refreshTimers.get(poolAddress);
@@ -1,2 +1,4 @@
1
1
  export * from './clmm_tick_cache';
2
2
  export * from './tick_lens_loaders';
3
+ export * from './cached_tick_data_provider';
4
+ export * from './state_view_tick_loader';
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./clmm_tick_cache"), exports);
18
18
  __exportStar(require("./tick_lens_loaders"), exports);
19
+ __exportStar(require("./cached_tick_data_provider"), exports);
20
+ __exportStar(require("./state_view_tick_loader"), exports);
@@ -0,0 +1,17 @@
1
+ import { TickInfo, TickLensLoader } from './clmm_tick_cache';
2
+ export declare class StateViewTickLensLoader implements TickLensLoader {
3
+ private ethersLib;
4
+ private provider;
5
+ private stateViewAddress;
6
+ private stateViewIface;
7
+ private multicallContract;
8
+ private tickSpacingMap;
9
+ constructor(params: {
10
+ ethersLib: any;
11
+ provider: any;
12
+ stateViewAddress: string;
13
+ multicallAddress?: string;
14
+ });
15
+ setTickSpacing(poolAddress: string, tickSpacing: number): void;
16
+ loadBitmapWords(poolAddress: string, bitmapIndexes: number[]): Promise<Map<number, TickInfo>>;
17
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.StateViewTickLensLoader = void 0;
13
+ const ttd_core_1 = require("@clonegod/ttd-core");
14
+ const tick_lens_loaders_1 = require("./tick_lens_loaders");
15
+ const STATE_VIEW_ABI = [
16
+ {
17
+ type: 'function',
18
+ name: 'getTickBitmap',
19
+ inputs: [
20
+ { name: 'poolId', type: 'bytes32' },
21
+ { name: 'tick', type: 'int16' },
22
+ ],
23
+ outputs: [
24
+ { name: 'tickBitmap', type: 'uint256' },
25
+ ],
26
+ stateMutability: 'view',
27
+ },
28
+ {
29
+ type: 'function',
30
+ name: 'getTickLiquidity',
31
+ inputs: [
32
+ { name: 'poolId', type: 'bytes32' },
33
+ { name: 'tick', type: 'int24' },
34
+ ],
35
+ outputs: [
36
+ { name: 'liquidityGross', type: 'uint128' },
37
+ { name: 'liquidityNet', type: 'int128' },
38
+ ],
39
+ stateMutability: 'view',
40
+ },
41
+ ];
42
+ const MULTICALL3_ABI = [
43
+ {
44
+ type: 'function',
45
+ name: 'aggregate',
46
+ inputs: [
47
+ {
48
+ name: 'calls',
49
+ type: 'tuple[]',
50
+ components: [
51
+ { name: 'target', type: 'address' },
52
+ { name: 'callData', type: 'bytes' },
53
+ ],
54
+ },
55
+ ],
56
+ outputs: [
57
+ { name: 'blockNumber', type: 'uint256' },
58
+ { name: 'returnData', type: 'bytes[]' },
59
+ ],
60
+ stateMutability: 'nonpayable',
61
+ },
62
+ ];
63
+ function getInitializedTicksFromBitmap(bitmapIndex, bitmap, tickSpacing) {
64
+ const ticks = [];
65
+ for (let bit = 0; bit < 256; bit++) {
66
+ if ((bitmap >> BigInt(bit)) & BigInt(1)) {
67
+ const tickIndex = (bitmapIndex * 256 + bit) * tickSpacing;
68
+ ticks.push(tickIndex);
69
+ }
70
+ }
71
+ return ticks;
72
+ }
73
+ class StateViewTickLensLoader {
74
+ constructor(params) {
75
+ var _a;
76
+ this.tickSpacingMap = new Map();
77
+ this.ethersLib = params.ethersLib;
78
+ this.provider = params.provider;
79
+ this.stateViewAddress = params.stateViewAddress;
80
+ const InterfaceClass = params.ethersLib.Interface || ((_a = params.ethersLib.utils) === null || _a === void 0 ? void 0 : _a.Interface);
81
+ if (!InterfaceClass) {
82
+ throw new Error('ethersLib.Interface not found');
83
+ }
84
+ this.stateViewIface = new InterfaceClass(STATE_VIEW_ABI);
85
+ this.multicallContract = new params.ethersLib.Contract(params.multicallAddress || tick_lens_loaders_1.MULTICALL3_ADDRESS, MULTICALL3_ABI, this.provider);
86
+ }
87
+ setTickSpacing(poolAddress, tickSpacing) {
88
+ this.tickSpacingMap.set(poolAddress.toLowerCase(), tickSpacing);
89
+ }
90
+ loadBitmapWords(poolAddress, bitmapIndexes) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ var _a;
93
+ const result = new Map();
94
+ if (bitmapIndexes.length === 0)
95
+ return result;
96
+ const tickSpacing = this.tickSpacingMap.get(poolAddress.toLowerCase());
97
+ if (!tickSpacing) {
98
+ (0, ttd_core_1.log_warn)(`[StateViewLoader] tickSpacing not set for ${poolAddress}, defaulting to 1`);
99
+ }
100
+ const spacing = tickSpacing || 1;
101
+ try {
102
+ const bitmapCalls = bitmapIndexes.map(bitmapIndex => ({
103
+ target: this.stateViewAddress,
104
+ callData: this.stateViewIface.encodeFunctionData('getTickBitmap', [poolAddress, bitmapIndex]),
105
+ }));
106
+ const callMethod = this.multicallContract.aggregate.staticCall || ((_a = this.multicallContract.callStatic) === null || _a === void 0 ? void 0 : _a.aggregate);
107
+ const [, bitmapReturnData] = yield callMethod(bitmapCalls);
108
+ const initializedTicks = [];
109
+ for (let i = 0; i < bitmapReturnData.length; i++) {
110
+ try {
111
+ const decoded = this.stateViewIface.decodeFunctionResult('getTickBitmap', bitmapReturnData[i]);
112
+ const bitmap = typeof decoded[0] === 'bigint' ? decoded[0] : BigInt(decoded[0].toString());
113
+ const ticks = getInitializedTicksFromBitmap(bitmapIndexes[i], bitmap, spacing);
114
+ initializedTicks.push(...ticks);
115
+ }
116
+ catch (e) {
117
+ (0, ttd_core_1.log_warn)(`[StateViewLoader] Failed to decode bitmap ${bitmapIndexes[i]}: ${e.message}`);
118
+ }
119
+ }
120
+ if (initializedTicks.length === 0) {
121
+ (0, ttd_core_1.log_debug)(`[StateViewLoader] No initialized ticks in ${bitmapIndexes.length} bitmap words`);
122
+ return result;
123
+ }
124
+ const liquidityCalls = initializedTicks.map(tick => ({
125
+ target: this.stateViewAddress,
126
+ callData: this.stateViewIface.encodeFunctionData('getTickLiquidity', [poolAddress, tick]),
127
+ }));
128
+ const [, liquidityReturnData] = yield callMethod(liquidityCalls);
129
+ for (let i = 0; i < liquidityReturnData.length; i++) {
130
+ try {
131
+ const decoded = this.stateViewIface.decodeFunctionResult('getTickLiquidity', liquidityReturnData[i]);
132
+ const liquidityGross = typeof decoded[0] === 'bigint' ? decoded[0] : BigInt(decoded[0].toString());
133
+ const liquidityNet = typeof decoded[1] === 'bigint' ? decoded[1] : BigInt(decoded[1].toString());
134
+ result.set(initializedTicks[i], { liquidityNet, liquidityGross });
135
+ }
136
+ catch (e) {
137
+ (0, ttd_core_1.log_warn)(`[StateViewLoader] Failed to decode tick ${initializedTicks[i]}: ${e.message}`);
138
+ }
139
+ }
140
+ (0, ttd_core_1.log_debug)(`[StateViewLoader] Loaded ${result.size} ticks from ${bitmapIndexes.length} bitmap words (2 Multicall3 calls)`);
141
+ }
142
+ catch (error) {
143
+ (0, ttd_core_1.log_warn)(`[StateViewLoader] Multicall failed: ${error.message}`);
144
+ }
145
+ return result;
146
+ });
147
+ }
148
+ }
149
+ exports.StateViewTickLensLoader = StateViewTickLensLoader;
@@ -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.12",
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",