@clonegod/ttd-core 3.1.25 → 3.1.27

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.
@@ -68,6 +68,7 @@ exports.ALERT_CODES = {
68
68
  },
69
69
  INFRA_PROCESS_CRASH_LOOP: { code: 'INFRA_PROCESS_CRASH_LOOP', severity: S.CRITICAL, category: 'infra', desc: '进程 5 分钟内反复重启', suggested_action: '停用该进程' },
70
70
  INFRA_ENV_MISSING: { code: 'INFRA_ENV_MISSING', severity: S.ERROR, category: 'infra', desc: '关键 env 未配置' },
71
+ CONFIG_TOKEN_DUPLICATE: { code: 'CONFIG_TOKEN_DUPLICATE', severity: S.WARN, category: 'infra', desc: '同一 address 对应多个 symbol 或 pool 引用缺失 token(配置垃圾数据)', suggested_action: '人工清理 token-list.json / pool-list.json / fixed_symbol_address.json' },
71
72
  ANALYZE_UPSTREAM_MISSING: { code: 'ANALYZE_UPSTREAM_MISSING', severity: S.WARN, category: 'analyze', desc: '上游消息缺环节(PreTrade/OnTrade/PostTrade)' },
72
73
  ANALYZE_SQLITE_FULL: { code: 'ANALYZE_SQLITE_FULL', severity: S.WARN, category: 'analyze', desc: 'SQLite 接近容量上限' },
73
74
  ANALYZE_SQLITE_WRITE_FAIL: { code: 'ANALYZE_SQLITE_WRITE_FAIL', severity: S.ERROR, category: 'analyze', desc: 'SQLite 写失败' },
