@clonegod/ttd-bsc-common 1.0.90 → 2.0.1

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.
Files changed (55) hide show
  1. package/dist/config/BscQuoteAppConfig.d.ts +11 -0
  2. package/dist/config/BscQuoteAppConfig.js +62 -0
  3. package/dist/config/index.d.ts +1 -0
  4. package/dist/config/index.js +1 -0
  5. package/dist/quote/event/index.d.ts +1 -0
  6. package/dist/quote/event/index.js +1 -0
  7. package/dist/quote/event/swap_debouncer.d.ts +22 -0
  8. package/dist/quote/event/swap_debouncer.js +92 -0
  9. package/dist/quote/index.d.ts +1 -0
  10. package/dist/quote/index.js +1 -0
  11. package/dist/quote/pricing/index.d.ts +2 -0
  12. package/dist/quote/pricing/index.js +2 -0
  13. package/dist/quote/pricing/pool_state_initializer.d.ts +8 -0
  14. package/dist/quote/pricing/pool_state_initializer.js +142 -0
  15. package/dist/quote/pricing/sdk_token_factory.d.ts +2 -0
  16. package/dist/quote/pricing/sdk_token_factory.js +21 -0
  17. package/dist/quote/tick/clmm_tick_cache.d.ts +40 -0
  18. package/dist/quote/tick/clmm_tick_cache.js +219 -0
  19. package/dist/quote/tick/index.d.ts +2 -0
  20. package/dist/{trade/send → quote/tick}/index.js +2 -8
  21. package/dist/quote/tick/tick_lens_loaders.d.ts +25 -0
  22. package/dist/quote/tick/tick_lens_loaders.js +170 -0
  23. package/dist/redis/redis_client.d.ts +1 -0
  24. package/dist/redis/redis_client.js +6 -0
  25. package/dist/trade/abstract_dex_trade.d.ts +30 -16
  26. package/dist/trade/abstract_dex_trade.js +223 -108
  27. package/dist/trade/caller_manager.d.ts +35 -0
  28. package/dist/trade/caller_manager.js +178 -0
  29. package/dist/trade/index.d.ts +1 -2
  30. package/dist/trade/index.js +1 -2
  31. package/dist/types/pool_state.d.ts +31 -0
  32. package/package.json +3 -2
  33. package/dist/trade/abstract_dex_trade_plus.d.ts +0 -44
  34. package/dist/trade/abstract_dex_trade_plus.js +0 -449
  35. package/dist/trade/send/48club.d.ts +0 -17
  36. package/dist/trade/send/48club.js +0 -123
  37. package/dist/trade/send/48club_member.d.ts +0 -1
  38. package/dist/trade/send/48club_member.js +0 -25
  39. package/dist/trade/send/48club_sp.d.ts +0 -9
  40. package/dist/trade/send/48club_sp.js +0 -137
  41. package/dist/trade/send/blockrazor.d.ts +0 -7
  42. package/dist/trade/send/blockrazor.js +0 -78
  43. package/dist/trade/send/blxr.d.ts +0 -0
  44. package/dist/trade/send/blxr.js +0 -0
  45. package/dist/trade/send/bsc_rpc.d.ts +0 -6
  46. package/dist/trade/send/bsc_rpc.js +0 -47
  47. package/dist/trade/send/index.d.ts +0 -6
  48. package/dist/trade/send/send_bundle_proxy.d.ts +0 -7
  49. package/dist/trade/send/send_bundle_proxy.js +0 -30
  50. package/dist/trade/send/send_bundle_ws.d.ts +0 -7
  51. package/dist/trade/send/send_bundle_ws.js +0 -30
  52. package/dist/trade/send/send_tx.d.ts +0 -9
  53. package/dist/trade/send/send_tx.js +0 -119
  54. package/dist/ws/bsc_stream_ws_client.d.ts +0 -10
  55. package/dist/ws/bsc_stream_ws_client.js +0 -95
@@ -8,146 +8,261 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AbstractEvmDexTrade = void 0;
15
+ exports.AbstractDexTrade = void 0;
16
+ exports.buildTradeConfig = buildTradeConfig;
13
17
  const dist_1 = require("@clonegod/ttd-core/dist");
14
18
  const ethers_1 = require("ethers");
