@clonegod/ttd-core 3.1.24 → 3.1.26

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 (31) hide show
  1. package/dist/alert/codes.js +2 -0
  2. package/dist/alert/log_rules.js +2 -0
  3. package/dist/app_config/AppConfig.d.ts +1 -3
  4. package/dist/app_config/AppConfig.js +0 -11
  5. package/dist/app_config/EnvArgs.d.ts +2 -1
  6. package/dist/app_config/env_registry.js +2 -1
  7. package/dist/cache/arb_cache.d.ts +3 -3
  8. package/dist/cache/arb_cache.js +89 -46
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +0 -1
  11. package/dist/market_price/estimate_token_amount.js +3 -3
  12. package/dist/token/price/get_bsc_token_price.d.ts +4 -1
  13. package/dist/token/price/get_bsc_token_price.js +27 -12
  14. package/dist/token/price/get_eth_token_price.d.ts +4 -1
  15. package/dist/token/price/get_eth_token_price.js +27 -12
  16. package/dist/token/price/get_solana_token_price.d.ts +4 -1
  17. package/dist/token/price/get_solana_token_price.js +27 -12
  18. package/dist/token/price/get_sui_token_price.d.ts +4 -1
  19. package/dist/token/price/get_sui_token_price.js +27 -12
  20. package/dist/token/price/get_tron_token_price.d.ts +4 -1
  21. package/dist/token/price/get_tron_token_price.js +27 -12
  22. package/dist/token/price/get_xlayer_token_price.d.ts +4 -1
  23. package/dist/token/price/get_xlayer_token_price.js +27 -12
  24. package/dist/token/price/index.d.ts +2 -0
  25. package/dist/token/price/index.js +2 -0
  26. package/dist/token/price/price_cache.d.ts +6 -1
  27. package/dist/token/price/price_cache.js +67 -76
  28. package/dist/token/price/token_price_syncer.d.ts +25 -0
  29. package/dist/token/price/token_price_syncer.js +80 -0
  30. package/package.json +1 -1
  31. package/types/index.d.ts +3 -1