@@ -46,6 +46,7 @@ registerLogRules([
46
46
  { pattern: /pricefeed.*(parse failed|not an array|empty)|empty response at page/i,
47
47
  level: 'warn', code: 'THIRDPARTY_DATA_INVALID' },
48
48
  { pattern: /price cache miss/i, level: 'warn', code: 'QUOTE_PRICE_CACHE_MISS' },
49
+ { pattern: /\[token-duplicate\]|\[pool-skip\]/, level: 'warn', code: 'CONFIG_TOKEN_DUPLICATE' },
49
50
  { pattern: /sqlite.*(disk|full|i\/o error|cleanup error|write.*fail)/i,
50
51
  level: 'error', code: 'ANALYZE_SQLITE_WRITE_FAIL' },
51
52
  { pattern: /db size.*\d+mb.*limit/i, level: 'warn', code: 'ANALYZE_SQLITE_FULL' },
@@ -33,7 +33,34 @@ export declare class ArbCache {
33
33
  get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
34
34
  publish_token_change_event(): Promise<void>;
35
35
  cache_pool_list(): Promise<void>;
36
- set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): void;
36
+ private scan_duplicate_address;
37
+ resolve_duplicate_address(authoritative_mappings: Array<{
38
+ address: string;
39
+ symbol: string;
40
+ }>): Promise<{
41
+ cleaned_tokens: Array<{
42
+ address: string;
43
+ dropped_symbols: string[];
44
+ kept_symbol: string;
45
+ }>;
46
+ cleaned_pools: Array<{
47
+ pool_address: string;
48
+ pool_name: string;
49
+ dex_id: string;
50
+ }>;
51
+ skipped_trading_pools: Array<{
52
+ pool_address: string;
53
+ pool_name: string;
54
+ dex_id: string;
55
+ reason: string;
56
+ }>;
57
+ not_found: Array<{
58
+ address: string;
59
+ symbol: string;
60
+ reason: string;
61
+ }>;
62
+ }>;
63
+ set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
37
64
  set_pool_extra(pool: StandardPoolInfoType): void;
38
65
  update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
39
66
  get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;
@@ -17,6 +17,7 @@ const core_env_1 = require("../app_config/core_env");
17
17
  const fs_1 = __importDefault(require("fs"));
18
18
  const path_1 = __importDefault(require("path"));
19
19
  const index_1 = require("../index");
20
+ const fixed_symbol_address_1 = require("../token/fixed_symbol_address");
20
21
  class ArbCache {
21
22
  constructor(env_args) {
22
23
  this.chain_id = env_args.chain_id;
@@ -81,33 +82,23 @@ class ArbCache {
81
82
  return path_1.default.join('config', chain_id, file_name);
82
83
  }
83
84
  refresh_local_cache_token_list(token_list) {
84
- const new_by_addr = new Map();
85
+ const prev_by_symbol = new Map(this.token_symbol_map);
86
+ this.token_symbol_map.clear();
87
+ this.token_address_map.clear();
85
88
  const merged = [];
86
89
  for (const fresh of token_list) {
87
- new_by_addr.set(fresh.address, fresh);
88
- }
89
- const to_delete_addr = [];
90
- for (const [addr, existing] of this.token_address_map) {
91
- const fresh = new_by_addr.get(addr);
92
- if (fresh) {
90
+ const existing = prev_by_symbol.get(fresh.symbol);
91
+ let token_ref;
92
+ if (existing) {
93
93
  Object.assign(existing, fresh);
94
- merged.push(existing);
95
- new_by_addr.delete(addr);
94
+ token_ref = existing;
96
95
  }
97
96
  else {
98
- to_delete_addr.push(addr);
97
+ token_ref = fresh;
99
98
  }
100
- }
101
- for (const addr of to_delete_addr) {
102
- this.token_address_map.delete(addr);
103
- }
104
- for (const fresh of new_by_addr.values()) {
105
- this.token_address_map.set(fresh.address, fresh);
106
- merged.push(fresh);
107
- }
108
- this.token_symbol_map.clear();
109
- for (const token of merged) {
110
- this.token_symbol_map.set(token.symbol, token);
99
+ this.token_symbol_map.set(token_ref.symbol, token_ref);
100
+ this.token_address_map.set(token_ref.address, token_ref);
101
+ merged.push(token_ref);
111
102
  }
112
103
  this.token_list = merged;
113
104
  this.token_list_cache_last_update_time = (0, index_1.getCurDateTime)();
@@ -156,6 +147,7 @@ class ArbCache {
156
147
  }
157
148
  let config = (0, index_1.readFile)(filepath);
158
149
  let token_list = config.token_list.filter(e => e.enable);
150
+ this.scan_duplicate_address(token_list);
159
151
  for (let token of token_list) {
160
152
  let field = token.symbol;
161
153
  let value = JSON.stringify(token);
@@ -254,12 +246,22 @@ class ArbCache {
254
246
  let all_dex_pool = [];
255
247
  let config = (0, index_1.readFile)(filepath);
256
248
  let token_list = yield this.get_token_list();
249
+ const skipped = [];
257
250
  for (let { dex_id, pool_list } of config.dex_list) {
258
251
  pool_list = pool_list.filter(e => e.enable);
259
252
  (0, index_1.log_trace)(`cache_pool_list`, { dex_id, pool_list_len: pool_list.length });
253
+ const valid_pools = [];
260
254
  for (let pool of pool_list) {
261
255
  pool.dex_id = dex_id;
262
- this.set_pool_token_info(pool, token_list);
256
+ if (!this.set_pool_token_info(pool, token_list)) {
257
+ const [t0, t1] = pool.pool_name.split('/');
258
+ const missing = [
259
+ !pool.tokenA ? t0 : null,
260
+ !pool.tokenB ? t1 : null,
261
+ ].filter(Boolean).join(',');
262
+ skipped.push({ pool_address: pool.pool_address, pool_name: pool.pool_name, dex_id, missing });
263
+ continue;
264
+ }
263
265
  this.set_pool_extra(pool);
264
266
  let field = pool.pool_address;
265
267
  let value = JSON.stringify(pool);
@@ -267,14 +269,131 @@ class ArbCache {
267
269
  if (index_1.LOG.debug) {
268
270
  (0, index_1.log_trace)(`cache_pool_list, pool_address=${pool.pool_address}, result: ` + JSON.stringify(result));
269
271
  }
272
+ valid_pools.push(pool);
270
273
  }
271
- all_dex_pool.push(...pool_list);
274
+ all_dex_pool.push(...valid_pools);
275
+ }
276
+ if (skipped.length > 0) {
277
+ const lines = skipped.map((s, i) => ` ${i + 1}. [${s.dex_id}] ${s.pool_name} (pool=${s.pool_address}) — missing symbol(s): ${s.missing}`);
278
+ (0, index_1.log_warn)(`[pool-skip] 跳过 ${skipped.length} 个池子:token 在 token-list 中找不到(多 symbol 冲突 / symbol 已删等),请人工清理 pool-list.json 或相关 token 映射:\n${lines.join('\n')}`);
272
279
  }
273
280
  this.refresh_local_cache_pool_list(all_dex_pool);
274
281
  let result = yield this.loading_cache.hlen(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST);
275
282
  (0, index_1.log_trace)(`cache_pool_list, end`, result);
276
283
  });
277
284
  }
285
+ scan_duplicate_address(token_list) {
286
+ var _a;
287
+ const by_addr = new Map();
288
+ for (const t of token_list) {
289
+ const addr = (t.address || '').toLowerCase();
290
+ if (!addr)
291
+ continue;
292
+ if (!by_addr.has(addr))
293
+ by_addr.set(addr, []);
294
+ by_addr.get(addr).push(t);
295
+ }
296
+ const fixed_by_addr = new Map();
297
+ for (const f of (0, fixed_symbol_address_1.get_fixed_symbol_address)()) {
298
+ fixed_by_addr.set((f.address || '').toLowerCase(), f.symbol);
299
+ }
300
+ const conflicts = [];
301
+ for (const [addr, tokens] of by_addr) {
302
+ if (tokens.length <= 1)
303
+ continue;
304
+ conflicts.push({
305
+ address: addr,
306
+ symbols: tokens.map(t => t.symbol),
307
+ fixed_symbol: (_a = fixed_by_addr.get(addr)) !== null && _a !== void 0 ? _a : null,
308
+ });
309
+ }
310
+ if (conflicts.length === 0)
311
+ return;
312
+ const lines = conflicts.map((c, i) => {
313
+ const auth = c.fixed_symbol ? ` 权威 symbol=${c.fixed_symbol}` : ' 未在 fixed_symbol_address.json 登记(需先登记权威 symbol)';
314
+ return ` ${i + 1}. address=${c.address} symbols=[${c.symbols.join(', ')}]${auth}`;
315
+ });
316
+ (0, index_1.log_warn)(`[token-duplicate] 发现 ${conflicts.length} 个 address 被多 symbol 引用(垃圾数据,需人工清理 token-list.json + pool-list.json + Redis):\n${lines.join('\n')}`);
317
+ }
318
+ resolve_duplicate_address(authoritative_mappings) {
319
+ return __awaiter(this, void 0, void 0, function* () {
320
+ const token_list = yield this.get_token_list();
321
+ const by_addr = new Map();
322
+ for (const t of token_list) {
323
+ const addr = (t.address || '').toLowerCase();
324
+ if (!addr)
325
+ continue;
326
+ if (!by_addr.has(addr))
327
+ by_addr.set(addr, []);
328
+ by_addr.get(addr).push(t);
329
+ }
330
+ const tokens_to_disable = [];
331
+ const cleaned_tokens = [];
332
+ const not_found = [];
333
+ for (const { address, symbol } of authoritative_mappings) {
334
+ const addr = (address || '').toLowerCase();
335
+ const tokens = by_addr.get(addr) || [];
336
+ if (tokens.length === 0) {
337
+ not_found.push({ address: addr, symbol, reason: `address 在 token-list 中未找到` });
338
+ continue;
339
+ }
340
+ const canonical = tokens.find(t => t.symbol === symbol);
341
+ if (!canonical) {
342
+ not_found.push({ address: addr, symbol, reason: `symbol=${symbol} 在 address ${addr} 的条目中未找到(token-list 里 symbols=[${tokens.map(t => t.symbol).join(',')}])` });
343
+ continue;
344
+ }
345
+ const dropped = tokens.filter(t => t.symbol !== symbol);
346
+ if (dropped.length === 0)
347
+ continue;
348
+ for (const t of dropped) {
349
+ tokens_to_disable.push(Object.assign(Object.assign({}, t), { enable: false }));
350
+ }
351
+ cleaned_tokens.push({
352
+ address: addr,
353
+ dropped_symbols: dropped.map(t => t.symbol),
354
+ kept_symbol: symbol,
355
+ });
356
+ }
357
+ const dropped_symbols = new Set(tokens_to_disable.map(t => t.symbol));
358
+ const cleaned_pools = [];
359
+ const skipped_trading_pools = [];
360
+ if (dropped_symbols.size > 0) {
361
+ const pool_list = yield this.get_pool_list();
362
+ const trade_pool_set = new Set((yield this.get_all_trade_pools()).map(p => p.pool_address));
363
+ const pools_to_disable_by_dex = new Map();
364
+ for (const p of pool_list) {
365
+ const [t0, t1] = (p.pool_name || '').split('/');
366
+ if (!dropped_symbols.has(t0) && !dropped_symbols.has(t1))
367
+ continue;
368
+ if (trade_pool_set.has(p.pool_address)) {
369
+ skipped_trading_pools.push({
370
+ pool_address: p.pool_address,
371
+ pool_name: p.pool_name,
372
+ dex_id: p.dex_id,
373
+ reason: 'pool in trading config (protected)',
374
+ });
375
+ continue;
376
+ }
377
+ if (!pools_to_disable_by_dex.has(p.dex_id))
378
+ pools_to_disable_by_dex.set(p.dex_id, []);
379
+ pools_to_disable_by_dex.get(p.dex_id).push(Object.assign(Object.assign({}, p), { enable: false }));
380
+ cleaned_pools.push({ pool_address: p.pool_address, pool_name: p.pool_name, dex_id: p.dex_id });
381
+ }
382
+ if (pools_to_disable_by_dex.size > 0) {
383
+ const dex_pool_list = [];
384
+ for (const [dex_id, pools] of pools_to_disable_by_dex) {
385
+ dex_pool_list.push({ dex_id: dex_id, pool_list: pools });
386
+ }
387
+ yield this.update_pool_list(dex_pool_list);
388
+ }
389
+ }
390
+ if (tokens_to_disable.length > 0) {
391
+ yield this.update_token_list(tokens_to_disable);
392
+ }
393
+ (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} 个`);
394
+ return { cleaned_tokens, cleaned_pools, skipped_trading_pools, not_found };
395
+ });
396
+ }
278
397
  set_pool_token_info(pool, token_info_list) {
279
398
  if (index_1.LOG.debug) {
280
399
  (0, index_1.log_trace)(`set_pool_token_info`);
@@ -282,13 +401,7 @@ class ArbCache {
282
401
  let [token0, token1] = pool.pool_name.split('/');
283
402
  pool.tokenA = token_info_list.find(e => e.symbol === token0);
284
403
  pool.tokenB = token_info_list.find(e => e.symbol === token1);
285
- if (!pool.tokenA || !pool.tokenB) {
286
- (0, index_1.log_warn)('set_pool_token_info failed! tokenA or tokenB not exists!', {
287
- pool_info: pool,
288
- token_symbol_list: token_info_list.map(e => e.symbol)
289
- });
290
- throw new Error(`set_pool_token_info failed! tokenA or tokenB not exists!`);
291
- }
404
+ return !!(pool.tokenA && pool.tokenB);
292
405
  }
293
406
  set_pool_extra(pool) {
294
407
  if (index_1.LOG.debug) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-core",
3
- "version": "3.1.25",
3
+ "version": "3.1.27",
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
@@ -599,7 +599,13 @@ export declare class ArbCache {
599
599
  get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
600
600
  publish_token_change_event(): Promise<void>;
601
601
  cache_pool_list(): Promise<void>;
602
- set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): void;
602
+ set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
603
+ resolve_duplicate_address(authoritative_mappings: Array<{ address: string; symbol: string }>): Promise<{
604
+ cleaned_tokens: Array<{ address: string; dropped_symbols: string[]; kept_symbol: string }>;
605
+ cleaned_pools: Array<{ pool_address: string; pool_name: string; dex_id: string }>;
606
+ skipped_trading_pools: Array<{ pool_address: string; pool_name: string; dex_id: string; reason: string }>;
607
+ not_found: Array<{ address: string; symbol: string; reason: string }>;
608
+ }>;
603
609
  set_pool_extra(pool: StandardPoolInfoType): void;
604
610
  update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
605
611
  get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;