@clonegod/ttd-bsc-common 1.0.90 → 3.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 (69) hide show
  1. package/dist/config/BscQuoteAppConfig.d.ts +11 -0
  2. package/dist/config/BscQuoteAppConfig.js +62 -0
  3. package/dist/config/bsc_env_args.d.ts +1 -1
  4. package/dist/config/bsc_env_args.js +2 -2
  5. package/dist/config/index.d.ts +1 -0
  6. package/dist/config/index.js +1 -0
  7. package/dist/quote/event/index.d.ts +1 -0
  8. package/dist/quote/event/index.js +1 -0
  9. package/dist/quote/event/pool_event_listener.d.ts +1 -1
  10. package/dist/quote/event/pool_event_listener.js +31 -31
  11. package/dist/quote/event/swap_debouncer.d.ts +22 -0
  12. package/dist/quote/event/swap_debouncer.js +92 -0
  13. package/dist/quote/index.d.ts +1 -0
  14. package/dist/quote/index.js +1 -0
  15. package/dist/quote/pricing/index.d.ts +2 -0
  16. package/dist/quote/pricing/index.js +2 -0
  17. package/dist/quote/pricing/pool_state_initializer.d.ts +8 -0
  18. package/dist/quote/pricing/pool_state_initializer.js +142 -0
  19. package/dist/quote/pricing/sdk_token_factory.d.ts +2 -0
  20. package/dist/quote/pricing/sdk_token_factory.js +21 -0
  21. package/dist/quote/pricing/token_price_cache.js +4 -4
  22. package/dist/quote/tick/clmm_tick_cache.d.ts +40 -0
  23. package/dist/quote/tick/clmm_tick_cache.js +219 -0
  24. package/dist/quote/tick/index.d.ts +2 -0
  25. package/dist/{trade/send → quote/tick}/index.js +2 -8
  26. package/dist/quote/tick/tick_lens_loaders.d.ts +25 -0
  27. package/dist/quote/tick/tick_lens_loaders.js +170 -0
  28. package/dist/redis/redis_client.d.ts +1 -0
  29. package/dist/redis/redis_client.js +12 -6
  30. package/dist/trade/abstract_dex_trade.d.ts +31 -17
  31. package/dist/trade/abstract_dex_trade.js +214 -109
  32. package/dist/trade/caller_manager.d.ts +35 -0
  33. package/dist/trade/caller_manager.js +178 -0
  34. package/dist/trade/check/tx_websocket_manager.js +11 -11
  35. package/dist/trade/index.d.ts +1 -2
  36. package/dist/trade/index.js +1 -2
  37. package/dist/trade/parse/base_parser.js +2 -2
  38. package/dist/types/pool_state.d.ts +31 -0
  39. package/dist/utils/gas_helper.js +9 -9
  40. package/dist/utils/index.d.ts +1 -0
  41. package/dist/utils/index.js +1 -0
  42. package/dist/utils/trade_direction.d.ts +15 -0
  43. package/dist/utils/trade_direction.js +23 -0
  44. package/dist/ws/event_filter.js +2 -2
  45. package/dist/yyws/yyws_client.js +2 -2
  46. package/package.json +3 -2
  47. package/dist/trade/abstract_dex_trade_plus.d.ts +0 -44
  48. package/dist/trade/abstract_dex_trade_plus.js +0 -449
  49. package/dist/trade/send/48club.d.ts +0 -17
  50. package/dist/trade/send/48club.js +0 -123
  51. package/dist/trade/send/48club_member.d.ts +0 -1
  52. package/dist/trade/send/48club_member.js +0 -25
  53. package/dist/trade/send/48club_sp.d.ts +0 -9
  54. package/dist/trade/send/48club_sp.js +0 -137
  55. package/dist/trade/send/blockrazor.d.ts +0 -7
  56. package/dist/trade/send/blockrazor.js +0 -78
  57. package/dist/trade/send/blxr.d.ts +0 -0
  58. package/dist/trade/send/blxr.js +0 -0
  59. package/dist/trade/send/bsc_rpc.d.ts +0 -6
  60. package/dist/trade/send/bsc_rpc.js +0 -47
  61. package/dist/trade/send/index.d.ts +0 -6
  62. package/dist/trade/send/send_bundle_proxy.d.ts +0 -7
  63. package/dist/trade/send/send_bundle_proxy.js +0 -30
  64. package/dist/trade/send/send_bundle_ws.d.ts +0 -7
  65. package/dist/trade/send/send_bundle_ws.js +0 -30
  66. package/dist/trade/send/send_tx.d.ts +0 -9
  67. package/dist/trade/send/send_tx.js +0 -119
  68. package/dist/ws/bsc_stream_ws_client.d.ts +0 -10
  69. package/dist/ws/bsc_stream_ws_client.js +0 -95
