@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.
- package/dist/alert/codes.js +2 -0
- package/dist/alert/log_rules.js +2 -0
- package/dist/app_config/AppConfig.d.ts +1 -3
- package/dist/app_config/AppConfig.js +0 -11
- package/dist/app_config/EnvArgs.d.ts +2 -1
- package/dist/app_config/env_registry.js +2 -1
- package/dist/cache/arb_cache.d.ts +3 -3
- package/dist/cache/arb_cache.js +89 -46
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/market_price/estimate_token_amount.js +3 -3
- package/dist/token/price/get_bsc_token_price.d.ts +4 -1
- package/dist/token/price/get_bsc_token_price.js +27 -12
- package/dist/token/price/get_eth_token_price.d.ts +4 -1
- package/dist/token/price/get_eth_token_price.js +27 -12
- package/dist/token/price/get_solana_token_price.d.ts +4 -1
- package/dist/token/price/get_solana_token_price.js +27 -12
- package/dist/token/price/get_sui_token_price.d.ts +4 -1
- package/dist/token/price/get_sui_token_price.js +27 -12
- package/dist/token/price/get_tron_token_price.d.ts +4 -1
- package/dist/token/price/get_tron_token_price.js +27 -12
- package/dist/token/price/get_xlayer_token_price.d.ts +4 -1
- package/dist/token/price/get_xlayer_token_price.js +27 -12
- package/dist/token/price/index.d.ts +2 -0
- package/dist/token/price/index.js +2 -0
- package/dist/token/price/price_cache.d.ts +6 -1
- package/dist/token/price/price_cache.js +67 -76
- package/dist/token/price/token_price_syncer.d.ts +25 -0
- package/dist/token/price/token_price_syncer.js +80 -0
- package/package.json +1 -1
- package/types/index.d.ts +3 -1
package/dist/alert/codes.js
CHANGED
|
@@ -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 写失败' },
|
package/dist/alert/log_rules.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<{
|
package/dist/cache/arb_cache.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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(...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
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
|
|
64
|
+
(0, index_1.log_debug)(`try-2 get market price from in-memory token_address_map`);
|
|
65
65
|
}
|
|
66
|
-
token_info =
|
|
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
|
|
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
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
(
|
|
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 (
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
|
51
|
+
(0, __1.log_warn)('global_app_config is not set, skip cache_new_market_price_batch');
|
|
85
52
|
return;
|
|
86
53
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 (
|
|
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
|
-
|
|
97
|
-
|
|
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
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[]):
|
|
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[]>;
|