15
- const common_1 = require("../common");
16
- class AbstractEvmDexTrade extends dist_1.AbastrcatTrade {
19
+ const caller_manager_1 = require("./caller_manager");
20
+ const ttd_bsc_send_tx_1 = require("@clonegod/ttd-bsc-send-tx");
21
+ const redis_1 = require("../redis");
22
+ const decimal_js_1 = __importDefault(require("decimal.js"));
23
+ function buildTradeConfig() {
24
+ const vaultAddress = process.env.VAULT_ADDRESS;
25
+ if (!vaultAddress) {
26
+ throw new Error('环境变量 VAULT_ADDRESS 未配置');
27
+ }
28
+ const pancakeIdName = process.env.PANCAKE_EXECUTOR_ID || 'PANCAKE';
29
+ const uniswapIdName = process.env.UNISWAP_EXECUTOR_ID || 'UNISWAP';
30
+ return {
31
+ vaultAddress,
32
+ executorIds: {
33
+ pancake: ethers_1.ethers.utils.id(pancakeIdName),
34
+ uniswap: ethers_1.ethers.utils.id(uniswapIdName),
35
+ },
36
+ };
37
+ }
38
+ const VAULT_ABI = [
39
+ 'function delegatecallExecutorToTrade(bytes32 executorId, bytes calldata data, uint256 deadline) external payable returns (bytes memory result)'
40
+ ];
41
+ class AbstractDexTrade extends dist_1.AbastrcatTrade {
17
42
  constructor(appConfig) {
18
43
  super();
19
44
  this.appConfig = appConfig;
20
- this.approvedTokens = new Map();
21
- this.pairContracts = new Map();
22
- this.tokenContracts = new Map();
23
- this.approvedTokens = new Map();
24
- this.pairContracts = new Map();
25
- this.tokenContracts = new Map();
26
45
  this.initConfigs();
46
+ this.chainNameLower = this.appConfig.env_args.chain_id.toLowerCase();
47
+ this.redisClient = new redis_1.SimpleRedisClient(`${this.chainNameLower}:tx`);
48
+ this.vaultInterface = new ethers_1.ethers.utils.Interface(VAULT_ABI);
27
49
  }
28
50
  init() {
29
51
  return __awaiter(this, void 0, void 0, function* () {
30
52
  this.provider = new ethers_1.ethers.providers.JsonRpcProvider(this.chainConfig.rpcEndpoint);
31
- this.wallet = new ethers_1.ethers.Wallet(this.appConfig.trade_runtime.wallet.private_key, this.provider);
32
- (0, dist_1.log_info)(`钱包已初始化,地址: ${this.wallet.address}`);
33
- this.routerContract = new ethers_1.ethers.Contract(this.dexConfig.routerAddress, this.dexConfig.routerAbi, this.wallet);
34
- (0, dist_1.log_info)(`${this.dexConfig.dexName} Router已初始化, 地址: ${this.dexConfig.routerAddress}`);
53
+ this.transactionSender = new ttd_bsc_send_tx_1.TransactionSender(this.appConfig);
54
+ const callerGroupIds = (process.env.CALLER_GROUP_IDS || '').trim().split(',').filter(Boolean);
55
+ this.callerManager = new caller_manager_1.CallerManager({
56
+ chainName: this.chainNameLower,
57
+ provider: this.provider,
58
+ callerGroupIds,
59
+ chainId: this.chainConfig.chainId,
60
+ });
61
+ yield this.callerManager.init();
62
+ this.subscribeTradeMonitor();
63
+ (0, dist_1.log_info)(`AbstractDexTrade initialized, vault=${this.tradeConfig.vaultAddress}, callers=${this.callerManager.getCallerCount()}`);
64
+ });
65
+ }
66
+ execute(context) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ var _a;
69
+ const { price_msg, order_msg, slippage_bps, order_trace_id, pool_info } = context;
70
+ const { pair } = price_msg;
71
+ const { aToB, amount, unique_order_msg_id } = order_msg;
72
+ (0, dist_1.log_info)(`执行 Vault 交易, orderId=${order_trace_id}`, {
73
+ unique_order_msg_id,
74
+ pair,
75
+ aToB,
76
+ amount,
77
+ slippage_bps
78
+ });
79
+ let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
80
+ const callerHandle = yield this.callerManager.acquireCaller();
81
+ const { wallet: caller, nonce: initialNonce } = callerHandle;
82
+ try {
83
+ let nonce = initialNonce;
84
+ let nonce_from_error = 0;
85
+ let txid = '';
86
+ let i = 1;
87
+ do {
88
+ try {
89
+ if (i > 1) {
90
+ if (nonce_from_error > 0) {
91
+ nonce = nonce_from_error;
92
+ nonce_from_error = 0;
93
+ (0, dist_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
94
+ }
95
+ else {
96
+ (0, dist_1.log_warn)(`Attempt ${i}/${maxAttempts}, no nonce in error msg, abort`, {}, order_trace_id);
97
+ break;
98
+ }
99
+ }
100
+ const { executorId, data } = this.encodeTradeData(context);
101
+ const deadline = Math.floor(Date.now() / 1000) + 60;
102
+ const vaultCalldata = this.vaultInterface.encodeFunctionData('delegatecallExecutorToTrade', [executorId, data, deadline]);
103
+ const gasPriceGwei = this.getGasPriceGwei(context);
104
+ const realGasPriceGwei = Math.min(Number(gasPriceGwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
105
+ const tx = {
106
+ to: this.tradeConfig.vaultAddress,
107
+ data: vaultCalldata,
108
+ gasLimit: this.chainConfig.gasOptions.gasLimit,
109
+ gasPrice: ethers_1.ethers.utils.parseUnits(realGasPriceGwei, 'gwei'),
110
+ nonce,
111
+ chainId: this.chainConfig.chainId,
112
+ value: 0,
113
+ };
114
+ const signedTx = yield caller.signTransaction(tx);
115
+ txid = ethers_1.ethers.utils.keccak256(signedTx);
116
+ (0, dist_1.log_info)(`Vault 交易已签名`, { txid, nonce, gasPriceGwei: realGasPriceGwei }, order_trace_id);
117
+ const tipNonce = nonce + 1;
118
+ const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
119
+ const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
120
+ context.ui_tip_amount = new decimal_js_1.default(transfer_amount_gwei).div(Math.pow(10, 9)).toNumber();
121
+ return yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller);
122
+ });
123
+ const only_bundle = order_msg.is_dex_maker;
124
+ yield this.transactionSender.sendTransaction(signedTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
125
+ (0, dist_1.log_info)(`Vault 交易发送成功`, {
126
+ pair, unique_order_msg_id, txid,
127
+ attempt: i, caller: caller.address
128
+ }, order_trace_id);
129
+ return txid;
130
+ }
131
+ catch (error) {
132
+ const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
133
+ if (errorMessage.includes('rate limit')) {
134
+ (0, dist_1.log_warn)(`Rate limit error, no retry! i=${i}`, {}, order_trace_id);
135
+ return txid;
136
+ }
137
+ if (errorMessage.includes('replacement transaction underpriced')) {
138
+ (0, dist_1.log_warn)(`Replacement tx underpriced, continue! i=${i}`, {}, order_trace_id);
139
+ continue;
140
+ }
141
+ const isNonceError = errorMessage.includes('nonce');
142
+ if (!isNonceError || i >= maxAttempts) {
143
+ (0, dist_1.log_warn)(`Non-nonce error or max retries, i=${i}, error: ${errorMessage}`, {}, order_trace_id);
144
+ return txid;
145
+ }
146
+ (0, dist_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
147
+ if (errorMessage.includes('nonce too')) {
148
+ const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
149
+ if (correctNonce !== null && correctNonce > 0) {
150
+ nonce_from_error = correctNonce;
151
+ }
152
+ }
153
+ }
154
+ } while (++i <= maxAttempts);
155
+ if (!txid) {
156
+ throw new Error(`Vault 交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
157
+ }
158
+ return txid;
159
+ }
160
+ finally {
161
+ yield callerHandle.release();
162
+ }
35
163
  });
36
164
  }
37
- getTokenContract(tokenAddress) {
38
- if (!this.tokenContracts.has(tokenAddress)) {
39
- const tokenContract = new ethers_1.ethers.Contract(tokenAddress, common_1.ERC20_ABI, this.wallet);
40
- this.tokenContracts.set(tokenAddress, tokenContract);
165
+ subscribeTradeMonitor() {
166
+ const wsUrl = process.env.TRADE_MONITOR_WS_URL;
167
+ if (!wsUrl) {
168
+ (0, dist_1.log_warn)('TRADE_MONITOR_WS_URL not configured, trade result nonce correction disabled');
169
+ return;
41
170
  }
42
- return this.tokenContracts.get(tokenAddress);
171
+ const wsClient = new dist_1.WebSocketClient(wsUrl);
172
+ wsClient.onOpen(() => {
173
+ wsClient.send(JSON.stringify({
174
+ vault_address: this.tradeConfig.vaultAddress,
175
+ }));
176
+ (0, dist_1.log_info)(`Subscribed to trade-monitor for vault=${this.tradeConfig.vaultAddress}`);
177
+ });
178
+ wsClient.onMessage((event) => {
179
+ if (event.type === 'TradeResult' && event.data) {
180
+ const { caller, callerNonce } = event.data;
181
+ if (caller) {
182
+ const nextNonce = callerNonce + 2;
183
+ this.callerManager.confirmNonce(caller, nextNonce).catch(err => (0, dist_1.log_warn)(`Failed to confirm nonce from trade-monitor`, err));
184
+ }
185
+ }
186
+ });
187
+ wsClient.connect();
43
188
  }
44
189
  getGasPriceGwei(context) {
45
- let { evm_gas_limit, evm_gas_price_gwei, evm_tip_amount_gwei } = context.trade_runtime.settings.strategy;
46
- if (evm_gas_price_gwei === undefined || evm_gas_price_gwei === null || evm_gas_price_gwei <= 0) {
190
+ let { evm_gas_price_gwei } = context.trade_runtime.settings.strategy;
191
+ if (!evm_gas_price_gwei || evm_gas_price_gwei <= 0) {
47
192
  evm_gas_price_gwei = this.chainConfig.gasOptions.defaultGasPriceGwei;
48
193
  }
49
- (0, dist_1.log_info)(`getGasPriceGwei: ${evm_gas_price_gwei} Gwei`);
50
194
  return evm_gas_price_gwei.toString();
51
195
  }
52
196
  getBuilderTipAmoutGwei(context) {
53
- let { evm_gas_limit, evm_gas_price_gwei, evm_tip_amount_gwei } = context.trade_runtime.settings.strategy;
54
- if (evm_tip_amount_gwei === undefined || evm_tip_amount_gwei === null || evm_tip_amount_gwei <= 0) {
197
+ let { evm_tip_amount_gwei } = context.trade_runtime.settings.strategy;
198
+ if (!evm_tip_amount_gwei || evm_tip_amount_gwei <= 0) {
55
199
  evm_tip_amount_gwei = this.chainConfig.gasOptions.defaultTipAmountGwei;
56
200
  }
57
- (0, dist_1.log_info)(`getGasTipAmoutGwei: ${evm_tip_amount_gwei} Gwei`);
58
201
  return evm_tip_amount_gwei.toString();
59
202
  }
60
- checkTradeTokenApprove(context, routerAddress) {
203
+ buildTipTransferTx(to, transfer_amount_gwei, gas_price_gwei, nonce, wallet) {
61
204
  return __awaiter(this, void 0, void 0, function* () {
62
- const { pool_info } = context;
63
- const { tokenA, tokenB } = pool_info;
64
- const router = routerAddress || this.dexConfig.routerAddress;
65
- yield Promise.all([tokenA, tokenB].map((_a) => __awaiter(this, [_a], void 0, function* ({ symbol, address }) {
66
- const tokenContract = this.getTokenContract(address);
67
- yield this.checkTokenApprove(address, symbol, tokenContract, router);
68
- })));
69
- });
70
- }
71
- checkTokenApprove(tokenAddress, tokenSymbol, tokenContract, spenderAddress) {
72
- return __awaiter(this, void 0, void 0, function* () {
73
- if (this.approvedTokens.get(tokenAddress)) {
74
- (0, dist_1.log_info)(`代币 ${tokenSymbol} 已授权,跳过授权检查`);
75
- return true;
76
- }
77
- const allowance = yield tokenContract.allowance(this.wallet.address, spenderAddress);
78
- const maxAllowance = ethers_1.ethers.constants.MaxUint256.div(2);
79
- (0, dist_1.log_info)(`代币授权检查:`, {
80
- tokenSymbol,
81
- currentAllowance: allowance.toString(),
82
- maxAllowance: maxAllowance.toString(),
83
- isSufficient: allowance.gte(maxAllowance)
84
- });
85
- if (allowance.lt(maxAllowance)) {
86
- const maxApprovalAmount = ethers_1.ethers.utils.formatUnits(ethers_1.ethers.constants.MaxUint256, yield tokenContract.decimals());
87
- const tokenSymbolDisplay = tokenSymbol || `${tokenAddress.substring(0, 6)}...`;
88
- (0, dist_1.log_info)(`正在为合约授权代币 ${tokenSymbolDisplay}`, {
89
- tokenAddress,
90
- tokenSymbol: tokenSymbolDisplay,
91
- currentAllowance: ethers_1.ethers.utils.formatUnits(allowance, yield tokenContract.decimals()),
92
- newAllowance: maxApprovalAmount
93
- });
94
- const tx = yield tokenContract.approve(spenderAddress, ethers_1.ethers.constants.MaxUint256, {
95
- gasLimit: this.chainConfig.gasOptions.gasLimit
96
- });
97
- yield tx.wait();
98
- this.approvedTokens.set(tokenAddress, true);
99
- (0, dist_1.log_info)(`代币 ${tokenSymbolDisplay} 授权完成,数量: ${maxApprovalAmount} ${tokenSymbolDisplay}, 交易哈希: ${tx.hash}`);
100
- return true;
101
- }
102
- else {
103
- this.approvedTokens.set(tokenAddress, true);
104
- return true;
105
- }
106
- });
107
- }
108
- isNonceRelatedError(error) {
109
- if (!error)
110
- return false;
111
- const errorMessage = error.message ? error.message.toLowerCase() : '';
112
- const nonceErrorKeywords = [
113
- 'nonce',
114
- 'nonce too low',
115
- 'nonce too high',
116
- 'nonce has already been used',
117
- 'already known',
118
- 'replacement transaction underpriced',
119
- 'transaction with same nonce',
120
- 'transaction nonce is too low',
121
- 'invalid transaction nonce',
122
- 'insufficient funds',
123
- 'sign bundle to get failure details',
124
- ];
125
- return nonceErrorKeywords.some(keyword => errorMessage.includes(keyword));
126
- }
127
- isNativeCurrency(symbol) {
128
- return symbol.toUpperCase() === this.chainConfig.nativeCurrency;
129
- }
130
- getWrappedNativeAddress() {
131
- return this.chainConfig.wrappedNativeCurrencyAddress;
132
- }
133
- buildTipTransferTx(to, transfer_amount_gwei, gas_price_gwei, transfer_nonce) {
134
- return __awaiter(this, void 0, void 0, function* () {
135
- let real_transfer_amount_gwei = Math.min(Number(transfer_amount_gwei), this.chainConfig.gasOptions.maxTipAmountGwei).toString();
136
- let real_gas_price_gwei = Math.min(Number(gas_price_gwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
137
- let tx_data = {
138
- from: this.wallet.address,
205
+ const real_transfer_amount_gwei = Math.min(Number(transfer_amount_gwei), this.chainConfig.gasOptions.maxTipAmountGwei).toString();
206
+ const real_gas_price_gwei = Math.min(Number(gas_price_gwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
207
+ const tx_data = {
208
+ from: wallet.address,
139
209
  to,
140
210
  value: ethers_1.ethers.utils.parseUnits(real_transfer_amount_gwei, 'gwei'),
141
- gasLimit: 21000,
211
+ gasLimit: 42000,
142
212
  gasPrice: ethers_1.ethers.utils.parseUnits(real_gas_price_gwei, 'gwei'),
143
- nonce: transfer_nonce,
213
+ nonce,
144
214
  chainId: this.chainConfig.chainId
145
215
  };
146
- let signedTx = yield this.wallet.signTransaction(tx_data);
147
- (0, dist_1.log_info)(`构建转账交易: `, Object.assign(Object.assign({}, tx_data), { real_transfer_amount_gwei,
148
- real_gas_price_gwei, txhash: ethers_1.ethers.utils.keccak256(signedTx) }));
216
+ const signedTx = yield wallet.signTransaction(tx_data);
217
+ (0, dist_1.log_info)(`构建 tip 转账交易`, {
218
+ to, nonce,
219
+ tipGwei: real_transfer_amount_gwei,
220
+ txhash: ethers_1.ethers.utils.keccak256(signedTx)
221
+ });
149
222
  return signedTx;
150
223
  });
151
224
  }
225
+ determineInputOutputTokens(order_msg, pool_info) {
226
+ const { aToB } = order_msg;
227
+ const { tokenA, tokenB, quote_token } = pool_info;
228
+ const quoteToken = [tokenA, tokenB].find(token => token.symbol === quote_token);
229
+ const nonQuoteToken = [tokenA, tokenB].find(token => token.symbol !== quote_token);
230
+ const inputToken = aToB ? nonQuoteToken : quoteToken;
231
+ const outputToken = aToB ? quoteToken : nonQuoteToken;
232
+ if (!inputToken || !outputToken) {
233
+ throw new Error(`无法确定输入/输出代币: ${JSON.stringify({
234
+ tokenA: tokenA.symbol, tokenB: tokenB.symbol,
235
+ quote: quote_token, aToB
236
+ })}`);
237
+ }
238
+ return { inputToken, outputToken };
239
+ }
240
+ calculateAmountOutMin(context, inputToken, outputToken) {
241
+ const { price_msg, slippage_bps, order_msg } = context;
242
+ const { aToB } = order_msg;
243
+ const slippage = slippage_bps / 10000;
244
+ const inputAmount = new decimal_js_1.default(order_msg.amount.toString());
245
+ let expectedOut;
246
+ if (aToB) {
247
+ const price = new decimal_js_1.default(price_msg.bid.price);
248
+ expectedOut = inputAmount.mul(price);
249
+ }
250
+ else {
251
+ const price = new decimal_js_1.default(price_msg.ask.price);
252
+ expectedOut = inputAmount.div(price);
253
+ }
254
+ const minOutput = expectedOut.mul(new decimal_js_1.default(1).sub(new decimal_js_1.default(slippage)));
255
+ return ethers_1.ethers.utils.parseUnits(minOutput.toFixed(outputToken.decimals), outputToken.decimals);
256
+ }
257
+ extractNonceFromErrorMsg(errorMessage) {
258
+ try {
259
+ const match = errorMessage.match(/tx:\s*(\d+)\s*state:\s*(\d+)/);
260
+ if (match) {
261
+ return parseInt(match[2]);
262
+ }
263
+ }
264
+ catch (_a) { }
265
+ return null;
266
+ }
152
267
  }
153
- exports.AbstractEvmDexTrade = AbstractEvmDexTrade;
268
+ exports.AbstractDexTrade = AbstractDexTrade;
@@ -0,0 +1,35 @@
1
+ import { ethers } from "ethers";
2
+ export interface CallerHandle {
3
+ wallet: ethers.Wallet;
4
+ nonce: number;
5
+ release: () => Promise<void>;
6
+ }
7
+ export interface CallerManagerConfig {
8
+ chainName: string;
9
+ provider: ethers.providers.JsonRpcProvider;
10
+ callerGroupIds: string[];
11
+ chainId: number;
12
+ lockExpireSeconds?: number;
13
+ acquireTimeoutMs?: number;
14
+ acquireRetryIntervalMs?: number;
15
+ }
16
+ export declare class CallerManager {
17
+ private callers;
18
+ private redis;
19
+ private config;
20
+ constructor(config: CallerManagerConfig);
21
+ init(): Promise<void>;
22
+ acquireCaller(): Promise<CallerHandle>;
23
+ confirmNonce(address: string, confirmedNonce: number): Promise<void>;
24
+ forceSetNonce(address: string, nonce: number): Promise<void>;
25
+ getCallerCount(): number;
26
+ getCallerAddresses(): string[];
27
+ private getNonceRedisKey;
28
+ private getLastUsedRedisKey;
29
+ private getLockKey;
30
+ private getNonce;
31
+ private setNonce;
32
+ private advanceNonce;
33
+ private getCallersSortedByLRU;
34
+ private updateLastUsed;
35
+ }
@@ -0,0 +1,178 @@
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.CallerManager = void 0;
13
+ const dist_1 = require("@clonegod/ttd-core/dist");
14
+ const ethers_1 = require("ethers");
15
+ const redis_1 = require("../redis");
16
+ const CALLER_NONCE_KEY = 'caller:nonce';
17
+ const CALLER_LAST_USED_KEY = 'caller:last_used';
18
+ const VAULT_CALLERS_KEY = 'vault:callers';
19
+ class CallerManager {
20
+ constructor(config) {
21
+ this.callers = [];
22
+ this.config = Object.assign({ lockExpireSeconds: 3, acquireTimeoutMs: 2000, acquireRetryIntervalMs: 50 }, config);
23
+ this.redis = new redis_1.SimpleRedisClient(`${config.chainName}:caller`);
24
+ }
25
+ init() {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const walletInfos = (0, dist_1.load_wallet_multi)(this.config.callerGroupIds, false);
28
+ const allWallets = walletInfos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.config.provider));
29
+ (0, dist_1.log_info)(`CallerManager: loaded ${allWallets.length} wallets from CALLER_GROUP_IDS`, allWallets.map(w => w.address));
30
+ const vaultCallersKey = `${this.config.chainName}:${VAULT_CALLERS_KEY}`;
31
+ const allowedAddresses = yield this.redis.lrange(vaultCallersKey);
32
+ if (allowedAddresses && allowedAddresses.length > 0) {
33
+ const allowedSet = new Set(allowedAddresses.map(a => a.toLowerCase()));
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, dist_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, matched);
38
+ if (skipped.length > 0) {
39
+ (0, dist_1.log_warn)(`CallerManager: skipped ${skipped.length} wallets not in Vault whitelist`, skipped);
40
+ }
41
+ }
42
+ else {
43
+ this.callers = allWallets;
44
+ (0, dist_1.log_warn)(`CallerManager: Vault whitelist not found in Redis (${vaultCallersKey}), using all ${this.callers.length} loaded wallets`);
45
+ }
46
+ if (this.callers.length === 0) {
47
+ throw new Error('CallerManager: no valid callers after whitelist matching');
48
+ }
49
+ yield Promise.all(this.callers.map((caller) => __awaiter(this, void 0, void 0, function* () {
50
+ const address = caller.address.toLowerCase();
51
+ const nonceKey = this.getNonceRedisKey();
52
+ const existing = yield this.redis.hgetvalue(nonceKey, address);
53
+ if (existing !== null && existing !== undefined) {
54
+ (0, dist_1.log_info)(`Caller ${caller.address} nonce from Redis: ${existing}`);
55
+ return;
56
+ }
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, dist_1.log_info)(`Caller ${caller.address} nonce from chain: ${nonce}`);
60
+ })));
61
+ (0, dist_1.log_info)(`CallerManager initialized, ${this.callers.length} active callers`, this.callers.map(w => w.address));
62
+ });
63
+ }
64
+ acquireCaller() {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ 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, dist_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.advanceNonce(address, nonce + 2);
86
+ yield this.redis.releaseLock(lockKey, lockValue);
87
+ })
88
+ };
89
+ }
90
+ yield (0, dist_1.sleep)(retryInterval);
91
+ }
92
+ throw new Error(`acquireCaller failed: all ${this.callers.length} callers are busy, waited ${Date.now() - startTime}ms`);
93
+ });
94
+ }
95
+ confirmNonce(address, confirmedNonce) {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const current = yield this.getNonce(address);
98
+ if (confirmedNonce > current) {
99
+ yield this.setNonce(address, confirmedNonce);
100
+ (0, dist_1.log_info)(`confirmNonce: ${address}, ${current} → ${confirmedNonce}`);
101
+ }
102
+ });
103
+ }
104
+ forceSetNonce(address, nonce) {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ const current = yield this.getNonce(address);
107
+ if (nonce !== current) {
108
+ yield this.setNonce(address, nonce);
109
+ (0, dist_1.log_info)(`forceSetNonce: ${address}, ${current} → ${nonce}`);
110
+ }
111
+ });
112
+ }
113
+ getCallerCount() {
114
+ return this.callers.length;
115
+ }
116
+ getCallerAddresses() {
117
+ return this.callers.map(w => w.address);
118
+ }
119
+ getNonceRedisKey() {
120
+ return `${this.config.chainName}:${CALLER_NONCE_KEY}`;
121
+ }
122
+ getLastUsedRedisKey() {
123
+ return `${this.config.chainName}:${CALLER_LAST_USED_KEY}`;
124
+ }
125
+ getLockKey(address) {
126
+ return `${this.config.chainName}:lock:caller:${address.toLowerCase()}`;
127
+ }
128
+ getNonce(address) {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const nonceKey = this.getNonceRedisKey();
131
+ const val = yield this.redis.hgetvalue(nonceKey, address.toLowerCase());
132
+ if (val !== null && val !== undefined) {
133
+ return parseInt(val, 10);
134
+ }
135
+ throw new Error(`Caller ${address} nonce not found in Redis, init() may not have been called`);
136
+ });
137
+ }
138
+ setNonce(address, nonce) {
139
+ return __awaiter(this, void 0, void 0, function* () {
140
+ const nonceKey = this.getNonceRedisKey();
141
+ yield this.redis.hsetValue(nonceKey, address.toLowerCase(), String(nonce), 24 * 60 * 60);
142
+ });
143
+ }
144
+ advanceNonce(address, newNonce) {
145
+ return __awaiter(this, void 0, void 0, function* () {
146
+ const current = yield this.getNonce(address);
147
+ if (newNonce > current) {
148
+ yield this.setNonce(address, newNonce);
149
+ }
150
+ });
151
+ }
152
+ getCallersSortedByLRU() {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ const lastUsedKey = this.getLastUsedRedisKey();
155
+ const lastUsedData = yield this.redis.hgetall(lastUsedKey);
156
+ const callersWithTime = this.callers.map(caller => {
157
+ let lastUsedAt = 0;
158
+ const data = lastUsedData === null || lastUsedData === void 0 ? void 0 : lastUsedData[caller.address.toLowerCase()];
159
+ if (data) {
160
+ try {
161
+ lastUsedAt = parseInt(data, 10) || 0;
162
+ }
163
+ catch (_a) { }
164
+ }
165
+ return { caller, lastUsedAt };
166
+ });
167
+ callersWithTime.sort((a, b) => a.lastUsedAt - b.lastUsedAt);
168
+ return callersWithTime.map(item => item.caller);
169
+ });
170
+ }
171
+ updateLastUsed(address) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const lastUsedKey = this.getLastUsedRedisKey();
174
+ yield this.redis.hsetValue(lastUsedKey, address.toLowerCase(), String(Date.now()), 24 * 60 * 60);
175
+ });
176
+ }
177
+ }
178
+ exports.CallerManager = CallerManager;
@@ -1,5 +1,4 @@
1
- export * from './send';
2
1
  export * from './parse';
3
2
  export * from './check';
3
+ export * from './caller_manager';
4
4
  export * from './abstract_dex_trade';
5
- export * from './abstract_dex_trade_plus';
@@ -14,8 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./send"), exports);
18
17
  __exportStar(require("./parse"), exports);
19
18
  __exportStar(require("./check"), exports);
19
+ __exportStar(require("./caller_manager"), exports);
20
20
  __exportStar(require("./abstract_dex_trade"), exports);
21
- __exportStar(require("./abstract_dex_trade_plus"), exports);
@@ -10,6 +10,33 @@ export interface OnchainPoolData {
10
10
  sqrtPriceX96: string;
11
11
  liquidity: string;
12
12
  }
13
+ export interface AmmPoolState {
14
+ address: string;
15
+ token0: string;
16
+ token1: string;
17
+ fee: number;
18
+ reserve0: string;
19
+ reserve1: string;
20
+ }
21
+ export interface ClmmPoolState {
22
+ address: string;
23
+ token0: string;
24
+ token1: string;
25
+ fee: number;
26
+ tickSpacing: number;
27
+ tick: number;
28
+ sqrtPriceX96: string;
29
+ liquidity: string;
30
+ }
31
+ export interface InfinityPoolState extends ClmmPoolState {
32
+ hooks: string;
33
+ poolManager: string;
34
+ parameters: string;
35
+ currency0: string;
36
+ currency1: string;
37
+ lpFee?: number;
38
+ protocolFee?: number;
39
+ }
13
40
  export interface OnchainPoolChangeEvent {
14
41
  provider_id?: string;
15
42
  pool_address: string;
@@ -26,5 +53,9 @@ export interface OnchainPoolChangeEvent {
26
53
  tick: number;
27
54
  sqrtPriceX96: BigInt | string;
28
55
  liquidity: BigInt | string;
56
+ tickLower?: number;
57
+ tickUpper?: number;
58
+ liquidityDelta?: string;
59
+ amount?: string;
29
60
  };
30
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "1.0.90",
3
+ "version": "2.0.1",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,8 @@
14
14
  "push": "npm run build && npm publish"
15
15
  },
16
16
  "dependencies": {
17
- "@clonegod/ttd-core": "2.1.35",
17
+ "@clonegod/ttd-core": "2.1.37",
18
+ "@clonegod/ttd-bsc-send-tx": "1.0.0",
18
19
  "axios": "^1.12.0",
19
20
  "dotenv": "^16.4.7",
20
21
  "ethers": "^5.8.0"