@@ -8,146 +8,251 @@ 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;
13
- const dist_1 = require("@clonegod/ttd-core/dist");
15
+ exports.AbstractDexTrade = void 0;
16
+ exports.buildTradeConfig = buildTradeConfig;
17
+ const ttd_core_1 = require("@clonegod/ttd-core");
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 trade_direction_1 = require("../utils/trade_direction");
23
+ const decimal_js_1 = __importDefault(require("decimal.js"));
24
+ function buildTradeConfig() {
25
+ const vaultAddress = process.env.VAULT_ADDRESS;
26
+ if (!vaultAddress) {
27
+ throw new Error('环境变量 VAULT_ADDRESS 未配置');
28
+ }
29
+ const pancakeIdName = process.env.PANCAKE_EXECUTOR_ID || 'PANCAKE';
30
+ const uniswapIdName = process.env.UNISWAP_EXECUTOR_ID || 'UNISWAP';
31
+ return {
32
+ vaultAddress,
33
+ executorIds: {
34
+ pancake: ethers_1.ethers.utils.id(pancakeIdName),
35
+ uniswap: ethers_1.ethers.utils.id(uniswapIdName),
36
+ },
37
+ };
38
+ }
39
+ const VAULT_ABI = [
40
+ 'function delegatecallExecutorToTrade(bytes32 executorId, bytes calldata data, uint256 deadline) external payable returns (bytes memory result)'
41
+ ];
42
+ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
17
43
  constructor(appConfig) {
18
44
  super();
19
45
  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
46
  this.initConfigs();
47
+ this.chainNameLower = this.appConfig.env_args.chain_id.toLowerCase();
48
+ this.redisClient = new redis_1.SimpleRedisClient(`${this.chainNameLower}:tx`);
49
+ this.vaultInterface = new ethers_1.ethers.utils.Interface(VAULT_ABI);
27
50
  }
