@clonegod/ttd-bsc-common 3.1.18 → 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.
Files changed (37) hide show
  1. package/dist/config/BscQuoteAppConfig.js +2 -0
  2. package/dist/config/bsc_env_args.d.ts +60 -1
  3. package/dist/config/bsc_env_args.js +121 -21
  4. package/dist/quote/verify/quote_price_verify.js +1 -1
  5. package/dist/trade/abstract_dex_trade.d.ts +2 -1
  6. package/dist/trade/abstract_dex_trade.js +10 -12
  7. package/dist/trade/caller_manager.d.ts +3 -0
  8. package/dist/trade/caller_manager.js +53 -5
  9. package/dist/trade/check/abstract_tx_result_checker.d.ts +28 -0
  10. package/dist/trade/check/abstract_tx_result_checker.js +169 -0
  11. package/dist/trade/check/base_tx_result_checker.js +1 -1
  12. package/dist/trade/check/index.d.ts +1 -1
  13. package/dist/trade/check/index.js +1 -1
  14. package/dist/trade/parse/base_parser.js +1 -1
  15. package/package.json +3 -3
  16. package/dist/quote/accuracy/index.d.ts +0 -2
  17. package/dist/quote/accuracy/index.js +0 -5
  18. package/dist/quote/accuracy/quote_accuracy_checker.d.ts +0 -42
  19. package/dist/quote/accuracy/quote_accuracy_checker.js +0 -144
  20. package/dist/quote/depth/verify_depth.d.ts +0 -10
  21. package/dist/quote/depth/verify_depth.js +0 -55
  22. package/dist/quote/event/verify_clmm_swap_event.d.ts +0 -1
  23. package/dist/quote/event/verify_clmm_swap_event.js +0 -178
  24. package/dist/redis/index.d.ts +0 -1
  25. package/dist/redis/index.js +0 -17
  26. package/dist/redis/redis_client.d.ts +0 -23
  27. package/dist/redis/redis_client.js +0 -126
  28. package/dist/trade/check/tx_websocket_manager.d.ts +0 -21
  29. package/dist/trade/check/tx_websocket_manager.js +0 -167
  30. package/dist/yyws/index.d.ts +0 -2
  31. package/dist/yyws/index.js +0 -18
  32. package/dist/yyws/mock_ws_server.d.ts +0 -1
  33. package/dist/yyws/mock_ws_server.js +0 -77
  34. package/dist/yyws/type.d.ts +0 -25
  35. package/dist/yyws/type.js +0 -2
  36. package/dist/yyws/yyws_client.d.ts +0 -2
  37. 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 BSC_KEYS = [
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
- need_block_time_info: { env: 'NEED_BLOCK_TIME_INFO', type: 'boolean', default: false, desc: '区块时间信息开关' },
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
- const cfg = (0, ttd_core_1.loadEnvConfig)(BSC_KEYS);
35
- this.gas_price_gwei = cfg.gas_price_gwei;
36
- this.gas_limit = cfg.gas_limit;
37
- this.tip_amount_gwei = cfg.tip_amount_gwei;
38
- this.send_tx_blockrazor_bundle = cfg.send_tx_blockrazor_bundle;
39
- this.send_tx_48club_bundle = cfg.send_tx_48club_bundle;
40
- this.send_tx_48club_bundle_ws = cfg.send_tx_48club_bundle_ws;
41
- this.send_tx_blox_bundle_ws = cfg.send_tx_blox_bundle_ws;
42
- }
43
- print(moduleName) {
44
- super.print(moduleName);
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.report_trade_analyze_data)('QuoteVerify', {
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 base_tx_result_checker_1 = require("./check/base_tx_result_checker");
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 = process.env.TRADE_GROUP_ID || process.env.VAULT_GROUP_ID;
55
+ function buildTradeConfig(envArgs) {
56
+ const vaultGroupId = envArgs.trade_group_id;
57
57
  if (!vaultGroupId) {
58
- throw new Error('环境变量 TRADE_GROUP_ID 未配置');
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(pancakeIdName),
70
- uniswap: ethers_compat_1.ethersCompat.id(uniswapIdName),
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
- let maxAttempts = Math.min(Math.max(parseInt(process.env.NONCE_LOCK_MAX_RETRIES || '3'), 1), 3);
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
- base_tx_result_checker_1.TradeResultSubscriber.getInstance().sendNonceWatch(caller.address, txid);
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 = process.env.WALLET_DIR || path.join((0, ttd_core_1.home_dir)(), 'data', 'keypairs');
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
- const nonce = parseInt(nonceStr, 10);
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;