@clonegod/ttd-base-common 1.0.25 → 1.1.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 (144) hide show
  1. package/dist/appconfig/BaseQuoteAppConfig.d.ts +10 -0
  2. package/dist/appconfig/BaseQuoteAppConfig.js +36 -0
  3. package/dist/appconfig/BaseTradeAppConfig.d.ts +7 -0
  4. package/dist/appconfig/BaseTradeAppConfig.js +13 -0
  5. package/dist/appconfig/base_dex_env_args.d.ts +5 -0
  6. package/dist/appconfig/base_dex_env_args.js +68 -0
  7. package/dist/appconfig/base_env_args.d.ts +82 -0
  8. package/dist/appconfig/base_env_args.js +91 -0
  9. package/dist/appconfig/ensure_core_env.d.ts +1 -0
  10. package/dist/appconfig/ensure_core_env.js +18 -0
  11. package/dist/appconfig/index.d.ts +5 -0
  12. package/dist/appconfig/index.js +21 -0
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +2 -2
  15. package/dist/quote/depth/amm_depth_calculator.d.ts +19 -0
  16. package/dist/quote/depth/amm_depth_calculator.js +55 -0
  17. package/dist/quote/depth/clmm_depth_calculator.d.ts +28 -0
  18. package/dist/quote/depth/clmm_depth_calculator.js +176 -0
  19. package/dist/quote/depth/index.d.ts +51 -0
  20. package/dist/quote/depth/index.js +264 -0
  21. package/dist/quote/depth/tick_liquidity_snapshot.d.ts +58 -0
  22. package/dist/quote/depth/tick_liquidity_snapshot.js +143 -0
  23. package/dist/quote/event/index.d.ts +1 -0
  24. package/dist/quote/event/index.js +1 -0
  25. package/dist/quote/event/pool_event_listener.d.ts +5 -3
  26. package/dist/quote/event/pool_event_listener.js +128 -150
  27. package/dist/quote/event/swap_debouncer.d.ts +22 -0
  28. package/dist/quote/event/swap_debouncer.js +80 -0
  29. package/dist/quote/get_base_token_price.d.ts +6 -0
  30. package/dist/quote/get_base_token_price.js +90 -0
  31. package/dist/quote/index.d.ts +7 -0
  32. package/dist/quote/index.js +7 -0
  33. package/dist/quote/preload_token_prices.d.ts +2 -0
  34. package/dist/quote/preload_token_prices.js +37 -0
  35. package/dist/quote/price_feed_handler.d.ts +15 -0
  36. package/dist/quote/price_feed_handler.js +56 -0
  37. package/dist/quote/pricing/fee_helpers.d.ts +13 -0
  38. package/dist/quote/pricing/fee_helpers.js +68 -0
  39. package/dist/quote/pricing/index.d.ts +3 -1
  40. package/dist/quote/pricing/index.js +3 -1
  41. package/dist/quote/pricing/pool_state_initializer.d.ts +12 -0
  42. package/dist/quote/pricing/pool_state_initializer.js +191 -0
  43. package/dist/quote/pricing/sdk_token_factory.d.ts +2 -0
  44. package/dist/quote/pricing/sdk_token_factory.js +21 -0
  45. package/dist/quote/quote_amount.d.ts +4 -0
  46. package/dist/quote/quote_amount.js +24 -0
  47. package/dist/quote/tick/cached_tick_data_provider.d.ts +12 -0
  48. package/dist/quote/tick/cached_tick_data_provider.js +45 -0
  49. package/dist/quote/tick/clmm_tick_cache.d.ts +42 -0
  50. package/dist/quote/tick/clmm_tick_cache.js +236 -0
  51. package/dist/quote/tick/index.d.ts +4 -0
  52. package/dist/{ws → quote/tick}/index.js +4 -2
  53. package/dist/quote/tick/state_view_tick_loader.d.ts +17 -0
  54. package/dist/quote/tick/state_view_tick_loader.js +136 -0
  55. package/dist/quote/tick/tick_lens_loaders.d.ts +24 -0
  56. package/dist/quote/tick/tick_lens_loaders.js +158 -0
  57. package/dist/quote/verify/index.d.ts +2 -0
  58. package/dist/quote/verify/index.js +5 -0
  59. package/dist/quote/verify/quote_price_verify.d.ts +30 -0
  60. package/dist/quote/verify/quote_price_verify.js +240 -0
  61. package/dist/redis/redis_client.d.ts +3 -2
  62. package/dist/redis/redis_client.js +86 -116
  63. package/dist/send-tx/constants.d.ts +2 -0
  64. package/dist/send-tx/constants.js +6 -0
  65. package/dist/send-tx/index.d.ts +2 -0
  66. package/dist/{config → send-tx}/index.js +2 -1
  67. package/dist/send-tx/types.d.ts +4 -0
  68. package/dist/send-tx/types.js +2 -0
  69. package/dist/trade/abstract_dex_trade.d.ts +43 -21
  70. package/dist/trade/abstract_dex_trade.js +347 -133
  71. package/dist/trade/caller_manager.d.ts +31 -0
  72. package/dist/trade/caller_manager.js +202 -0
  73. package/dist/trade/check/abstract_tx_result_checker.d.ts +28 -0
  74. package/dist/trade/check/abstract_tx_result_checker.js +192 -0
  75. package/dist/trade/check/index.d.ts +1 -1
  76. package/dist/trade/check/index.js +1 -1
  77. package/dist/trade/index.d.ts +2 -2
  78. package/dist/trade/index.js +2 -2
  79. package/dist/trade/parse/base_parser.d.ts +1 -2
  80. package/dist/trade/parse/base_parser.js +36 -36
  81. package/dist/trade/trade_trace.d.ts +17 -0
  82. package/dist/trade/trade_trace.js +65 -0
  83. package/dist/types/config_types.d.ts +3 -3
  84. package/dist/types/event_types.d.ts +3 -3
  85. package/dist/types/pool_state.d.ts +140 -13
  86. package/dist/utils/ethers_compat.d.ts +13 -0
  87. package/dist/utils/ethers_compat.js +18 -0
  88. package/dist/utils/fast_signer.d.ts +1 -0
  89. package/dist/utils/fast_signer.js +87 -0
  90. package/dist/utils/gas_helper.d.ts +2 -2
  91. package/dist/utils/gas_helper.js +48 -60
  92. package/dist/utils/index.d.ts +5 -2
  93. package/dist/utils/index.js +6 -2
  94. package/dist/utils/pool_filter.d.ts +8 -0
  95. package/dist/utils/pool_filter.js +38 -0
  96. package/dist/utils/trade_direction.d.ts +14 -0
  97. package/dist/utils/trade_direction.js +23 -0
  98. package/package.json +2 -2
  99. package/dist/config/base_env_args.d.ts +0 -11
  100. package/dist/config/base_env_args.js +0 -19
  101. package/dist/config/index.d.ts +0 -1
  102. package/dist/quote/event/verify_clmm_swap_event.d.ts +0 -1
  103. package/dist/quote/event/verify_clmm_swap_event.js +0 -178
  104. package/dist/quote/pricing/token_price_cache.d.ts +0 -10
  105. package/dist/quote/pricing/token_price_cache.js +0 -40
  106. package/dist/trade/abstract_dex_trade_plus.d.ts +0 -43
  107. package/dist/trade/abstract_dex_trade_plus.js +0 -421
  108. package/dist/trade/check/tx_websocket_manager.d.ts +0 -23
  109. package/dist/trade/check/tx_websocket_manager.js +0 -119
  110. package/dist/trade/send/alchemy_base.d.ts +0 -5
  111. package/dist/trade/send/alchemy_base.js +0 -48
  112. package/dist/trade/send/ankr_base.d.ts +0 -5
  113. package/dist/trade/send/ankr_base.js +0 -48
  114. package/dist/trade/send/base_rpc.d.ts +0 -5
  115. package/dist/trade/send/base_rpc.js +0 -48
  116. package/dist/trade/send/blockpi_base.d.ts +0 -5
  117. package/dist/trade/send/blockpi_base.js +0 -48
  118. package/dist/trade/send/bloxroute_base.d.ts +0 -11
  119. package/dist/trade/send/bloxroute_base.js +0 -115
  120. package/dist/trade/send/chainstack_base.d.ts +0 -5
  121. package/dist/trade/send/chainstack_base.js +0 -48
  122. package/dist/trade/send/drpc_base.d.ts +0 -5
  123. package/dist/trade/send/drpc_base.js +0 -48
  124. package/dist/trade/send/getblock_base.d.ts +0 -5
  125. package/dist/trade/send/getblock_base.js +0 -48
  126. package/dist/trade/send/index.d.ts +0 -15
  127. package/dist/trade/send/index.js +0 -33
  128. package/dist/trade/send/infura_base.d.ts +0 -5
  129. package/dist/trade/send/infura_base.js +0 -48
  130. package/dist/trade/send/moralis_base.d.ts +0 -5
  131. package/dist/trade/send/moralis_base.js +0 -48
  132. package/dist/trade/send/onerpc_base.d.ts +0 -5
  133. package/dist/trade/send/onerpc_base.js +0 -48
  134. package/dist/trade/send/quicknode_base.d.ts +0 -5
  135. package/dist/trade/send/quicknode_base.js +0 -48
  136. package/dist/trade/send/send_tx.d.ts +0 -17
  137. package/dist/trade/send/send_tx.js +0 -163
  138. package/dist/ws/event_filter.d.ts +0 -8
  139. package/dist/ws/event_filter.js +0 -36
  140. package/dist/ws/index.d.ts +0 -2
  141. package/dist/ws/subscribe_v2_events.d.ts +0 -14
  142. package/dist/ws/subscribe_v2_events.js +0 -174
  143. package/dist/ws/subscribe_v3_events.d.ts +0 -14
  144. package/dist/ws/subscribe_v3_events.js +0 -174
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallerManager = void 0;
4
+ const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const logger = (0, dist_1.createLogger)(__filename);
6
+ const ethers_1 = require("ethers");
7
+ const ttd_core_1 = require("@clonegod/ttd-core");
8
+ const LUA_ACQUIRE_CALLER = `
9
+ local last_used_key = KEYS[1]
10
+ local now = tonumber(ARGV[#ARGV])
11
+ local count = #ARGV - 1
12
+
13
+ local min_ts = nil
14
+ local min_addr = nil
15
+ for i = 1, count do
16
+ local addr = ARGV[i]
17
+ local ts_raw = redis.call('HGET', last_used_key, addr)
18
+ local ts = ts_raw and tonumber(ts_raw) or 0
19
+ if min_ts == nil or ts < min_ts then
20
+ min_ts = ts
21
+ min_addr = addr
22
+ end
23
+ end
24
+ if min_addr then
25
+ redis.call('HSET', last_used_key, min_addr, tostring(now))
26
+ end
27
+ return min_addr
28
+ `;
29
+ const CALLER_NONCE_KEY = 'caller:nonce';
30
+ const CALLER_LAST_USED_KEY = 'caller:last_used';
31
+ const VAULT_CALLERS_KEY = 'vault:callers';
32
+ class CallerManager {
33
+ constructor(config) {
34
+ this.callers = [];
35
+ this.config = config;
36
+ this.redis = new ttd_core_1.RedisClient(`${config.chainName}:caller`);
37
+ }
38
+ async init() {
39
+ const walletInfos = (0, dist_1.load_wallet_multi)(this.config.callerGroupIds, false);
40
+ const allWallets = walletInfos.map(info => new ethers_1.ethers.Wallet(info.private_key, this.config.provider));
41
+ const vaultCallersKey = `${this.config.chainName}:${VAULT_CALLERS_KEY}`;
42
+ const rawJson = await this.redis.hget(vaultCallersKey, this.config.groupId);
43
+ let allowedAddresses = [];
44
+ if (rawJson) {
45
+ try {
46
+ allowedAddresses = JSON.parse(rawJson);
47
+ }
48
+ catch (err) {
49
+ logger.error(`CallerManager: failed to parse vault whitelist for group ${this.config.groupId}`, err);
50
+ }
51
+ }
52
+ let skipped = [];
53
+ if (allowedAddresses.length > 0) {
54
+ const allowedSet = new Set(allowedAddresses.map(a => a.toLowerCase()));
55
+ this.callers = allWallets.filter(w => allowedSet.has(w.address.toLowerCase()));
56
+ skipped = allWallets.filter(w => !allowedSet.has(w.address.toLowerCase()));
57
+ }
58
+ else {
59
+ this.callers = allWallets;
60
+ logger.warn(`CallerManager: Vault whitelist not found in Redis (${vaultCallersKey} -> ${this.config.groupId}), using all ${this.callers.length} loaded wallets`);
61
+ }
62
+ if (this.callers.length === 0) {
63
+ throw new Error('CallerManager: no valid callers after whitelist matching');
64
+ }
65
+ const nonceKey = this.getNonceRedisKey();
66
+ const callerAddresses = this.callers.map(c => c.address.toLowerCase());
67
+ const nonces = await this.redis.hmget(nonceKey, callerAddresses);
68
+ const callerSummary = {};
69
+ let missingNonceCount = 0;
70
+ this.callers.forEach((caller, i) => {
71
+ const nonce = nonces[i];
72
+ if (nonce === null || nonce === undefined) {
73
+ callerSummary[caller.address] = 'MISSING';
74
+ missingNonceCount++;
75
+ }
76
+ else {
77
+ callerSummary[caller.address] = `nonce=${nonce}`;
78
+ }
79
+ });
80
+ if (missingNonceCount > 0) {
81
+ logger.warn(`CallerManager: ${missingNonceCount} callers nonce not found in Redis, stream-trade may not be running`);
82
+ }
83
+ logger.info(`CallerManager initialized for ${this.config.groupId}: loaded=${allWallets.length}, active=${this.callers.length}, skipped=${skipped.length}`, callerSummary);
84
+ }
85
+ async acquireCaller() {
86
+ const strategy = (0, dist_1.getCoreEnv)()?.caller_select_strategy;
87
+ if (strategy === 'lua_cas') {
88
+ return this.acquireCallerWithLuaCas();
89
+ }
90
+ return this.acquireCallerWithLock();
91
+ }
92
+ async acquireCallerWithLock() {
93
+ const lockKey = `${this.config.chainName}:caller:lock:select`;
94
+ const lockValue = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
95
+ const lockExpireSeconds = 1;
96
+ const maxRetries = 20;
97
+ const retryDelayMs = 10;
98
+ const startTime = Date.now();
99
+ let acquired = false;
100
+ let retries = 0;
101
+ for (; retries < maxRetries; retries++) {
102
+ acquired = await this.redis.acquireLock(lockKey, lockValue, lockExpireSeconds);
103
+ if (acquired)
104
+ break;
105
+ await new Promise(r => setTimeout(r, retryDelayMs));
106
+ }
107
+ if (!acquired) {
108
+ throw new Error(`acquireCaller: failed to acquire lock after ${maxRetries} retries (${Date.now() - startTime}ms)`);
109
+ }
110
+ const lockAcquiredTime = Date.now();
111
+ let callerAddr;
112
+ let selectedIdx;
113
+ try {
114
+ const addresses = this.callers.map(w => w.address.toLowerCase());
115
+ const lastUsedKey = this.getLastUsedRedisKey();
116
+ const lastUsedData = await this.redis.hgetall(lastUsedKey);
117
+ selectedIdx = 0;
118
+ let minLastUsed = Number.MAX_SAFE_INTEGER;
119
+ for (let i = 0; i < this.callers.length; i++) {
120
+ const redisTs = parseInt(lastUsedData?.[addresses[i]] || '0', 10);
121
+ if (redisTs < minLastUsed) {
122
+ minLastUsed = redisTs;
123
+ selectedIdx = i;
124
+ }
125
+ }
126
+ callerAddr = addresses[selectedIdx];
127
+ await this.redis.hset(lastUsedKey, callerAddr, String(Date.now()), 24 * 60 * 60);
128
+ }
129
+ finally {
130
+ this.redis.releaseLock(lockKey, lockValue).catch(() => { });
131
+ }
132
+ const nonce = await this.readNonce(callerAddr);
133
+ const totalMs = Date.now() - startTime;
134
+ const lockWaitMs = lockAcquiredTime - startTime;
135
+ logger.info(`acquireCaller[lock]: ${callerAddr} nonce=${nonce}, lock_wait=${lockWaitMs}ms, total=${totalMs}ms${retries > 0 ? `, retries=${retries}` : ''}`);
136
+ return { wallet: this.callers[selectedIdx], nonce };
137
+ }
138
+ async acquireCallerWithLuaCas() {
139
+ const startTime = Date.now();
140
+ const lastUsedKey = this.getLastUsedRedisKey();
141
+ const addresses = this.callers.map(w => w.address.toLowerCase());
142
+ const now = Date.now();
143
+ const selectedAddr = await this.redis.eval(LUA_ACQUIRE_CALLER, [lastUsedKey], [...addresses, String(now)]);
144
+ if (!selectedAddr) {
145
+ throw new Error('acquireCaller[lua_cas]: script returned no caller');
146
+ }
147
+ const selectedIdx = addresses.indexOf(selectedAddr);
148
+ if (selectedIdx < 0) {
149
+ throw new Error(`acquireCaller[lua_cas]: selected addr ${selectedAddr} not in pool`);
150
+ }
151
+ const nonce = await this.readNonce(selectedAddr);
152
+ logger.info(`acquireCaller[lua_cas]: ${selectedAddr} nonce=${nonce}, total=${Date.now() - startTime}ms`);
153
+ return { wallet: this.callers[selectedIdx], nonce };
154
+ }
155
+ async readNonce(callerAddr) {
156
+ const nonceKey = this.getNonceRedisKey();
157
+ const nonceStr = await this.redis.hget(nonceKey, callerAddr);
158
+ if (nonceStr === null || nonceStr === undefined) {
159
+ throw new Error(`Caller ${callerAddr} nonce not found in Redis, stream-trade may not be running`);
160
+ }
161
+ return parseInt(nonceStr, 10);
162
+ }
163
+ async confirmNonce(address, confirmedNonce) {
164
+ const current = await this.getNonce(address);
165
+ if (confirmedNonce > current) {
166
+ await this.setNonce(address, confirmedNonce);
167
+ logger.info(`confirmNonce: ${address}, ${current} → ${confirmedNonce}`);
168
+ }
169
+ }
170
+ async forceSetNonce(address, nonce) {
171
+ const current = await this.getNonce(address);
172
+ if (nonce !== current) {
173
+ await this.setNonce(address, nonce);
174
+ logger.info(`forceSetNonce: ${address}, ${current} → ${nonce}`);
175
+ }
176
+ }
177
+ getCallerCount() {
178
+ return this.callers.length;
179
+ }
180
+ getCallerAddresses() {
181
+ return this.callers.map(w => w.address);
182
+ }
183
+ getNonceRedisKey() {
184
+ return `${this.config.chainName}:${CALLER_NONCE_KEY}`;
185
+ }
186
+ getLastUsedRedisKey() {
187
+ return `${this.config.chainName}:${CALLER_LAST_USED_KEY}`;
188
+ }
189
+ async getNonce(address) {
190
+ const nonceKey = this.getNonceRedisKey();
191
+ const val = await this.redis.hget(nonceKey, address.toLowerCase());
192
+ if (val !== null && val !== undefined) {
193
+ return parseInt(val, 10);
194
+ }
195
+ throw new Error(`Caller ${address} nonce not found in Redis, init() may not have been called`);
196
+ }
197
+ async setNonce(address, nonce) {
198
+ const nonceKey = this.getNonceRedisKey();
199
+ await this.redis.hset(nonceKey, address.toLowerCase(), String(nonce), 24 * 60 * 60);
200
+ }
201
+ }
202
+ exports.CallerManager = CallerManager;
@@ -0,0 +1,28 @@
1
+ import { EnvArgs } from '@clonegod/ttd-core';
2
+ import { AbstractTransactionResultCheck } from "@clonegod/ttd-core/dist/trade";
3
+ import { EventEmitter } from 'events';
4
+ export declare class TradeResultSubscriber {
5
+ private static instance;
6
+ private ws;
7
+ private connected;
8
+ private listeners;
9
+ static getInstance(): TradeResultSubscriber;
10
+ listen(txHash: string, callback: (receipt: any) => void, timeoutMs?: number): void;
11
+ remove(txHash: string): void;
12
+ sendNonceWatch(caller: string, txHash: string): void;
13
+ private cachedSubscriptions;
14
+ private cachedClientName;
15
+ private getSubscriptions;
16
+ private getClientName;
17
+ private ensureConnected;
18
+ }
19
+ export declare abstract class AbstractTxResultChecker extends AbstractTransactionResultCheck {
20
+ protected provider: any;
21
+ constructor(env_args: EnvArgs, event_emitter: EventEmitter);
22
+ protected abstract createParser(): {
23
+ parseTransaction(txReceipt: any, poolInfo: any): Promise<any>;
24
+ };
25
+ check_tx_result_interval(): Promise<void>;
26
+ on_subscibe_transaction(): Promise<void>;
27
+ private processTransactionResult;
28
+ }
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractTxResultChecker = exports.TradeResultSubscriber = void 0;
4
+ const ttd_core_1 = require("@clonegod/ttd-core");
5
+ const logger = (0, ttd_core_1.createLogger)(__filename);
6
+ const trade_1 = require("@clonegod/ttd-core/dist/trade");
7
+ const ethers_compat_1 = require("../../utils/ethers_compat");
8
+ class TradeResultSubscriber {
9
+ constructor() {
10
+ this.ws = null;
11
+ this.connected = false;
12
+ this.listeners = new Map();
13
+ this.cachedSubscriptions = null;
14
+ this.cachedClientName = null;
15
+ }
16
+ static getInstance() {
17
+ if (!TradeResultSubscriber.instance) {
18
+ TradeResultSubscriber.instance = new TradeResultSubscriber();
19
+ }
20
+ return TradeResultSubscriber.instance;
21
+ }
22
+ listen(txHash, callback, timeoutMs = 30000) {
23
+ const key = txHash.toLowerCase();
24
+ this.listeners.set(key, callback);
25
+ setTimeout(() => {
26
+ this.listeners.delete(key);
27
+ }, timeoutMs);
28
+ this.ensureConnected();
29
+ }
30
+ remove(txHash) {
31
+ this.listeners.delete(txHash.toLowerCase());
32
+ }
33
+ sendNonceWatch(caller, txHash) {
34
+ this.ensureConnected();
35
+ if (this.ws && this.connected) {
36
+ this.ws.send(JSON.stringify({ type: 'nonceWatch', caller, txHash }));
37
+ }
38
+ }
39
+ getSubscriptions() {
40
+ if (this.cachedSubscriptions)
41
+ return this.cachedSubscriptions;
42
+ const groupIds = ((0, ttd_core_1.getCoreEnv)()?.group_id || '').split(',').map(s => s.trim()).filter(Boolean);
43
+ const subs = [];
44
+ for (const gid of groupIds) {
45
+ const w = (0, ttd_core_1.load_wallet)(gid, true);
46
+ if (w.public_key) {
47
+ subs.push({ address: w.public_key, groupId: gid });
48
+ }
49
+ }
50
+ this.cachedSubscriptions = subs;
51
+ return subs;
52
+ }
53
+ getClientName() {
54
+ if (this.cachedClientName)
55
+ return this.cachedClientName;
56
+ this.cachedClientName = process.env.APP_NAME
57
+ || process.env.name
58
+ || (process.env.pm_id ? `pm2-${process.env.pm_id}` : null)
59
+ || `pid-${process.pid}`;
60
+ return this.cachedClientName;
61
+ }
62
+ ensureConnected() {
63
+ if (this.ws)
64
+ return;
65
+ const host = process.env.STREAM_TRADE_WS_HOST || '127.0.0.1';
66
+ const wsUrl = `ws://${host}:${ttd_core_1.SERVICE_PORT.STREAM_TRADE_WS}`;
67
+ this.ws = new ttd_core_1.WebSocketClient(wsUrl);
68
+ this.ws.onOpen(() => {
69
+ this.connected = true;
70
+ const subs = this.getSubscriptions();
71
+ const clientName = this.getClientName();
72
+ for (const { address, groupId } of subs) {
73
+ this.ws.send(JSON.stringify({ address, groupId, clientName }));
74
+ }
75
+ logger.info(`TradeResultSubscriber connected: ${wsUrl}, subscribed=${subs.length}`);
76
+ });
77
+ this.ws.onMessage((msg) => {
78
+ if (msg.type !== 'TradeResult' || !msg.data)
79
+ return;
80
+ const { txHash, receipt } = msg.data;
81
+ if (!txHash || !receipt)
82
+ return;
83
+ const key = txHash.toLowerCase();
84
+ const callback = this.listeners.get(key);
85
+ if (callback) {
86
+ this.listeners.delete(key);
87
+ callback(receipt);
88
+ }
89
+ });
90
+ this.ws.connect();
91
+ }
92
+ }
93
+ exports.TradeResultSubscriber = TradeResultSubscriber;
94
+ TradeResultSubscriber.instance = null;
95
+ class AbstractTxResultChecker extends trade_1.AbstractTransactionResultCheck {
96
+ constructor(env_args, event_emitter) {
97
+ super(env_args, event_emitter);
98
+ this.provider = new ethers_compat_1.ethersCompat.JsonRpcProvider(this.env_args.rpc_endpoint);
99
+ }
100
+ async check_tx_result_interval() {
101
+ const check_start_time = Date.now();
102
+ const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
103
+ const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '15000');
104
+ if (check_interval >= check_timeout)
105
+ return;
106
+ const intervalId = setInterval(async () => {
107
+ this.check_count += 1;
108
+ logger.info(`check transaction start: seq=[${this.check_count}], txhash=${this.txid}, trace_id=${this.trace_id}`);
109
+ try {
110
+ if (Date.now() - check_start_time < check_timeout) {
111
+ let txReceipt = await this.provider.getTransactionReceipt(this.txid);
112
+ if (!txReceipt)
113
+ return;
114
+ logger.info(`Received transaction result via polling: ${this.txid}`);
115
+ clearInterval(intervalId);
116
+ await this.processTransactionResult(txReceipt, 'interval');
117
+ }
118
+ else {
119
+ clearInterval(intervalId);
120
+ ttd_core_1.ALERT_TYPES.TRADE_TX_PENDING_TOO_LONG.report({
121
+ identity: String(this.txid).toLowerCase(),
122
+ scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
123
+ title: `Tx pending > ${check_timeout}ms: ${this.txid}`,
124
+ detail: { txid: this.txid, timeout_ms: check_timeout, checks: this.check_count },
125
+ });
126
+ }
127
+ }
128
+ catch (err) {
129
+ clearInterval(intervalId);
130
+ logger.error('parse transaction error!', err);
131
+ }
132
+ }, check_interval);
133
+ this.intervalId = intervalId;
134
+ }
135
+ async on_subscibe_transaction() {
136
+ logger.info(`Subscribing trade result, txid=${this.txid}`);
137
+ TradeResultSubscriber.getInstance().listen(this.txid, (receipt) => {
138
+ logger.info(`Received transaction result via stream-trade: ${this.txid}`);
139
+ this.processTransactionResult(receipt, 'websocket')
140
+ .catch(err => logger.error(`Error processing trade result: ${this.txid}`, err));
141
+ });
142
+ }
143
+ async processTransactionResult(txReceipt, source) {
144
+ if (ttd_core_1.LOG.debug) {
145
+ (0, ttd_core_1.writeFile)(`./dist/tx_receipt_${this.txid}_${source}.json`, JSON.stringify(txReceipt, null, 2));
146
+ }
147
+ if (this.trade_result_already_processed) {
148
+ logger.info(`trade_result_already_processed, ignore result from ${source}`);
149
+ return;
150
+ }
151
+ const parser = this.createParser();
152
+ const swap_detail = await parser.parseTransaction(txReceipt, this.pool_info);
153
+ let trade_result = this.map_swap_result_to_tx_result(swap_detail);
154
+ this.trade_result_already_processed = true;
155
+ if (this.intervalId) {
156
+ clearInterval(this.intervalId);
157
+ this.intervalId = null;
158
+ }
159
+ if (trade_result.success) {
160
+ this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_SUCCESS, trade_result);
161
+ }
162
+ else {
163
+ this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_FAILED, trade_result);
164
+ const status = txReceipt?.status;
165
+ if (status === 0) {
166
+ ttd_core_1.ALERT_TYPES.TRADE_VAULT_REVERT.report({
167
+ identity: String(this.txid).toLowerCase(),
168
+ scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
169
+ title: `Vault revert: tx=${this.txid}`,
170
+ detail: { txid: this.txid, status, source, gas_used: txReceipt?.gasUsed?.toString?.() },
171
+ });
172
+ }
173
+ else {
174
+ ttd_core_1.ALERT_TYPES.TRADE_RESULT_MISSING.report({
175
+ identity: String(this.txid).toLowerCase(),
176
+ scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
177
+ title: `TradeResult event missing: tx=${this.txid}`,
178
+ detail: { txid: this.txid, status, source, parse_result: trade_result },
179
+ });
180
+ }
181
+ }
182
+ if (source === 'interval') {
183
+ console.log('--------------------- Transaction Result from Polling ---------------------');
184
+ }
185
+ else {
186
+ console.log('===================== Transaction Result from stream-trade =====================');
187
+ }
188
+ console.log(JSON.stringify(trade_result, null, 2));
189
+ console.log('-----------------------------------------------------------------------------');
190
+ }
191
+ }
192
+ exports.AbstractTxResultChecker = AbstractTxResultChecker;
@@ -1 +1 @@
1
- export * from "./tx_websocket_manager";
1
+ export * from "./abstract_tx_result_checker";
@@ -14,4 +14,4 @@ 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("./tx_websocket_manager"), exports);
17
+ __exportStar(require("./abstract_tx_result_checker"), exports);
@@ -1,5 +1,5 @@
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';
5
+ export * from './trade_trace';
@@ -14,8 +14,8 @@ 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);
21
+ __exportStar(require("./trade_trace"), exports);
@@ -1,11 +1,10 @@
1
1
  import { StandardPoolInfoType, StandardSwapDetailType } from "@clonegod/ttd-core";
