@clonegod/ttd-core 3.1.12 → 3.1.13
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.d.ts +5 -0
- package/dist/alert/codes.js +85 -0
- package/dist/alert/index.d.ts +4 -0
- package/dist/alert/index.js +20 -0
- package/dist/alert/log_rules.d.ts +8 -0
- package/dist/alert/log_rules.js +84 -0
- package/dist/alert/reporter.d.ts +3 -0
- package/dist/alert/reporter.js +130 -0
- package/dist/alert/types.d.ts +40 -0
- package/dist/alert/types.js +11 -0
- package/dist/analyze/index.js +16 -2
- package/dist/app_config/env_registry.js +16 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -1
- package/dist/log_bus.d.ts +17 -0
- package/dist/log_bus.js +21 -0
- package/dist/token/index.d.ts +1 -0
- package/dist/token/index.js +1 -0
- package/dist/token/types.d.ts +4 -0
- package/dist/token/zh_pinyin_map.d.ts +3 -0
- package/dist/token/zh_pinyin_map.js +46 -0
- package/dist/util/duration.d.ts +2 -0
- package/dist/util/duration.js +49 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/package.json +1 -1
- package/types/index.d.ts +2 -3
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AlertCodeDef, AlertSeverity } from './types';
|
|
2
|
+
export declare const ALERT_CODES: Record<string, AlertCodeDef>;
|
|
3
|
+
export declare function getDefaultSeverity(code: string): AlertSeverity;
|
|
4
|
+
export declare function getCategory(code: string): string;
|
|
5
|
+
export declare function isKnownCode(code: string): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALERT_CODES = void 0;
|
|
4
|
+
exports.getDefaultSeverity = getDefaultSeverity;
|
|
5
|
+
exports.getCategory = getCategory;
|
|
6
|
+
exports.isKnownCode = isKnownCode;
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
const S = types_1.AlertSeverity;
|
|
9
|
+
exports.ALERT_CODES = {
|
|
10
|
+
QUOTE_RPC_TIMEOUT: { code: 'QUOTE_RPC_TIMEOUT', severity: S.WARN, category: 'quote', desc: '单次 RPC 请求超时', suggested_action: '框架自动重试' },
|
|
11
|
+
QUOTE_RPC_DISCONNECT: { code: 'QUOTE_RPC_DISCONNECT', severity: S.WARN, category: 'quote', desc: '单个 RPC ws 断开', suggested_action: '自动重连' },
|
|
12
|
+
QUOTE_RPC_DISCONNECT_ALL: { code: 'QUOTE_RPC_DISCONNECT_ALL', severity: S.CRITICAL, category: 'quote', desc: '所有 RPC 全断', suggested_action: '立即人工介入' },
|
|
13
|
+
QUOTE_BLOCK_LAG_HIGH: { code: 'QUOTE_BLOCK_LAG_HIGH', severity: S.WARN, category: 'quote', desc: 'block 接收延迟过高' },
|
|
14
|
+
QUOTE_BLOCK_LAG_PERSIST: { code: 'QUOTE_BLOCK_LAG_PERSIST', severity: S.ERROR, category: 'quote', desc: 'block 延迟持续 > 2 分钟', suggested_action: '考虑切 RPC' },
|
|
15
|
+
QUOTE_TICK_FETCH_FAIL: { code: 'QUOTE_TICK_FETCH_FAIL', severity: S.WARN, category: 'quote', desc: 'tick cache 刷新失败' },
|
|
16
|
+
QUOTE_TICK_STALE: { code: 'QUOTE_TICK_STALE', severity: S.ERROR, category: 'quote', desc: 'tick 数据过期但仍在报价', suggested_action: '风险提示' },
|
|
17
|
+
QUOTE_PRICE_ANOMALY: { code: 'QUOTE_PRICE_ANOMALY', severity: S.ERROR, category: 'quote', desc: '报价偏离参考价 > 阈值', suggested_action: '检查数据源' },
|
|
18
|
+
QUOTE_INTERVAL_BREACH: { code: 'QUOTE_INTERVAL_BREACH', severity: S.WARN, category: 'quote', desc: '询价间隔超出预期' },
|
|
19
|
+
TRADE_ENCODE_FAIL: { code: 'TRADE_ENCODE_FAIL', severity: S.ERROR, category: 'trade', desc: 'calldata 编码失败', suggested_action: '检查订单参数' },
|
|
20
|
+
TRADE_SIGN_FAIL: { code: 'TRADE_SIGN_FAIL', severity: S.ERROR, category: 'trade', desc: '交易签名失败', suggested_action: '检查 caller 钱包' },
|
|
21
|
+
TRADE_NONCE_CONFLICT: {
|
|
22
|
+
code: 'TRADE_NONCE_CONFLICT', severity: S.WARN, category: 'trade',
|
|
23
|
+
desc: '单次 nonce 冲突', suggested_action: '自动重试',
|
|
24
|
+
escalate: { burst_threshold: 5, escalated_severity: S.CRITICAL },
|
|
25
|
+
},
|
|
26
|
+
TRADE_NONCE_CONFLICT_BURST: { code: 'TRADE_NONCE_CONFLICT_BURST', severity: S.CRITICAL, category: 'trade', desc: '短时间大量 nonce 冲突', suggested_action: 'nonce 管理异常,排查 stream-trade' },
|
|
27
|
+
TRADE_CALLER_LOW_BALANCE: { code: 'TRADE_CALLER_LOW_BALANCE', severity: S.ERROR, category: 'trade', desc: 'caller 原生币低于警戒线', suggested_action: '尽快充值' },
|
|
28
|
+
TRADE_CALLER_EMPTY: { code: 'TRADE_CALLER_EMPTY', severity: S.CRITICAL, category: 'trade', desc: 'caller 原生币为 0', suggested_action: '立即停用或充值' },
|
|
29
|
+
TRADE_BUILDER_REJECT: { code: 'TRADE_BUILDER_REJECT', severity: S.WARN, category: 'trade', desc: '单个 Builder 拒绝' },
|
|
30
|
+
TRADE_BUILDER_REJECT_ALL: { code: 'TRADE_BUILDER_REJECT_ALL', severity: S.CRITICAL, category: 'trade', desc: '所有 Builder 都拒', suggested_action: '检查私钥白名单 / builder 配置' },
|
|
31
|
+
TRADE_SUBMIT_TIMEOUT: { code: 'TRADE_SUBMIT_TIMEOUT', severity: S.ERROR, category: 'trade', desc: '提交后 N 秒未 receipt' },
|
|
32
|
+
TRADE_RECEIPT_MISS: { code: 'TRADE_RECEIPT_MISS', severity: S.ERROR, category: 'trade', desc: '最终查不到 receipt', suggested_action: '交易可能丢' },
|
|
33
|
+
TRADE_REVERT_SLIPPAGE: {
|
|
34
|
+
code: 'TRADE_REVERT_SLIPPAGE', severity: S.WARN, category: 'trade',
|
|
35
|
+
desc: '链上 revert(滑点)', suggested_action: '市场合理',
|
|
36
|
+
escalate: { burst_threshold: 5, escalated_severity: S.ERROR },
|
|
37
|
+
},
|
|
38
|
+
TRADE_REVERT_SLIPPAGE_BURST: { code: 'TRADE_REVERT_SLIPPAGE_BURST', severity: S.ERROR, category: 'trade', desc: '短时间内大量 slippage revert', suggested_action: '滑点参数需调' },
|
|
39
|
+
TRADE_REVERT_UNKNOWN: { code: 'TRADE_REVERT_UNKNOWN', severity: S.ERROR, category: 'trade', desc: '链上 revert(非滑点原因)', suggested_action: '必须人工看 revert reason' },
|
|
40
|
+
TRADE_GAS_UNDERPRICED: { code: 'TRADE_GAS_UNDERPRICED', severity: S.WARN, category: 'trade', desc: 'gas 过低被挤出' },
|
|
41
|
+
CHAIN_RPC_TIMEOUT: { code: 'CHAIN_RPC_TIMEOUT', severity: S.WARN, category: 'chain', desc: '单次 RPC 请求超时' },
|
|
42
|
+
CHAIN_RPC_DISCONNECT: { code: 'CHAIN_RPC_DISCONNECT', severity: S.WARN, category: 'chain', desc: '单个 RPC ws 断' },
|
|
43
|
+
CHAIN_RPC_DISCONNECT_ALL: { code: 'CHAIN_RPC_DISCONNECT_ALL', severity: S.CRITICAL, category: 'chain', desc: '所有 RPC 全断', suggested_action: '立即检查网络' },
|
|
44
|
+
CHAIN_RPC_LAG_HIGH: { code: 'CHAIN_RPC_LAG_HIGH', severity: S.WARN, category: 'chain', desc: 'RPC 响应延迟异常' },
|
|
45
|
+
CHAIN_BLOCK_SUB_LAG: { code: 'CHAIN_BLOCK_SUB_LAG', severity: S.WARN, category: 'chain', desc: 'stream-block 订阅延迟' },
|
|
46
|
+
CHAIN_POOL_EVENT_GAP: { code: 'CHAIN_POOL_EVENT_GAP', severity: S.WARN, category: 'chain', desc: '池子事件 block 跳跃' },
|
|
47
|
+
CHAIN_POOL_EVENT_LOSS: { code: 'CHAIN_POOL_EVENT_LOSS', severity: S.ERROR, category: 'chain', desc: '确认池子事件丢失' },
|
|
48
|
+
THIRDPARTY_RATE_LIMITED: {
|
|
49
|
+
code: 'THIRDPARTY_RATE_LIMITED', severity: S.WARN, category: 'thirdparty',
|
|
50
|
+
desc: '第三方服务限流(429)',
|
|
51
|
+
escalate: { burst_threshold: 5, escalated_severity: S.ERROR },
|
|
52
|
+
},
|
|
53
|
+
THIRDPARTY_RATE_LIMITED_BURST: { code: 'THIRDPARTY_RATE_LIMITED_BURST', severity: S.ERROR, category: 'thirdparty', desc: '短时连续限流', suggested_action: '降频 / 换 key' },
|
|
54
|
+
THIRDPARTY_API_FAIL: { code: 'THIRDPARTY_API_FAIL', severity: S.WARN, category: 'thirdparty', desc: '单次失败(5xx / 网络 / 解析)' },
|
|
55
|
+
THIRDPARTY_API_DOWN: { code: 'THIRDPARTY_API_DOWN', severity: S.ERROR, category: 'thirdparty', desc: '持续失败 > N 分钟' },
|
|
56
|
+
THIRDPARTY_DATA_INVALID: { code: 'THIRDPARTY_DATA_INVALID', severity: S.WARN, category: 'thirdparty', desc: '返回数据格式错' },
|
|
57
|
+
THIRDPARTY_AUTH_FAIL: { code: 'THIRDPARTY_AUTH_FAIL', severity: S.ERROR, category: 'thirdparty', desc: '认证失败(API key 过期)' },
|
|
58
|
+
INFRA_REDIS_DISCONNECT: { code: 'INFRA_REDIS_DISCONNECT', severity: S.CRITICAL, category: 'infra', desc: 'Redis 连接断', suggested_action: '立即查 Redis' },
|
|
59
|
+
INFRA_REDIS_LATENCY_HIGH: { code: 'INFRA_REDIS_LATENCY_HIGH', severity: S.WARN, category: 'infra', desc: 'Redis P95 延迟异常' },
|
|
60
|
+
INFRA_CONFIG_CENTER_DOWN: { code: 'INFRA_CONFIG_CENTER_DOWN', severity: S.ERROR, category: 'infra', desc: 'config-center 不可达' },
|
|
61
|
+
INFRA_MARKET_DATA_DOWN: { code: 'INFRA_MARKET_DATA_DOWN', severity: S.ERROR, category: 'infra', desc: 'market-data 不可达' },
|
|
62
|
+
INFRA_TRADE_MGT_DOWN: { code: 'INFRA_TRADE_MGT_DOWN', severity: S.ERROR, category: 'infra', desc: 'trade-mgt 不可达' },
|
|
63
|
+
INFRA_PROCESS_CRASH: {
|
|
64
|
+
code: 'INFRA_PROCESS_CRASH', severity: S.ERROR, category: 'infra',
|
|
65
|
+
desc: 'PM2 restart_count 增加',
|
|
66
|
+
escalate: { burst_threshold: 3, escalated_severity: S.CRITICAL },
|
|
67
|
+
},
|
|
68
|
+
INFRA_PROCESS_CRASH_LOOP: { code: 'INFRA_PROCESS_CRASH_LOOP', severity: S.CRITICAL, category: 'infra', desc: '进程 5 分钟内反复重启', suggested_action: '停用该进程' },
|
|
69
|
+
INFRA_ENV_MISSING: { code: 'INFRA_ENV_MISSING', severity: S.ERROR, category: 'infra', desc: '关键 env 未配置' },
|
|
70
|
+
ANALYZE_UPSTREAM_MISSING: { code: 'ANALYZE_UPSTREAM_MISSING', severity: S.WARN, category: 'analyze', desc: '上游消息缺环节(PreTrade/OnTrade/PostTrade)' },
|
|
71
|
+
ANALYZE_SQLITE_FULL: { code: 'ANALYZE_SQLITE_FULL', severity: S.WARN, category: 'analyze', desc: 'SQLite 接近容量上限' },
|
|
72
|
+
ANALYZE_SQLITE_WRITE_FAIL: { code: 'ANALYZE_SQLITE_WRITE_FAIL', severity: S.ERROR, category: 'analyze', desc: 'SQLite 写失败' },
|
|
73
|
+
ANALYZE_ALERT_FLOOD: { code: 'ANALYZE_ALERT_FLOOD', severity: S.ERROR, category: 'analyze', desc: '告警上报速率异常(防噪音自保)' },
|
|
74
|
+
};
|
|
75
|
+
function getDefaultSeverity(code) {
|
|
76
|
+
var _a;
|
|
77
|
+
return ((_a = exports.ALERT_CODES[code]) === null || _a === void 0 ? void 0 : _a.severity) || types_1.AlertSeverity.ERROR;
|
|
78
|
+
}
|
|
79
|
+
function getCategory(code) {
|
|
80
|
+
var _a;
|
|
81
|
+
return ((_a = exports.ALERT_CODES[code]) === null || _a === void 0 ? void 0 : _a.category) || 'unknown';
|
|
82
|
+
}
|
|
83
|
+
function isKnownCode(code) {
|
|
84
|
+
return code in exports.ALERT_CODES;
|
|
85
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./codes"), exports);
|
|
19
|
+
__exportStar(require("./reporter"), exports);
|
|
20
|
+
__exportStar(require("./log_rules"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface LogAlertRule {
|
|
2
|
+
pattern: RegExp;
|
|
3
|
+
level?: 'warn' | 'error';
|
|
4
|
+
code: string;
|
|
5
|
+
context?: Record<string, any> | ((match: RegExpMatchArray, msg: string) => Record<string, any>);
|
|
6
|
+
}
|
|
7
|
+
export declare function registerLogRules(rules: LogAlertRule[]): void;
|
|
8
|
+
export declare function getRegisteredRuleCount(): number;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerLogRules = registerLogRules;
|
|
4
|
+
exports.getRegisteredRuleCount = getRegisteredRuleCount;
|
|
5
|
+
const reporter_1 = require("./reporter");
|
|
6
|
+
const log_bus_1 = require("../log_bus");
|
|
7
|
+
const _rules = [];
|
|
8
|
+
function registerLogRules(rules) {
|
|
9
|
+
_rules.push(...rules);
|
|
10
|
+
}
|
|
11
|
+
function getRegisteredRuleCount() {
|
|
12
|
+
return _rules.length;
|
|
13
|
+
}
|
|
14
|
+
function detectProvider(msg) {
|
|
15
|
+
const m = msg.toLowerCase();
|
|
16
|
+
if (m.includes('geckoterminal') || m.includes('gecko-terminal'))
|
|
17
|
+
return 'geckoterminal';
|
|
18
|
+
if (m.includes('dexscreener'))
|
|
19
|
+
return 'dexscreener';
|
|
20
|
+
if (m.includes('birdeye'))
|
|
21
|
+
return 'birdeye';
|
|
22
|
+
if (m.includes('coingecko'))
|
|
23
|
+
return 'coingecko';
|
|
24
|
+
if (m.includes('defillama'))
|
|
25
|
+
return 'defillama';
|
|
26
|
+
return 'unknown';
|
|
27
|
+
}
|
|
28
|
+
registerLogRules([
|
|
29
|
+
{ pattern: /redis.*(disconnect|connection.*lost|econnrefused|pubsub.*error|subscribe.*failed|exceeded max reconnect|连接失败)/i,
|
|
30
|
+
code: 'INFRA_REDIS_DISCONNECT' },
|
|
31
|
+
{ pattern: /econnrefused.*4000|config-center.*(down|unavailable)|(get|update|delete|post|check)\s+(pools|tokens|group|fixed-symbol|zh-pinyin).*error|init_.*client.*error/i,
|
|
32
|
+
level: 'error', code: 'INFRA_CONFIG_CENTER_DOWN' },
|
|
33
|
+
{ pattern: /uncaught exception|unhandled rejection|startup failed|main error|create_trade_runtime error|arb_cache.*init.*error/i,
|
|
34
|
+
level: 'error', code: 'INFRA_PROCESS_CRASH' },
|
|
35
|
+
{ pattern: /未设置.*api.*key|missing.*api.*key|env.*not.*configured/i,
|
|
36
|
+
code: 'INFRA_ENV_MISSING' },
|
|
37
|
+
{ pattern: /\[collector\].*(run error|fatal)|market.?data.*(down|unavailable)/i,
|
|
38
|
+
level: 'error', code: 'INFRA_MARKET_DATA_DOWN' },
|
|
39
|
+
{ pattern: /\b429\b|too many requests/i, code: 'THIRDPARTY_RATE_LIMITED',
|
|
40
|
+
context: (_m, msg) => ({ provider: detectProvider(msg) }) },
|
|
41
|
+
{ pattern: /geckoterminal.*(error|fail)|defillama.*(error|fail)|failed to get prices.*after trying all channels/i,
|
|
42
|
+
code: 'THIRDPARTY_API_FAIL',
|
|
43
|
+
context: (_m, msg) => ({ provider: detectProvider(msg) }) },
|
|
44
|
+
{ pattern: /api.*(unauthorized|invalid api key|auth.*failed)/i,
|
|
45
|
+
level: 'error', code: 'THIRDPARTY_AUTH_FAIL' },
|
|
46
|
+
{ pattern: /pricefeed.*(parse failed|not an array|empty)|empty response at page/i,
|
|
47
|
+
level: 'warn', code: 'THIRDPARTY_DATA_INVALID' },
|
|
48
|
+
{ pattern: /sqlite.*(disk|full|i\/o error|cleanup error|write.*fail)/i,
|
|
49
|
+
level: 'error', code: 'ANALYZE_SQLITE_WRITE_FAIL' },
|
|
50
|
+
{ pattern: /db size.*\d+mb.*limit/i, level: 'warn', code: 'ANALYZE_SQLITE_FULL' },
|
|
51
|
+
{ pattern: /poolsync.*(failed|skip)|blocktimecache.*(failed|skip)|\[preload\].*failed/i,
|
|
52
|
+
level: 'warn', code: 'ANALYZE_UPSTREAM_MISSING' },
|
|
53
|
+
]);
|
|
54
|
+
function matchAndReport(level, msg, err) {
|
|
55
|
+
setImmediate(() => {
|
|
56
|
+
try {
|
|
57
|
+
if (!msg)
|
|
58
|
+
return;
|
|
59
|
+
for (const rule of _rules) {
|
|
60
|
+
if (rule.level && rule.level !== level)
|
|
61
|
+
continue;
|
|
62
|
+
const match = msg.match(rule.pattern);
|
|
63
|
+
if (!match)
|
|
64
|
+
continue;
|
|
65
|
+
const context = typeof rule.context === 'function'
|
|
66
|
+
? rule.context(match, msg)
|
|
67
|
+
: (rule.context || {});
|
|
68
|
+
(0, reporter_1.reportAlert)({
|
|
69
|
+
code: rule.code,
|
|
70
|
+
source: '',
|
|
71
|
+
message: msg.length > 500 ? msg.slice(0, 500) + '...' : msg,
|
|
72
|
+
context,
|
|
73
|
+
err: err instanceof Error ? err : undefined,
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (_a) {
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
log_bus_1.logBus.onLog((event) => {
|
|
83
|
+
matchAndReport(event.level, event.msg, event.err || event.data);
|
|
84
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
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.reportAlert = reportAlert;
|
|
13
|
+
exports.flushAlerts = flushAlerts;
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
const codes_1 = require("./codes");
|
|
16
|
+
let _axios = null;
|
|
17
|
+
function getAxios() {
|
|
18
|
+
if (!_axios)
|
|
19
|
+
_axios = require('axios');
|
|
20
|
+
return _axios;
|
|
21
|
+
}
|
|
22
|
+
function getConfig() {
|
|
23
|
+
return {
|
|
24
|
+
analyze_host: process.env.ALERT_ANALYZE_HOST || '',
|
|
25
|
+
analyze_port: parseInt(process.env.ALERT_ANALYZE_PORT || '8004'),
|
|
26
|
+
flush_size: parseInt(process.env.ALERT_FLUSH_SIZE || '10'),
|
|
27
|
+
flush_ms: parseInt(process.env.ALERT_FLUSH_MS || '5000'),
|
|
28
|
+
dedup_window_ms: parseInt(process.env.ALERT_DEDUP_WINDOW_MS || '60000'),
|
|
29
|
+
source_prefix: process.env.APP_NAME || process.env.NAMESPACE || 'unknown',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const buffer = new Map();
|
|
33
|
+
let flushTimer = null;
|
|
34
|
+
function dedupKey(code, source) {
|
|
35
|
+
return `${code}|${source}`;
|
|
36
|
+
}
|
|
37
|
+
function reportAlert(payload) {
|
|
38
|
+
try {
|
|
39
|
+
const cfg = getConfig();
|
|
40
|
+
if (!cfg.analyze_host)
|
|
41
|
+
return;
|
|
42
|
+
const severity = payload.severity || (0, codes_1.getDefaultSeverity)(payload.code);
|
|
43
|
+
if (severity === types_1.AlertSeverity.DEBUG)
|
|
44
|
+
return;
|
|
45
|
+
const normalized = Object.assign(Object.assign({}, payload), { severity, source: payload.source || cfg.source_prefix, ts: payload.ts || Date.now(), err: payload.err instanceof Error ? serializeError(payload.err) : payload.err });
|
|
46
|
+
const key = dedupKey(normalized.code, normalized.source);
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const existing = buffer.get(key);
|
|
49
|
+
if (existing && now - existing.first_ts < cfg.dedup_window_ms) {
|
|
50
|
+
existing.count += 1;
|
|
51
|
+
existing.last_ts = now;
|
|
52
|
+
existing.payload.context = Object.assign(Object.assign({}, (existing.payload.context || {})), (normalized.context || {}));
|
|
53
|
+
const def = codes_1.ALERT_CODES[normalized.code];
|
|
54
|
+
if ((def === null || def === void 0 ? void 0 : def.escalate) && !existing.escalated && existing.count >= def.escalate.burst_threshold) {
|
|
55
|
+
existing.payload.severity = def.escalate.escalated_severity;
|
|
56
|
+
existing.escalated = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
buffer.set(key, {
|
|
61
|
+
payload: normalized,
|
|
62
|
+
first_ts: now,
|
|
63
|
+
last_ts: now,
|
|
64
|
+
count: 1,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (buffer.size >= cfg.flush_size) {
|
|
68
|
+
flush();
|
|
69
|
+
}
|
|
70
|
+
else if (!flushTimer) {
|
|
71
|
+
flushTimer = setTimeout(flush, cfg.flush_ms);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
safeLogWarn('reportAlert internal error', e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function flush() {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
if (flushTimer) {
|
|
81
|
+
clearTimeout(flushTimer);
|
|
82
|
+
flushTimer = null;
|
|
83
|
+
}
|
|
84
|
+
if (buffer.size === 0)
|
|
85
|
+
return;
|
|
86
|
+
const cfg = getConfig();
|
|
87
|
+
const items = Array.from(buffer.values());
|
|
88
|
+
buffer.clear();
|
|
89
|
+
const url = `http://${cfg.analyze_host}:${cfg.analyze_port}/trade/analyze/alert/ingest`;
|
|
90
|
+
const body = {
|
|
91
|
+
alerts: items.map(b => ({
|
|
92
|
+
code: b.payload.code,
|
|
93
|
+
severity: b.payload.severity,
|
|
94
|
+
source: b.payload.source,
|
|
95
|
+
message: b.payload.message,
|
|
96
|
+
context: b.payload.context,
|
|
97
|
+
err: b.payload.err,
|
|
98
|
+
count: b.count,
|
|
99
|
+
first_ts: b.first_ts,
|
|
100
|
+
last_ts: b.last_ts,
|
|
101
|
+
escalated: b.escalated || false,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
yield getAxios().post(url, body, {
|
|
106
|
+
timeout: 3000,
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
safeLogWarn(`[alert] flush failed (${items.length} items): ${e.message}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function flushAlerts() {
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
yield flush();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
process.on('SIGTERM', () => { flushAlerts(); });
|
|
121
|
+
process.on('SIGINT', () => { flushAlerts(); });
|
|
122
|
+
function serializeError(err) {
|
|
123
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
124
|
+
}
|
|
125
|
+
function safeLogWarn(msg, err) {
|
|
126
|
+
try {
|
|
127
|
+
console.warn(`[alert] ${msg}`, err ? (err.message || err) : '');
|
|
128
|
+
}
|
|
129
|
+
catch (_a) { }
|
|
130
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare enum AlertSeverity {
|
|
2
|
+
DEBUG = "debug",
|
|
3
|
+
INFO = "info",
|
|
4
|
+
WARN = "warn",
|
|
5
|
+
ERROR = "error",
|
|
6
|
+
CRITICAL = "critical"
|
|
7
|
+
}
|
|
8
|
+
export interface AlertCodeDef {
|
|
9
|
+
code: string;
|
|
10
|
+
severity: AlertSeverity;
|
|
11
|
+
category: 'chain' | 'quote' | 'trade' | 'thirdparty' | 'infra' | 'analyze';
|
|
12
|
+
desc: string;
|
|
13
|
+
suggested_action?: string;
|
|
14
|
+
escalate?: {
|
|
15
|
+
burst_threshold: number;
|
|
16
|
+
escalated_severity: AlertSeverity;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface AlertPayload {
|
|
20
|
+
code: string;
|
|
21
|
+
severity?: AlertSeverity;
|
|
22
|
+
source: string;
|
|
23
|
+
message: string;
|
|
24
|
+
context?: Record<string, any>;
|
|
25
|
+
err?: {
|
|
26
|
+
name?: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
stack?: string;
|
|
29
|
+
} | Error;
|
|
30
|
+
ts?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface AlertRecord extends AlertPayload {
|
|
33
|
+
id: string;
|
|
34
|
+
ts: number;
|
|
35
|
+
count: number;
|
|
36
|
+
first_ts: number;
|
|
37
|
+
last_ts: number;
|
|
38
|
+
escalated?: boolean;
|
|
39
|
+
read?: boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AlertSeverity = void 0;
|
|
4
|
+
var AlertSeverity;
|
|
5
|
+
(function (AlertSeverity) {
|
|
6
|
+
AlertSeverity["DEBUG"] = "debug";
|
|
7
|
+
AlertSeverity["INFO"] = "info";
|
|
8
|
+
AlertSeverity["WARN"] = "warn";
|
|
9
|
+
AlertSeverity["ERROR"] = "error";
|
|
10
|
+
AlertSeverity["CRITICAL"] = "critical";
|
|
11
|
+
})(AlertSeverity || (exports.AlertSeverity = AlertSeverity = {}));
|
package/dist/analyze/index.js
CHANGED
|
@@ -19,11 +19,25 @@ const ANALYZE_HOST = process.env.TRADE_ANALYZE_HOST || '';
|
|
|
19
19
|
const ANALYZE_PORT = process.env.TRADE_ANALYZE_PORT || '8004';
|
|
20
20
|
const ANALYZE_BASE = ANALYZE_HOST ? `http://${ANALYZE_HOST}:${ANALYZE_PORT}/trade/analyze` : '';
|
|
21
21
|
const headers = { 'Content-Type': 'application/json' };
|
|
22
|
+
const INGEST_PATH = {
|
|
23
|
+
PriceMessageType: '/ingest/price',
|
|
24
|
+
OrderMessageType: '/ingest/order',
|
|
25
|
+
DepthLevels: '/ingest/depth',
|
|
26
|
+
TickLiquidity: '/ingest/tick',
|
|
27
|
+
TradeResultType: '/ingest/trade-result',
|
|
28
|
+
SendTxLogType: '/ingest/send-tx',
|
|
29
|
+
QuoteVerify: '/ingest/quote-verify',
|
|
30
|
+
};
|
|
22
31
|
const report_trade_analyze_data = (type, message) => {
|
|
23
32
|
if (!ANALYZE_BASE)
|
|
24
33
|
return;
|
|
34
|
+
const path = INGEST_PATH[type];
|
|
35
|
+
if (!path) {
|
|
36
|
+
(0, index_1.log_warn)(`[analyze] unknown report type: ${type}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
25
39
|
try {
|
|
26
|
-
axios_1.default.post(ANALYZE_BASE
|
|
40
|
+
axios_1.default.post(ANALYZE_BASE + path, message, { headers, timeout: 3000 })
|
|
27
41
|
.then(() => (0, index_1.log_trace)(`[analyze] report ${type} ok`))
|
|
28
42
|
.catch(err => (0, index_1.log_warn)(`[analyze] report ${type} fail: ${err.message}`));
|
|
29
43
|
}
|
|
@@ -36,7 +50,7 @@ const get_pool_latest_quote_price = (unique_orderbook_id) => __awaiter(void 0, v
|
|
|
36
50
|
if (!ANALYZE_BASE || (0, index_1.isEmpty)(unique_orderbook_id))
|
|
37
51
|
return null;
|
|
38
52
|
try {
|
|
39
|
-
const url = `${ANALYZE_BASE}/
|
|
53
|
+
const url = `${ANALYZE_BASE}/quote/price-msg?unique_orderbook_id=${unique_orderbook_id}`;
|
|
40
54
|
const res = (yield axios_1.default.get(url, { headers, timeout: 3000 })).data;
|
|
41
55
|
return res.data;
|
|
42
56
|
}
|
|
@@ -16,8 +16,22 @@ registerEnvVars({
|
|
|
16
16
|
dex_id: { env: 'DEX_ID', type: 'string', desc: 'DEX 标识' },
|
|
17
17
|
redis_host: { env: 'REDIS_HOST', type: 'string', default: '127.0.0.1', desc: 'Redis 地址' },
|
|
18
18
|
redis_port: { env: 'REDIS_PORT', type: 'number', default: 6379, desc: 'Redis 端口' },
|
|
19
|
-
config_center_host: { env: 'CONFIG_CENTER_HOST', type: 'string', default: '', desc: 'config-center 地址' },
|
|
20
|
-
trade_analyze_host: { env: 'TRADE_ANALYZE_HOST', type: 'string', default: '', desc: 'analyze 地址' },
|
|
19
|
+
config_center_host: { env: 'CONFIG_CENTER_HOST', type: 'string', default: '127.0.0.1', desc: 'config-center 地址(IP),端口固定 SERVICE_PORT.CONFIG_CENTER_HTTP' },
|
|
20
|
+
trade_analyze_host: { env: 'TRADE_ANALYZE_HOST', type: 'string', default: '127.0.0.1', desc: 'analyze 地址(IP)' },
|
|
21
|
+
trade_mgt_host: { env: 'TRADE_MGT_HOST', type: 'string', default: '127.0.0.1', desc: 'trade-mgt 默认地址(IP),端口固定 SERVICE_PORT.TRADE_MGT_HTTP' },
|
|
22
|
+
trade_mgt_hosts: { env: 'TRADE_MGT_HOSTS', type: 'string', default: '', desc: 'trade-mgt 可选地址列表(IP逗号分隔),UI 可切换;未配置则使用 TRADE_MGT_HOST' },
|
|
23
|
+
alert_analyze_host: { env: 'ALERT_ANALYZE_HOST', type: 'string', default: '', desc: 'analyze 告警端点地址(IP),为空则不上报;默认 127.0.0.1' },
|
|
24
|
+
alert_analyze_port: { env: 'ALERT_ANALYZE_PORT', type: 'number', default: 8004, desc: 'analyze 告警端点端口' },
|
|
25
|
+
alert_flush_size: { env: 'ALERT_FLUSH_SIZE', type: 'number', default: 10, desc: 'alert 批处理阈值(条数)' },
|
|
26
|
+
alert_flush_ms: { env: 'ALERT_FLUSH_MS', type: 'number', default: 5000, desc: 'alert 批处理阈值(ms)' },
|
|
27
|
+
alert_dedup_window_ms: { env: 'ALERT_DEDUP_WINDOW_MS', type: 'number', default: 60000, desc: 'alert 去重窗口(ms)' },
|
|
28
|
+
analyze_sqlite_retain: { env: 'ANALYZE_SQLITE_RETAIN', type: 'string', default: '8h', desc: 'analyze SQLite 数据保留期,支持 8h/2d/30m 格式' },
|
|
29
|
+
alert_webhook_url: { env: 'ALERT_WEBHOOK_URL', type: 'string', default: '', sensitive: true, desc: '外推 webhook URL;为空则禁用外推' },
|
|
30
|
+
alert_webhook_type: { env: 'ALERT_WEBHOOK_TYPE', type: 'string', default: '', desc: 'webhook 类型(lark/slack/dingtalk/generic);省略则按 URL 自动识别' },
|
|
31
|
+
alert_webhook_min_severity: { env: 'ALERT_WEBHOOK_MIN_SEVERITY', type: 'string', default: 'critical', desc: '只外推 >= 此级别的告警(warn/error/critical)' },
|
|
32
|
+
alert_webhook_rate_limit: { env: 'ALERT_WEBHOOK_RATE_LIMIT', type: 'number', default: 10, desc: 'webhook 速率上限(条/分钟),防刷屏' },
|
|
33
|
+
alert_webhook_dedup_ms: { env: 'ALERT_WEBHOOK_DEDUP_MS', type: 'string', default: '5m', desc: 'webhook 去重窗口(同 code+source 不重复推),支持 5m/30s/1h' },
|
|
34
|
+
alert_webhook_system_label: { env: 'ALERT_WEBHOOK_SYSTEM_LABEL', type: 'string', default: '', desc: 'webhook 消息中显示的系统标识,省略时使用 CHAIN_ID' },
|
|
21
35
|
trade_analyze_port: { env: 'TRADE_ANALYZE_PORT', type: 'number', default: 8004, desc: 'analyze 端口' },
|
|
22
36
|
trade_analyze_url: { env: 'TRADE_ANALYZE_URL', type: 'string', default: '', desc: 'analyze URL(旧)' },
|
|
23
37
|
rpc_endpoint: { env: 'RPC_ENDPOINT', type: 'string', default: '', desc: 'RPC HTTP 端点' },
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export * from './quote';
|
|
|
15
15
|
export * from './token';
|
|
16
16
|
export * from './trade';
|
|
17
17
|
export * from './chains';
|
|
18
|
+
export * from './alert';
|
|
19
|
+
export * from './log_bus';
|
|
18
20
|
export * from './ws';
|
|
19
21
|
export * from './util';
|
|
20
22
|
export type { CommonServiceType, ConfigCenterServiceType, QuoteServiceType, OrderbookServiceType, TradeProxyServiceType, StandardTokenConfigType, StandardTokenInfoType, StandardPoolConfigType, StandardDexPoolConfigType, StandardPoolInfoType, StandardPairType, TradeServiceConfigType, TradeGroupType, TradePairType, TradePairDexPoolsType, TradeSettingsType, RpcInfoType, TradeStrategyType, TradeCommandLineArgs, TradeRuntimeType, TradeProcessInstanceType, TokenPriceWithAmountType, QuoteResultType, PriceMessageType, QuoteSourceType, UniqueOrderbookIdType, PriceType, QuoteTimeInfoType, OrderbookPriceType, Ladder, OrderMessageType, OrderSubmitResultType, OrderSubmitResponseType, StandardSwapDetailType, TokenBalChangeType, TokenBalanceChangeType, TradeResponseType, TradeResultType, TradeResultBalanceChangeType, TradeExecutionInfoType, TradeTimeFlowType, TradeBroadcastType, TradeGasFeeType, ServerInfoType, SendTxLogType, WalletInfoType, WalletTokenBalanceInfoType, RedisConfigChangeEventMessageType, RedisOrderEventMessageType, RedisQuoteEventMessageType, PreTradeEventType, ProviderComparisonType, ProviderArrivalType, OnTradeType, OnTradePriceType, OnTradeDepthType, OnTradeExecutionType, OnTradeResultType, AnalyzeDepthLevelType, AnalyzeTokenAmountType, PostTradeType, AnalyzeQuoteSnapshotType, AnalyzePoolEventType, AnalyzeEventSummaryType, AnalyzeSameBlockType, } from '../types';
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ exports.load_wallet = load_wallet;
|
|
|
76
76
|
exports.load_wallet_multi = load_wallet_multi;
|
|
77
77
|
exports.chunkArray = chunkArray;
|
|
78
78
|
require('dotenv').config();
|
|
79
|
+
const log_bus_1 = require("./log_bus");
|
|
79
80
|
const bn_js_1 = __importDefault(require("bn.js"));
|
|
80
81
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
81
82
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -97,6 +98,8 @@ __exportStar(require("./quote"), exports);
|
|
|
97
98
|
__exportStar(require("./token"), exports);
|
|
98
99
|
__exportStar(require("./trade"), exports);
|
|
99
100
|
__exportStar(require("./chains"), exports);
|
|
101
|
+
__exportStar(require("./alert"), exports);
|
|
102
|
+
__exportStar(require("./log_bus"), exports);
|
|
100
103
|
__exportStar(require("./ws"), exports);
|
|
101
104
|
__exportStar(require("./util"), exports);
|
|
102
105
|
const short = require('short-uuid');
|
|
@@ -395,7 +398,16 @@ function log_debug(msg, data = {}, traceId = '') {
|
|
|
395
398
|
log_info(msg, data, traceId, _caller(), LOG_LEVEL.DEBUG);
|
|
396
399
|
}
|
|
397
400
|
function log_warn(msg, data = {}, traceId = '') {
|
|
398
|
-
|
|
401
|
+
const caller = _caller();
|
|
402
|
+
log_info(`!!! ${msg}`, data, traceId, caller, LOG_LEVEL.WARN);
|
|
403
|
+
log_bus_1.logBus.emitLog({
|
|
404
|
+
level: 'warn',
|
|
405
|
+
msg,
|
|
406
|
+
data,
|
|
407
|
+
caller: caller === null || caller === void 0 ? void 0 : caller.module,
|
|
408
|
+
trace_id: traceId,
|
|
409
|
+
ts: Date.now(),
|
|
410
|
+
});
|
|
399
411
|
}
|
|
400
412
|
function log_info(msg, data = {}, traceId = '', caller = undefined, level = LOG_LEVEL.INFO) {
|
|
401
413
|
if (level < _log_level) {
|
|
@@ -424,6 +436,14 @@ function log_error(msg, err, traceId = '', caller = undefined) {
|
|
|
424
436
|
else {
|
|
425
437
|
console.error(`${getCurDateTime()} - [5] - [${caller.module}] - ${traceId} - ${msg}`, err);
|
|
426
438
|
}
|
|
439
|
+
log_bus_1.logBus.emitLog({
|
|
440
|
+
level: 'error',
|
|
441
|
+
msg,
|
|
442
|
+
err,
|
|
443
|
+
caller: caller === null || caller === void 0 ? void 0 : caller.module,
|
|
444
|
+
trace_id: traceId,
|
|
445
|
+
ts: Date.now(),
|
|
446
|
+
});
|
|
427
447
|
}
|
|
428
448
|
function createLogger(filename) {
|
|
429
449
|
const module = path_1.default.basename(filename).replace(/\.(js|ts)$/, '');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface LogEvent {
|
|
3
|
+
level: 'warn' | 'error';
|
|
4
|
+
msg: string;
|
|
5
|
+
err?: any;
|
|
6
|
+
data?: any;
|
|
7
|
+
caller?: string;
|
|
8
|
+
trace_id?: string;
|
|
9
|
+
ts: number;
|
|
10
|
+
}
|
|
11
|
+
declare class LogBus extends EventEmitter {
|
|
12
|
+
constructor();
|
|
13
|
+
emitLog(event: LogEvent): void;
|
|
14
|
+
onLog(handler: (event: LogEvent) => void): this;
|
|
15
|
+
}
|
|
16
|
+
export declare const logBus: LogBus;
|
|
17
|
+
export {};
|
package/dist/log_bus.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logBus = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
class LogBus extends events_1.EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.setMaxListeners(50);
|
|
9
|
+
}
|
|
10
|
+
emitLog(event) {
|
|
11
|
+
try {
|
|
12
|
+
this.emit('log', event);
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
onLog(handler) {
|
|
18
|
+
return this.on('log', handler);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.logBus = new LogBus();
|
package/dist/token/index.d.ts
CHANGED
package/dist/token/index.js
CHANGED
package/dist/token/types.d.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.get_zh_pinyin_map = void 0;
|
|
7
|
+
exports.replace_zh_symbols = replace_zh_symbols;
|
|
8
|
+
require('dotenv').config();
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const index_1 = require("../index");
|
|
12
|
+
let _first_load_logged = false;
|
|
13
|
+
const get_zh_pinyin_map = () => {
|
|
14
|
+
const chain_id = process.env.CHAIN_ID;
|
|
15
|
+
if (!chain_id) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const filepath = process.env.ZH_PINYIN_MAP_FILE ||
|
|
19
|
+
path_1.default.join(os_1.default.homedir(), 'data', 'onchain_data', chain_id.toLowerCase(), 'zh_pinyin_map.json');
|
|
20
|
+
const print_log = process.env.PRINT_ZH_PINYIN_MAP === 'true';
|
|
21
|
+
let list = [];
|
|
22
|
+
try {
|
|
23
|
+
list = (0, index_1.readFile)(filepath, true, true);
|
|
24
|
+
if (print_log && !_first_load_logged) {
|
|
25
|
+
(0, index_1.log_info)(`[zh_pinyin_map] loaded ${list.length} entries from ${filepath}`);
|
|
26
|
+
_first_load_logged = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
(0, index_1.log_error)(`[zh_pinyin_map] load failed from ${filepath}`, error);
|
|
31
|
+
}
|
|
32
|
+
return list;
|
|
33
|
+
};
|
|
34
|
+
exports.get_zh_pinyin_map = get_zh_pinyin_map;
|
|
35
|
+
function replace_zh_symbols(text) {
|
|
36
|
+
if (!text)
|
|
37
|
+
return text;
|
|
38
|
+
const list = (0, exports.get_zh_pinyin_map)();
|
|
39
|
+
let s = text;
|
|
40
|
+
for (const row of list) {
|
|
41
|
+
if (row.zh && s.includes(row.zh)) {
|
|
42
|
+
s = s.replace(row.zh, row.pinyin || '');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return s;
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDuration = parseDuration;
|
|
4
|
+
exports.formatDuration = formatDuration;
|
|
5
|
+
function parseDuration(value) {
|
|
6
|
+
if (value === undefined || value === null || value === '')
|
|
7
|
+
return 0;
|
|
8
|
+
if (typeof value === 'number')
|
|
9
|
+
return value;
|
|
10
|
+
const s = String(value).trim();
|
|
11
|
+
if (!s)
|
|
12
|
+
return 0;
|
|
13
|
+
const match = s.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/i);
|
|
14
|
+
if (!match) {
|
|
15
|
+
const n = parseInt(s, 10);
|
|
16
|
+
return isNaN(n) ? 0 : n;
|
|
17
|
+
}
|
|
18
|
+
const n = parseFloat(match[1]);
|
|
19
|
+
const unit = (match[2] || 'ms').toLowerCase();
|
|
20
|
+
const multipliers = {
|
|
21
|
+
ms: 1,
|
|
22
|
+
s: 1000,
|
|
23
|
+
m: 60000,
|
|
24
|
+
h: 3600000,
|
|
25
|
+
d: 86400000,
|
|
26
|
+
};
|
|
27
|
+
return Math.round(n * (multipliers[unit] || 1));
|
|
28
|
+
}
|
|
29
|
+
function formatDuration(ms) {
|
|
30
|
+
if (!ms || ms <= 0)
|
|
31
|
+
return '0ms';
|
|
32
|
+
const days = Math.floor(ms / 86400000);
|
|
33
|
+
const hours = Math.floor((ms % 86400000) / 3600000);
|
|
34
|
+
const mins = Math.floor((ms % 3600000) / 60000);
|
|
35
|
+
const secs = Math.floor((ms % 60000) / 1000);
|
|
36
|
+
const remMs = ms % 1000;
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (days > 0)
|
|
39
|
+
parts.push(`${days}d`);
|
|
40
|
+
if (hours > 0)
|
|
41
|
+
parts.push(`${hours}h`);
|
|
42
|
+
if (mins > 0)
|
|
43
|
+
parts.push(`${mins}m`);
|
|
44
|
+
if (secs > 0)
|
|
45
|
+
parts.push(`${secs}s`);
|
|
46
|
+
if (remMs > 0 && parts.length === 0)
|
|
47
|
+
parts.push(`${remMs}ms`);
|
|
48
|
+
return parts.join(' ') || '0ms';
|
|
49
|
+
}
|
package/dist/util/index.d.ts
CHANGED
package/dist/util/index.js
CHANGED
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -922,9 +922,8 @@ export interface OnTradeResultType {
|
|
|
922
922
|
error_code?: string
|
|
923
923
|
token_in: AnalyzeTokenAmountType
|
|
924
924
|
token_out: AnalyzeTokenAmountType
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
gas_cost_usd: number
|
|
925
|
+
/** gas 成本 — 单位为链的原生币(BNB/ETH/SOL/...),USD 计价由消费者用原生币市价换算 */
|
|
926
|
+
gas_cost_native: number
|
|
928
927
|
}
|
|
929
928
|
|
|
930
929
|
export interface AnalyzeTokenAmountType {
|