@clonegod/ttd-bsc-common 3.1.17 → 3.1.21
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/config/BscQuoteAppConfig.js +2 -0
- package/dist/config/bsc_env_args.d.ts +60 -1
- package/dist/config/bsc_env_args.js +121 -21
- package/dist/quote/verify/quote_price_verify.js +1 -1
- package/dist/trade/abstract_dex_trade.d.ts +2 -1
- package/dist/trade/abstract_dex_trade.js +10 -12
- package/dist/trade/caller_manager.d.ts +3 -0
- package/dist/trade/caller_manager.js +53 -5
- package/dist/trade/check/abstract_tx_result_checker.d.ts +28 -0
- package/dist/trade/check/abstract_tx_result_checker.js +169 -0
- package/dist/trade/check/base_tx_result_checker.js +1 -1
- package/dist/trade/check/index.d.ts +1 -1
- package/dist/trade/check/index.js +1 -1
- package/dist/trade/parse/base_parser.js +1 -1
- package/package.json +3 -3
- package/dist/quote/accuracy/index.d.ts +0 -2
- package/dist/quote/accuracy/index.js +0 -5
- package/dist/quote/accuracy/quote_accuracy_checker.d.ts +0 -42
- package/dist/quote/accuracy/quote_accuracy_checker.js +0 -144
- package/dist/quote/depth/verify_depth.d.ts +0 -10
- package/dist/quote/depth/verify_depth.js +0 -55
- package/dist/quote/event/verify_clmm_swap_event.d.ts +0 -1
- package/dist/quote/event/verify_clmm_swap_event.js +0 -178
- package/dist/redis/index.d.ts +0 -1
- package/dist/redis/index.js +0 -17
- package/dist/redis/redis_client.d.ts +0 -23
- package/dist/redis/redis_client.js +0 -126
- package/dist/trade/check/tx_websocket_manager.d.ts +0 -21
- package/dist/trade/check/tx_websocket_manager.js +0 -167
- package/dist/yyws/index.d.ts +0 -2
- package/dist/yyws/index.js +0 -18
- package/dist/yyws/mock_ws_server.d.ts +0 -1
- package/dist/yyws/mock_ws_server.js +0 -77
- package/dist/yyws/type.d.ts +0 -25
- package/dist/yyws/type.js +0 -2
- package/dist/yyws/yyws_client.d.ts +0 -2
- package/dist/yyws/yyws_client.js +0 -76
|
@@ -9,6 +9,7 @@ class BscQuoteAppConfig extends ttd_core_1.AppConfig {
|
|
|
9
9
|
super();
|
|
10
10
|
this.eventEmitter = new events_1.EventEmitter();
|
|
11
11
|
this.env_args = new bsc_env_args_1.BscEnvArgs();
|
|
12
|
+
(0, ttd_core_1.setCoreEnv)(this.env_args);
|
|
12
13
|
}
|
|
13
14
|
on(eventName, listener) {
|
|
14
15
|
this.eventEmitter.on(eventName, listener);
|
|
@@ -36,6 +37,7 @@ class BscQuoteAppConfig extends ttd_core_1.AppConfig {
|
|
|
36
37
|
(0, ttd_core_1.log_info)('Config change received', config);
|
|
37
38
|
if (config.env_args) {
|
|
38
39
|
this.env_args = new bsc_env_args_1.BscEnvArgs();
|
|
40
|
+
(0, ttd_core_1.setCoreEnv)(this.env_args);
|
|
39
41
|
}
|
|
40
42
|
if (config.arb_cache) {
|
|
41
43
|
await this.arb_cache.init();
|
|
@@ -3,10 +3,69 @@ export declare class BscEnvArgs extends EnvArgs {
|
|
|
3
3
|
gas_price_gwei: number;
|
|
4
4
|
gas_limit: number;
|
|
5
5
|
tip_amount_gwei: number;
|
|
6
|
+
pancake_executor_id: string;
|
|
7
|
+
uniswap_executor_id: string;
|
|
8
|
+
caller_select_strategy: string;
|
|
6
9
|
send_tx_blockrazor_bundle: boolean;
|
|
7
10
|
send_tx_48club_bundle: boolean;
|
|
8
11
|
send_tx_48club_bundle_ws: boolean;
|
|
9
12
|
send_tx_blox_bundle_ws: boolean;
|
|
13
|
+
stream_quote_ws_host: string;
|
|
14
|
+
min_quote_interval_ms: number;
|
|
15
|
+
skip_price_feed_yynode: boolean;
|
|
16
|
+
push_price_feed: boolean;
|
|
17
|
+
trade_parse_fetch_block_time: boolean;
|
|
18
|
+
tick_cache_neighboring_words: number;
|
|
19
|
+
tick_cache_ttl: number;
|
|
20
|
+
tick_cache_min_update_interval: number;
|
|
21
|
+
ws_push_enable: boolean;
|
|
22
|
+
print_event_data: boolean;
|
|
23
|
+
pool_sync_interval_ms: number;
|
|
24
|
+
redis_sync_pool_enable: boolean;
|
|
25
|
+
redis_url: string;
|
|
26
|
+
skip_pool_list: string;
|
|
27
|
+
price_feed_skip_dup: boolean;
|
|
28
|
+
trade_analyze_port: number;
|
|
29
|
+
log_max_size_mb: number;
|
|
30
|
+
log_check_interval_min: number;
|
|
31
|
+
log_dir: string;
|
|
32
|
+
chain_name: string;
|
|
33
|
+
caller_balance_check_interval: string;
|
|
34
|
+
caller_balance_low_threshold: string;
|
|
35
|
+
caller_balance_empty_threshold: string;
|
|
36
|
+
send_tx_ws_host: string;
|
|
37
|
+
send_tx_48club_ws: boolean;
|
|
38
|
+
send_tx_bloxroute_ws: boolean;
|
|
39
|
+
send_tx_default_rpc: boolean;
|
|
40
|
+
send_tx_blockrazor_private: boolean;
|
|
41
|
+
send_tx_48club_private: boolean;
|
|
42
|
+
bsc_rpc_endpoint: string;
|
|
43
|
+
bloxroute_ws_url: string;
|
|
44
|
+
blox_auth_key: string;
|
|
45
|
+
_48club_ws_url: string;
|
|
46
|
+
_48club_rpc_url: string;
|
|
47
|
+
_48club_sp_wallet_id: string;
|
|
48
|
+
blockrazor_rpc_url: string;
|
|
49
|
+
blockrazor_auth_token: string;
|
|
50
|
+
gecko_network: string;
|
|
51
|
+
chain_id_num: number;
|
|
52
|
+
native_token_symbol: string;
|
|
53
|
+
native_token_address: string;
|
|
54
|
+
wrapped_native_address: string;
|
|
55
|
+
sync_pool_interval_ms: number;
|
|
56
|
+
fetch_api_wait_ms: number;
|
|
57
|
+
fetch_min_tvl: number;
|
|
58
|
+
fetch_min_vol: number;
|
|
59
|
+
pool_default_tvl: number;
|
|
60
|
+
fetch_max_page_no: number;
|
|
61
|
+
token_batch_size: number;
|
|
62
|
+
fetch_on_startup: boolean;
|
|
63
|
+
vault_mgt_rpc_url: string;
|
|
64
|
+
vault_mgt_chain_id: number;
|
|
65
|
+
vault_mgt_chain_name: string;
|
|
66
|
+
vault_mgt_vault_groups: string;
|
|
67
|
+
vault_mgt_native_symbol: string;
|
|
68
|
+
vault_mgt_wrapped_native_address: string;
|
|
69
|
+
vault_mgt_fund_caller_max: string;
|
|
10
70
|
constructor();
|
|
11
|
-
print(moduleName?: string): void;
|
|
12
71
|
}
|
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BscEnvArgs = void 0;
|
|
4
4
|
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
-
const
|
|
6
|
-
'gas_price_gwei',
|
|
7
|
-
'gas_limit',
|
|
8
|
-
'tip_amount_gwei',
|
|
9
|
-
'send_tx_blockrazor_bundle',
|
|
10
|
-
'send_tx_48club_bundle',
|
|
11
|
-
'send_tx_48club_bundle_ws',
|
|
12
|
-
'send_tx_blox_bundle_ws',
|
|
13
|
-
];
|
|
5
|
+
const constants_1 = require("../common/constants");
|
|
14
6
|
(0, ttd_core_1.registerEnvVars)({
|
|
15
7
|
gas_price_gwei: { env: 'GAS_PRICE_GWEI', type: 'number', default: 1, desc: '交易 gas 价格(Gwei)' },
|
|
16
8
|
gas_limit: { env: 'GAS_LIMIT', type: 'number', default: 300000, desc: '交易 gas 上限' },
|
|
17
9
|
tip_amount_gwei: { env: 'TIP_AMOUNT_GWEI', type: 'number', default: 10000, desc: 'Builder tip(Gwei)' },
|
|
10
|
+
pancake_executor_id: { env: 'PANCAKE_EXECUTOR_ID', type: 'string', default: 'PANCAKE', desc: 'Vault 中 Pancake executor 注册名(keccak256 后作为 bytes32)' },
|
|
11
|
+
uniswap_executor_id: { env: 'UNISWAP_EXECUTOR_ID', type: 'string', default: 'UNISWAP', desc: 'Vault 中 Uniswap executor 注册名(keccak256 后作为 bytes32)' },
|
|
12
|
+
caller_select_strategy: { env: 'CALLER_SELECT_STRATEGY', type: 'string', default: 'lock', desc: 'CallerManager LRU 选择策略:lock=Redis 分布式锁(默认) | lua_cas=Lua 原子脚本(单 RTT,无锁重试)' },
|
|
18
13
|
send_tx_blockrazor_bundle: { env: 'SEND_TX_BLOCKRAZOR_BUNDLE', type: 'boolean', default: false, desc: 'BlockRazor Bundle 开关' },
|
|
19
14
|
send_tx_48club_bundle: { env: 'SEND_TX_48CLUB_BUNDLE', type: 'boolean', default: false, desc: '48Club Bundle HTTP 开关' },
|
|
20
15
|
send_tx_48club_bundle_ws: { env: 'SEND_TX_48CLUB_BUNDLE_WS', type: 'boolean', default: false, desc: '48Club Bundle WS 开关' },
|
|
@@ -23,25 +18,130 @@ const BSC_KEYS = [
|
|
|
23
18
|
min_quote_interval_ms: { env: 'MIN_QUOTE_INTERVAL_MS', type: 'number', default: 10000, desc: '最小询价间隔(ms)' },
|
|
24
19
|
skip_price_feed_yynode: { env: 'SKIP_PRICE_FEED_YYNODE', type: 'boolean', default: false, desc: '跳过 YYNode PriceFeed' },
|
|
25
20
|
push_price_feed: { env: 'PUSH_PRICE_FEED', type: 'boolean', default: false, desc: 'PriceFeed 推送开关' },
|
|
26
|
-
|
|
21
|
+
trade_parse_fetch_block_time: { env: 'TRADE_PARSE_FETCH_BLOCK_TIME', type: 'boolean', default: false, desc: '解析交易 receipt 时是否额外 RPC 查询 block 时间戳填入结果' },
|
|
27
22
|
tick_cache_neighboring_words: { env: 'TICK_CACHE_NEIGHBORING_WORDS', type: 'number', default: 2, desc: 'tick 预加载 words 数' },
|
|
28
23
|
tick_cache_ttl: { env: 'TICK_CACHE_TTL', type: 'number', default: 30000, desc: 'tick 缓存 TTL(ms)' },
|
|
29
24
|
tick_cache_min_update_interval: { env: 'TICK_CACHE_MIN_UPDATE_INTERVAL', type: 'number', default: 3000, desc: 'tick 最小刷新间隔(ms)' },
|
|
25
|
+
ws_push_enable: { env: 'WS_PUSH_ENABLE', type: 'boolean', default: true, desc: 'WS 推送开关' },
|
|
26
|
+
print_event_data: { env: 'PRINT_EVENT_DATA', type: 'boolean', default: false, desc: '打印原始事件数据' },
|
|
27
|
+
pool_sync_interval_ms: { env: 'POOL_SYNC_INTERVAL_MS', type: 'number', default: 5000, desc: '池子同步间隔(ms)' },
|
|
28
|
+
redis_sync_pool_enable: { env: 'REDIS_SYNC_POOL_ENABLE', type: 'boolean', default: false, desc: 'Redis 同步池子开关' },
|
|
29
|
+
redis_url: { env: 'REDIS_URL', type: 'string', default: 'redis://127.0.0.1:6379', desc: 'Redis 连接 URL' },
|
|
30
|
+
skip_pool_list: { env: 'SKIP_POOL_LIST', type: 'string', default: '', desc: '跳过的池子名称列表(逗号分隔)' },
|
|
31
|
+
price_feed_skip_dup: { env: 'PRICE_FEED_SKIP_DUP', type: 'boolean', default: false, desc: 'PriceFeed 去重过滤' },
|
|
32
|
+
trade_analyze_port: { env: 'TRADE_ANALYZE_PORT', type: 'number', default: 8004, desc: '上报 analyze 的 HTTP 端口' },
|
|
33
|
+
log_max_size_mb: { env: 'LOG_MAX_SIZE_MB', type: 'number', default: 20, desc: '日志文件大小上限(MB)' },
|
|
34
|
+
log_check_interval_min: { env: 'LOG_CHECK_INTERVAL_MIN', type: 'number', default: 10, desc: '日志检查间隔(分钟)' },
|
|
35
|
+
log_dir: { env: 'LOG_DIR', type: 'string', default: 'logs', desc: '日志目录' },
|
|
36
|
+
chain_name: { env: 'CHAIN_NAME', type: 'string', default: '', desc: '链名称(Redis key 前缀),stream-trade 建议必填' },
|
|
37
|
+
caller_balance_check_interval: { env: 'CALLER_BALANCE_CHECK_INTERVAL', type: 'string', default: '1h', desc: 'caller 余额检查周期,支持 8h/30m/45s/100ms 格式' },
|
|
38
|
+
caller_balance_low_threshold: { env: 'CALLER_BALANCE_LOW_THRESHOLD', type: 'string', default: '0.01', desc: 'caller 低余额阈值(原生币单位),低于报 ERROR' },
|
|
39
|
+
caller_balance_empty_threshold: { env: 'CALLER_BALANCE_EMPTY_THRESHOLD', type: 'string', default: '0', desc: 'caller 耗尽阈值,低于报 CRITICAL;为 0 表示禁用此级' },
|
|
40
|
+
send_tx_ws_host: { env: 'SEND_TX_WS_HOST', type: 'string', default: '127.0.0.1', desc: 'send-tx WS 地址' },
|
|
41
|
+
send_tx_48club_ws: { env: 'SEND_TX_48CLUB_WS', type: 'boolean', default: false, desc: '48Club WS server 开关' },
|
|
42
|
+
send_tx_bloxroute_ws: { env: 'SEND_TX_BLOXROUTE_WS', type: 'boolean', default: false, desc: 'BloxRoute WS server 开关' },
|
|
43
|
+
send_tx_default_rpc: { env: 'SEND_TX_DEFAULT_RPC', type: 'boolean', default: false, desc: '默认 RPC 私有发送' },
|
|
44
|
+
send_tx_blockrazor_private: { env: 'SEND_TX_BLOCKRAZOR_PRIVATE', type: 'boolean', default: false, desc: 'BlockRazor 私有发送' },
|
|
45
|
+
send_tx_48club_private: { env: 'SEND_TX_48CLUB_PRIVATE', type: 'boolean', default: false, desc: '48Club 私有发送' },
|
|
46
|
+
bsc_rpc_endpoint: { env: 'BSC_RPC_ENDPOINT', type: 'string', default: '', desc: 'BSC RPC 端点(default_rpc 发送用)' },
|
|
47
|
+
bloxroute_ws_url: { env: 'BLOXROUTE_WS_URL', type: 'string', default: 'wss://api.blxrbdn.com/ws', desc: 'BloxRoute WS URL' },
|
|
48
|
+
blox_auth_key: { env: 'BLOX_AUTH_KEY', type: 'string', default: '', sensitive: true, desc: 'BloxRoute 认证 key' },
|
|
49
|
+
_48club_ws_url: { env: '_48CLUB_WS_URL', type: 'string', default: 'wss://puissant-builder.48.club/', desc: '48Club WS URL' },
|
|
50
|
+
_48club_rpc_url: { env: '_48CLUB_RPC_URL', type: 'string', default: 'https://puissant-builder.48.club/', desc: '48Club RPC URL' },
|
|
51
|
+
_48club_sp_wallet_id: { env: '_48CLUB_SP_WALLET_ID', type: 'string', default: 'TTD-PAYMENT', desc: '48Club SoulPoint 钱包 ID' },
|
|
52
|
+
blockrazor_rpc_url: { env: 'BLOCKRAZOR_RPC_URL', type: 'string', default: 'https://rpc.blockrazor.builders', desc: 'BlockRazor RPC URL' },
|
|
53
|
+
blockrazor_auth_token: { env: 'BLOCKRAZOR_AUTH_TOKEN', type: 'string', default: '', sensitive: true, desc: 'BlockRazor 认证 Token' },
|
|
54
|
+
gecko_network: { env: 'GECKO_NETWORK', type: 'string', default: '', desc: 'GeckoTerminal 网络标识(可选;缺省按 chain_id 推断)' },
|
|
55
|
+
chain_id_num: { env: 'CHAIN_ID_NUM', type: 'number', default: 56, desc: '链 ID 数字' },
|
|
56
|
+
native_token_symbol: { env: 'NATIVE_TOKEN_SYMBOL', type: 'string', default: 'BNB', desc: '原生代币符号' },
|
|
57
|
+
native_token_address: { env: 'NATIVE_TOKEN_ADDRESS', type: 'string', default: constants_1.NATIVE_BNB_ADDRESS, desc: '原生代币地址' },
|
|
58
|
+
wrapped_native_address: { env: 'WRAPPED_NATIVE_ADDRESS', type: 'string', default: constants_1.WBNB_ADDRESS, desc: 'Wrapped 代币地址' },
|
|
59
|
+
sync_pool_interval_ms: { env: 'SYNC_POOL_INTERVAL_MS', type: 'number', default: 3600000, desc: '定时池子全量同步间隔(毫秒)' },
|
|
60
|
+
fetch_api_wait_ms: { env: 'FETCH_API_WAIT_MS', type: 'number', default: 6000, desc: 'Gecko/DefiLlama 等外部 API 调用间隔(毫秒)' },
|
|
61
|
+
fetch_min_tvl: { env: 'FETCH_MIN_TVL', type: 'number', default: 50000, desc: '最小 TVL(USD)' },
|
|
62
|
+
fetch_min_vol: { env: 'FETCH_MIN_VOL', type: 'number', default: 100000, desc: '最小 24h 成交量(USD)' },
|
|
63
|
+
pool_default_tvl: { env: 'POOL_DEFAULT_TVL', type: 'number', default: 100000, desc: '默认 TVL' },
|
|
64
|
+
fetch_max_page_no: { env: 'FETCH_MAX_PAGE_NO', type: 'number', default: 10, desc: '最大翻页数' },
|
|
65
|
+
token_batch_size: { env: 'TOKEN_BATCH_SIZE', type: 'number', default: 10, desc: 'token 批量大小' },
|
|
66
|
+
fetch_on_startup: { env: 'FETCH_ON_STARTUP', type: 'boolean', default: true, desc: '启动时立即执行' },
|
|
67
|
+
vault_mgt_rpc_url: { env: 'VAULT_MGT_RPC_URL', type: 'string', default: '', desc: 'Vault 管理 RPC(contract-mgt 必填)' },
|
|
68
|
+
vault_mgt_chain_id: { env: 'VAULT_MGT_CHAIN_ID', type: 'number', default: 0, desc: '链 ID 数字(contract-mgt 必填)' },
|
|
69
|
+
vault_mgt_chain_name: { env: 'VAULT_MGT_CHAIN_NAME', type: 'string', default: '', desc: '链名称(contract-mgt 必填)' },
|
|
70
|
+
vault_mgt_vault_groups: { env: 'VAULT_MGT_VAULT_GROUPS', type: 'string', default: '', desc: 'Vault 分组列表(逗号分隔,contract-mgt 必填)' },
|
|
71
|
+
vault_mgt_native_symbol: { env: 'VAULT_MGT_NATIVE_SYMBOL', type: 'string', default: '', desc: '原生代币符号(contract-mgt 必填)' },
|
|
72
|
+
vault_mgt_wrapped_native_address: { env: 'VAULT_MGT_WRAPPED_NATIVE_ADDRESS', type: 'string', default: '', desc: 'Wrapped 代币地址(contract-mgt 必填)' },
|
|
73
|
+
vault_mgt_fund_caller_max: { env: 'VAULT_MGT_FUND_CALLER_MAX', type: 'string', default: '', desc: 'Caller 充值上限' },
|
|
30
74
|
});
|
|
31
75
|
class BscEnvArgs extends ttd_core_1.EnvArgs {
|
|
32
76
|
constructor() {
|
|
33
77
|
super();
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
this.gas_price_gwei = this._cfg.gas_price_gwei;
|
|
79
|
+
this.gas_limit = this._cfg.gas_limit;
|
|
80
|
+
this.tip_amount_gwei = this._cfg.tip_amount_gwei;
|
|
81
|
+
this.pancake_executor_id = this._cfg.pancake_executor_id;
|
|
82
|
+
this.uniswap_executor_id = this._cfg.uniswap_executor_id;
|
|
83
|
+
this.caller_select_strategy = this._cfg.caller_select_strategy;
|
|
84
|
+
this.send_tx_blockrazor_bundle = this._cfg.send_tx_blockrazor_bundle;
|
|
85
|
+
this.send_tx_48club_bundle = this._cfg.send_tx_48club_bundle;
|
|
86
|
+
this.send_tx_48club_bundle_ws = this._cfg.send_tx_48club_bundle_ws;
|
|
87
|
+
this.send_tx_blox_bundle_ws = this._cfg.send_tx_blox_bundle_ws;
|
|
88
|
+
this.stream_quote_ws_host = this._cfg.stream_quote_ws_host;
|
|
89
|
+
this.min_quote_interval_ms = this._cfg.min_quote_interval_ms;
|
|
90
|
+
this.skip_price_feed_yynode = this._cfg.skip_price_feed_yynode;
|
|
91
|
+
this.push_price_feed = this._cfg.push_price_feed;
|
|
92
|
+
this.trade_parse_fetch_block_time = this._cfg.trade_parse_fetch_block_time;
|
|
93
|
+
this.tick_cache_neighboring_words = this._cfg.tick_cache_neighboring_words;
|
|
94
|
+
this.tick_cache_ttl = this._cfg.tick_cache_ttl;
|
|
95
|
+
this.tick_cache_min_update_interval = this._cfg.tick_cache_min_update_interval;
|
|
96
|
+
this.ws_push_enable = this._cfg.ws_push_enable;
|
|
97
|
+
this.print_event_data = this._cfg.print_event_data;
|
|
98
|
+
this.pool_sync_interval_ms = this._cfg.pool_sync_interval_ms;
|
|
99
|
+
this.redis_sync_pool_enable = this._cfg.redis_sync_pool_enable;
|
|
100
|
+
this.redis_url = this._cfg.redis_url;
|
|
101
|
+
this.skip_pool_list = this._cfg.skip_pool_list;
|
|
102
|
+
this.price_feed_skip_dup = this._cfg.price_feed_skip_dup;
|
|
103
|
+
this.trade_analyze_port = this._cfg.trade_analyze_port;
|
|
104
|
+
this.log_max_size_mb = this._cfg.log_max_size_mb;
|
|
105
|
+
this.log_check_interval_min = this._cfg.log_check_interval_min;
|
|
106
|
+
this.log_dir = this._cfg.log_dir;
|
|
107
|
+
this.chain_name = this._cfg.chain_name;
|
|
108
|
+
this.caller_balance_check_interval = this._cfg.caller_balance_check_interval;
|
|
109
|
+
this.caller_balance_low_threshold = this._cfg.caller_balance_low_threshold;
|
|
110
|
+
this.caller_balance_empty_threshold = this._cfg.caller_balance_empty_threshold;
|
|
111
|
+
this.send_tx_ws_host = this._cfg.send_tx_ws_host;
|
|
112
|
+
this.send_tx_48club_ws = this._cfg.send_tx_48club_ws;
|
|
113
|
+
this.send_tx_bloxroute_ws = this._cfg.send_tx_bloxroute_ws;
|
|
114
|
+
this.send_tx_default_rpc = this._cfg.send_tx_default_rpc;
|
|
115
|
+
this.send_tx_blockrazor_private = this._cfg.send_tx_blockrazor_private;
|
|
116
|
+
this.send_tx_48club_private = this._cfg.send_tx_48club_private;
|
|
117
|
+
this.bsc_rpc_endpoint = this._cfg.bsc_rpc_endpoint;
|
|
118
|
+
this.bloxroute_ws_url = this._cfg.bloxroute_ws_url;
|
|
119
|
+
this.blox_auth_key = this._cfg.blox_auth_key;
|
|
120
|
+
this._48club_ws_url = this._cfg._48club_ws_url;
|
|
121
|
+
this._48club_rpc_url = this._cfg._48club_rpc_url;
|
|
122
|
+
this._48club_sp_wallet_id = this._cfg._48club_sp_wallet_id;
|
|
123
|
+
this.blockrazor_rpc_url = this._cfg.blockrazor_rpc_url;
|
|
124
|
+
this.blockrazor_auth_token = this._cfg.blockrazor_auth_token;
|
|
125
|
+
this.gecko_network = (this._cfg.gecko_network || this.chain_id || 'bsc').toLowerCase();
|
|
126
|
+
this.chain_id_num = this._cfg.chain_id_num;
|
|
127
|
+
this.native_token_symbol = this._cfg.native_token_symbol;
|
|
128
|
+
this.native_token_address = this._cfg.native_token_address;
|
|
129
|
+
this.wrapped_native_address = this._cfg.wrapped_native_address;
|
|
130
|
+
this.sync_pool_interval_ms = this._cfg.sync_pool_interval_ms;
|
|
131
|
+
this.fetch_api_wait_ms = this._cfg.fetch_api_wait_ms;
|
|
132
|
+
this.fetch_min_tvl = this._cfg.fetch_min_tvl;
|
|
133
|
+
this.fetch_min_vol = this._cfg.fetch_min_vol;
|
|
134
|
+
this.pool_default_tvl = this._cfg.pool_default_tvl;
|
|
135
|
+
this.fetch_max_page_no = this._cfg.fetch_max_page_no;
|
|
136
|
+
this.token_batch_size = this._cfg.token_batch_size;
|
|
137
|
+
this.fetch_on_startup = this._cfg.fetch_on_startup;
|
|
138
|
+
this.vault_mgt_rpc_url = this._cfg.vault_mgt_rpc_url;
|
|
139
|
+
this.vault_mgt_chain_id = this._cfg.vault_mgt_chain_id;
|
|
140
|
+
this.vault_mgt_chain_name = this._cfg.vault_mgt_chain_name;
|
|
141
|
+
this.vault_mgt_vault_groups = this._cfg.vault_mgt_vault_groups;
|
|
142
|
+
this.vault_mgt_native_symbol = this._cfg.vault_mgt_native_symbol;
|
|
143
|
+
this.vault_mgt_wrapped_native_address = this._cfg.vault_mgt_wrapped_native_address;
|
|
144
|
+
this.vault_mgt_fund_caller_max = this._cfg.vault_mgt_fund_caller_max;
|
|
45
145
|
}
|
|
46
146
|
}
|
|
47
147
|
exports.BscEnvArgs = BscEnvArgs;
|
|
@@ -141,7 +141,7 @@ class QuotePriceVerify {
|
|
|
141
141
|
const msg = ` ↳ [Verify] ${side} ${usdStr} (${tradeFlow}) ${quoteTag} ${priceLabel}=${refPrice.toFixed(12)} vs ${swapTag} exec=${execPriceNum.toFixed(12)} diff=${diffBps > 0 ? '+' : ''}${diffBps.toFixed(1)}bps ${status}${rangeTag}`;
|
|
142
142
|
(0, ttd_core_1.log_info)(msg);
|
|
143
143
|
try {
|
|
144
|
-
(0, ttd_core_1.
|
|
144
|
+
(0, ttd_core_1.report_data_to_analyze)('QuoteVerify', {
|
|
145
145
|
pool_address: poolAddress,
|
|
146
146
|
pool_name: poolName,
|
|
147
147
|
price_id: cached.priceId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbastrcatTrade, AppConfig, TradeContext } from '@clonegod/ttd-core';
|
|
2
|
+
import { BscEnvArgs } from '../config/bsc_env_args';
|
|
2
3
|
import { ethers } from "ethers";
|
|
3
4
|
import { EvmChainConfig } from "../types";
|
|
4
5
|
import { CallerManager } from "./caller_manager";
|
|
@@ -8,7 +9,7 @@ export interface TradeConfig {
|
|
|
8
9
|
vaultAddress: string;
|
|
9
10
|
executorIds: Record<string, string>;
|
|
10
11
|
}
|
|
11
|
-
export declare function buildTradeConfig(): TradeConfig;
|
|
12
|
+
export declare function buildTradeConfig(envArgs: BscEnvArgs): TradeConfig;
|
|
12
13
|
export interface TradeCalldata {
|
|
13
14
|
executorId: string;
|
|
14
15
|
data: string;
|
|
@@ -45,29 +45,27 @@ const path = __importStar(require("path"));
|
|
|
45
45
|
const constants_1 = require("../common/constants");
|
|
46
46
|
const caller_manager_1 = require("./caller_manager");
|
|
47
47
|
const send_tx_1 = require("../send-tx");
|
|
48
|
-
const
|
|
48
|
+
const abstract_tx_result_checker_1 = require("./check/abstract_tx_result_checker");
|
|
49
49
|
const ttd_core_2 = require("@clonegod/ttd-core");
|
|
50
50
|
const trade_direction_1 = require("../utils/trade_direction");
|
|
51
51
|
const ethers_compat_1 = require("../utils/ethers_compat");
|
|
52
52
|
const fast_signer_1 = require("../utils/fast_signer");
|
|
53
53
|
const trade_trace_1 = require("./trade_trace");
|
|
54
54
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
55
|
-
function buildTradeConfig() {
|
|
56
|
-
const vaultGroupId =
|
|
55
|
+
function buildTradeConfig(envArgs) {
|
|
56
|
+
const vaultGroupId = envArgs.trade_group_id;
|
|
57
57
|
if (!vaultGroupId) {
|
|
58
|
-
throw new Error('
|
|
58
|
+
throw new Error('TRADE_GROUP_ID 未配置(envArgs.trade_group_id 为空)');
|
|
59
59
|
}
|
|
60
60
|
const vaultWallet = (0, ttd_core_1.load_wallet)(vaultGroupId, true);
|
|
61
61
|
if (!vaultWallet.public_key) {
|
|
62
62
|
throw new Error(`load_wallet(${vaultGroupId}) 未返回 public_key`);
|
|
63
63
|
}
|
|
64
|
-
const pancakeIdName = process.env.PANCAKE_EXECUTOR_ID || 'PANCAKE';
|
|
65
|
-
const uniswapIdName = process.env.UNISWAP_EXECUTOR_ID || 'UNISWAP';
|
|
66
64
|
return {
|
|
67
65
|
vaultAddress: vaultWallet.public_key,
|
|
68
66
|
executorIds: {
|
|
69
|
-
pancake: ethers_compat_1.ethersCompat.id(
|
|
70
|
-
uniswap: ethers_compat_1.ethersCompat.id(
|
|
67
|
+
pancake: ethers_compat_1.ethersCompat.id(envArgs.pancake_executor_id),
|
|
68
|
+
uniswap: ethers_compat_1.ethersCompat.id(envArgs.uniswap_executor_id),
|
|
71
69
|
},
|
|
72
70
|
};
|
|
73
71
|
}
|
|
@@ -102,7 +100,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
102
100
|
maxTipAmountGwei: 500000,
|
|
103
101
|
}
|
|
104
102
|
};
|
|
105
|
-
this.tradeConfig = buildTradeConfig();
|
|
103
|
+
this.tradeConfig = buildTradeConfig(env);
|
|
106
104
|
}
|
|
107
105
|
async init(transactionSender) {
|
|
108
106
|
(0, fast_signer_1.patchEthersV5Signer)();
|
|
@@ -140,7 +138,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
140
138
|
trace.set('input', `${amount} ${inputToken.symbol}`);
|
|
141
139
|
trace.set('outputMin', `${ethers_compat_1.ethersCompat.formatUnits(amountOutMin, outputToken.decimals)} ${outputToken.symbol}`);
|
|
142
140
|
trace.set('slippage', `${slippage_bps / 100}%`);
|
|
143
|
-
|
|
141
|
+
const maxAttempts = 3;
|
|
144
142
|
const { wallet: caller, nonce: initialNonce } = await this.callerManager.acquireCaller();
|
|
145
143
|
trace.mark('caller');
|
|
146
144
|
trace.set('caller', caller.address);
|
|
@@ -207,7 +205,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
207
205
|
context._execution_marks = trace.getAbsoluteMarks();
|
|
208
206
|
context._execution_start_time = trace.getStartTime();
|
|
209
207
|
try {
|
|
210
|
-
|
|
208
|
+
abstract_tx_result_checker_1.TradeResultSubscriber.getInstance().sendNonceWatch(caller.address, txid);
|
|
211
209
|
}
|
|
212
210
|
catch { }
|
|
213
211
|
return txid;
|
|
@@ -245,7 +243,7 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
245
243
|
return txid;
|
|
246
244
|
}
|
|
247
245
|
scanCallerFiles(groupId) {
|
|
248
|
-
const walletDir =
|
|
246
|
+
const walletDir = this.appConfig.env_args.wallet_dir || path.join((0, ttd_core_1.home_dir)(), 'data', 'keypairs');
|
|
249
247
|
const prefix = `${groupId}-CALLER-`;
|
|
250
248
|
const files = fs.readdirSync(walletDir)
|
|
251
249
|
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
|
@@ -17,6 +17,9 @@ export declare class CallerManager {
|
|
|
17
17
|
constructor(config: CallerManagerConfig);
|
|
18
18
|
init(): Promise<void>;
|
|
19
19
|
acquireCaller(): Promise<CallerHandle>;
|
|
20
|
+
private acquireCallerWithLock;
|
|
21
|
+
private acquireCallerWithLuaCas;
|
|
22
|
+
private readNonce;
|
|
20
23
|
confirmNonce(address: string, confirmedNonce: number): Promise<void>;
|
|
21
24
|
forceSetNonce(address: string, nonce: number): Promise<void>;
|
|
22
25
|
getCallerCount(): number;
|
|
@@ -5,6 +5,27 @@ const dist_1 = require("@clonegod/ttd-core/dist");
|
|
|
5
5
|
const logger = (0, dist_1.createLogger)(__filename);
|
|
6
6
|
const ethers_1 = require("ethers");
|
|
7
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
|
+
`;
|
|
8
29
|
const CALLER_NONCE_KEY = 'caller:nonce';
|
|
9
30
|
const CALLER_LAST_USED_KEY = 'caller:last_used';
|
|
10
31
|
const VAULT_CALLERS_KEY = 'vault:callers';
|
|
@@ -62,6 +83,13 @@ class CallerManager {
|
|
|
62
83
|
logger.info(`CallerManager initialized for ${this.config.groupId}: loaded=${allWallets.length}, active=${this.callers.length}, skipped=${skipped.length}`, callerSummary);
|
|
63
84
|
}
|
|
64
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() {
|
|
65
93
|
const lockKey = `${this.config.chainName}:caller:lock:select`;
|
|
66
94
|
const lockValue = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
67
95
|
const lockExpireSeconds = 1;
|
|
@@ -101,16 +129,36 @@ class CallerManager {
|
|
|
101
129
|
finally {
|
|
102
130
|
this.redis.releaseLock(lockKey, lockValue).catch(() => { });
|
|
103
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) {
|
|
104
156
|
const nonceKey = this.getNonceRedisKey();
|
|
105
157
|
const nonceStr = await this.redis.hget(nonceKey, callerAddr);
|
|
106
158
|
if (nonceStr === null || nonceStr === undefined) {
|
|
107
159
|
throw new Error(`Caller ${callerAddr} nonce not found in Redis, stream-trade may not be running`);
|
|
108
160
|
}
|
|
109
|
-
|
|
110
|
-
const totalMs = Date.now() - startTime;
|
|
111
|
-
const lockWaitMs = lockAcquiredTime - startTime;
|
|
112
|
-
logger.info(`acquireCaller: ${callerAddr} nonce=${nonce}, lock_wait=${lockWaitMs}ms, total=${totalMs}ms${retries > 0 ? `, retries=${retries}` : ''}`);
|
|
113
|
-
return { wallet: this.callers[selectedIdx], nonce };
|
|
161
|
+
return parseInt(nonceStr, 10);
|
|
114
162
|
}
|
|
115
163
|
async confirmNonce(address, confirmedNonce) {
|
|
116
164
|
const current = await this.getNonce(address);
|
|
@@ -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,169 @@
|
|
|
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)()?.trade_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
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
clearInterval(intervalId);
|
|
124
|
+
logger.error('parse transaction error!', err);
|
|
125
|
+
}
|
|
126
|
+
}, check_interval);
|
|
127
|
+
this.intervalId = intervalId;
|
|
128
|
+
}
|
|
129
|
+
async on_subscibe_transaction() {
|
|
130
|
+
logger.info(`Subscribing trade result, txid=${this.txid}`);
|
|
131
|
+
TradeResultSubscriber.getInstance().listen(this.txid, (receipt) => {
|
|
132
|
+
logger.info(`Received transaction result via stream-trade: ${this.txid}`);
|
|
133
|
+
this.processTransactionResult(receipt, 'websocket')
|
|
134
|
+
.catch(err => logger.error(`Error processing trade result: ${this.txid}`, err));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async processTransactionResult(txReceipt, source) {
|
|
138
|
+
if (ttd_core_1.LOG.debug) {
|
|
139
|
+
(0, ttd_core_1.writeFile)(`./dist/tx_receipt_${this.txid}_${source}.json`, JSON.stringify(txReceipt, null, 2));
|
|
140
|
+
}
|
|
141
|
+
if (this.trade_result_already_processed) {
|
|
142
|
+
logger.info(`trade_result_already_processed, ignore result from ${source}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const parser = this.createParser();
|
|
146
|
+
const swap_detail = await parser.parseTransaction(txReceipt, this.pool_info);
|
|
147
|
+
let trade_result = this.map_swap_result_to_tx_result(swap_detail);
|
|
148
|
+
this.trade_result_already_processed = true;
|
|
149
|
+
if (this.intervalId) {
|
|
150
|
+
clearInterval(this.intervalId);
|
|
151
|
+
this.intervalId = null;
|
|
152
|
+
}
|
|
153
|
+
if (trade_result.success) {
|
|
154
|
+
this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_SUCCESS, trade_result);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_FAILED, trade_result);
|
|
158
|
+
}
|
|
159
|
+
if (source === 'interval') {
|
|
160
|
+
console.log('--------------------- Transaction Result from Polling ---------------------');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log('===================== Transaction Result from stream-trade =====================');
|
|
164
|
+
}
|
|
165
|
+
console.log(JSON.stringify(trade_result, null, 2));
|
|
166
|
+
console.log('-----------------------------------------------------------------------------');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
exports.AbstractTxResultChecker = AbstractTxResultChecker;
|