28
51
  init() {
29
52
  return __awaiter(this, void 0, void 0, function* () {
30
53
  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}`);
54
+ this.transactionSender = new ttd_bsc_send_tx_1.TransactionSender(this.appConfig);
55
+ const callerGroupIds = (process.env.CALLER_GROUP_IDS || '').trim().split(',').filter(Boolean);
56
+ this.callerManager = new caller_manager_1.CallerManager({
57
+ chainName: this.chainNameLower,
58
+ provider: this.provider,
59
+ callerGroupIds,
60
+ chainId: this.chainConfig.chainId,
61
+ });
62
+ yield this.callerManager.init();
63
+ this.subscribeTradeMonitor();
64
+ (0, ttd_core_1.log_info)(`AbstractDexTrade initialized, vault=${this.tradeConfig.vaultAddress}, callers=${this.callerManager.getCallerCount()}`);
35
65
  });
36
66
  }
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);
67
+ execute(context) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ var _a;
70
+ const { price_msg, order_msg, slippage_bps, order_trace_id, pool_info } = context;
71
+ const { pair } = price_msg;
72
+ const { aToB, amount, unique_order_msg_id } = order_msg;
73
+ (0, ttd_core_1.log_info)(`执行 Vault 交易, orderId=${order_trace_id}`, {
74
+ unique_order_msg_id,
75
+ pair,
76
+ aToB,
77
+ amount,
78
+ slippage_bps
79
+ });
80
+ let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
81
+ const callerHandle = yield this.callerManager.acquireCaller();
82
+ const { wallet: caller, nonce: initialNonce } = callerHandle;
83
+ try {
84
+ let nonce = initialNonce;
85
+ let nonce_from_error = 0;
86
+ let txid = '';
87
+ let i = 1;
88
+ do {
89
+ try {
90
+ if (i > 1) {
91
+ if (nonce_from_error > 0) {
92
+ nonce = nonce_from_error;
93
+ nonce_from_error = 0;
94
+ (0, ttd_core_1.log_info)(`Attempt ${i}/${maxAttempts}, using nonce from error msg: ${nonce}`, {}, order_trace_id);
95
+ }
96
+ else {
97
+ (0, ttd_core_1.log_warn)(`Attempt ${i}/${maxAttempts}, no nonce in error msg, abort`, {}, order_trace_id);
98
+ break;
99
+ }
100
+ }
101
+ const { executorId, data } = this.encodeTradeData(context);
102
+ const deadline = Math.floor(Date.now() / 1000) + 60;
103
+ const vaultCalldata = this.vaultInterface.encodeFunctionData('delegatecallExecutorToTrade', [executorId, data, deadline]);
104
+ const gasPriceGwei = this.getGasPriceGwei(context);
105
+ const realGasPriceGwei = Math.min(Number(gasPriceGwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
106
+ const tx = {
107
+ to: this.tradeConfig.vaultAddress,
108
+ data: vaultCalldata,
109
+ gasLimit: this.chainConfig.gasOptions.gasLimit,
110
+ gasPrice: ethers_1.ethers.utils.parseUnits(realGasPriceGwei, 'gwei'),
111
+ nonce,
112
+ chainId: this.chainConfig.chainId,
113
+ value: 0,
114
+ };
115
+ const signedTx = yield caller.signTransaction(tx);
116
+ txid = ethers_1.ethers.utils.keccak256(signedTx);
117
+ (0, ttd_core_1.log_info)(`Vault 交易已签名`, { txid, nonce, gasPriceGwei: realGasPriceGwei }, order_trace_id);
118
+ const tipNonce = nonce + 1;
119
+ const eoa_tip_transaction = (eoa_address) => __awaiter(this, void 0, void 0, function* () {
120
+ const transfer_amount_gwei = this.getBuilderTipAmoutGwei(context);
121
+ context.ui_tip_amount = new decimal_js_1.default(transfer_amount_gwei).div(Math.pow(10, 9)).toNumber();
122
+ return yield this.buildTipTransferTx(eoa_address, transfer_amount_gwei, realGasPriceGwei, tipNonce, caller);
123
+ });
124
+ const only_bundle = order_msg.is_dex_maker;
125
+ yield this.transactionSender.sendTransaction(signedTx, eoa_tip_transaction, order_trace_id, pair, only_bundle);
126
+ (0, ttd_core_1.log_info)(`Vault 交易发送成功`, {
127
+ pair, unique_order_msg_id, txid,
128
+ attempt: i, caller: caller.address
129
+ }, order_trace_id);
130
+ return txid;
131
+ }
132
+ catch (error) {
133
+ const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
134
+ if (errorMessage.includes('rate limit')) {
135
+ (0, ttd_core_1.log_warn)(`Rate limit error, no retry! i=${i}`, {}, order_trace_id);
136
+ return txid;
137
+ }
138
+ if (errorMessage.includes('replacement transaction underpriced')) {
139
+ (0, ttd_core_1.log_warn)(`Replacement tx underpriced, continue! i=${i}`, {}, order_trace_id);
140
+ continue;
141
+ }
142
+ const isNonceError = errorMessage.includes('nonce');
143
+ if (!isNonceError || i >= maxAttempts) {
144
+ (0, ttd_core_1.log_warn)(`Non-nonce error or max retries, i=${i}, error: ${errorMessage}`, {}, order_trace_id);
145
+ return txid;
146
+ }
147
+ (0, ttd_core_1.log_info)(`Nonce error detected, will retry! i=${i}`, {}, order_trace_id);
148
+ if (errorMessage.includes('nonce too')) {
149
+ const correctNonce = this.extractNonceFromErrorMsg(errorMessage);
150
+ if (correctNonce !== null && correctNonce > 0) {
151
+ nonce_from_error = correctNonce;
152
+ }
153
+ }
154
+ }
155
+ } while (++i <= maxAttempts);
156
+ if (!txid) {
157
+ throw new Error(`Vault 交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
158
+ }
159
+ return txid;
160
+ }
161
+ finally {
162
+ yield callerHandle.release();
163
+ }
164
+ });
165
+ }
166
+ subscribeTradeMonitor() {
167
+ const wsUrl = process.env.TRADE_MONITOR_WS_URL;
168
+ if (!wsUrl) {
169
+ (0, ttd_core_1.log_warn)('TRADE_MONITOR_WS_URL not configured, trade result nonce correction disabled');
170
+ return;
41
171
  }
42
- return this.tokenContracts.get(tokenAddress);
172
+ const wsClient = new ttd_core_1.WebSocketClient(wsUrl);
173
+ wsClient.onOpen(() => {
174
+ wsClient.send(JSON.stringify({
175
+ vault_address: this.tradeConfig.vaultAddress,
176
+ }));
177
+ (0, ttd_core_1.log_info)(`Subscribed to trade-monitor for vault=${this.tradeConfig.vaultAddress}`);
178
+ });
179
+ wsClient.onMessage((event) => {
180
+ if (event.type === 'TradeResult' && event.data) {
181
+ const { caller, callerNonce } = event.data;
182
+ if (caller) {
183
+ const nextNonce = callerNonce + 2;
184
+ this.callerManager.confirmNonce(caller, nextNonce).catch(err => (0, ttd_core_1.log_warn)(`Failed to confirm nonce from trade-monitor`, err));
185
+ }
186
+ }
187
+ });
188
+ wsClient.connect();
43
189
  }