2
2
  import { TransactionReceipt } from '@ethersproject/providers';
3
- import { ethers } from 'ethers';
4
3
  import { ITxParser } from "./abstract_parser";
5
4
  import { ParserConfig } from "../../types";
6
5
  export declare abstract class BaseTxParser implements ITxParser {
7
6
  protected config: ParserConfig;
8
- protected provider: ethers.providers.JsonRpcProvider;
7
+ protected provider: any;
9
8
  protected blockTimeCache: Map<number, number>;
10
9
  protected readonly CACHE_EXPIRY: number;
11
10
  constructor(config: ParserConfig);
@@ -1,53 +1,53 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.BaseTxParser = void 0;
13
- const dist_1 = require("@clonegod/ttd-core/dist");
4
+ const ttd_core_1 = require("@clonegod/ttd-core");
5
+ const logger = (0, ttd_core_1.createLogger)(__filename);
14
6
  const ethers_1 = require("ethers");
7
+ const ethers_compat_1 = require("../../utils/ethers_compat");
15
8
  class BaseTxParser {
16
9
  constructor(config) {
17
10
  this.blockTimeCache = new Map();
18
11
  this.CACHE_EXPIRY = 1000 * 60 * 60;
19
12
  this.config = config;
20
- this.provider = new ethers_1.ethers.providers.JsonRpcProvider(config.rpcEndpoint);
13
+ this.provider = new ethers_compat_1.ethersCompat.JsonRpcProvider(config.rpcEndpoint);
21
14
  }
22
- getBlockTime(blockNumber) {
23
- return __awaiter(this, void 0, void 0, function* () {
24
- if (process.env.NEED_BLOCK_TIME_INFO !== 'true') {
25
- return 0;
26
- }
27
- try {
28
- if (this.blockTimeCache.has(blockNumber)) {
29
- return this.blockTimeCache.get(blockNumber);
30
- }
31
- const block = yield this.provider.getBlock(blockNumber);
32
- const timestamp = block.timestamp * 1000;
33
- this.blockTimeCache.set(blockNumber, timestamp);
34
- setTimeout(() => {
35
- this.blockTimeCache.delete(blockNumber);
36
- }, this.CACHE_EXPIRY);
37
- return timestamp;
38
- }
39
- catch (error) {
40
- console.error('获取区块时间失败:', error);
41
- return 0;
15
+ async getBlockTime(blockNumber) {
16
+ if (!(0, ttd_core_1.getCoreEnv)().trade_parse_fetch_block_time) {
17
+ return 0;
18
+ }
19
+ try {
20
+ if (this.blockTimeCache.has(blockNumber)) {
21
+ return this.blockTimeCache.get(blockNumber);
42
22
  }
43
- });
23
+ const block = await this.provider.getBlock(blockNumber);
24
+ const timestamp = block.timestamp * 1000;
25
+ this.blockTimeCache.set(blockNumber, timestamp);
26
+ setTimeout(() => {
27
+ this.blockTimeCache.delete(blockNumber);
28
+ }, this.CACHE_EXPIRY);
29
+ return timestamp;
30
+ }
31
+ catch (error) {
32
+ console.error('获取区块时间失败:', error);
33
+ return 0;
34
+ }
44
35
  }
45
36
  calculateGasFee(txReceipt) {
46
37
  try {
47
- const gasUsed = txReceipt.gasUsed || ethers_1.ethers.BigNumber.from(0);
48
- const effectiveGasPrice = txReceipt.effectiveGasPrice || ethers_1.ethers.BigNumber.from(0);
38
+ const toBN = (val) => {
39
+ if (!val)
40
+ return ethers_1.ethers.BigNumber.from(0);
41
+ if (ethers_1.ethers.BigNumber.isBigNumber(val))
42
+ return val;
43
+ if (val.hex)
44
+ return ethers_1.ethers.BigNumber.from(val.hex);
45
+ return ethers_1.ethers.BigNumber.from(val);
46
+ };
47
+ const gasUsed = toBN(txReceipt.gasUsed);
48
+ const effectiveGasPrice = toBN(txReceipt.effectiveGasPrice || txReceipt.gasPrice);
49
49
  const gasFeeBN = gasUsed.mul(effectiveGasPrice);
50
- const base_fee = Number(ethers_1.ethers.utils.formatEther(gasFeeBN));
50
+ const base_fee = Number(ethers_compat_1.ethersCompat.formatEther(gasFeeBN));
51
51
  const priority_fee = 0;
52
52
  const total_fee = base_fee + priority_fee;
53
53
  return {
@@ -57,7 +57,7 @@ class BaseTxParser {
57
57
  };
58
58
  }
59
59
  catch (error) {
60
- (0, dist_1.log_error)('calculateGasFee error:', error);
60
+ logger.error('calculateGasFee error:', error);
61
61
  return {
62
62
  base_fee: 0,
63
63
  priority_fee: 0,
@@ -0,0 +1,17 @@
1
+ export declare class TradeTrace {
2
+ readonly orderId: string;
3
+ readonly pair: string;
4
+ readonly direction: string;
5
+ private startTime;
6
+ private marks;
7
+ private data;
8
+ private error;
9
+ constructor(orderId: string, pair?: string, direction?: string);
10
+ mark(name: string): void;
11
+ set(key: string, value: any): void;
12
+ markError(stage: string, message: string): void;
13
+ private buildTimeline;
14
+ getAbsoluteMarks(): Record<string, number>;
15
+ getStartTime(): number;
16
+ flush(): void;
17
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TradeTrace = void 0;
4
+ const ttd_core_1 = require("@clonegod/ttd-core");
5
+ const logger = (0, ttd_core_1.createLogger)(__filename);
6
+ class TradeTrace {
7
+ constructor(orderId, pair = '', direction = '') {
8
+ this.orderId = orderId;
9
+ this.pair = pair;
10
+ this.direction = direction;
11
+ this.marks = [];
12
+ this.data = {};
13
+ this.error = null;
14
+ this.startTime = Date.now();
15
+ }
16
+ mark(name) {
17
+ this.marks.push({ name, elapsed: Date.now() - this.startTime });
18
+ }
19
+ set(key, value) {
20
+ this.data[key] = value;
21
+ }
22
+ markError(stage, message) {
23
+ this.error = { stage, message };
24
+ }
25
+ buildTimeline() {
26
+ if (this.marks.length === 0)
27
+ return 'recv';
28
+ let prev = 0;
29
+ const parts = this.marks.map(m => {
30
+ const delta = m.elapsed - prev;
31
+ prev = m.elapsed;
32
+ return `${m.name}(${delta}ms)`;
33
+ });
34
+ const total = this.marks[this.marks.length - 1].elapsed;
35
+ return `recv -> ${parts.join(' -> ')} = ${total}ms`;
36
+ }
37
+ getAbsoluteMarks() {
38
+ const result = {};
39
+ for (const m of this.marks) {
40
+ result[m.name] = this.startTime + m.elapsed;
41
+ }
42
+ return result;
43
+ }
44
+ getStartTime() {
45
+ return this.startTime;
46
+ }
47
+ flush() {
48
+ const timeline = this.buildTimeline();
49
+ if (this.error) {
50
+ logger.error(`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction} | stage=${this.error.stage} | ${this.error.message}`, new Error(this.error.message));
51
+ logger.info(`[TRADE FAILED detail] ${this.orderId}`, {
52
+ timeline,
53
+ error_stage: this.error.stage,
54
+ ...this.data,
55
+ });
56
+ }
57
+ else {
58
+ logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, {
59
+ timeline,
60
+ ...this.data,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ exports.TradeTrace = TradeTrace;
@@ -1,12 +1,12 @@
1
1
  export interface GasOptions {
2
2
  gasLimit: number;
3
+ maxGasLimit: number;
3
4
  defaultGasPriceGwei: number;
4
5
  maxGasPriceGwei: number;
6
+ maxFeePerGasGwei: number;
7
+ maxPriorityFeePerGasGwei: number;
5
8
  defaultTipAmountGwei: number;
6
9
  maxTipAmountGwei: number;
7
- txStatusCheckIntervalMills: number;
8
- txCancelTimeoutMills: number;
9
- txCancelGasBoostFactor: number;
10
10
  }
11
11
  export interface EvmChainConfig {
12
12
  chainId: number;