@clonegod/ttd-bsc-common 1.0.0
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.
- package/dist/common/abi.d.ts +31 -0
- package/dist/common/abi.js +68 -0
- package/dist/common/constants.d.ts +21 -0
- package/dist/common/constants.js +37 -0
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.js +18 -0
- package/dist/config/bsc_env_args.d.ts +11 -0
- package/dist/config/bsc_env_args.js +19 -0
- package/dist/config/env_args.d.ts +13 -0
- package/dist/config/env_args.js +23 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +17 -0
- package/dist/constants/networks.d.ts +10 -0
- package/dist/constants/networks.js +28 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +22 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +21 -0
- package/dist/quote/event/index.d.ts +1 -0
- package/dist/quote/event/index.js +17 -0
- package/dist/quote/event/pool_event_listener.d.ts +39 -0
- package/dist/quote/event/pool_event_listener.js +231 -0
- package/dist/quote/event/verify_clmm_swap_event.d.ts +1 -0
- package/dist/quote/event/verify_clmm_swap_event.js +178 -0
- package/dist/quote/index.d.ts +2 -0
- package/dist/quote/index.js +18 -0
- package/dist/quote/pricing/index.d.ts +2 -0
- package/dist/quote/pricing/index.js +18 -0
- package/dist/quote/pricing/pool.d.ts +13 -0
- package/dist/quote/pricing/pool.js +21 -0
- package/dist/quote/pricing/token_price_cache.d.ts +9 -0
- package/dist/quote/pricing/token_price_cache.js +40 -0
- package/dist/trade/abstract_dex_trade.d.ts +27 -0
- package/dist/trade/abstract_dex_trade.js +152 -0
- package/dist/trade/check/index.d.ts +1 -0
- package/dist/trade/check/index.js +17 -0
- package/dist/trade/check/tx_websocket_manager.d.ts +23 -0
- package/dist/trade/check/tx_websocket_manager.js +119 -0
- package/dist/trade/index.d.ts +4 -0
- package/dist/trade/index.js +20 -0
- package/dist/trade/parse/abstract_parser.d.ts +8 -0
- package/dist/trade/parse/abstract_parser.js +2 -0
- package/dist/trade/parse/base_parser.d.ts +30 -0
- package/dist/trade/parse/base_parser.js +108 -0
- package/dist/trade/parse/index.d.ts +1 -0
- package/dist/trade/parse/index.js +5 -0
- package/dist/trade/send/48club.d.ts +18 -0
- package/dist/trade/send/48club.js +97 -0
- package/dist/trade/send/blockrazor.d.ts +7 -0
- package/dist/trade/send/blockrazor.js +79 -0
- package/dist/trade/send/bsc_rpc.d.ts +6 -0
- package/dist/trade/send/bsc_rpc.js +43 -0
- package/dist/trade/send/index.d.ts +5 -0
- package/dist/trade/send/index.js +23 -0
- package/dist/trade/send/send_tx.d.ts +8 -0
- package/dist/trade/send/send_tx.js +85 -0
- package/dist/types/config_types.d.ts +28 -0
- package/dist/types/config_types.js +2 -0
- package/dist/types/event_types.d.ts +30 -0
- package/dist/types/event_types.js +2 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +20 -0
- package/dist/types/quote_types.d.ts +27 -0
- package/dist/types/quote_types.js +2 -0
- package/dist/types/trade_types.d.ts +38 -0
- package/dist/types/trade_types.js +8 -0
- package/dist/utils/gas_helper.d.ts +5 -0
- package/dist/utils/gas_helper.js +72 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +43 -0
- package/dist/utils/redis_lock.d.ts +14 -0
- package/dist/utils/redis_lock.js +100 -0
- package/package.json +30 -0
|
@@ -0,0 +1,231 @@
|
|
|
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.PoolEventListener = void 0;
|
|
13
|
+
const ethers_1 = require("ethers");
|
|
14
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
15
|
+
const constants_1 = require("../../common/constants");
|
|
16
|
+
class PoolEventListener {
|
|
17
|
+
constructor(appConfig, wsProvider) {
|
|
18
|
+
this.poolList = [];
|
|
19
|
+
this.isConnected = false;
|
|
20
|
+
this.isStarted = false;
|
|
21
|
+
this.reconnectAttempts = 0;
|
|
22
|
+
this.maxReconnectAttempts = 10;
|
|
23
|
+
this.baseReconnectDelay = 1000;
|
|
24
|
+
this.maxReconnectDelay = 30000;
|
|
25
|
+
this.heartbeatInterval = null;
|
|
26
|
+
this.connectionTimeout = null;
|
|
27
|
+
this.connectionTimeoutMs = 15000;
|
|
28
|
+
this.lastProcessedBlockNumber = 0;
|
|
29
|
+
this.isBlockListenerActive = false;
|
|
30
|
+
this.appConfig = appConfig;
|
|
31
|
+
this.wsProvider = wsProvider;
|
|
32
|
+
}
|
|
33
|
+
init(poolList) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
this.poolList = poolList.filter(pool => ethers_1.ethers.utils.isAddress(pool.pool_address));
|
|
36
|
+
if (this.poolList.length !== poolList.length) {
|
|
37
|
+
(0, dist_1.log_warn)(`发现 ${poolList.length - this.poolList.length} 个无效池地址,已过滤`, '');
|
|
38
|
+
}
|
|
39
|
+
yield this.initProvider();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
start() {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
(0, dist_1.log_info)(`开始启动区块监听...`);
|
|
45
|
+
if (!this.isConnected) {
|
|
46
|
+
(0, dist_1.log_warn)('WebSocket 尚未连接,无法启动监听', '');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.startBlockListener();
|
|
50
|
+
this.isStarted = true;
|
|
51
|
+
(0, dist_1.log_info)('区块监听已成功启动');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
stop() {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
(0, dist_1.log_info)(`停止区块监听...`);
|
|
57
|
+
this.stopBlockListener();
|
|
58
|
+
this.cleanupProvider();
|
|
59
|
+
this.isStarted = false;
|
|
60
|
+
(0, dist_1.log_info)(`区块监听已停止`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
initProvider() {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
try {
|
|
66
|
+
(0, dist_1.log_info)(`正在连接WebSocket: ${this.appConfig.env_args.ws_endpoint}`);
|
|
67
|
+
yield this.wsProvider.ready;
|
|
68
|
+
this.setConnectionTimeout();
|
|
69
|
+
this.clearConnectionTimeout();
|
|
70
|
+
this.isConnected = true;
|
|
71
|
+
this.reconnectAttempts = 0;
|
|
72
|
+
(0, dist_1.log_info)(`WebSocket Provider 已连接`);
|
|
73
|
+
this.wsProvider.on('error', (err) => {
|
|
74
|
+
(0, dist_1.log_error)(`WebSocket Provider 错误:`, err, '');
|
|
75
|
+
this.handleConnectionIssue();
|
|
76
|
+
});
|
|
77
|
+
this.wsProvider._websocket.on('close', (code) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
(0, dist_1.log_warn)(`WebSocket 连接关闭, 代码: ${code}. 尝试重新连接...`, '');
|
|
79
|
+
this.handleConnectionIssue();
|
|
80
|
+
}));
|
|
81
|
+
this.startHeartbeat();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
(0, dist_1.log_error)('初始化 WebSocket 提供者失败:', error, '');
|
|
85
|
+
this.clearConnectionTimeout();
|
|
86
|
+
this.handleConnectionIssue();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
setConnectionTimeout() {
|
|
91
|
+
this.clearConnectionTimeout();
|
|
92
|
+
this.connectionTimeout = setTimeout(() => {
|
|
93
|
+
(0, dist_1.log_error)(`WebSocket 连接超时`, new Error('Connection timeout'), '');
|
|
94
|
+
this.handleConnectionIssue();
|
|
95
|
+
}, this.connectionTimeoutMs);
|
|
96
|
+
}
|
|
97
|
+
clearConnectionTimeout() {
|
|
98
|
+
if (this.connectionTimeout) {
|
|
99
|
+
clearTimeout(this.connectionTimeout);
|
|
100
|
+
this.connectionTimeout = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
handleConnectionIssue() {
|
|
104
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
+
if (!this.isConnected)
|
|
106
|
+
return;
|
|
107
|
+
this.isConnected = false;
|
|
108
|
+
this.stopHeartbeat();
|
|
109
|
+
const delay = Math.min(this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay);
|
|
110
|
+
this.reconnectAttempts++;
|
|
111
|
+
(0, dist_1.log_warn)(`WebSocket 断开连接. 重连尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts}, 延迟 ${delay}ms`, '');
|
|
112
|
+
if (this.reconnectAttempts > this.maxReconnectAttempts) {
|
|
113
|
+
(0, dist_1.log_error)(`超过最大重连尝试次数 (${this.maxReconnectAttempts}),停止重连`, new Error('Max reconnect attempts exceeded'), '');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
yield (0, dist_1.sleep)(delay);
|
|
117
|
+
try {
|
|
118
|
+
yield this.initProvider();
|
|
119
|
+
if (this.isStarted) {
|
|
120
|
+
(0, dist_1.log_info)('重连成功,正在重新启动区块监听...');
|
|
121
|
+
yield this.start();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
(0, dist_1.log_error)('重新连接失败:', err, '');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
startHeartbeat() {
|
|
130
|
+
this.stopHeartbeat();
|
|
131
|
+
this.heartbeatInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
try {
|
|
133
|
+
if (!this.wsProvider)
|
|
134
|
+
return;
|
|
135
|
+
const blockNumber = yield this.wsProvider.getBlockNumber();
|
|
136
|
+
(0, dist_1.log_debug)(`心跳检测成功,当前区块: ${blockNumber}`);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
(0, dist_1.log_warn)(`心跳检测失败,正在重连...`);
|
|
140
|
+
this.handleConnectionIssue();
|
|
141
|
+
}
|
|
142
|
+
}), 15000);
|
|
143
|
+
}
|
|
144
|
+
stopHeartbeat() {
|
|
145
|
+
if (this.heartbeatInterval) {
|
|
146
|
+
clearInterval(this.heartbeatInterval);
|
|
147
|
+
this.heartbeatInterval = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
cleanupProvider() {
|
|
151
|
+
this.stopHeartbeat();
|
|
152
|
+
this.clearConnectionTimeout();
|
|
153
|
+
this.stopBlockListener();
|
|
154
|
+
if (this.wsProvider) {
|
|
155
|
+
try {
|
|
156
|
+
this.wsProvider.removeAllListeners();
|
|
157
|
+
if (this.wsProvider._websocket) {
|
|
158
|
+
this.wsProvider._websocket.removeAllListeners();
|
|
159
|
+
if (this.wsProvider._websocket.readyState === 1) {
|
|
160
|
+
this.wsProvider._websocket.close(1000, "Normal closure");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
(0, dist_1.log_error)(`清理 Provider 资源时出错:`, error, '');
|
|
166
|
+
}
|
|
167
|
+
this.wsProvider = undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
startBlockListener() {
|
|
171
|
+
(0, dist_1.log_info)(`开始启动区块监听...`);
|
|
172
|
+
this.stopBlockListener();
|
|
173
|
+
if (!this.isConnected || !this.wsProvider) {
|
|
174
|
+
(0, dist_1.log_warn)(`无法启动区块监听: WebSocket未连接`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.wsProvider.getBlockNumber()
|
|
178
|
+
.then(blockNumber => {
|
|
179
|
+
this.lastProcessedBlockNumber = blockNumber;
|
|
180
|
+
(0, dist_1.log_info)(`初始化区块监听,当前区块: ${blockNumber}`);
|
|
181
|
+
})
|
|
182
|
+
.catch(error => {
|
|
183
|
+
(0, dist_1.log_error)(`获取当前区块失败:`, error, '');
|
|
184
|
+
});
|
|
185
|
+
this.wsProvider.on('block', (blockNumber) => __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
try {
|
|
187
|
+
if (!this.isConnected || !this.isBlockListenerActive)
|
|
188
|
+
return;
|
|
189
|
+
(0, dist_1.log_info)(`------- Block: ${blockNumber} -------`);
|
|
190
|
+
const previousBlockNumber = this.lastProcessedBlockNumber;
|
|
191
|
+
this.lastProcessedBlockNumber = blockNumber;
|
|
192
|
+
const blockUpdateEvent = {
|
|
193
|
+
blockNumber: blockNumber,
|
|
194
|
+
previousBlockNumber: previousBlockNumber,
|
|
195
|
+
timestamp: Date.now()
|
|
196
|
+
};
|
|
197
|
+
this.appConfig.emit(constants_1.EVENT_NAMES.BLOCK_UPDATE, blockUpdateEvent);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
(0, dist_1.log_error)(`处理区块事件出错:`, error, '');
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
this.isBlockListenerActive = true;
|
|
204
|
+
(0, dist_1.log_info)(`区块监听已启动,使用WebSocket事件订阅方式`);
|
|
205
|
+
}
|
|
206
|
+
stopBlockListener() {
|
|
207
|
+
if (this.isBlockListenerActive && this.wsProvider) {
|
|
208
|
+
this.wsProvider.removeListener('block', () => { });
|
|
209
|
+
this.isBlockListenerActive = false;
|
|
210
|
+
(0, dist_1.log_info)(`区块监听已停止`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
isWSConnected() {
|
|
214
|
+
return this.isConnected;
|
|
215
|
+
}
|
|
216
|
+
getConnectionDiagnostics() {
|
|
217
|
+
return {
|
|
218
|
+
isConnected: this.isConnected,
|
|
219
|
+
isStarted: this.isStarted,
|
|
220
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
221
|
+
hasActiveHeartbeat: this.heartbeatInterval !== null,
|
|
222
|
+
wsEndpoint: this.appConfig.env_args.ws_endpoint,
|
|
223
|
+
wsReadyState: this.wsProvider && this.wsProvider._websocket
|
|
224
|
+
? this.wsProvider._websocket.readyState
|
|
225
|
+
: null,
|
|
226
|
+
trackingPools: this.poolList.length,
|
|
227
|
+
lastProcessedBlock: this.lastProcessedBlockNumber
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.PoolEventListener = PoolEventListener;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
16
|
+
dotenv_1.default.config();
|
|
17
|
+
const ethers_1 = require("ethers");
|
|
18
|
+
const constants_1 = require("../../common/constants");
|
|
19
|
+
const log = (level, msg, data) => {
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
console.log(`${timestamp} [${level}] ${msg}`);
|
|
22
|
+
if (data)
|
|
23
|
+
console.log(data);
|
|
24
|
+
};
|
|
25
|
+
function verifySwapEventParsing() {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const wsEndpoint = process.env.WS_ENDPOINT || '';
|
|
28
|
+
const poolAddress = '0xc1a780989734a0e5df875cebe410748562e1c5e6';
|
|
29
|
+
const poolName = 'B2/BNB';
|
|
30
|
+
log('INFO', `正在连接到WebSocket: ${wsEndpoint}`);
|
|
31
|
+
const wsProvider = new ethers_1.ethers.providers.WebSocketProvider(wsEndpoint);
|
|
32
|
+
yield wsProvider.ready;
|
|
33
|
+
log('INFO', '连接成功,准备测试事件解析');
|
|
34
|
+
const updatedSwapHash = ethers_1.ethers.utils.id(constants_1.EVENT_SIGNATURES.SWAP_RAW);
|
|
35
|
+
log('INFO', `更新后的Swap事件哈希: ${updatedSwapHash}`);
|
|
36
|
+
const capturedSwapHash = '0x19b47279256b2a23a1665c810c8d55a1758940ee09377d4f8d26497a3577dc83';
|
|
37
|
+
log('INFO', `之前捕获的Swap事件哈希: ${capturedSwapHash}`);
|
|
38
|
+
if (updatedSwapHash === capturedSwapHash) {
|
|
39
|
+
log('INFO', '✅ 事件哈希匹配,签名修改正确');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
log('WARN', '⚠️ 事件哈希不匹配,可能需要调整签名格式');
|
|
43
|
+
}
|
|
44
|
+
const methods = [
|
|
45
|
+
{
|
|
46
|
+
name: "方法1:直接使用更新后的SWAP_RAW签名",
|
|
47
|
+
createInterface: () => new ethers_1.ethers.utils.Interface([constants_1.EVENT_SIGNATURES.SWAP_RAW])
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "方法2:添加event前缀(推荐方式)",
|
|
51
|
+
createInterface: () => new ethers_1.ethers.utils.Interface([`event ${constants_1.EVENT_SIGNATURES.SWAP_RAW}`])
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "方法3:带错误处理的解析方式(新增)",
|
|
55
|
+
parse: (log) => {
|
|
56
|
+
try {
|
|
57
|
+
const completeSwapDefinition = "event " + constants_1.EVENT_SIGNATURES.SWAP_RAW;
|
|
58
|
+
const iface = new ethers_1.ethers.utils.Interface([completeSwapDefinition]);
|
|
59
|
+
try {
|
|
60
|
+
return iface.parseLog(log);
|
|
61
|
+
}
|
|
62
|
+
catch (parseError) {
|
|
63
|
+
if (parseError.code === 'BUFFER_OVERRUN') {
|
|
64
|
+
console.log('\x1b[33m%s\x1b[0m', `检测到BUFFER_OVERRUN错误,触发备用解析流程`);
|
|
65
|
+
return {
|
|
66
|
+
args: {
|
|
67
|
+
sender: "备用解析流程",
|
|
68
|
+
recipient: "备用解析流程",
|
|
69
|
+
amount0: ethers_1.ethers.BigNumber.from(0),
|
|
70
|
+
amount1: ethers_1.ethers.BigNumber.from(0),
|
|
71
|
+
sqrtPriceX96: ethers_1.ethers.BigNumber.from(0),
|
|
72
|
+
tick: 0,
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
throw parseError;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
log('INFO', `设置原始事件监听器,只按地址过滤`);
|
|
86
|
+
const filter = { address: poolAddress };
|
|
87
|
+
wsProvider.on(filter, (log) => {
|
|
88
|
+
var _a, _b, _c, _d;
|
|
89
|
+
try {
|
|
90
|
+
const isSwapEvent = (log.topics[0] === capturedSwapHash || log.topics[0] === updatedSwapHash);
|
|
91
|
+
if (isSwapEvent) {
|
|
92
|
+
console.log('\x1b[32m%s\x1b[0m', `收到Swap事件: 交易哈希=${log.transactionHash}, 区块=${log.blockNumber}`);
|
|
93
|
+
for (const method of methods) {
|
|
94
|
+
try {
|
|
95
|
+
let parsedEvent;
|
|
96
|
+
if (method.parse) {
|
|
97
|
+
parsedEvent = method.parse(log);
|
|
98
|
+
}
|
|
99
|
+
else if (method.createInterface) {
|
|
100
|
+
const iface = method.createInterface();
|
|
101
|
+
parsedEvent = iface.parseLog(log);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw new Error("方法未正确定义");
|
|
105
|
+
}
|
|
106
|
+
console.log('\x1b[32m%s\x1b[0m', `${method.name} - 解析成功!`);
|
|
107
|
+
console.log({
|
|
108
|
+
sender: parsedEvent.args.sender,
|
|
109
|
+
recipient: parsedEvent.args.recipient,
|
|
110
|
+
amount0: (_a = parsedEvent.args.amount0) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
111
|
+
amount1: (_b = parsedEvent.args.amount1) === null || _b === void 0 ? void 0 : _b.toString(),
|
|
112
|
+
sqrtPriceX96: (_c = parsedEvent.args.sqrtPriceX96) === null || _c === void 0 ? void 0 : _c.toString(),
|
|
113
|
+
tick: (_d = parsedEvent.args.tick) === null || _d === void 0 ? void 0 : _d.toString()
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.log('\x1b[31m%s\x1b[0m', `${method.name} - 解析失败: ${error.message}`);
|
|
118
|
+
console.log(error.code ? `错误代码: ${error.code}` : '无错误代码');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log('----------------------------');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
log('ERROR', `处理事件出错:`, error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
log('INFO', '测试脚本启动成功,等待Swap事件...');
|
|
129
|
+
const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
try {
|
|
131
|
+
const blockNumber = yield wsProvider.getBlockNumber();
|
|
132
|
+
log('DEBUG', `心跳检测成功,当前区块: ${blockNumber}`);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
log('ERROR', '心跳检测失败,可能断开连接');
|
|
136
|
+
}
|
|
137
|
+
}), 15000);
|
|
138
|
+
process.on('SIGINT', () => {
|
|
139
|
+
log('INFO', '收到退出信号,正在清理...');
|
|
140
|
+
clearInterval(interval);
|
|
141
|
+
wsProvider.removeAllListeners();
|
|
142
|
+
if (wsProvider._websocket)
|
|
143
|
+
wsProvider._websocket.close();
|
|
144
|
+
log('INFO', '已清理,退出程序');
|
|
145
|
+
process.exit(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function updateCurrentPoolState(wsProvider, poolAddress, poolName) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
log('INFO', `[${poolName}] 模拟备用方法:通过直接查询获取池状态`);
|
|
152
|
+
try {
|
|
153
|
+
const poolAbi = [
|
|
154
|
+
"function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)",
|
|
155
|
+
"function liquidity() external view returns (uint128)"
|
|
156
|
+
];
|
|
157
|
+
const poolContract = new ethers_1.ethers.Contract(poolAddress, poolAbi, wsProvider);
|
|
158
|
+
const [slot0Data, liquidityData] = yield Promise.all([
|
|
159
|
+
poolContract.slot0(),
|
|
160
|
+
poolContract.liquidity()
|
|
161
|
+
]);
|
|
162
|
+
const [sqrtPriceX96, tick] = slot0Data;
|
|
163
|
+
const liquidity = liquidityData;
|
|
164
|
+
log('INFO', `[${poolName}] 备用方法成功: tick=${tick}, liquidity=${liquidity.toString()}`);
|
|
165
|
+
return { sqrtPriceX96, tick, liquidity };
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
log('ERROR', `[${poolName}] 备用方法失败:`, error);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
verifySwapEventParsing()
|
|
174
|
+
.then(() => log('INFO', '验证脚本启动成功'))
|
|
175
|
+
.catch(error => {
|
|
176
|
+
log('ERROR', '验证脚本启动失败:', error);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./event"), exports);
|
|
18
|
+
__exportStar(require("./pricing"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./token_price_cache"), exports);
|
|
18
|
+
__exportStar(require("./pool"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
export declare class Pool {
|
|
3
|
+
readonly address: string;
|
|
4
|
+
readonly token0: string;
|
|
5
|
+
readonly token1: string;
|
|
6
|
+
readonly fee: number;
|
|
7
|
+
sqrtPriceX96: ethers.BigNumber;
|
|
8
|
+
liquidity: ethers.BigNumber;
|
|
9
|
+
tickCurrent: number;
|
|
10
|
+
readonly tickSpacing: number;
|
|
11
|
+
constructor(address: string, token0: string, token1: string, fee: number, sqrtPriceX96: ethers.BigNumber, tickCurrent: number, liquidity: ethers.BigNumber, tickSpacing: number);
|
|
12
|
+
updateState(sqrtPriceX96: ethers.BigNumber, tickCurrent: number, liquidity: ethers.BigNumber): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Pool = void 0;
|
|
4
|
+
class Pool {
|
|
5
|
+
constructor(address, token0, token1, fee, sqrtPriceX96, tickCurrent, liquidity, tickSpacing) {
|
|
6
|
+
this.address = address;
|
|
7
|
+
this.token0 = token0;
|
|
8
|
+
this.token1 = token1;
|
|
9
|
+
this.fee = fee;
|
|
10
|
+
this.sqrtPriceX96 = sqrtPriceX96;
|
|
11
|
+
this.liquidity = liquidity;
|
|
12
|
+
this.tickCurrent = tickCurrent;
|
|
13
|
+
this.tickSpacing = tickSpacing;
|
|
14
|
+
}
|
|
15
|
+
updateState(sqrtPriceX96, tickCurrent, liquidity) {
|
|
16
|
+
this.sqrtPriceX96 = sqrtPriceX96;
|
|
17
|
+
this.tickCurrent = tickCurrent;
|
|
18
|
+
this.liquidity = liquidity;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.Pool = Pool;
|
|
@@ -0,0 +1,40 @@
|
|
|
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.TokenPriceCache = void 0;
|
|
13
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
14
|
+
class TokenPriceCache {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.tokenPriceCache = new Map();
|
|
17
|
+
this.PRICE_CACHE_TIMEOUT_MILLS = parseInt(process.env.PRICE_CACHE_TIMEOUT_MILLS || Number(1000 * 60 * 60 * 1).toString());
|
|
18
|
+
(0, dist_1.log_info)(`代币价格缓存超时时间: ${this.PRICE_CACHE_TIMEOUT_MILLS} ms`);
|
|
19
|
+
}
|
|
20
|
+
getTokenPrice(tokenAddress) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
tokenAddress = tokenAddress.toLowerCase();
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const cachedData = this.tokenPriceCache.get(tokenAddress);
|
|
25
|
+
if (cachedData && (now - cachedData.timestamp) < this.PRICE_CACHE_TIMEOUT_MILLS) {
|
|
26
|
+
(0, dist_1.log_debug)(`use cached token price: ${tokenAddress}, price: ${cachedData.price}`, '');
|
|
27
|
+
return cachedData;
|
|
28
|
+
}
|
|
29
|
+
const priceMap = yield (0, dist_1.get_bsc_token_price_info)([tokenAddress]);
|
|
30
|
+
const tokenPrice = priceMap.get(tokenAddress);
|
|
31
|
+
if (!tokenPrice || !tokenPrice.price || Number(tokenPrice.price) <= 0) {
|
|
32
|
+
throw new Error(`无法获取代币 ${tokenAddress} 的有效USD价格`);
|
|
33
|
+
}
|
|
34
|
+
const newPrice = { price: tokenPrice.price, timestamp: now };
|
|
35
|
+
this.tokenPriceCache.set(tokenAddress, newPrice);
|
|
36
|
+
return newPrice;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.TokenPriceCache = TokenPriceCache;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AbastrcatTrade, AppConfig, TradeContext } from "@clonegod/ttd-core/dist";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import { DexConfig, EvmChainConfig } from "../types";
|
|
4
|
+
export declare abstract class AbstractEvmDexTrade extends AbastrcatTrade {
|
|
5
|
+
protected appConfig: AppConfig;
|
|
6
|
+
protected wallet: ethers.Wallet;
|
|
7
|
+
protected provider: ethers.providers.JsonRpcProvider;
|
|
8
|
+
protected approvedTokens: Map<string, boolean>;
|
|
9
|
+
protected pairContracts: Map<string, ethers.Contract>;
|
|
10
|
+
protected tokenContracts: Map<string, ethers.Contract>;
|
|
11
|
+
protected routerContract: ethers.Contract;
|
|
12
|
+
protected chainConfig: EvmChainConfig;
|
|
13
|
+
protected dexConfig: DexConfig;
|
|
14
|
+
constructor(appConfig: AppConfig);
|
|
15
|
+
protected abstract initConfigs(): void;
|
|
16
|
+
init(): Promise<void>;
|
|
17
|
+
protected getTokenContract(tokenAddress: string): ethers.Contract;
|
|
18
|
+
protected getGasPriceGwei(context: TradeContext): string;
|
|
19
|
+
protected getBuilderTipAmoutGwei(context: TradeContext): string;
|
|
20
|
+
protected checkTradeTokenApprove(context: TradeContext, routerAddress?: string): Promise<void>;
|
|
21
|
+
protected checkTokenApprove(tokenAddress: string, tokenSymbol: string, tokenContract: ethers.Contract, spenderAddress: string): Promise<boolean>;
|
|
22
|
+
protected isNonceRelatedError(error: any): boolean;
|
|
23
|
+
protected isNativeCurrency(symbol: string): boolean;
|
|
24
|
+
protected getWrappedNativeAddress(): string;
|
|
25
|
+
abstract execute(context: TradeContext, retryCount?: number): Promise<string>;
|
|
26
|
+
protected buildTipTransferTx(to: string, transfer_amount_gwei: string, gas_price_gwei: string, transfer_nonce: number): Promise<string>;
|
|
27
|
+
}
|