44
190
  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) {
191
+ let { evm_gas_price_gwei } = context.trade_runtime.settings.strategy;
192
+ if (!evm_gas_price_gwei || evm_gas_price_gwei <= 0) {
47
193
  evm_gas_price_gwei = this.chainConfig.gasOptions.defaultGasPriceGwei;
48
194
  }
49
- (0, dist_1.log_info)(`getGasPriceGwei: ${evm_gas_price_gwei} Gwei`);
50
195
  return evm_gas_price_gwei.toString();
51
196
  }
52
197
  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) {
198
+ let { evm_tip_amount_gwei } = context.trade_runtime.settings.strategy;
199
+ if (!evm_tip_amount_gwei || evm_tip_amount_gwei <= 0) {
55
200
  evm_tip_amount_gwei = this.chainConfig.gasOptions.defaultTipAmountGwei;
56
201
  }
57
- (0, dist_1.log_info)(`getGasTipAmoutGwei: ${evm_tip_amount_gwei} Gwei`);
58
202
  return evm_tip_amount_gwei.toString();
59
203
  }
60
- checkTradeTokenApprove(context, routerAddress) {
61
- 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) {
204
+ buildTipTransferTx(to, transfer_amount_gwei, gas_price_gwei, nonce, wallet) {
134
205
  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,
206
+ const real_transfer_amount_gwei = Math.min(Number(transfer_amount_gwei), this.chainConfig.gasOptions.maxTipAmountGwei).toString();
207
+ const real_gas_price_gwei = Math.min(Number(gas_price_gwei), this.chainConfig.gasOptions.maxGasPriceGwei).toString();
208
+ const tx_data = {
209
+ from: wallet.address,
139
210
  to,
140
211
  value: ethers_1.ethers.utils.parseUnits(real_transfer_amount_gwei, 'gwei'),
141
- gasLimit: 21000,
212
+ gasLimit: 42000,
142
213
  gasPrice: ethers_1.ethers.utils.parseUnits(real_gas_price_gwei, 'gwei'),
143
- nonce: transfer_nonce,
214
+ nonce,
144
215
  chainId: this.chainConfig.chainId
145
216
  };
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) }));
217
+ const signedTx = yield wallet.signTransaction(tx_data);
218
+ (0, ttd_core_1.log_info)(`构建 tip 转账交易`, {
219
+ to, nonce,
220
+ tipGwei: real_transfer_amount_gwei,
221
+ txhash: ethers_1.ethers.utils.keccak256(signedTx)
222
+ });
149
223
  return signedTx;