@@ -16,6 +16,7 @@ exports.ALERT_CODES = {
16
16
  QUOTE_TICK_STALE: { code: 'QUOTE_TICK_STALE', severity: S.ERROR, category: 'quote', desc: 'tick 数据过期但仍在报价', suggested_action: '风险提示' },
17
17
  QUOTE_PRICE_ANOMALY: { code: 'QUOTE_PRICE_ANOMALY', severity: S.ERROR, category: 'quote', desc: '报价偏离参考价 > 阈值', suggested_action: '检查数据源' },
18
18
  QUOTE_INTERVAL_BREACH: { code: 'QUOTE_INTERVAL_BREACH', severity: S.WARN, category: 'quote', desc: '询价间隔超出预期' },
19
+ QUOTE_PRICE_CACHE_MISS: { code: 'QUOTE_PRICE_CACHE_MISS', severity: S.WARN, category: 'quote', desc: 'token 在系统里但 cache 无价(scheduler 未刷到)', suggested_action: '观察 market-data TokenPriceSyncer 是否正常' },
19
20
  TRADE_ENCODE_FAIL: { code: 'TRADE_ENCODE_FAIL', severity: S.ERROR, category: 'trade', desc: 'calldata 编码失败', suggested_action: '检查订单参数' },
20
21
  TRADE_SIGN_FAIL: { code: 'TRADE_SIGN_FAIL', severity: S.ERROR, category: 'trade', desc: '交易签名失败', suggested_action: '检查 caller 钱包' },
21
22
  TRADE_NONCE_CONFLICT: {
@@ -67,6 +68,7 @@ exports.ALERT_CODES = {
67
68
  },
68
69
  INFRA_PROCESS_CRASH_LOOP: { code: 'INFRA_PROCESS_CRASH_LOOP', severity: S.CRITICAL, category: 'infra', desc: '进程 5 分钟内反复重启', suggested_action: '停用该进程' },
69
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' },
70
72
  ANALYZE_UPSTREAM_MISSING: { code: 'ANALYZE_UPSTREAM_MISSING', severity: S.WARN, category: 'analyze', desc: '上游消息缺环节(PreTrade/OnTrade/PostTrade)' },
71
73
  ANALYZE_SQLITE_FULL: { code: 'ANALYZE_SQLITE_FULL', severity: S.WARN, category: 'analyze', desc: 'SQLite 接近容量上限' },
72
74
  ANALYZE_SQLITE_WRITE_FAIL: { code: 'ANALYZE_SQLITE_WRITE_FAIL', severity: S.ERROR, category: 'analyze', desc: 'SQLite 写失败' },
@@ -45,6 +45,8 @@ registerLogRules([
45
45
  level: 'error', code: 'THIRDPARTY_AUTH_FAIL' },
46
46
  { pattern: /pricefeed.*(parse failed|not an array|empty)|empty response at page/i,
47
47
  level: 'warn', code: 'THIRDPARTY_DATA_INVALID' },
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' },
48
50
  { pattern: /sqlite.*(disk|full|i\/o error|cleanup error|write.*fail)/i,
49
51
  level: 'error', code: 'ANALYZE_SQLITE_WRITE_FAIL' },
50
52
  { pattern: /db size.*\d+mb.*limit/i, level: 'warn', code: 'ANALYZE_SQLITE_FULL' },
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "events";
2
- import { StandardTokenInfoType, TradeRuntimeType } from "../../types";
2
+ import { TradeRuntimeType } from "../../types";
3
3
  import { ArbCache, ArbEventSubscriber } from "../cache";
4
4
  import { EnvArgs } from "./EnvArgs";
5
5
  export declare class AppConfig extends EventEmitter {
@@ -11,6 +11,4 @@ export declare class AppConfig extends EventEmitter {
11
11
  constructor();
12
12
  init(): Promise<void>;
13
13
  subscribe_config_change(): Promise<void>;
14
- cache_token_market_price(token_info_with_price: StandardTokenInfoType, ttl?: number): Promise<void>;
15
- get_token_market_price(symbol: string): Promise<StandardTokenInfoType>;
16
14
  }
@@ -47,16 +47,5 @@ class AppConfig extends events_1.default {
47
47
  yield this.arb_event_subscriber.subscribe_config_change_event(refresh_trade_runtime);
48
48
  });
49
49
  }
50
- cache_token_market_price(token_info_with_price_1) {
51
- return __awaiter(this, arguments, void 0, function* (token_info_with_price, ttl = -1) {
52
- token_info_with_price.update_time = (0, __1.getCurDateTime)();
53
- yield this.arb_cache.cache_token_market_price(token_info_with_price, ttl);
54
- });
55
- }
56
- get_token_market_price(symbol) {
57
- return __awaiter(this, void 0, void 0, function* () {
58
- return yield this.arb_cache.get_token_market_price(symbol);
59
- });
60
- }
61
50
  }
62
51
  exports.AppConfig = AppConfig;
@@ -10,6 +10,7 @@ export declare class EnvArgs {
10
10
  redis_host: string;
11
11
  redis_port: string;
12
12
  config_center_host: string;
13
+ market_data_host: string;
13
14
  enable_init_cache: string;
14
15
  trade_analyze_url: string;
15
16
  trade_analyze_host: string;
@@ -31,7 +32,7 @@ export declare class EnvArgs {
31
32
  trade_pair: string;
32
33
  trade_log_report_cex_url: string;
33
34
  trade_log_report_cex_data_type: string;
34
- token_price_cache_seconds: number;
35
+ token_price_refresh_interval_seconds: number;
35
36
  token_market_price_url: string;
36
37
  wallet_dir: string;
37
38
  encryption_key: string;
@@ -24,6 +24,7 @@ registerEnvVars({
24
24
  grpc_endpoint: { env: 'GRPC_ENDPOINT', type: 'string', default: '', desc: 'gRPC 端点' },
25
25
  grpc_token: { env: 'GRPC_TOKEN', type: 'string', default: '', sensitive: true, desc: 'gRPC 认证 token' },
26
26
  config_center_host: { env: 'CONFIG_CENTER_HOST', type: 'string', default: '127.0.0.1', desc: '配置中心' },
27
+ market_data_host: { env: 'MARKET_DATA_HOST', type: 'string', default: '127.0.0.1', desc: 'market-data 服务(L3 现查 / fetch-pool 调用)' },
27
28
  enable_init_cache: { env: 'ENABLE_INIT_CACHE', type: 'string', default: '', desc: '仅 config-center:是否初始化缓存(空或 true 视为启用;false 则跳过)' },
28
29
  trade_analyze_host: { env: 'TRADE_ANALYZE_HOST', type: 'string', default: '', desc: 'analyze 上报地址(IP);为空则不上报' },
29
30
  auto_quote_enable: { env: 'AUTO_QUOTE_ENABLE', type: 'boolean', default: false, desc: '自动报价开关' },
@@ -44,7 +45,7 @@ registerEnvVars({
44
45
  encryption_key: { env: 'ENCRYPTION_KEY', type: 'string', default: '', sensitive: true, desc: '对称加密密钥(用于钱包文件等)' },
45
46
  trade_analyze_url: { env: 'TRADE_ANALYZE_URL', type: 'string', default: '', desc: 'analyze HTTP 基址(旧 EnvArgs;未配则 analyze 上报走默认拼接)' },
46
47
  providers_file_path: { env: 'PROVIDERS_FILE_PATH', type: 'string', default: '', desc: 'stream-quote providers.json 绝对路径;留空则仅写 Redis' },
47
- token_price_cache_seconds: { env: 'TOKEN_PRICE_CACHE_SECONDS', type: 'number', default: 1200, desc: 'token 价格缓存时间(s)' },
48
+ token_price_refresh_interval_seconds: { env: 'TOKEN_PRICE_REFRESH_INTERVAL_SECONDS', type: 'number', default: 600, desc: 'TokenPriceSyncer 刷价周期(s):market-data 拉价并推送给 config-center 写 token_list 的频率' },
48
49
  token_market_price_url: { env: 'TOKEN_MARKET_PRICE_URL', type: 'string', default: '', desc: '【使用方: ttd-core estimate_token_amount】config-center 市价估算 HTTP 基址;为空则跳过远程回退' },
49
50
  fixed_symbol_address_file: { env: 'FIXED_SYMBOL_ADDRESS_FILE', type: 'string', default: '', desc: '固定 symbol/address 映射文件路径(覆盖默认按 CHAIN_ID 推断的路径)' },
50
51
  zh_pinyin_map_file: { env: 'ZH_PINYIN_MAP_FILE', type: 'string', default: '', desc: '中文拼音映射文件路径(覆盖默认路径)' },
@@ -33,7 +33,8 @@ 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
+ set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
37
38
  set_pool_extra(pool: StandardPoolInfoType): void;
38
39
  update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
39
40
  get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;
@@ -51,8 +52,7 @@ export declare class ArbCache {
51
52
  create_trade_runtime(chain_id: string, group_id: string, dex_id: string, pair_name: string): Promise<TradeRuntimeType>;
52
53
  find_pair_in_trade_config(pair_name: string, trade_config: TradeServiceConfigType): TradePairType;
53
54
  find_pair_dex_in_trade_config(dex_id: string, pair_config: TradePairType): TradePairDexPoolsType;
54
- cache_token_market_price(token_with_price: StandardTokenInfoType, ttl?: number): Promise<void>;
55
- get_token_market_price(symbol: string): Promise<StandardTokenInfoType>;
55
+ update_token_prices(input_tokens: StandardTokenInfoType[]): Promise<number>;
56
56
  cache_price_message(price_msg: PriceMessageType, ttl?: number): Promise<void>;
57
57
  get_price_message(price_id: string): Promise<PriceMessageType>;
58
58
  cache_order_message(order_msg: OrderMessageType, ttl?: number): Promise<{
@@ -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,18 +82,30 @@ 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
- this.token_list = token_list;
85
+ const prev_by_symbol = new Map(this.token_symbol_map);
85
86
  this.token_symbol_map.clear();
86
87
  this.token_address_map.clear();
87
- for (let token of token_list) {
88
- this.token_symbol_map.set(token.symbol, token);
89
- this.token_address_map.set(token.address, token);
88
+ const merged = [];
89
+ for (const fresh of token_list) {
90
+ const existing = prev_by_symbol.get(fresh.symbol);
91
+ let token_ref;
92
+ if (existing) {
93
+ Object.assign(existing, fresh);
94
+ token_ref = existing;
95
+ }
96
+ else {
97
+ token_ref = fresh;
98
+ }
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);
90
102
  }
103
+ this.token_list = merged;
91
104
  this.token_list_cache_last_update_time = (0, index_1.getCurDateTime)();
92
105
  (0, index_1.log_trace)('refresh_local_cache_token_list, success', {
93
106
  token_list_len: this.token_list.length,
94
107
  token_symbol_map_size: this.token_symbol_map.size,
95
- token_address_map_szie: this.token_address_map.size
108
+ token_address_map_szie: this.token_address_map.size,
96
109
  });
97
110
  }
98
111
  refresh_local_cache_pool_list(pool_list) {
@@ -134,6 +147,7 @@ class ArbCache {
134
147
  }
135
148
  let config = (0, index_1.readFile)(filepath);
136
149
  let token_list = config.token_list.filter(e => e.enable);
150
+ this.scan_duplicate_address(token_list);
137
151
  for (let token of token_list) {
138
152
  let field = token.symbol;
139
153
  let value = JSON.stringify(token);
@@ -141,9 +155,6 @@ class ArbCache {
141
155
  if (index_1.LOG.debug) {
142
156
  (0, index_1.log_trace)(`cache_token_list, symbol=${field}, result: ` + JSON.stringify(result));
143
157
  }
144
- if (token.market_price) {
145
- this.cache_token_market_price(token, -1);
146
- }
147
158
  }
148
159
  this.refresh_local_cache_token_list(token_list);
149
160
  let result = yield this.loading_cache.hlen(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST);
@@ -235,12 +246,22 @@ class ArbCache {
235
246
  let all_dex_pool = [];
236
247
  let config = (0, index_1.readFile)(filepath);
237
248
  let token_list = yield this.get_token_list();
249
+ const skipped = [];
238
250
  for (let { dex_id, pool_list } of config.dex_list) {
239
251
  pool_list = pool_list.filter(e => e.enable);
240
252
  (0, index_1.log_trace)(`cache_pool_list`, { dex_id, pool_list_len: pool_list.length });
253
+ const valid_pools = [];
241
254
  for (let pool of pool_list) {
242
255
  pool.dex_id = dex_id;
243
- 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
+ }
244
265
  this.set_pool_extra(pool);
245
266
  let field = pool.pool_address;
246
267
  let value = JSON.stringify(pool);
@@ -248,14 +269,52 @@ class ArbCache {
248
269
  if (index_1.LOG.debug) {
249
270
  (0, index_1.log_trace)(`cache_pool_list, pool_address=${pool.pool_address}, result: ` + JSON.stringify(result));
250
271
  }
272
+ valid_pools.push(pool);
251
273
  }
252
- 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')}`);
253
279
  }
254
280
  this.refresh_local_cache_pool_list(all_dex_pool);
255
281
  let result = yield this.loading_cache.hlen(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST);
256
282
  (0, index_1.log_trace)(`cache_pool_list, end`, result);
257
283
  });
258
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
+ }
259
318
  set_pool_token_info(pool, token_info_list) {
260
319
  if (index_1.LOG.debug) {
261
320
  (0, index_1.log_trace)(`set_pool_token_info`);
@@ -263,13 +322,7 @@ class ArbCache {
263
322
  let [token0, token1] = pool.pool_name.split('/');
264
323
  pool.tokenA = token_info_list.find(e => e.symbol === token0);
265
324
  pool.tokenB = token_info_list.find(e => e.symbol === token1);
266
- if (!pool.tokenA || !pool.tokenB) {
267
- (0, index_1.log_warn)('set_pool_token_info failed! tokenA or tokenB not exists!', {
268
- pool_info: pool,
269
- token_symbol_list: token_info_list.map(e => e.symbol)
270
- });
271
- throw new Error(`set_pool_token_info failed! tokenA or tokenB not exists!`);
272
- }
325
+ return !!(pool.tokenA && pool.tokenB);
273
326
  }
274
327
  set_pool_extra(pool) {
275
328
  if (index_1.LOG.debug) {
@@ -644,36 +697,26 @@ class ArbCache {
644
697
  let pair_dex_config = pair_config.dex_list.find(e => e.dex_id === dex_id);
645
698
  return pair_dex_config;
646
699
  }
647
- cache_token_market_price(token_with_price_1) {
648
- return __awaiter(this, arguments, void 0, function* (token_with_price, ttl = -1) {
649
- (0, index_1.log_trace)(`cache_market_price, start`);
650
- if ((0, index_1.isEmpty)(token_with_price.market_price)) {
651
- (0, index_1.log_warn)(`cache_token_market_price skip: empty market_price, symbol=${token_with_price.symbol}, address=${token_with_price.address}`);
652
- return;
653
- }
654
- let { symbol } = token_with_price;
655
- let field = (0, index_1.format_symbol_name)(symbol);
656
- let value = JSON.stringify(token_with_price);
657
- let result = yield this.loading_cache.hset_ex(this.chain_id, index_1.CACHE_KEY_TYPE.MARKET_TOKEN_PRICE, field, value, ttl);
658
- (0, index_1.log_trace)('cache_market_price, end', result);
659
- });
660
- }
661
- get_token_market_price(symbol) {
700
+ update_token_prices(input_tokens) {
662
701
  return __awaiter(this, void 0, void 0, function* () {
663
- try {
664
- let id = (0, index_1.format_symbol_name)(symbol);
665
- let res = yield this.loading_cache.hget(this.chain_id, index_1.CACHE_KEY_TYPE.MARKET_TOKEN_PRICE, id);
666
- if (index_1.LOG.debug) {
667
- (0, index_1.log_trace)(`get_token_market_price, symbol=${symbol}, res=`, res);
668
- }
669
- let { key, field, value } = res;
670
- let token_with_price = JSON.parse(value);
671
- return token_with_price;
672
- }
673
- catch (err) {
674
- (0, index_1.log_error)('get_token_market_price error!!! key expired ???', err);
675
- throw new Error(`get_token_market_price error! err=${err.message}`);
676
- }
702
+ if ((0, index_1.isEmpty)(input_tokens))
703
+ return 0;
704
+ let count = 0;
705
+ for (const token of input_tokens) {
706
+ if (!(token === null || token === void 0 ? void 0 : token.symbol) || (0, index_1.isEmpty)(token.market_price))
707
+ continue;
708
+ const field = (0, index_1.format_symbol_name)(token.symbol);
709
+ yield this.loading_cache.hset(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_TOKEN_LIST, field, JSON.stringify(token));
710
+ count++;
711
+ }
712
+ if (count > 0) {
713
+ yield this.redis_event_publisher.publish_config_change_event({
714
+ event_type: index_1.REDIS_EVENT_TYPE_CONFIG_CHANGE.TOKEN_CONFIG_CHANGE,
715
+ event_time: Date.now(),
716
+ });
717
+ }
718
+ (0, index_1.log_trace)(`update_token_prices, end. count=${count}`);
719
+ return count;
677
720
  });
678
721
  }
679
722
  cache_price_message(price_msg_1) {
package/dist/index.d.ts CHANGED
@@ -115,7 +115,6 @@ export declare enum OnChainDataSubscribeType {
115
115
  GRPC = "grpc"
116
116
  }
117
117
  export declare enum CACHE_KEY_TYPE {
118
- MARKET_TOKEN_PRICE = "m:price",
119
118
  CONFIG_TOKEN_LIST = "c:token",
120
119
  CONFIG_POOL_LIST = "c:pool",
121
120
  CONFIG_PAIR_LIST = "c:pair",
package/dist/index.js CHANGED
@@ -207,7 +207,6 @@ var OnChainDataSubscribeType;
207
207
  })(OnChainDataSubscribeType || (exports.OnChainDataSubscribeType = OnChainDataSubscribeType = {}));
208
208
  var CACHE_KEY_TYPE;
209
209
  (function (CACHE_KEY_TYPE) {
210
- CACHE_KEY_TYPE["MARKET_TOKEN_PRICE"] = "m:price";
211
210
  CACHE_KEY_TYPE["CONFIG_TOKEN_LIST"] = "c:token";
212
211
  CACHE_KEY_TYPE["CONFIG_POOL_LIST"] = "c:pool";
213
212
  CACHE_KEY_TYPE["CONFIG_PAIR_LIST"] = "c:pair";
@@ -61,12 +61,12 @@ class EstimateAmountInToken {
61
61
  }
62
62
  else {
63
63
  if (index_1.LOG.debug) {
64
- (0, index_1.log_debug)(`try-2 get market price from remote cache`);
64
+ (0, index_1.log_debug)(`try-2 get market price from in-memory token_address_map`);
65
65
  }
66
- token_info = yield arb_cache.get_token_market_price(symbol);
66
+ token_info = arb_cache.token_symbol_map.get(symbol);
67
67
  set_tmp_market_price(token_info);
68
68
  if (index_1.LOG.debug) {
69
- (0, index_1.log_debug)(`try-2 get market price from remote cache, res=`, token_info);
69
+ (0, index_1.log_debug)(`try-2 get market price from in-memory map, res=`, token_info);
70
70
  }
71
71
  }
72
72
  let market_price = token_info === null || token_info === void 0 ? void 0 : token_info.market_price;
@@ -1,2 +1,5 @@
1
1
  import { FormattedTokenPrice } from "../types";
2
- export declare function get_bsc_token_price_info(addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
2
+ export type PriceSource = 'cache_only' | 'cache_first' | 'force_fetch';
3
+ export declare function get_bsc_token_price_info(addresses: string[], opts?: {
4
+ source?: PriceSource;
5
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -15,17 +15,19 @@ const index_1 = require("../../index");
15
15
  const defi_llama_1 = require("./defi_llama");
16
16
  const gecko_terminal_1 = require("./gecko_terminal");
17
17
  const price_cache_1 = require("./price_cache");
18
- function get_bsc_token_price_info(addresses) {
18
+ function get_bsc_token_price_info(addresses, opts) {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
+ var _a;
21
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
20
22
  addresses = addresses.map(addr => addr.toLowerCase());
21
23
  const result = new Map();
22
- const PRICE_CHANNELS = [
23
- {
24
- name: 'CachedPrice',
25
- fetchFn: price_cache_1.fetchPriceFromCache,
26
- batchSize: 100,
27
- batchDelay: 1000,
28
- },
24
+ const cachedChannel = {
25
+ name: 'CachedPrice',
26
+ fetchFn: price_cache_1.fetchPriceFromCache,
27
+ batchSize: 100,
28
+ batchDelay: 1000,
29
+ };
30
+ const externalChannels = [
29
31
  {
30
32
  name: 'DefiLlama',
31
33
  fetchFn: (address_list) => (0, defi_llama_1.fetchPriceFromDefiLlama)(index_1.CHAIN_ID.BSC, address_list),
@@ -39,6 +41,10 @@ function get_bsc_token_price_info(addresses) {
39
41
  batchDelay: 1000,
40
42
  },
41
43
  ];
44
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
45
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
46
+ [...externalChannels];
47
+ const externalHits = [];
42
48
  try {
43
49
  for (const channel of PRICE_CHANNELS) {
44
50
  if (addresses.length === 0)
@@ -58,7 +64,7 @@ function get_bsc_token_price_info(addresses) {
58
64
  result.set(address, priceInfo);
59
65
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
60
66
  if (channel.name !== 'CachedPrice') {
61
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
67
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
62
68
  }
63
69
  }
64
70
  }
@@ -75,17 +81,26 @@ function get_bsc_token_price_info(addresses) {
75
81
  break;
76
82
  }
77
83
  }
78
- if (result.size === 0) {
84
+ if (externalHits.length > 0) {
85
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
86
+ }
87
+ if (source === 'cache_only' && addresses.length > 0) {
88
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
89
+ }
90
+ if (source === 'force_fetch' && result.size === 0) {
79
91
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
80
92
  }
81
- if (addresses.length > 0) {
93
+ if (source !== 'cache_only' && addresses.length > 0) {
82
94
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
83
95
  }
84
96
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
85
97
  return result;
86
98
  }
87
99
  catch (error) {
88
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
100
+ if (source === 'force_fetch') {
101
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
+ return result;
89
104
  }
90
105
  });
91
106
  }
@@ -1,3 +1,6 @@
1
1
  import { EvmChainInfoType } from '../../chains';
2
2
  import { FormattedTokenPrice } from "../types";
3
- export declare function get_eth_token_price_info(evm_chain_info: EvmChainInfoType, addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
3
+ import type { PriceSource } from './get_bsc_token_price';
4
+ export declare function get_eth_token_price_info(evm_chain_info: EvmChainInfoType, addresses: string[], opts?: {
5
+ source?: PriceSource;
6
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -19,17 +19,19 @@ const index_1 = require("../../index");
19
19
  const defi_llama_1 = require("./defi_llama");
20
20
  const gecko_terminal_1 = require("./gecko_terminal");
21
21
  const price_cache_1 = require("./price_cache");
22
- function get_eth_token_price_info(evm_chain_info, addresses) {
22
+ function get_eth_token_price_info(evm_chain_info, addresses, opts) {
23
23
  return __awaiter(this, void 0, void 0, function* () {
24
+ var _a;
25
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
24
26
  addresses = addresses.map(addr => addr.toLowerCase());
25
27
  const result = new Map();
26
- const PRICE_CHANNELS = [
27
- {
28
- name: 'CachedPrice',
29
- fetchFn: price_cache_1.fetchPriceFromCache,
30
- batchSize: 100,
31
- batchDelay: 1000,
32
- },
28
+ const cachedChannel = {
29
+ name: 'CachedPrice',
30
+ fetchFn: price_cache_1.fetchPriceFromCache,
31
+ batchSize: 100,
32
+ batchDelay: 1000,
33
+ };
34
+ const externalChannels = [
33
35
  {
34
36
  name: 'DefiLlama',
35
37
  fetchFn: (address_list) => (0, defi_llama_1.fetchPriceFromDefiLlama)(evm_chain_info.chain_short_name, address_list),
@@ -49,6 +51,10 @@ function get_eth_token_price_info(evm_chain_info, addresses) {
49
51
  batchDelay: 1000,
50
52
  },
51
53
  ];
54
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
55
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
56
+ [...externalChannels];
57
+ const externalHits = [];
52
58
  try {
53
59
  for (const channel of PRICE_CHANNELS) {
54
60
  if (addresses.length === 0)
@@ -68,7 +74,7 @@ function get_eth_token_price_info(evm_chain_info, addresses) {
68
74
  result.set(address, priceInfo);
69
75
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
70
76
  if (channel.name !== 'CachedPrice') {
71
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
77
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
72
78
  }
73
79
  }
74
80
  }
@@ -85,17 +91,26 @@ function get_eth_token_price_info(evm_chain_info, addresses) {
85
91
  break;
86
92
  }
87
93
  }
88
- if (result.size === 0) {
94
+ if (externalHits.length > 0) {
95
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
96
+ }
97
+ if (source === 'cache_only' && addresses.length > 0) {
98
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
99
+ }
100
+ if (source === 'force_fetch' && result.size === 0) {
89
101
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
90
102
  }
91
- if (addresses.length > 0) {
103
+ if (source !== 'cache_only' && addresses.length > 0) {
92
104
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
93
105
  }
94
106
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
95
107
  return result;
96
108
  }
97
109
  catch (error) {
98
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
110
+ if (source === 'force_fetch') {
111
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ return result;
99
114
  }
100
115
  });
101
116
  }
@@ -1,2 +1,5 @@
1
1
  import { FormattedTokenPrice } from "../types";
2
- export declare function get_solana_token_price_info(addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
2
+ import type { PriceSource } from './get_bsc_token_price';
3
+ export declare function get_solana_token_price_info(addresses: string[], opts?: {
4
+ source?: PriceSource;
5
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -18,16 +18,18 @@ const index_1 = require("../../index");
18
18
  const gecko_terminal_1 = require("./gecko_terminal");
19
19
  const price_cache_1 = require("./price_cache");
20
20
  const defi_llama_1 = require("./defi_llama");
21
- function get_solana_token_price_info(addresses) {
21
+ function get_solana_token_price_info(addresses, opts) {
22
22
  return __awaiter(this, void 0, void 0, function* () {
23
+ var _a;
24
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
23
25
  const result = new Map();
24
- const PRICE_CHANNELS = [
25
- {
26
- name: 'CachedPrice',
27
- fetchFn: price_cache_1.fetchPriceFromCache,
28
- batchSize: 100,
29
- batchDelay: 1000,
30
- },
26
+ const cachedChannel = {
27
+ name: 'CachedPrice',
28
+ fetchFn: price_cache_1.fetchPriceFromCache,
29
+ batchSize: 100,
30
+ batchDelay: 1000,
31
+ };
32
+ const externalChannels = [
31
33
  {
32
34
  name: 'Jupiter',
33
35
  fetchFn: fetchPriceFromJupiter,
@@ -47,6 +49,10 @@ function get_solana_token_price_info(addresses) {
47
49
  batchDelay: 2000,
48
50
  },
49
51
  ];
52
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
53
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
54
+ [...externalChannels];
55
+ const externalHits = [];
50
56
  try {
51
57
  for (const channel of PRICE_CHANNELS) {
52
58
  if (addresses.length === 0)
@@ -66,7 +72,7 @@ function get_solana_token_price_info(addresses) {
66
72
  result.set(address, priceInfo);
67
73
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
68
74
  if (channel.name !== 'CachedPrice') {
69
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
75
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
70
76
  }
71
77
  }
72
78
  (0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
@@ -84,17 +90,26 @@ function get_solana_token_price_info(addresses) {
84
90
  break;
85
91
  }
86
92
  }
87
- if (result.size === 0) {
93
+ if (externalHits.length > 0) {
94
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
95
+ }
96
+ if (source === 'cache_only' && addresses.length > 0) {
97
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
98
+ }
99
+ if (source === 'force_fetch' && result.size === 0) {
88
100
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
89
101
  }
90
- if (addresses.length > 0) {
102
+ if (source !== 'cache_only' && addresses.length > 0) {
91
103
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
92
104
  }
93
105
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
94
106
  return result;
95
107
  }
96
108
  catch (error) {
97
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
109
+ if (source === 'force_fetch') {
110
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
111
+ }
112
+ return result;
98
113
  }
99
114
  });
100
115
  }
@@ -1,2 +1,5 @@
1
1
  import { FormattedTokenPrice } from "../types";
2
- export declare function get_sui_token_price_info(addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
2
+ import type { PriceSource } from './get_bsc_token_price';
3
+ export declare function get_sui_token_price_info(addresses: string[], opts?: {
4
+ source?: PriceSource;
5
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -15,17 +15,19 @@ const index_1 = require("../../index");
15
15
  const defi_llama_1 = require("./defi_llama");
16
16
  const gecko_terminal_1 = require("./gecko_terminal");
17
17
  const price_cache_1 = require("./price_cache");
18
- function get_sui_token_price_info(addresses) {
18
+ function get_sui_token_price_info(addresses, opts) {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
+ var _a;
20
21
  (0, index_1.log_info)(`get_sui_token_price_info`, addresses);
22
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
21
23
  const result = new Map();
22
- const PRICE_CHANNELS = [
23
- {
24
- name: 'CachedPrice',
25
- fetchFn: price_cache_1.fetchPriceFromCache,
26
- batchSize: 100,
27
- batchDelay: 1000,
28
- },
24
+ const cachedChannel = {
25
+ name: 'CachedPrice',
26
+ fetchFn: price_cache_1.fetchPriceFromCache,
27
+ batchSize: 100,
28
+ batchDelay: 1000,
29
+ };
30
+ const externalChannels = [
29
31
  {
30
32
  name: 'DefiLlama',
31
33
  fetchFn: (address_list) => (0, defi_llama_1.fetchPriceFromDefiLlama)(index_1.CHAIN_ID.SUI, address_list),
@@ -39,6 +41,10 @@ function get_sui_token_price_info(addresses) {
39
41
  batchDelay: 2000,
40
42
  },
41
43
  ];
44
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
45
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
46
+ [...externalChannels];
47
+ const externalHits = [];
42
48
  try {
43
49
  for (const channel of PRICE_CHANNELS) {
44
50
  if (addresses.length === 0)
@@ -58,7 +64,7 @@ function get_sui_token_price_info(addresses) {
58
64
  result.set(address, priceInfo);
59
65
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
60
66
  if (channel.name !== 'CachedPrice') {
61
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
67
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
62
68
  }
63
69
  }
64
70
  (0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
@@ -76,17 +82,26 @@ function get_sui_token_price_info(addresses) {
76
82
  break;
77
83
  }
78
84
  }
79
- if (result.size === 0) {
85
+ if (externalHits.length > 0) {
86
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
87
+ }
88
+ if (source === 'cache_only' && addresses.length > 0) {
89
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
90
+ }
91
+ if (source === 'force_fetch' && result.size === 0) {
80
92
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
81
93
  }
82
- if (addresses.length > 0) {
94
+ if (source !== 'cache_only' && addresses.length > 0) {
83
95
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
84
96
  }
85
97
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
86
98
  return result;
87
99
  }
88
100
  catch (error) {
89
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
101
+ if (source === 'force_fetch') {
102
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
103
+ }
104
+ return result;
90
105
  }
91
106
  });
92
107
  }
@@ -1,2 +1,5 @@
1
1
  import { FormattedTokenPrice } from "../types";
2
- export declare function get_tron_token_price_info(addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
2
+ import type { PriceSource } from './get_bsc_token_price';
3
+ export declare function get_tron_token_price_info(addresses: string[], opts?: {
4
+ source?: PriceSource;
5
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -18,17 +18,19 @@ const axios_1 = __importDefault(require("axios"));
18
18
  const index_1 = require("../../index");
19
19
  const gecko_terminal_1 = require("./gecko_terminal");
20
20
  const price_cache_1 = require("./price_cache");
21
- function get_tron_token_price_info(addresses) {
21
+ function get_tron_token_price_info(addresses, opts) {
22
22
  return __awaiter(this, void 0, void 0, function* () {
23
+ var _a;
23
24
  (0, index_1.log_info)(`get_tron_token_price_info`, addresses);
25
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
24
26
  const result = new Map();
25
- const PRICE_CHANNELS = [
26
- {
27
- name: 'CachedPrice',
28
- fetchFn: price_cache_1.fetchPriceFromCache,
29
- batchSize: 100,
30
- batchDelay: 1000,
31
- },
27
+ const cachedChannel = {
28
+ name: 'CachedPrice',
29
+ fetchFn: price_cache_1.fetchPriceFromCache,
30
+ batchSize: 100,
31
+ batchDelay: 1000,
32
+ };
33
+ const externalChannels = [
32
34
  {
33
35
  name: 'TronscanALL',
34
36
  fetchFn: fetchPriceFromTronscanALL,
@@ -48,6 +50,10 @@ function get_tron_token_price_info(addresses) {
48
50
  batchDelay: 2000,
49
51
  },
50
52
  ];
53
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
54
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
55
+ [...externalChannels];
56
+ const externalHits = [];
51
57
  try {
52
58
  for (const channel of PRICE_CHANNELS) {
53
59
  if (addresses.length === 0)
@@ -67,7 +73,7 @@ function get_tron_token_price_info(addresses) {
67
73
  result.set(address, priceInfo);
68
74
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
69
75
  if (channel.name !== 'CachedPrice') {
70
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
76
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
71
77
  }
72
78
  }
73
79
  (0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
@@ -85,17 +91,26 @@ function get_tron_token_price_info(addresses) {
85
91
  break;
86
92
  }
87
93
  }
88
- if (result.size === 0) {
94
+ if (externalHits.length > 0) {
95
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
96
+ }
97
+ if (source === 'cache_only' && addresses.length > 0) {
98
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
99
+ }
100
+ if (source === 'force_fetch' && result.size === 0) {
89
101
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
90
102
  }
91
- if (addresses.length > 0) {
103
+ if (source !== 'cache_only' && addresses.length > 0) {
92
104
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
93
105
  }
94
106
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
95
107
  return result;
96
108
  }
97
109
  catch (error) {
98
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
110
+ if (source === 'force_fetch') {
111
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ return result;
99
114
  }
100
115
  });
101
116
  }
@@ -1,2 +1,5 @@
1
1
  import { FormattedTokenPrice } from "../types";
2
- export declare function get_xlayer_token_price_info(addresses: string[]): Promise<Map<string, FormattedTokenPrice>>;
2
+ import type { PriceSource } from './get_bsc_token_price';
3
+ export declare function get_xlayer_token_price_info(addresses: string[], opts?: {
4
+ source?: PriceSource;
5
+ }): Promise<Map<string, FormattedTokenPrice>>;
@@ -15,18 +15,20 @@ const index_1 = require("../../index");
15
15
  const defi_llama_1 = require("./defi_llama");
16
16
  const gecko_terminal_1 = require("./gecko_terminal");
17
17
  const price_cache_1 = require("./price_cache");
18
- function get_xlayer_token_price_info(addresses) {
18
+ function get_xlayer_token_price_info(addresses, opts) {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
+ var _a;
21
+ const source = (_a = opts === null || opts === void 0 ? void 0 : opts.source) !== null && _a !== void 0 ? _a : 'cache_only';
20
22
  addresses = addresses.map(addr => addr.toLowerCase());
21
23
  (0, index_1.log_info)(`get_xlayer_token_price_info`, addresses);
22
24
  const result = new Map();
23
- const PRICE_CHANNELS = [
24
- {
25
- name: 'CachedPrice',
26
- fetchFn: price_cache_1.fetchPriceFromCache,
27
- batchSize: 100,
28
- batchDelay: 1000,
29
- },
25
+ const cachedChannel = {
26
+ name: 'CachedPrice',
27
+ fetchFn: price_cache_1.fetchPriceFromCache,
28
+ batchSize: 100,
29
+ batchDelay: 1000,
30
+ };
31
+ const externalChannels = [
30
32
  {
31
33
  name: 'DefiLlama',
32
34
  fetchFn: (address_list) => (0, defi_llama_1.fetchPriceFromDefiLlama)(index_1.CHAIN_ID.XLAYER, address_list),
@@ -40,6 +42,10 @@ function get_xlayer_token_price_info(addresses) {
40
42
  batchDelay: 2000,
41
43
  },
42
44
  ];
45
+ const PRICE_CHANNELS = source === 'cache_only' ? [cachedChannel] :
46
+ source === 'cache_first' ? [cachedChannel, ...externalChannels] :
47
+ [...externalChannels];
48
+ const externalHits = [];
43
49
  try {
44
50
  for (const channel of PRICE_CHANNELS) {
45
51
  if (addresses.length === 0)
@@ -59,7 +65,7 @@ function get_xlayer_token_price_info(addresses) {
59
65
  result.set(address, priceInfo);
60
66
  remainingAddresses = remainingAddresses.filter(addr => addr !== address);
61
67
  if (channel.name !== 'CachedPrice') {
62
- (0, price_cache_1.cache_new_market_price)(address, priceInfo.price, channel.name);
68
+ externalHits.push({ address, price: priceInfo.price, source: channel.name });
63
69
  }
64
70
  }
65
71
  (0, index_1.log_debug)(`[get_token_price_info] ${channel.name} found prices for ${channelResult.size}/${batch.length} tokens in batch ${i + 1}`);
@@ -77,17 +83,26 @@ function get_xlayer_token_price_info(addresses) {
77
83
  break;
78
84
  }
79
85
  }
80
- if (result.size === 0) {
86
+ if (externalHits.length > 0) {
87
+ yield (0, price_cache_1.cache_new_market_price_batch)(externalHits);
88
+ }
89
+ if (source === 'cache_only' && addresses.length > 0) {
90
+ (0, price_cache_1.warnPriceCacheMiss)(addresses);
91
+ }
92
+ if (source === 'force_fetch' && result.size === 0) {
81
93
  throw new Error(`Unable to get price information for any token: ${addresses.join(', ')}`);
82
94
  }
83
- if (addresses.length > 0) {
95
+ if (source !== 'cache_only' && addresses.length > 0) {
84
96
  (0, index_1.log_warn)(`[get_token_price_info] Failed to get prices for ${addresses.length} tokens after trying all channels: ${addresses.join(', ')}`);
85
97
  }
86
98
  (0, index_1.log_debug)(`[get_token_price_info] Completed price fetching for ${result.size} tokens`);
87
99
  return result;
88
100
  }
89
101
  catch (error) {
90
- throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
102
+ if (source === 'force_fetch') {
103
+ throw new Error(`Failed to get token price information: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ return result;
91
106
  }
92
107
  });
93
108
  }
@@ -4,3 +4,5 @@ export * from './get_solana_token_price';
4
4
  export * from './get_tron_token_price';
5
5
  export * from './get_sui_token_price';
6
6
  export * from './get_xlayer_token_price';
7
+ export * from './price_cache';
8
+ export * from './token_price_syncer';
@@ -20,3 +20,5 @@ __exportStar(require("./get_solana_token_price"), exports);
20
20
  __exportStar(require("./get_tron_token_price"), exports);
21
21
  __exportStar(require("./get_sui_token_price"), exports);
22
22
  __exportStar(require("./get_xlayer_token_price"), exports);
23
+ __exportStar(require("./price_cache"), exports);
24
+ __exportStar(require("./token_price_syncer"), exports);
@@ -1,3 +1,8 @@
1
1
  import { FormattedTokenPrice } from "../..";
2
2
  export declare const fetchPriceFromCache: (token_address_list: string[]) => Promise<Map<string, FormattedTokenPrice>>;
3
- export declare function cache_new_market_price(token_address: string, market_price: string, market_source: string): Promise<void>;
3
+ export declare function cache_new_market_price_batch(hits: Array<{
4
+ address: string;
5
+ price: string;
6
+ source: string;
7
+ }>): Promise<void>;
8
+ export declare function warnPriceCacheMiss(missed_addresses: string[]): void;
@@ -10,21 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.fetchPriceFromCache = void 0;
13
- exports.cache_new_market_price = cache_new_market_price;
13
+ exports.cache_new_market_price_batch = cache_new_market_price_batch;
14
+ exports.warnPriceCacheMiss = warnPriceCacheMiss;
14
15
  const __1 = require("../..");
15
- const check_token_price_timeout = (token_info, token_price_timeout_seconds) => {
16
- if ((0, __1.isEmpty)(token_info.market_price) || (0, __1.isEmpty)(token_info.update_time)) {
17
- return true;
18
- }
19
- let last_update_time = token_info.update_time;
20
- let diff_seconds = Math.floor((Date.now() - (0, __1.parseDateTimeStrToMills)(last_update_time)) / 1000);
21
- return diff_seconds > token_price_timeout_seconds;
22
- };
23
- const get_diff_seconds = (last_update_time, now) => {
24
- return Math.floor((now - (0, __1.parseDateTimeStrToMills)(last_update_time)) / 1000);
25
- };
26
16
  const fetchPriceFromCache = (token_address_list) => __awaiter(void 0, void 0, void 0, function* () {
27
- let result = new Map();
17
+ const result = new Map();
28
18
  let global_app_config;
29
19
  try {
30
20
  global_app_config = (0, __1.getGlobalAppConfig)();
@@ -33,85 +23,86 @@ const fetchPriceFromCache = (token_address_list) => __awaiter(void 0, void 0, vo
33
23
  (0, __1.log_warn)('global_app_config is not set, skip get token price from cache');
34
24
  return result;
35
25
  }
36
- let token_price_timeout_seconds = global_app_config.env_args.token_price_cache_seconds;
37
- let all_token_list = yield global_app_config.arb_cache.get_token_list();
38
- let token_list = all_token_list.filter(e => token_address_list.includes(e.address) && !(0, __1.isEmpty)(e.market_price) && !(0, __1.isEmpty)(e.update_time));
39
- let not_found_token_list = [];
40
- for (let token_info of token_list) {
41
- try {
42
- if (check_token_price_timeout(token_info, token_price_timeout_seconds)) {
43
- not_found_token_list.push(token_info);
44
- }
45
- else {
46
- let diff_seconds = get_diff_seconds(token_info.update_time, Date.now());
47
- result.set(token_info.address, {
48
- address: token_info.address,
49
- price: token_info.market_price,
50
- update_time: token_info.update_time,
51
- decimals: token_info.decimals,
52
- });
53
- (0, __1.log_debug)(`get token price from cache(token_info) success: ${token_info.symbol} ${token_info.address}, price=${token_info.market_price} (${diff_seconds}s ago)`);
54
- }
55
- }
56
- catch (err) {
57
- (0, __1.log_error)(`get token price from cache failed`, err);
58
- }
59
- }
60
- for (let token_info of not_found_token_list) {
61
- let price_info = yield global_app_config.get_token_market_price(token_info.symbol);
62
- if (!price_info || check_token_price_timeout(price_info, token_price_timeout_seconds)) {
63
- continue;
26
+ const map = global_app_config.arb_cache.token_address_map;
27
+ for (const address of token_address_list) {
28
+ const token = map.get(address.toLowerCase());
29
+ if (token === null || token === void 0 ? void 0 : token.market_price) {
30
+ result.set(address, {
31
+ address: token.address,
32
+ price: token.market_price,
33
+ update_time: token.update_time,
34
+ decimals: token.decimals,
35
+ });
36
+ (0, __1.log_debug)(`cache hit: ${token.symbol} ${address}, price=${token.market_price}, update_time=${token.update_time}`);
64
37
  }
65
- result.set(token_info.address, {
66
- address: token_info.address,
67
- price: price_info.market_price,
68
- update_time: price_info.update_time,
69
- decimals: price_info.decimals,
70
- });
71
- let diff_seconds = get_diff_seconds(price_info.update_time, Date.now());
72
- (0, __1.log_debug)(`get token price from cache(market_price) success: ${token_info.symbol} ${token_info.address}, price=${price_info.market_price} (${diff_seconds}s ago)`);
73
38
  }
74
39
  return result;
75
40
  });
76
41
  exports.fetchPriceFromCache = fetchPriceFromCache;
77
- function cache_new_market_price(token_address, market_price, market_source) {
42
+ function cache_new_market_price_batch(hits) {
78
43
  return __awaiter(this, void 0, void 0, function* () {
44
+ if (hits.length === 0)
45
+ return;
79
46
  let global_app_config;
80
47
  try {
81
48
  global_app_config = (0, __1.getGlobalAppConfig)();
82
49
  }
83
50
  catch (err) {
84
- (0, __1.log_warn)('global_app_config is not set, skip get token price from cache');
51
+ (0, __1.log_warn)('global_app_config is not set, skip cache_new_market_price_batch');
85
52
  return;
86
53
  }
87
- let ttl = global_app_config.env_args.token_price_cache_seconds;
88
- let tokenInfo = global_app_config.arb_cache.token_address_map.get(token_address);
89
- if (!tokenInfo) {
90
- tokenInfo = (yield global_app_config.arb_cache.get_token_list_no_cache()).find(e => e.address === token_address);
54
+ const map = global_app_config.arb_cache.token_address_map;
55
+ const updated = [];
56
+ for (const { address, price, source } of hits) {
57
+ if (!price || Number(price) <= 0) {
58
+ (0, __1.log_warn)(`skip cache invalid market_price from ${source}`, { address, price });
59
+ continue;
60
+ }
61
+ const tokenInfo = map.get(address.toLowerCase());
62
+ if (!tokenInfo) {
63
+ (0, __1.log_warn)(`tokenInfo not found in arb_cache.token_address_map: address=${address}`);
64
+ continue;
65
+ }
66
+ updated.push(Object.assign(Object.assign({}, tokenInfo), { market_price: price, update_time: (0, __1.getCurDateTime)() }));
91
67
  }
92
- if (!tokenInfo) {
93
- (0, __1.log_warn)(`tokenInfo is not found in cache: token_address=${token_address}`);
68
+ if (updated.length === 0)
94
69
  return;
70
+ try {
71
+ yield global_app_config.arb_cache.update_token_prices(updated);
72
+ (0, __1.log_info)(`cache_new_market_price_batch success: wrote ${updated.length} token prices`);
95
73
  }
96
- let { symbol, address } = tokenInfo;
97
- if (market_price && Number(market_price) > 0) {
98
- let clone_token_info = (0, __1.deep_clone)(tokenInfo);
99
- clone_token_info.market_price = market_price;
100
- global_app_config.cache_token_market_price(clone_token_info, ttl)
101
- .then(() => {
102
- (0, __1.log_info)(`cache new market price success: ${symbol} ${address}, price=${market_price} (${ttl}s), source=${market_source}`);
103
- })
104
- .catch(err => {
105
- (0, __1.log_error)(`cache new market price failed: ${symbol} ${address}, price=${market_price} (${ttl}s)`, err);
106
- });
107
- }
108
- else {
109
- (0, __1.log_warn)(`skip cache invalid market_price from ${market_source}`, {
110
- symbol,
111
- address,
112
- market_price,
113
- market_source,
114
- });
74
+ catch (err) {
75
+ (0, __1.log_error)(`cache_new_market_price_batch failed`, err);
115
76
  }
116
77
  });
117
78
  }
79
+ const _priceMissLastWarnAt = new Map();
80
+ const PRICE_MISS_THROTTLE_MS = 60000;
81
+ function warnPriceCacheMiss(missed_addresses) {
82
+ var _a;
83
+ if (missed_addresses.length === 0)
84
+ return;
85
+ let cfg;
86
+ try {
87
+ cfg = (0, __1.getGlobalAppConfig)();
88
+ }
89
+ catch (_b) {
90
+ return;
91
+ }
92
+ const now = Date.now();
93
+ const map = cfg.arb_cache.token_address_map;
94
+ const to_warn = [];
95
+ for (const addr of missed_addresses) {
96
+ const known = map.has(addr.toLowerCase());
97
+ if (!known)
98
+ continue;
99
+ const last = (_a = _priceMissLastWarnAt.get(addr)) !== null && _a !== void 0 ? _a : 0;
100
+ if (now - last < PRICE_MISS_THROTTLE_MS)
101
+ continue;
102
+ _priceMissLastWarnAt.set(addr, now);
103
+ to_warn.push(addr);
104
+ }
105
+ if (to_warn.length > 0) {
106
+ (0, __1.log_warn)(`price cache miss: ${to_warn.length} tokens known but no price in cache: ${to_warn.join(', ')}`);
107
+ }
108
+ }
@@ -0,0 +1,25 @@
1
+ import { ArbCache } from '../../cache/arb_cache';
2
+ import { CHAIN_ID } from '../..';
3
+ export interface FetchedTokenPrice {
4
+ price: string;
5
+ symbol?: string;
6
+ name?: string;
7
+ update_time?: string;
8
+ }
9
+ export interface TokenPriceSyncerOpts {
10
+ chain_id: CHAIN_ID;
11
+ arb_cache: ArbCache;
12
+ refresh_interval_seconds: number;
13
+ fetchPrices: (addresses: string[]) => Promise<Map<string, FetchedTokenPrice>>;
14
+ first_sync_delay_ms?: number;
15
+ }
16
+ export declare class TokenPriceSyncer {
17
+ private readonly opts;
18
+ private timer;
19
+ private trade_token_map;
20
+ constructor(opts: TokenPriceSyncerOpts);
21
+ start(): Promise<void>;
22
+ stop(): void;
23
+ runOnce(): Promise<void>;
24
+ private loadTradeTokensFromCache;
25
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TokenPriceSyncer = void 0;
13
+ const __1 = require("../..");
14
+ class TokenPriceSyncer {
15
+ constructor(opts) {
16
+ this.timer = null;
17
+ this.trade_token_map = new Map();
18
+ this.opts = opts;
19
+ }
20
+ start() {
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ var _a;
23
+ const delay = (_a = this.opts.first_sync_delay_ms) !== null && _a !== void 0 ? _a : 10000;
24
+ yield (0, __1.sleep)(delay);
25
+ (0, __1.log_info)(`[TokenPriceSyncer] first sync start, chain=${this.opts.chain_id}`);
26
+ const t0 = Date.now();
27
+ yield this.runOnce().catch(err => (0, __1.log_error)(`[TokenPriceSyncer] first sync error`, err));
28
+ (0, __1.log_info)(`[TokenPriceSyncer] first sync done, took ${Date.now() - t0}ms`);
29
+ (0, __1.log_info)(`[TokenPriceSyncer] timer started, interval=${this.opts.refresh_interval_seconds}s`);
30
+ this.timer = setInterval(() => this.runOnce().catch(err => (0, __1.log_error)(`[TokenPriceSyncer] run error`, err)), this.opts.refresh_interval_seconds * 1000);
31
+ });
32
+ }
33
+ stop() {
34
+ if (this.timer) {
35
+ clearInterval(this.timer);
36
+ this.timer = null;
37
+ }
38
+ }
39
+ runOnce() {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ var _a;
42
+ yield this.loadTradeTokensFromCache();
43
+ const addresses = Array.from(this.trade_token_map.keys());
44
+ if (addresses.length === 0) {
45
+ (0, __1.log_info)(`[TokenPriceSyncer] no trade tokens to sync`);
46
+ return;
47
+ }
48
+ (0, __1.log_info)(`[TokenPriceSyncer] syncing ${addresses.length} token prices`);
49
+ let priceMap;
50
+ try {
51
+ priceMap = yield this.opts.fetchPrices(addresses);
52
+ }
53
+ catch (err) {
54
+ (0, __1.log_error)(`[TokenPriceSyncer] fetchPrices error: ${err.message}`, err);
55
+ return;
56
+ }
57
+ const failed = [];
58
+ for (const addr of addresses) {
59
+ if (!((_a = priceMap.get(addr)) === null || _a === void 0 ? void 0 : _a.price)) {
60
+ const token = this.trade_token_map.get(addr);
61
+ if (token)
62
+ failed.push(`${token.symbol}(${addr})`);
63
+ }
64
+ }
65
+ (0, __1.log_info)(`[TokenPriceSyncer] sync done, priced=${priceMap.size}, failed=${failed.length}`);
66
+ if (failed.length > 0) {
67
+ (0, __1.log_warn)(`[TokenPriceSyncer] ${failed.length} tokens without price: ${failed.join(', ')}`);
68
+ }
69
+ });
70
+ }
71
+ loadTradeTokensFromCache() {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ const tokens = yield this.opts.arb_cache.get_all_trade_tokens();
74
+ for (const token of tokens) {
75
+ this.trade_token_map.set(token.address, token);
76
+ }
77
+ });
78
+ }
79
+ }
80
+ exports.TokenPriceSyncer = TokenPriceSyncer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-core",
3
- "version": "3.1.24",
3
+ "version": "3.1.26",
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
@@ -536,6 +536,8 @@ export interface WalletTokenBalanceInfoType {
536
536
  uiAmount: string
537
537
  price?: string
538
538
  balance?: string
539
+ /** 价格最后刷新时间(HH:MM:SS)。来自 token 的 update_time 字段,便于用户看到价格新鲜度 */
540
+ price_update_time?: string
539
541
  }
540
542
 
541
543
 
@@ -597,7 +599,7 @@ export declare class ArbCache {
597
599
  get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
598
600
  publish_token_change_event(): Promise<void>;
599
601
  cache_pool_list(): Promise<void>;
600
- set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): void;
602
+ set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
601
603
  set_pool_extra(pool: StandardPoolInfoType): void;
602
604
  update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
603
605
  get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;