@clonegod/ttd-core 3.1.40 → 3.1.42

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.
@@ -28,7 +28,7 @@ function detectProvider(msg) {
28
28
  registerLogRules([
29
29
  { pattern: /redis.*(disconnect|connection.*lost|econnrefused|pubsub.*error|subscribe.*failed|exceeded max reconnect|连接失败)/i,
30
30
  code: 'INFRA_REDIS_DISCONNECT' },
31
- { pattern: /econnrefused.*4000|config-center.*(down|unavailable)|(get|update|delete|post|check)\s+(pools|tokens|group|fixed-symbol|zh-pinyin).*error|init_.*client.*error/i,
31
+ { pattern: /econnrefused.*4000|config-center.*(down|unavailable)|(get|update|delete|post|check)\s+(pools|tokens|group|fixed-symbol).*error|init_.*client.*error/i,
32
32
  level: 'error', code: 'INFRA_CONFIG_CENTER_DOWN' },
33
33
  { pattern: /uncaught exception|unhandled rejection|startup failed|main error|create_trade_runtime error|arb_cache.*init.*error/i,
34
34
  level: 'error', code: 'INFRA_PROCESS_CRASH' },
@@ -35,11 +35,6 @@ export declare class EnvArgs {
35
35
  encryption_key: string;
36
36
  providers_file_path: string;
37
37
  fixed_symbol_address_file: string;
38
- zh_pinyin_map_file: string;
39
- alert_analyze_host: string;
40
- alert_flush_size: number;
41
- alert_flush_ms: number;
42
- alert_dedup_window_ms: number;
43
38
  namespace: string;
44
39
  server_ip_list: string;
45
40
  ip_exclude_prefix: string;
@@ -45,12 +45,7 @@ registerEnvVars({
45
45
  providers_file_path: { env: 'PROVIDERS_FILE_PATH', type: 'string', default: '', desc: 'stream-quote providers.json 绝对路径;留空则仅写 Redis' },
46
46
  token_price_refresh_interval_seconds: { env: 'TOKEN_PRICE_REFRESH_INTERVAL_SECONDS', type: 'number', default: 600, desc: 'TokenPriceSyncer 刷价周期(s):market-data 通过 force_fetch 拉外部价(DefiLlama→Gecko→...)并直接批量写入 Redis + 发 TOKEN_CONFIG_CHANGE 事件的频率' },
47
47
  fixed_symbol_address_file: { env: 'FIXED_SYMBOL_ADDRESS_FILE', type: 'string', default: '', desc: '固定 symbol/address 映射文件路径(覆盖默认按 CHAIN_ID 推断的路径)' },
48
- zh_pinyin_map_file: { env: 'ZH_PINYIN_MAP_FILE', type: 'string', default: '', desc: '中文拼音映射文件路径(覆盖默认路径)' },
49
48
  server_ip_list: { env: 'SERVER_IP_LIST', type: 'string', default: '', desc: '服务器可用 IP 列表(逗号分隔);配置后优先使用;为空则从网卡探测并结合 IP_EXCLUDE_PREFIX 过滤' },
50
49
  ip_exclude_prefix: { env: 'IP_EXCLUDE_PREFIX', type: 'string', default: '192.168', desc: 'IP 前缀排除列表(逗号分隔)' },
51
- alert_analyze_host: { env: 'ALERT_ANALYZE_HOST', type: 'string', default: '', desc: 'alert ingest 地址(IP);为空则不上报' },
52
- alert_flush_size: { env: 'ALERT_FLUSH_SIZE', type: 'number', default: 10, desc: 'alert 批处理阈值(条数)' },
53
- alert_flush_ms: { env: 'ALERT_FLUSH_MS', type: 'number', default: 5000, desc: 'alert 批处理阈值(ms)' },
54
- alert_dedup_window_ms: { env: 'ALERT_DEDUP_WINDOW_MS', type: 'number', default: 60000, desc: 'alert 去重窗口(ms)' },
55
- namespace: { env: 'NAMESPACE', type: 'string', default: '', desc: '服务命名空间(用于日志/告警 source 前缀等)' },
50
+ namespace: { env: 'NAMESPACE', type: 'string', default: '', desc: '服务命名空间(用于日志 source 前缀等)' },
56
51
  });
@@ -29,16 +29,21 @@ export declare class ArbCache {
29
29
  refresh_local_cache_group_map(trade_config: TradeServiceConfigType): void;
30
30
  cache_token_list(): Promise<void>;
31
31
  update_token_list(input_token_list: StandardTokenInfoType[]): Promise<void>;
32
- get_token_list(): Promise<StandardTokenInfoType[]>;
33
- get_token_list_no_cache(): Promise<StandardTokenInfoType[]>;
32
+ get_token_list(force_load?: boolean): Promise<StandardTokenInfoType[]>;
34
33
  get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
35
34
  publish_token_change_event(): Promise<void>;
36
35
  cache_pool_list(): Promise<void>;
37
- private scan_duplicate_address;
36
+ scan_duplicate_address(token_list: StandardTokenInfoType[]): void;
38
37
  resolve_duplicate_address(authoritative_mappings: Array<{
39
38
  address: string;
40
39
  symbol: string;
41
40
  }>): Promise<{
41
+ cleaned_redis_fields: Array<{
42
+ field: string;
43
+ address: string;
44
+ value_symbol: string;
45
+ kept_symbol: string;
46
+ }>;
42
47
  cleaned_tokens: Array<{
43
48
  address: string;
44
49
  dropped_symbols: string[];
@@ -60,13 +65,17 @@ export declare class ArbCache {
60
65
  symbol: string;
61
66
  reason: string;
62
67
  }>;
68
+ invalid_input: Array<{
69
+ address: string;
70
+ symbol: string;
71
+ reason: string;
72
+ }>;
63
73
  }>;
64
74
  set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
65
75
  set_pool_extra(pool: StandardPoolInfoType): void;
66
76
  update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
67
77
  get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;
68
- get_pool_list(): Promise<StandardPoolInfoType[]>;
69
- get_pool_list_no_cache(): Promise<StandardPoolInfoType[]>;
78
+ get_pool_list(force_load?: boolean): Promise<StandardPoolInfoType[]>;
70
79
  get_one_pool_info(pool_address: string, refresh_cache_if_not_found?: boolean): Promise<StandardPoolInfoType>;
71
80
  publish_pool_change_event(): Promise<void>;
72
81
  get_one_pair(input_pair: string): Promise<StandardPairType>;
@@ -42,8 +42,8 @@ class ArbCache {
42
42
  this.redis_event_publisher = (0, index_1.getArbEventPublisher)(this);
43
43
  (0, index_1.mkdirSync)(path_1.default.join('config', this.chain_id).toLowerCase(), true);
44
44
  yield Promise.all([
45
- this.get_token_list_no_cache(),
46
- this.get_pool_list_no_cache()
45
+ this.get_token_list(true),
46
+ this.get_pool_list(true)
47
47
  ]);
48
48
  if ((_a = (0, core_env_1.getCoreEnv)().app_name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('config-center')) {
49
49
  yield this.init_configs();
@@ -91,12 +91,19 @@ class ArbCache {
91
91
  return path_1.default.join('config', chain_id, file_name);
92
92
  }
93
93
  refresh_local_cache_token_list(token_list) {
94
- const prev_by_symbol = new Map(this.token_symbol_map);
94
+ const prev_by_id = new Map();
95
+ for (const t of this.token_address_map.values()) {
96
+ const id = t._id || (t.address || '').toLowerCase();
97
+ if (id)
98
+ prev_by_id.set(id, t);
99
+ }
95
100
  this.token_symbol_map.clear();
96
101
  this.token_address_map.clear();
97
102
  const merged = [];
98
103
  for (const fresh of token_list) {
99
- const existing = prev_by_symbol.get(fresh.symbol);
104
+ if (!fresh._id)
105
+ fresh._id = (fresh.address || '').toLowerCase();
106
+ const existing = prev_by_id.get(fresh._id);
100
107
  let token_ref;
101
108
  if (existing) {
102
109
  Object.assign(existing, fresh);
@@ -106,7 +113,7 @@ class ArbCache {
106
113
  token_ref = fresh;
107
114
  }
108
115
  this.token_symbol_map.set(token_ref.symbol, token_ref);
109
- this.token_address_map.set(token_ref.address, token_ref);
116
+ this.token_address_map.set(token_ref._id, token_ref);
110
117
  merged.push(token_ref);
111
118
  }
112
119
  this.token_list = merged;
@@ -114,28 +121,21 @@ class ArbCache {
114
121
  (0, index_1.log_trace)('refresh_local_cache_token_list, success', {
115
122
  token_list_len: this.token_list.length,
116
123
  token_symbol_map_size: this.token_symbol_map.size,
117
- token_address_map_szie: this.token_address_map.size,
124
+ token_address_map_size: this.token_address_map.size,
118
125
  });
119
126
  }
120
127
  getTokenByAddress(address) {
121
- var _a;
122
128
  if (!address)
123
129
  return undefined;
124
- const exact = this.token_address_map.get(address);
125
- if (exact)
126
- return exact;
127
- const lc = address.toLowerCase();
128
- for (const m of this.token_address_map.values()) {
129
- if (((_a = m.address) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === lc)
130
- return m;
131
- }
132
- return undefined;
130
+ return this.token_address_map.get(address.toLowerCase());
133
131
  }
134
132
  refresh_local_cache_pool_list(pool_list) {
135
133
  this.pool_list = pool_list;
136
134
  this.pool_address_map.clear();
137
135
  for (let pool of pool_list) {
138
- this.pool_address_map.set(pool.pool_address, pool);
136
+ if (!pool._id)
137
+ pool._id = (pool.pool_address || '').toLowerCase();
138
+ this.pool_address_map.set(pool._id, pool);
139
139
  }
140
140
  this.pool_list_cahce_last_update_time = (0, index_1.getCurDateTime)();
141
141
  (0, index_1.log_trace)('refresh_local_cache_pool_list, success', {
@@ -170,7 +170,10 @@ class ArbCache {
170
170
  }
171
171
  let config = (0, index_1.readFile)(filepath);
172
172
  let token_list = config.token_list.filter(e => e.enable);
173
- this.scan_duplicate_address(token_list);
173
+ for (const t of token_list) {
174
+ if (!t._id)
175
+ t._id = (t.address || '').toLowerCase();
176
+ }
174
177
  for (let token of token_list) {
175
178
  let field = token.symbol;
176
179
  let value = JSON.stringify(token);
@@ -196,18 +199,27 @@ class ArbCache {
196
199
  throw new Error(`update_token_list failed, config file not exist! filepath=${filepath}`);
197
200
  }
198
201
  let config = (0, index_1.readFile)(filepath);
199
- for (let token of input_token_list) {
200
- let others = config.token_list.filter(e => e.symbol.toUpperCase() !== token.symbol.toUpperCase());
202
+ const input_symbols_upper = new Set();
203
+ const upserts = [];
204
+ const hdels = [];
205
+ for (const token of input_token_list) {
206
+ if (!token._id)
207
+ token._id = (token.address || '').toLowerCase();
208
+ input_symbols_upper.add(token.symbol.toUpperCase());
201
209
  if (token.enable) {
202
- others.push(token);
210
+ upserts.push(token);
203
211
  }
204
212
  else {
205
213
  (0, index_1.log_warn)(`update_token_list, token not enable, delete`, token);
206
- let field = token.symbol;
207
- yield this.loading_cache.hdel(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST, field);
214
+ hdels.push(token.symbol);
208
215
  }
209
- others = others.sort((a, b) => a.symbol.localeCompare(b.symbol));
210
- config.token_list = others;
216
+ }
217
+ const others = config.token_list.filter(e => !input_symbols_upper.has(e.symbol.toUpperCase()));
218
+ others.push(...upserts);
219
+ others.sort((a, b) => a.symbol.localeCompare(b.symbol));
220
+ config.token_list = others;
221
+ for (const field of hdels) {
222
+ yield this.loading_cache.hdel(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST, field);
211
223
  }
212
224
  (0, index_1.writeFile)(filepath, (0, index_1.to_json_str)(config));
213
225
  yield this.cache_token_list();
@@ -215,22 +227,16 @@ class ArbCache {
215
227
  });
216
228
  }
217
229
  get_token_list() {
218
- return __awaiter(this, void 0, void 0, function* () {
219
- if (!(0, index_1.isEmpty)(this.token_list)) {
230
+ return __awaiter(this, arguments, void 0, function* (force_load = false) {
231
+ if (!force_load && !(0, index_1.isEmpty)(this.token_list)) {
220
232
  return this.token_list;
221
233
  }
222
- return yield this.get_token_list_no_cache();
223
- });
224
- }
225
- get_token_list_no_cache() {
226
- return __awaiter(this, void 0, void 0, function* () {
227
- let token_list = [];
228
- let res = yield this.loading_cache.hget_all(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST);
229
- let { key, value } = res;
230
- for (let name of Object.keys(value)) {
231
- token_list.push(JSON.parse(value[name]));
234
+ const res = yield this.loading_cache.hget_all(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST);
235
+ const token_list = [];
236
+ for (const name of Object.keys(res.value)) {
237
+ token_list.push(JSON.parse(res.value[name]));
232
238
  }
233
- (0, index_1.log_warn)(`get_token_list_no_cache, key=${key}, token_list.len=${token_list.length}`);
239
+ (0, index_1.log_trace)(`[get_token_list] reload from redis, key=${res.key}, len=${token_list.length}`);
234
240
  this.refresh_local_cache_token_list(token_list);
235
241
  return token_list;
236
242
  });
@@ -272,6 +278,10 @@ class ArbCache {
272
278
  const skipped = [];
273
279
  for (let { dex_id, pool_list } of config.dex_list) {
274
280
  pool_list = pool_list.filter(e => e.enable);
281
+ for (const p of pool_list) {
282
+ if (!p._id)
283
+ p._id = (p.pool_address || '').toLowerCase();
284
+ }
275
285
  (0, index_1.log_trace)(`cache_pool_list`, { dex_id, pool_list_len: pool_list.length });
276
286
  const valid_pools = [];
277
287
  for (let pool of pool_list) {
@@ -309,12 +319,12 @@ class ArbCache {
309
319
  var _a;
310
320
  const by_addr = new Map();
311
321
  for (const t of token_list) {
312
- const addr = (t.address || '').toLowerCase();
313
- if (!addr)
322
+ const id = t._id || (t.address || '').toLowerCase();
323
+ if (!id)
314
324
  continue;
315
- if (!by_addr.has(addr))
316
- by_addr.set(addr, []);
317
- by_addr.get(addr).push(t);
325
+ if (!by_addr.has(id))
326
+ by_addr.set(id, []);
327
+ by_addr.get(id).push(t);
318
328
  }
319
329
  const fixed_by_addr = new Map();
320
330
  for (const f of (0, fixed_symbol_address_1.get_fixed_symbol_address)()) {
@@ -340,21 +350,67 @@ class ArbCache {
340
350
  }
341
351
  resolve_duplicate_address(authoritative_mappings) {
342
352
  return __awaiter(this, void 0, void 0, function* () {
343
- const token_list = yield this.get_token_list();
353
+ const authMap = new Map();
354
+ const invalid_input = [];
355
+ for (const m of authoritative_mappings) {
356
+ const addr = (m.address || '').toLowerCase();
357
+ const sym = m.symbol || '';
358
+ if (!addr) {
359
+ invalid_input.push({ address: addr, symbol: sym, reason: 'address 为空' });
360
+ continue;
361
+ }
362
+ if (!/^[A-Z0-9]+$/.test(sym)) {
363
+ invalid_input.push({ address: addr, symbol: sym, reason: 'symbol 非 [A-Z0-9]+' });
364
+ continue;
365
+ }
366
+ authMap.set(addr, sym);
367
+ }
368
+ const cleaned_redis_fields = [];
369
+ const dropped_value_symbols = new Set();
370
+ const tokenHashRes = yield this.loading_cache.hget_all(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST);
371
+ const fields_by_addr = new Map();
372
+ for (const fieldName of Object.keys(tokenHashRes.value)) {
373
+ let v;
374
+ try {
375
+ v = JSON.parse(tokenHashRes.value[fieldName]);
376
+ }
377
+ catch (_a) {
378
+ continue;
379
+ }
380
+ const addr = (v.address || '').toLowerCase();
381
+ if (!addr)
382
+ continue;
383
+ if (!fields_by_addr.has(addr))
384
+ fields_by_addr.set(addr, []);
385
+ fields_by_addr.get(addr).push({ field: fieldName, v });
386
+ }
387
+ for (const [addr, authSym] of authMap) {
388
+ const fields = fields_by_addr.get(addr) || [];
389
+ for (const { field, v } of fields) {
390
+ if (field === authSym && v.symbol === authSym)
391
+ continue;
392
+ yield this.loading_cache.hdel(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST, field);
393
+ cleaned_redis_fields.push({ field, address: addr, value_symbol: v.symbol || '', kept_symbol: authSym });
394
+ if (v.symbol)
395
+ dropped_value_symbols.add(v.symbol);
396
+ if (field !== v.symbol)
397
+ dropped_value_symbols.add(field);
398
+ }
399
+ }
400
+ const token_list = yield this.get_token_list(true);
344
401
  const by_addr = new Map();
345
402
  for (const t of token_list) {
346
- const addr = (t.address || '').toLowerCase();
347
- if (!addr)
403
+ const id = t._id || (t.address || '').toLowerCase();
404
+ if (!id)
348
405
  continue;
349
- if (!by_addr.has(addr))
350
- by_addr.set(addr, []);
351
- by_addr.get(addr).push(t);
406
+ if (!by_addr.has(id))
407
+ by_addr.set(id, []);
408
+ by_addr.get(id).push(t);
352
409
  }
353
410
  const tokens_to_disable = [];
354
411
  const cleaned_tokens = [];
355
412
  const not_found = [];
356
- for (const { address, symbol } of authoritative_mappings) {
357
- const addr = (address || '').toLowerCase();
413
+ for (const [addr, symbol] of authMap) {
358
414
  const tokens = by_addr.get(addr) || [];
359
415
  if (tokens.length === 0) {
360
416
  not_found.push({ address: addr, symbol, reason: `address 在 token-list 中未找到` });
@@ -370,6 +426,7 @@ class ArbCache {
370
426
  continue;
371
427
  for (const t of dropped) {
372
428
  tokens_to_disable.push(Object.assign(Object.assign({}, t), { enable: false }));
429
+ dropped_value_symbols.add(t.symbol);
373
430
  }
374
431
  cleaned_tokens.push({
375
432
  address: addr,
@@ -382,13 +439,14 @@ class ArbCache {
382
439
  const skipped_trading_pools = [];
383
440
  if (dropped_symbols.size > 0) {
384
441
  const pool_list = yield this.get_pool_list();
385
- const trade_pool_set = new Set((yield this.get_all_trade_pools()).map(p => p.pool_address));
442
+ const trade_pool_set = new Set((yield this.get_all_trade_pools()).map(p => p._id || (p.pool_address || '').toLowerCase()));
386
443
  const pools_to_disable_by_dex = new Map();
387
444
  for (const p of pool_list) {
388
445
  const [t0, t1] = (p.pool_name || '').split('/');
389
446
  if (!dropped_symbols.has(t0) && !dropped_symbols.has(t1))
390
447
  continue;
391
- if (trade_pool_set.has(p.pool_address)) {
448
+ const pid = p._id || (p.pool_address || '').toLowerCase();
449
+ if (trade_pool_set.has(pid)) {
392
450
  skipped_trading_pools.push({
393
451
  pool_address: p.pool_address,
394
452
  pool_name: p.pool_name,
@@ -413,8 +471,8 @@ class ArbCache {
413
471
  if (tokens_to_disable.length > 0) {
414
472
  yield this.update_token_list(tokens_to_disable);
415
473
  }
416
- (0, index_1.log_warn)(`[resolve-duplicate] 完成: disabled ${tokens_to_disable.length} 个 symbol, 删 ${cleaned_pools.length} 个 pool, 跳过交易中 pool ${skipped_trading_pools.length} 个, 映射 not_found ${not_found.length} 个`);
417
- return { cleaned_tokens, cleaned_pools, skipped_trading_pools, not_found };
474
+ (0, index_1.log_warn)(`[resolve-duplicate] 完成: redis stale field ${cleaned_redis_fields.length}, file disabled ${tokens_to_disable.length} 个 symbol, 删 ${cleaned_pools.length} 个 pool, 跳过交易中 pool ${skipped_trading_pools.length} 个, not_found ${not_found.length}, invalid_input ${invalid_input.length}`);
475
+ return { cleaned_redis_fields, cleaned_tokens, cleaned_pools, skipped_trading_pools, not_found, invalid_input };
418
476
  });
419
477
  }
420
478
  set_pool_token_info(pool, token_info_list) {
@@ -496,43 +554,45 @@ class ArbCache {
496
554
  });
497
555
  }
498
556
  get_pool_list() {
499
- return __awaiter(this, void 0, void 0, function* () {
500
- if (!(0, index_1.isEmpty)(this.pool_list)) {
557
+ return __awaiter(this, arguments, void 0, function* (force_load = false) {
558
+ if (!force_load && !(0, index_1.isEmpty)(this.pool_list)) {
501
559
  return this.pool_list;
502
560
  }
503
- (0, index_1.log_warn)(`get_pool_list, local cache is empty, try get_pool_list_no_cache...`);
504
- return yield this.get_pool_list_no_cache();
505
- });
506
- }
507
- get_pool_list_no_cache() {
508
- return __awaiter(this, void 0, void 0, function* () {
509
561
  try {
510
- let res = yield this.loading_cache.hget_all(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST);
511
- let { key, value } = res;
512
- let pool_list = [];
513
- for (let name of Object.keys(value)) {
514
- pool_list.push(JSON.parse(value[name]));
562
+ const res = yield this.loading_cache.hget_all(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST);
563
+ const pool_list = [];
564
+ for (const name of Object.keys(res.value)) {
565
+ pool_list.push(JSON.parse(res.value[name]));
515
566
  }
516
- (0, index_1.log_warn)(`get_pool_list_no_cache, key=${key}, pool_list.len=${pool_list.length}`);
567
+ (0, index_1.log_trace)(`[get_pool_list] reload from redis, key=${res.key}, len=${pool_list.length}`);
517
568
  this.refresh_local_cache_pool_list(pool_list);
518
569
  return pool_list;
519
570
  }
520
571
  catch (err) {
521
- (0, index_1.log_error)('get_pool_list_no_cache error!!!', err);
522
- throw new Error(`get_pool_list_no_cache error! err=${err.message}`);
572
+ (0, index_1.log_error)('get_pool_list reload error!!!', err);
573
+ throw new Error(`get_pool_list error! err=${err.message}`);
523
574
  }
524
575
  });
525
576
  }
526
577
  get_one_pool_info(pool_address_1) {
527
578
  return __awaiter(this, arguments, void 0, function* (pool_address, refresh_cache_if_not_found = true) {
528
- let pool_info = null;
529
- if (this.pool_address_map.has(pool_address)) {
530
- pool_info = this.pool_address_map.get(pool_address);
531
- }
579
+ const target = (pool_address || '').toLowerCase();
580
+ let pool_info = this.pool_address_map.get(target) || null;
532
581
  if (!pool_info && refresh_cache_if_not_found) {
533
- (0, index_1.log_warn)(`get_one_pool_info, from local pool_address_map got: ${pool_info}, try get_pool_list_no_cache...`);
534
- let pool_list = yield this.get_pool_list_no_cache();
535
- pool_info = pool_list.find(e => e.pool_address === pool_address);
582
+ for (const field of [pool_address, target]) {
583
+ if (!field)
584
+ continue;
585
+ const res = yield this.loading_cache.hget(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST, field);
586
+ if (res === null || res === void 0 ? void 0 : res.value) {
587
+ pool_info = JSON.parse(res.value);
588
+ break;
589
+ }
590
+ }
591
+ if (!pool_info) {
592
+ (0, index_1.log_warn)(`get_one_pool_info, hget miss, try full reload`);
593
+ const pool_list = yield this.get_pool_list(true);
594
+ pool_info = pool_list.find(e => (e._id || (e.pool_address || '').toLowerCase()) === target);
595
+ }
536
596
  }
537
597
  if (!pool_info) {
538
598
  throw new Error(`get_one_pool_info failed, not exist! pool_address=${pool_address}`);
@@ -89,7 +89,7 @@ class ArbEventSubscriber {
89
89
  on_config_token_change_event(message) {
90
90
  return __awaiter(this, void 0, void 0, function* () {
91
91
  (0, index_1.log_debug)(`on_config_token_change_event, refresh local token cache, start`, { message });
92
- yield this.arb_cache.get_token_list_no_cache();
92
+ yield this.arb_cache.get_token_list(true);
93
93
  (0, index_1.log_debug)(`on_config_token_change_event, refresh local token cache, success`);
94
94
  return true;
95
95
  });
@@ -97,7 +97,7 @@ class ArbEventSubscriber {
97
97
  on_config_pool_change_event(message) {
98
98
  return __awaiter(this, void 0, void 0, function* () {
99
99
  (0, index_1.log_debug)(`on_config_pool_change_event, refresh local pool cache, start`, { message });
100
- yield this.arb_cache.get_pool_list_no_cache();
100
+ yield this.arb_cache.get_pool_list(true);
101
101
  (0, index_1.log_debug)(`on_config_pool_change_event, refresh local pool cache, success`);
102
102
  return true;
103
103
  });
package/dist/index.d.ts CHANGED
@@ -14,7 +14,6 @@ export * from './quote';
14
14
  export * from './token';
15
15
  export * from './trade';
16
16
  export * from './chains';
17
- export * from './alert';
18
17
  export * from './log_bus';
19
18
  export * from './ws';
20
19
  export * from './util';
@@ -128,7 +127,7 @@ export declare enum LOCAL_EVENT_NAME {
128
127
  EVENT_WALLET_TRANSACTION = "EVENT_WALLET_TRANSACTION",
129
128
  EVENT_WALLET_TRANSACTION_NEW_TXID = "EVENT_WALLET_TRANSACTION_NEW_TXID"
130
129
  }
131
- export declare const clean_token_symbol: (symbol: string) => string;
130
+ export declare const format_token_symbol: (symbol: string) => string;
132
131
  export declare const format_pair_symbol: (symbol: string) => string;
133
132
  export declare const format_unique_orderbook_id: (chain_id: CHAIN_ID, dex_id: DEX_ID, pool_id: string, fee_rate: number) => string;
134
133
  export declare const parse_unique_orderbook_id: (unique_orderbook_id: string) => UniqueOrderbookIdType;
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
49
49
  };
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.DATE_TIME_FORMAT_DEFAULT = exports.to_json_str = exports.DecimalUtil = exports.generateRandomNumber = exports.LOG = exports._active_log_level = exports.LOG_LEVEL = exports.REDIS_EVENT_TYPE_TRADE_INSTANCE_CHANGE = exports.REDIS_EVENT_TYPE_ORDER = exports.REDIS_EVENT_TYPE_QUOTE = exports.REDIS_EVENT_TYPE_CONFIG_CHANGE = exports.REDIS_EVENT_CHANNEL = exports.get_new_block_channel_name = exports.get_tx_result_channel_name = exports.get_wallet_raw_tx_channel_name = exports.get_order_channel_name = exports.get_quote_channel_name = exports.get_config_channel_name = exports.get_cache_key = exports.getArbEventSubscriber = exports.getArbEventPublisher = exports.getArbCache = exports.getLoadingCache = exports.getRedisClient = exports.format_order_lock_key = exports.format_unique_order_msg_id = exports.parse_unique_orderbook_id = exports.format_unique_orderbook_id = exports.format_pair_symbol = exports.clean_token_symbol = exports.LOCAL_EVENT_NAME = exports.CACHE_KEY_TYPE = exports.OnChainDataSubscribeType = exports.TradeErrorCodeType = exports.SystemErrorCodeType = exports.TRADE_TYPE = exports.GROUP_ID = exports.DEX_ID = exports.CHAIN_ID = exports.STREAMING_TRANSACTION_CONFIRMED = exports.TRANSACTION_STATE_FAILED = exports.TRANSACTION_STATE_SUCCESS = exports.TRANSACTION_STATE_PROCESSING = exports.QUOTE_AMOUNT_USD_MAX = exports.QUOTE_AMOUNT_USD_MIN = exports.REGEX_HEX_PREFIX = exports.PROCESSING = exports.NOT_FOUND = exports.SUCCESS = exports.FAILED = void 0;
51
+ exports.DATE_TIME_FORMAT_DEFAULT = exports.to_json_str = exports.DecimalUtil = exports.generateRandomNumber = exports.LOG = exports._active_log_level = exports.LOG_LEVEL = exports.REDIS_EVENT_TYPE_TRADE_INSTANCE_CHANGE = exports.REDIS_EVENT_TYPE_ORDER = exports.REDIS_EVENT_TYPE_QUOTE = exports.REDIS_EVENT_TYPE_CONFIG_CHANGE = exports.REDIS_EVENT_CHANNEL = exports.get_new_block_channel_name = exports.get_tx_result_channel_name = exports.get_wallet_raw_tx_channel_name = exports.get_order_channel_name = exports.get_quote_channel_name = exports.get_config_channel_name = exports.get_cache_key = exports.getArbEventSubscriber = exports.getArbEventPublisher = exports.getArbCache = exports.getLoadingCache = exports.getRedisClient = exports.format_order_lock_key = exports.format_unique_order_msg_id = exports.parse_unique_orderbook_id = exports.format_unique_orderbook_id = exports.format_pair_symbol = exports.format_token_symbol = exports.LOCAL_EVENT_NAME = exports.CACHE_KEY_TYPE = exports.OnChainDataSubscribeType = exports.TradeErrorCodeType = exports.SystemErrorCodeType = exports.TRADE_TYPE = exports.GROUP_ID = exports.DEX_ID = exports.CHAIN_ID = exports.STREAMING_TRANSACTION_CONFIRMED = exports.TRANSACTION_STATE_FAILED = exports.TRANSACTION_STATE_SUCCESS = exports.TRANSACTION_STATE_PROCESSING = exports.QUOTE_AMOUNT_USD_MAX = exports.QUOTE_AMOUNT_USD_MIN = exports.REGEX_HEX_PREFIX = exports.PROCESSING = exports.NOT_FOUND = exports.SUCCESS = exports.FAILED = void 0;
52
52
  exports.inspect_arb_local_cache = exports.save_trade_execution_logs = exports.get_input_out_token_fix = exports.get_input_out_token = exports.calc_amount_in_token = exports.failed = exports.success = exports.home_dir = exports.postJSON = exports.deep_merge_object = exports.isArray = exports.isTrue = exports.deep_clone = exports.uuid = exports.DATE_TIME_FORMAT_NO_WHITESPACE = void 0;
53
53
  exports._caller = _caller;
54
54
  exports.log_trace = log_trace;
@@ -97,7 +97,6 @@ __exportStar(require("./quote"), exports);
97
97
  __exportStar(require("./token"), exports);
98
98
  __exportStar(require("./trade"), exports);
99
99
  __exportStar(require("./chains"), exports);
100
- __exportStar(require("./alert"), exports);
101
100
  __exportStar(require("./log_bus"), exports);
102
101
  __exportStar(require("./ws"), exports);
103
102
  __exportStar(require("./util"), exports);
@@ -220,20 +219,17 @@ var LOCAL_EVENT_NAME;
220
219
  LOCAL_EVENT_NAME["EVENT_WALLET_TRANSACTION"] = "EVENT_WALLET_TRANSACTION";
221
220
  LOCAL_EVENT_NAME["EVENT_WALLET_TRANSACTION_NEW_TXID"] = "EVENT_WALLET_TRANSACTION_NEW_TXID";
222
221
  })(LOCAL_EVENT_NAME || (exports.LOCAL_EVENT_NAME = LOCAL_EVENT_NAME = {}));
223
- const clean_token_symbol = (symbol) => {
222
+ const format_token_symbol = (symbol) => {
224
223
  if (!symbol)
225
- return symbol;
226
- return symbol
227
- .replace(/\$/g, '')
228
- .replace(/-/g, '')
229
- .replace(/\s+/g, '')
230
- .toUpperCase();
224
+ return '';
225
+ const cleaned = symbol.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
226
+ return /^[A-Z0-9]+$/.test(cleaned) ? cleaned : '';
231
227
  };
232
- exports.clean_token_symbol = clean_token_symbol;
228
+ exports.format_token_symbol = format_token_symbol;
233
229
  const format_pair_symbol = (symbol) => {
234
230
  if (!symbol)
235
231
  return symbol;
236
- return (0, exports.clean_token_symbol)(symbol)
232
+ return (0, exports.format_token_symbol)(symbol)
237
233
  .replace('USDC', 'USDT').replace('USD1', 'USDT')
238
234
  .replace('WTRX', 'TRX').replace('WSOL', 'SOL').replace('WBTC', 'BTC')
239
235
  .replace('WETH', 'ETH').replace('WBNB', 'BNB').replace('WOKB', 'OKB');
@@ -1,2 +1,2 @@
1
1
  import { CHAIN_ID, DEX_ID, FormattedPoolInfo, FormattedTokenInfo } from "../index";
2
- export declare const cache_pool_config: (chain_id: CHAIN_ID, valid_tokens_map: Map<string, FormattedTokenInfo[]>, sol_pools_cache: Map<DEX_ID, FormattedPoolInfo[]>) => Promise<void>;
2
+ export declare const cache_pool_config: (chain_id: CHAIN_ID, tokens_map: Map<string, FormattedTokenInfo>, pools_map: Map<DEX_ID, FormattedPoolInfo[]>) => Promise<void>;
@@ -18,7 +18,7 @@ const core_env_1 = require("../appconfig/core_env");
18
18
  const index_1 = require("../index");
19
19
  const service_ports_1 = require("../constants/service_ports");
20
20
  const is_not_arb_token_1 = require("../token/is_not_arb_token");
21
- const cache_pool_config = (chain_id, valid_tokens_map, sol_pools_cache) => __awaiter(void 0, void 0, void 0, function* () {
21
+ const cache_pool_config = (chain_id, tokens_map, pools_map) => __awaiter(void 0, void 0, void 0, function* () {
22
22
  (0, index_1.log_info)(`cache_pool_config start`);
23
23
  let count = 0;
24
24
  let skip_pool_list = [];
@@ -26,15 +26,17 @@ const cache_pool_config = (chain_id, valid_tokens_map, sol_pools_cache) => __awa
26
26
  const ccBase = `http://${(genv.config_center_host || '127.0.0.1').trim()}:${service_ports_1.SERVICE_PORT.CONFIG_CENTER_HTTP}/${chain_id}/config`;
27
27
  const config_pool_url = `${ccBase}/pools`;
28
28
  const body = [];
29
- for (const [dex_id, pool_list] of sol_pools_cache) {
29
+ for (const [dex_id, pool_list] of pools_map) {
30
30
  const valid_pool_list = [];
31
31
  for (const pool of pool_list) {
32
- const is_contains_unkown_symbol = !valid_tokens_map.has(pool.tokenA.symbol) || !valid_tokens_map.has(pool.tokenB.symbol);
33
- if (is_contains_unkown_symbol || (0, is_not_arb_token_1.is_not_arb_token)(pool.pool_name)) {
32
+ const tokenAExists = tokens_map.has(pool.tokenA._id);
33
+ const tokenBExists = tokens_map.has(pool.tokenB._id);
34
+ if (!tokenAExists || !tokenBExists || (0, is_not_arb_token_1.is_not_arb_token)(pool.pool_name)) {
34
35
  skip_pool_list.push(pool);
35
36
  continue;
36
37
  }
37
38
  valid_pool_list.push({
39
+ _id: (pool.pool_address || '').toLowerCase(),
38
40
  program_id: pool.program_id,
39
41
  authority: pool.authority,
40
42
  subscribe_type: 'grpc',
@@ -42,7 +44,6 @@ const cache_pool_config = (chain_id, valid_tokens_map, sol_pools_cache) => __awa
42
44
  dex_id,
43
45
  pool_name: pool.pool_name,
44
46
  pool_address: pool.pool_address,
45
- pool_address_hex: '',
46
47
  tokenA: null,
47
48
  tokenB: null,
48
49
  vaultA: pool.vaultA,
@@ -1,5 +1,6 @@
1
1
  import { FormattedTokenInfo } from "../token/types";
2
2
  export interface FormattedPoolInfo {
3
+ _id: string;
3
4
  pair: string;
4
5
  quote_token: string;
5
6
  chain_id: string;
@@ -72,17 +72,17 @@ function to_price_message(appConfig_1, quote_amount_usd_1, tx_price_1, quote_ask
72
72
  unique_orderbook_id,
73
73
  is_reverse_token,
74
74
  tokenA: {
75
+ _id: tokenA._id || (tokenA.address || '').toLowerCase(),
75
76
  symbol: tokenA.symbol,
76
77
  address: tokenA.address,
77
- address_hex: tokenA.address_hex,
78
78
  decimals: tokenA.decimals,
79
79
  name: tokenA.name,
80
80
  enable: true,
81
81
  },
82
82
  tokenB: {
83
+ _id: tokenB._id || (tokenB.address || '').toLowerCase(),
83
84
  symbol: tokenB.symbol,
84
85
  address: tokenB.address,
85
- address_hex: tokenB.address_hex,
86
86
  decimals: tokenB.decimals,
87
87
  name: tokenB.name,
88
88
  enable: true,
@@ -1,2 +1,2 @@
1
1
  import { CHAIN_ID, FormattedTokenInfo } from "../index";
2
- export declare const cache_token_config: (chain_id: CHAIN_ID, valid_tokens_map: Map<string, FormattedTokenInfo[]>) => Promise<void>;
2
+ export declare const cache_token_config: (chain_id: CHAIN_ID, tokens_map: Map<string, FormattedTokenInfo>) => Promise<void>;
@@ -18,20 +18,22 @@ const core_env_1 = require("../appconfig/core_env");
18
18
  const index_1 = require("../index");
19
19
  const service_ports_1 = require("../constants/service_ports");
20
20
  const is_not_arb_token_1 = require("./is_not_arb_token");
21
- const cache_token_config = (chain_id, valid_tokens_map) => __awaiter(void 0, void 0, void 0, function* () {
21
+ const cache_token_config = (chain_id, tokens_map) => __awaiter(void 0, void 0, void 0, function* () {
22
22
  (0, index_1.log_info)(`cache_token_config start`);
23
23
  const genv = (0, core_env_1.getCoreEnv)();
24
24
  const ccBase = `http://${(genv.config_center_host || '127.0.0.1').trim()}:${service_ports_1.SERVICE_PORT.CONFIG_CENTER_HTTP}/${chain_id}/config`;
25
25
  const config_token_url = `${ccBase}/tokens`;
26
26
  const valid_tokens = [];
27
- for (const [, value] of valid_tokens_map) {
28
- if (!value || value.length !== 1)
27
+ for (const token of tokens_map.values()) {
28
+ if (!(token === null || token === void 0 ? void 0 : token.symbol) || !token.address)
29
29
  continue;
30
- const token = value[0];
31
- const std_token = {
30
+ if ((0, is_not_arb_token_1.is_not_arb_token)(token.symbol))
31
+ continue;
32
+ valid_tokens.push({
33
+ _id: (token.address || '').toLowerCase(),
32
34
  symbol: token.symbol,
35
+ raw_symbol: token.raw_symbol,
33
36
  address: token.address,
34
- address_hex: '',
35
37
  decimals: token.decimals,
36
38
  name: token.name,
37
39
  is_token2022: token.is_token_2022,
@@ -39,10 +41,7 @@ const cache_token_config = (chain_id, valid_tokens_map) => __awaiter(void 0, voi
39
41
  update_time: (0, index_1.getCurDateTime)(),
40
42
  alias: (0, index_1.get_token_symbol_alias)(token.address),
41
43
  enable: true,
42
- };
43
- if ((0, is_not_arb_token_1.is_not_arb_token)(std_token.symbol))
44
- continue;
45
- valid_tokens.push(std_token);
44
+ });
46
45
  }
47
46
  if (valid_tokens.length > 0) {
48
47
  const res = (yield axios_1.default.post(config_token_url, valid_tokens, {
@@ -4,4 +4,3 @@ export * from './is_not_arb_token';
4
4
  export * from './price';
5
5
  export * from './token_util';
6
6
  export * from './types';
7
- export * from './zh_pinyin_map';
@@ -20,4 +20,3 @@ __exportStar(require("./is_not_arb_token"), exports);
20
20
  __exportStar(require("./price"), exports);
21
21
  __exportStar(require("./token_util"), exports);
22
22
  __exportStar(require("./types"), exports);
23
- __exportStar(require("./zh_pinyin_map"), exports);
@@ -20,6 +20,9 @@ export declare class TokenPriceSyncer {
20
20
  constructor(opts: TokenPriceSyncerOpts);
21
21
  start(): Promise<void>;
22
22
  stop(): void;
23
+ private _is_running;
24
+ get is_running(): boolean;
23
25
  runOnce(): Promise<void>;
26
+ private _runOnceInner;
24
27
  private loadTradeTokensFromCache;
25
28
  }
@@ -15,6 +15,7 @@ class TokenPriceSyncer {
15
15
  constructor(opts) {
16
16
  this.timer = null;
17
17
  this.trade_token_map = new Map();
18
+ this._is_running = false;
18
19
  this.opts = opts;
19
20
  }
20
21
  start() {
@@ -36,7 +37,23 @@ class TokenPriceSyncer {
36
37
  this.timer = null;
37
38
  }
38
39
  }
40
+ get is_running() { return this._is_running; }
39
41
  runOnce() {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ if (this._is_running) {
44
+ (0, __1.log_warn)(`[TokenPriceSyncer] previous run still in flight, skip this tick`);
45
+ return;
46
+ }
47
+ this._is_running = true;
48
+ try {
49
+ yield this._runOnceInner();
50
+ }
51
+ finally {
52
+ this._is_running = false;
53
+ }
54
+ });
55
+ }
56
+ _runOnceInner() {
40
57
  return __awaiter(this, void 0, void 0, function* () {
41
58
  var _a;
42
59
  yield this.loadTradeTokensFromCache();
@@ -1,3 +1,2 @@
1
- export declare const formate_token_symbol: (symbol: string) => string;
2
1
  export declare const is_valid_symbol: (symbol: string) => boolean;
3
2
  export declare function get_token_symbol_alias(token_address: string): string;
@@ -1,26 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.is_valid_symbol = exports.formate_token_symbol = void 0;
3
+ exports.is_valid_symbol = void 0;
4
4
  exports.get_token_symbol_alias = get_token_symbol_alias;
5
5
  const __1 = require("..");
6
- const formate_token_symbol = (symbol) => {
7
- return symbol.toUpperCase()
8
- .replace('$', '')
9
- .replace('WSOL', 'SOL')
10
- .replace('WBTC', 'BTC')
11
- .replace('WETH', 'ETH').replace('WHETH', 'ETH')
12
- .replace('WBNB', 'BNB')
13
- .replace('WTRX', 'TRX')
14
- .replace('WAVAX', 'AVAX')
15
- .replace('WOKB', 'OKB')
16
- .trim();
17
- };
18
- exports.formate_token_symbol = formate_token_symbol;
19
6
  const is_valid_symbol = (symbol) => {
20
- return !(0, __1.isEmpty)(symbol) && /^[a-zA-Z0-9_.+-]+$/.test(symbol);
7
+ return !(0, __1.isEmpty)(symbol) && /^[A-Z0-9]+$/.test(symbol);
21
8
  };
22
9
  exports.is_valid_symbol = is_valid_symbol;
23
10
  function get_token_symbol_alias(token_address) {
24
- let token = (0, __1.get_fixed_symbol_address)().find(e => e.address === token_address);
11
+ const lc = (token_address || '').toLowerCase();
12
+ let token = (0, __1.get_fixed_symbol_address)().find(e => (e.address || '').toLowerCase() === lc);
25
13
  return (token === null || token === void 0 ? void 0 : token.alias) || '';
26
14
  }
@@ -11,16 +11,14 @@ export interface TokenFixedSymbolAddress {
11
11
  address: string;
12
12
  is_token2022?: boolean;
13
13
  }
14
- export interface ZhPinyinRow {
15
- zh: string;
16
- pinyin: string;
17
- }
18
14
  export interface FormattedTokenInfo {
19
15
  chainId: number;
16
+ _id: string;
20
17
  address: string;
21
18
  programId: string;
22
19
  logoURI: string;
23
20
  symbol: string;
21
+ raw_symbol?: string;
24
22
  name: string;
25
23
  decimals: number;
26
24
  is_token_2022: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-core",
3
- "version": "3.1.40",
3
+ "version": "3.1.42",
4
4
  "description": "Common types and utilities for trading systems - use `npm run push` to publish",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/types/index.d.ts CHANGED
@@ -9,9 +9,18 @@ export interface StandardTokenConfigType {
9
9
  }
10
10
 
11
11
  export interface StandardTokenInfoType {
12
+ /**
13
+ * 内部对比/Map key 专用:address.toLowerCase()
14
+ * 任何 token 比较、Map 索引、fixed_symbol 匹配都用这个;
15
+ * 链上调用(RPC / tx encoding)必须用 `address` 原值。
16
+ */
17
+ _id: string
18
+ /** 标准化后的 symbol:格式 [A-Z0-9]+,所有缓存/查询/比较都用这个字段 */
12
19
  symbol: string
20
+ /** 原始来源 symbol(Gecko / on-chain ERC20):用于排查问题、UI 显示,不参与匹配 */
21
+ raw_symbol?: string
22
+ /** 链上原始地址(Tron/Solana 区分大小写,必须保留原值) */
13
23
  address: string
14
- address_hex: string
15
24
  decimals: number
16
25
  name: string
17
26
  is_token2022?: boolean
@@ -37,6 +46,11 @@ export interface StandardDexPoolConfigType {
37
46
  }
38
47
 
39
48
  export interface StandardPoolInfoType {
49
+ /**
50
+ * 内部对比/Map key 专用:pool_address.toLowerCase()
51
+ * 任何 pool 比较、Map 索引都用这个;链上交互必须用 `pool_address` 原值。
52
+ */
53
+ _id: string
40
54
  program_id?: string // Raydium Liquidity Pool V4
41
55
  authority?: string // Vault owner
42
56
  subscribe_type?: string // ws, grpc
@@ -44,7 +58,6 @@ export interface StandardPoolInfoType {
44
58
  dex_id: string
45
59
  pool_name: string
46
60
  pool_address: string
47
- pool_address_hex: string
48
61
  tokenA: StandardTokenInfoType
49
62
  tokenB: StandardTokenInfoType
50
63
  vaultA?: string