150
224
  });
151
225
  }
226
+ determineInputOutputTokens(order_msg, pool_info) {
227
+ const { inputToken, outputToken } = (0, trade_direction_1.resolveTradeDirection)(pool_info, order_msg.isBuy);
228
+ return { inputToken, outputToken };
229
+ }
230
+ calculateAmountOutMin(context, inputToken, outputToken) {
231
+ const { price_msg, slippage_bps, order_msg } = context;
232
+ const { isBuy } = order_msg;
233
+ const slippage = slippage_bps / 10000;
234
+ const inputAmount = new decimal_js_1.default(order_msg.amount.toString());
235
+ let expectedOut;
236
+ if (isBuy) {
237
+ const price = new decimal_js_1.default(price_msg.ask.price);
238
+ expectedOut = inputAmount.div(price);
239
+ }
240
+ else {
241
+ const price = new decimal_js_1.default(price_msg.bid.price);
242
+ expectedOut = inputAmount.mul(price);
243
+ }
244
+ const minOutput = expectedOut.mul(new decimal_js_1.default(1).sub(new decimal_js_1.default(slippage)));
245
+ return ethers_1.ethers.utils.parseUnits(minOutput.toFixed(outputToken.decimals), outputToken.decimals);
246
+ }
247
+ extractNonceFromErrorMsg(errorMessage) {
248
+ try {
249
+ const match = errorMessage.match(/tx:\s*(\d+)\s*state:\s*(\d+)/);
250
+ if (match) {
251
+ return parseInt(match[2]);
252
+ }
253
+ }
254
+ catch (_a) { }
255
+ return null;
256
+ }
152
257
  }
153
- exports.AbstractEvmDexTrade = AbstractEvmDexTrade;
258
+ 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 ttd_core_1 = require("@clonegod/ttd-core");
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, ttd_core_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, ttd_core_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, ttd_core_1.log_info)(`CallerManager: matched ${this.callers.length} callers with Vault whitelist`, matched);
38
+ if (skipped.length > 0) {
39
+ (0, ttd_core_1.log_warn)(`CallerManager: skipped ${skipped.length} wallets not in Vault whitelist`, skipped);
40
+ }
41
+ }
42
+ else {
43
+ 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`);
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, ttd_core_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, ttd_core_1.log_info)(`Caller ${caller.address} nonce from chain: ${nonce}`);
60
+ })));
61
+ (0, ttd_core_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, 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.advanceNonce(address, nonce + 2);
86
+ yield this.redis.releaseLock(lockKey, lockValue);
87
+ })
88
+ };
89
+ }
90
+ yield (0, ttd_core_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, ttd_core_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, ttd_core_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;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebSocketManager = void 0;
4
4
  const ethers_1 = require("ethers");
5
- const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const ttd_core_1 = require("@clonegod/ttd-core");
6
6
  class WebSocketManager {
7
7
  constructor() {
8
8
  this.wsProvider = null;
@@ -50,32 +50,32 @@ class WebSocketManager {
50
50
  getWsProvider(wsEndpoint, network) {
51
51
  var _a;
52
52
  if (!wsEndpoint) {
53
- (0, dist_1.log_warn)('WebSocket endpoint not configured');
53
+ (0, ttd_core_1.log_warn)('WebSocket endpoint not configured');
54
54
  return null;
55
55
  }
56
56
  if (!this.wsProvider) {
57
- (0, dist_1.log_info)('Creating new WebSocket provider');
57
+ (0, ttd_core_1.log_info)('Creating new WebSocket provider');
58
58
  try {
59
59
  this.wsProvider = new ethers_1.ethers.providers.WebSocketProvider(wsEndpoint, network);
60
60
  this.wsProvider.on('error', (error) => {
61
- (0, dist_1.log_error)('[WS] provider error', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(error)));
61
+ (0, ttd_core_1.log_error)('[WS] provider error', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(error)));
62
62
  this.handleWsDisconnection(wsEndpoint, network);
63
63
  });
64
64
  const rawWs = (_a = this.wsProvider) === null || _a === void 0 ? void 0 : _a._websocket;
65
65
  if (rawWs === null || rawWs === void 0 ? void 0 : rawWs.on) {
66
66
  rawWs.on('close', (code, reason) => {
67
67
  const reasonText = this.normalizeReason(reason);
68
- (0, dist_1.log_warn)('[WS] socket close', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), { code, reason: reasonText }));
68
+ (0, ttd_core_1.log_warn)('[WS] socket close', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), { code, reason: reasonText }));
69
69
  this.handleWsDisconnection(wsEndpoint, network);
70
70
  });
71
71
  rawWs.on('error', (err) => {
72
- (0, dist_1.log_error)('[WS] socket error', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(err)));
72
+ (0, ttd_core_1.log_error)('[WS] socket error', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(err)));
73
73
  this.handleWsDisconnection(wsEndpoint, network);
74
74
  });
75
75
  }
76
76
  }
77
77
  catch (error) {
78
- (0, dist_1.log_error)('[WS] create provider failed', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(error)));
78
+ (0, ttd_core_1.log_error)('[WS] create provider failed', Object.assign(Object.assign({}, this.formatConn(wsEndpoint, network)), this.formatErr(error)));
79
79
  this.wsProvider = null;
80
80
  }
81
81
  }
@@ -97,16 +97,16 @@ class WebSocketManager {
97
97
  }
98
98
  incrementConnections() {
99
99
  this.wsConnections++;
100
- (0, dist_1.log_info)(`WebSocket active connections: ${this.wsConnections}`);
100
+ (0, ttd_core_1.log_info)(`WebSocket active connections: ${this.wsConnections}`);
101
101
  }
102
102
  decrementConnections() {
103
103
  var _a, _b, _c, _d;
104
104
  if (this.wsConnections > 0) {
105
105
  this.wsConnections--;
106
106
  }
107
- (0, dist_1.log_info)(`WebSocket active connections: ${this.wsConnections}`);
107
+ (0, ttd_core_1.log_info)(`WebSocket active connections: ${this.wsConnections}`);
108
108
  if (this.wsConnections === 0 && this.wsProvider) {
109
- (0, dist_1.log_info)('No active connections, closing WebSocket');
109
+ (0, ttd_core_1.log_info)('No active connections, closing WebSocket');
110
110
  try {
111
111
  this.wsProvider.removeAllListeners();
112
112
  const rawWs = (_a = this.wsProvider) === null || _a === void 0 ? void 0 : _a._websocket;
@@ -119,7 +119,7 @@ class WebSocketManager {
119
119
  }
120
120
  listenToEvent(eventName, handler) {
121
121
  if (!this.wsProvider) {
122
- (0, dist_1.log_warn)('WebSocket provider not available for event listener');
122
+ (0, ttd_core_1.log_warn)('WebSocket provider not available for event listener');
123
123
  return;
124
124
  }
125
125
  if (!this.eventListeners.has(eventName)) {
@@ -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';