@clonegod/ttd-sol-common 2.0.79 → 2.0.81

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.
@@ -1,5 +1,4 @@
1
1
  export * from './pricing';
2
- export * from './pool_subscription_registry';
3
2
  export * from './pool_event';
4
3
  export * from './chain_ops';
5
4
  export * from './abstract_dex_quote';
@@ -15,7 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./pricing"), exports);
18
- __exportStar(require("./pool_subscription_registry"), exports);
19
18
  __exportStar(require("./pool_event"), exports);
20
19
  __exportStar(require("./chain_ops"), exports);
21
20
  __exportStar(require("./abstract_dex_quote"), exports);
@@ -52,11 +52,18 @@ async function attachPoolSubscriptionLifecycle(appConfig, pools) {
52
52
  + '询价进程必须由 PM2/trade-mgt 以确定进程名启动,否则订阅会被 reconcile 当孤儿清除。');
53
53
  }
54
54
  await registerPoolSubscriptions(appConfig, pools, quote_app_id);
55
+ const heartbeatMs = Math.max(5000, parseInt(process.env.SUBSCRIPTION_HEARTBEAT_MS || '30000', 10) || 30000);
56
+ const heartbeat = setInterval(() => {
57
+ registerPoolSubscriptions(appConfig, pools, quote_app_id)
58
+ .catch(e => (0, dist_1.log_warn)(`[pool/subscriptions] heartbeat re-register failed: ${e.message}`));
59
+ }, heartbeatMs);
60
+ heartbeat.unref?.();
55
61
  let cleaned = false;
56
62
  const cleanup = async (sig) => {
57
63
  if (cleaned)
58
64
  return;
59
65
  cleaned = true;
66
+ clearInterval(heartbeat);
60
67
  (0, dist_1.log_info)(`[pool/subscriptions] ${sig} → unregister ${quote_app_id}`);
61
68
  await unregisterPoolSubscriptions(appConfig, quote_app_id);
62
69
  process.exit(0);
@@ -0,0 +1,9 @@
1
+ import { AbastrcatTrade, QuoteResultType, TradeContext } from '@clonegod/ttd-core/dist';
2
+ import { TransactionInstruction } from '@solana/web3.js';
3
+ import { SolTransactionBuilder } from './tx_builder';
4
+ export declare abstract class AbstractSolDexTrade<Q extends QuoteResultType = QuoteResultType> extends AbastrcatTrade {
5
+ abstract txBuilder: SolTransactionBuilder;
6
+ protected abstract getQuoteResult(context: TradeContext): Promise<Q> | Q;
7
+ protected abstract createSwapInstructions(context: TradeContext, quote_result: Q): Promise<TransactionInstruction[]>;
8
+ execute(context: TradeContext): Promise<string>;
9
+ }
@@ -0,0 +1,47 @@
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.AbstractSolDexTrade = void 0;
7
+ const dist_1 = require("@clonegod/ttd-core/dist");
8
+ const bs58_1 = __importDefault(require("bs58"));
9
+ const trade_trace_1 = require("./trade_trace");
10
+ const send_1 = require("./send");
11
+ class AbstractSolDexTrade extends dist_1.AbastrcatTrade {
12
+ async execute(context) {
13
+ const pair = context.price_msg.pair;
14
+ const { order_msg, slippage_bps, order_trace_id } = context;
15
+ const direction = order_msg.aToB ? 'SELL' : 'BUY';
16
+ const trace = new trade_trace_1.TradeTrace(order_trace_id, pair, direction);
17
+ trace.set('order_id', order_msg.unique_order_msg_id);
18
+ trace.set('amount', order_msg.amount);
19
+ trace.set('slippage', `${slippage_bps / 100}%`);
20
+ let stage = 'quote';
21
+ try {
22
+ const quote_result = await this.getQuoteResult(context);
23
+ trace.mark('quote');
24
+ trace.set('exec_price', quote_result.price);
25
+ stage = 'encode';
26
+ const swapIxs = await this.createSwapInstructions(context, quote_result);
27
+ trace.mark('encode');
28
+ stage = 'build';
29
+ const { sol_swqos_only } = context.trade_runtime.settings.strategy;
30
+ const swapTx = this.txBuilder.buildTransactionForSwap(context, swapIxs);
31
+ trace.mark('build');
32
+ const txid = bs58_1.default.encode(swapTx.signature);
33
+ trace.set('txid', txid);
34
+ stage = 'send';
35
+ new send_1.TransactionSender().sendTransaction(context, swapTx, sol_swqos_only);
36
+ trace.mark('sent');
37
+ trace.flush();
38
+ return txid;
39
+ }
40
+ catch (err) {
41
+ trace.markError(stage, err instanceof Error ? err.message : String(err));
42
+ trace.flush();
43
+ throw err;
44
+ }
45
+ }
46
+ }
47
+ exports.AbstractSolDexTrade = AbstractSolDexTrade;
@@ -1,3 +1,5 @@
1
+ export * from './trade_trace';
2
+ export * from './abstract_dex_trade';
1
3
  export * from './tx_result_check';
2
4
  export * from './tx_result_parse';
3
5
  export * from './tx_builder';
@@ -14,6 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./trade_trace"), exports);
18
+ __exportStar(require("./abstract_dex_trade"), exports);
17
19
  __exportStar(require("./tx_result_check"), exports);
18
20
  __exportStar(require("./tx_result_parse"), exports);
19
21
  __exportStar(require("./tx_builder"), exports);
@@ -0,0 +1,17 @@
1
+ export declare class TradeTrace {
2
+ readonly orderId: string;
3
+ readonly pair: string;
4
+ readonly direction: string;
5
+ private startTime;
6
+ private marks;
7
+ private data;
8
+ private error;
9
+ constructor(orderId: string, pair?: string, direction?: string);
10
+ mark(name: string): void;
11
+ set(key: string, value: any): void;
12
+ markError(stage: string, message: string): void;
13
+ private buildTimeline;
14
+ getAbsoluteMarks(): Record<string, number>;
15
+ getStartTime(): number;
16
+ flush(): void;
17
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TradeTrace = void 0;
4
+ const ttd_core_1 = require("@clonegod/ttd-core");
5
+ const logger = (0, ttd_core_1.createLogger)(__filename);
6
+ class TradeTrace {
7
+ constructor(orderId, pair = '', direction = '') {
8
+ this.orderId = orderId;
9
+ this.pair = pair;
10
+ this.direction = direction;
11
+ this.marks = [];
12
+ this.data = {};
13
+ this.error = null;
14
+ this.startTime = Date.now();
15
+ }
16
+ mark(name) {
17
+ this.marks.push({ name, elapsed: Date.now() - this.startTime });
18
+ }
19
+ set(key, value) {
20
+ this.data[key] = value;
21
+ }
22
+ markError(stage, message) {
23
+ this.error = { stage, message };
24
+ }
25
+ buildTimeline() {
26
+ if (this.marks.length === 0)
27
+ return 'recv';
28
+ let prev = 0;
29
+ const parts = this.marks.map(m => {
30
+ const delta = m.elapsed - prev;
31
+ prev = m.elapsed;
32
+ return `${m.name}(${delta}ms)`;
33
+ });
34
+ const total = this.marks[this.marks.length - 1].elapsed;
35
+ return `recv -> ${parts.join(' -> ')} = ${total}ms`;
36
+ }
37
+ getAbsoluteMarks() {
38
+ const result = {};
39
+ for (const m of this.marks) {
40
+ result[m.name] = this.startTime + m.elapsed;
41
+ }
42
+ return result;
43
+ }
44
+ getStartTime() {
45
+ return this.startTime;
46
+ }
47
+ flush() {
48
+ const timeline = this.buildTimeline();
49
+ if (this.error) {
50
+ logger.error(`[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction} | stage=${this.error.stage} | ${this.error.message}`, new Error(this.error.message));
51
+ logger.info(`[TRADE FAILED detail] ${this.orderId}`, {
52
+ timeline,
53
+ error_stage: this.error.stage,
54
+ ...this.data,
55
+ });
56
+ }
57
+ else {
58
+ logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, {
59
+ timeline,
60
+ ...this.data,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ exports.TradeTrace = TradeTrace;
@@ -237,6 +237,7 @@ class TransactionResultChecker extends dist_1.AbstractTransactionResultCheck {
237
237
  execution: trade_extra_info,
238
238
  c_id
239
239
  };
240
+ (0, dist_1.report_data_to_analyze)('TradeResultType', trade_result);
240
241
  return trade_result;
241
242
  }
242
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sol-common",
3
- "version": "2.0.79",
3
+ "version": "2.0.81",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,5 +1,4 @@
1
1
  export * from './pricing'
2
- export * from './pool_subscription_registry'
3
2
  export * from './pool_event'
4
3
  export * from './chain_ops'
5
4
  export * from './abstract_dex_quote'
@@ -0,0 +1,72 @@
1
+ import { AbastrcatTrade, QuoteResultType, TradeContext } from '@clonegod/ttd-core/dist'
2
+ import { TransactionInstruction } from '@solana/web3.js'
3
+ import bs58 from 'bs58'
4
+ import { TradeTrace } from './trade_trace'
5
+ import { SolTransactionBuilder } from './tx_builder'
6
+ import { TransactionSender } from './send'
7
+
8
+ /**
9
+ * AbstractSolDexTrade —— Solana 交易执行基类(对齐 BSC/BASE 的 abstract_dex_trade)。
10
+ *
11
+ * 共享一套交易热路径 + TradeTrace 打点(一处维护,8 个 DEX 共用),DEX 只实现两个差异点:
12
+ * - getQuoteResult:重新询价,拿 exec 用的 quote_result(可同步可异步)。
13
+ * - createSwapInstructions:组装这笔 swap 的指令(内部自取 poolData / account / 0-RPC 组装)。
14
+ *
15
+ * 流程(SOL 模型,非 EVM):recv → quote → encode → build(签名) → sent(Helius Sender 发出,fire-and-forget;
16
+ * 结果走 tx_result_check 异步回来)。无 nonce / caller。
17
+ *
18
+ * 输出对齐 BSC:成功 `[TRADE OK] <id> | pair dir { timeline: recv -> quote(Xms) -> encode -> build -> sent = N ms, ... }`,
19
+ * 失败 `[TRADE FAILED] ... stage=<阶段> | <错误>`。
20
+ */
21
+ export abstract class AbstractSolDexTrade<Q extends QuoteResultType = QuoteResultType> extends AbastrcatTrade {
22
+ /** DEX 子类持有(构造时 new SolTransactionBuilder(appConfig));基类只调用 buildTransactionForSwap。 */
23
+ abstract txBuilder: SolTransactionBuilder
24
+
25
+ /** DEX 实现:重新询价(exec 用)。Q 允许 DEX 用自己的 quote_result 子类型(如 PumfunQuoteResultType)。 */
26
+ protected abstract getQuoteResult(context: TradeContext): Promise<Q> | Q
27
+ /** DEX 实现:组装 swap 指令(内部自取 poolData / 0-RPC)。返回指令数组。 */
28
+ protected abstract createSwapInstructions(context: TradeContext, quote_result: Q): Promise<TransactionInstruction[]>
29
+
30
+ async execute(context: TradeContext): Promise<string> {
31
+ const pair = context.price_msg.pair
32
+ const { order_msg, slippage_bps, order_trace_id } = context
33
+ const direction = order_msg.aToB ? 'SELL' : 'BUY'
34
+
35
+ const trace = new TradeTrace(order_trace_id, pair, direction)
36
+ trace.set('order_id', order_msg.unique_order_msg_id)
37
+ trace.set('amount', order_msg.amount)
38
+ trace.set('slippage', `${slippage_bps / 100}%`)
39
+
40
+ let stage = 'quote'
41
+ try {
42
+ const quote_result = await this.getQuoteResult(context)
43
+ trace.mark('quote')
44
+ trace.set('exec_price', quote_result.price)
45
+
46
+ stage = 'encode'
47
+ const swapIxs = await this.createSwapInstructions(context, quote_result)
48
+ trace.mark('encode')
49
+
50
+ // 构建交易(签名)
51
+ stage = 'build'
52
+ const { sol_swqos_only } = context.trade_runtime.settings.strategy
53
+ const swapTx = this.txBuilder.buildTransactionForSwap(context, swapIxs)
54
+ trace.mark('build')
55
+
56
+ const txid = bs58.encode(swapTx.signature)
57
+ trace.set('txid', txid)
58
+
59
+ // 发送:只走 Helius Sender(swqos + jito 路由由 Sender 负责),fire-and-forget
60
+ stage = 'send'
61
+ new TransactionSender().sendTransaction(context, swapTx, sol_swqos_only)
62
+ trace.mark('sent')
63
+
64
+ trace.flush()
65
+ return txid
66
+ } catch (err) {
67
+ trace.markError(stage, err instanceof Error ? err.message : String(err))
68
+ trace.flush()
69
+ throw err
70
+ }
71
+ }
72
+ }
@@ -1,3 +1,5 @@
1
+ export * from './trade_trace'
2
+ export * from './abstract_dex_trade'
1
3
  export * from './tx_result_check'
2
4
  export * from './tx_result_parse'
3
5
  export * from './tx_builder'
@@ -0,0 +1,98 @@
1
+ import { createLogger } from '@clonegod/ttd-core';
2
+
3
+ const logger = createLogger(__filename);
4
+
5
+ /**
6
+ * TradeTrace — 交易链路追踪(对齐 BSC/BASE ttd-{bsc,base}-common/trade/trade_trace.ts,逐字节一致)
7
+ *
8
+ * 在交易热路径中收集关键时间点和数据,交易完成后一次性输出结构化日志。
9
+ * 不改变现有 log_info 逻辑,只是额外收集数据。
10
+ *
11
+ * 用法:
12
+ * const trace = new TradeTrace(order_trace_id, 'THREE/SOL', 'BUY');
13
+ * trace.mark('encode');
14
+ * trace.set('txid', '...');
15
+ * trace.flush(); // 一次性输出
16
+ */
17
+ export class TradeTrace {
18
+ private startTime: number;
19
+ private marks: Array<{ name: string; elapsed: number }> = [];
20
+ private data: Record<string, any> = {};
21
+ private error: { stage: string; message: string } | null = null;
22
+
23
+ constructor(
24
+ public readonly orderId: string,
25
+ public readonly pair: string = '',
26
+ public readonly direction: string = '',
27
+ ) {
28
+ this.startTime = Date.now();
29
+ }
30
+
31
+ /** 记录一个时间点 */
32
+ mark(name: string): void {
33
+ this.marks.push({ name, elapsed: Date.now() - this.startTime });
34
+ }
35
+
36
+ /** 记录键值数据 */
37
+ set(key: string, value: any): void {
38
+ this.data[key] = value;
39
+ }
40
+
41
+ /** 记录错误 */
42
+ markError(stage: string, message: string): void {
43
+ this.error = { stage, message };
44
+ }
45
+
46
+ /**
47
+ * 构建可视化 timeline 字符串:
48
+ * recv -> quote(2ms) -> encode(2ms) -> build(11ms) -> sent(33ms) = 48ms
49
+ * 每段括号内是该阶段耗时(增量,非累计)
50
+ */
51
+ private buildTimeline(): string {
52
+ if (this.marks.length === 0) return 'recv';
53
+ let prev = 0;
54
+ const parts = this.marks.map(m => {
55
+ const delta = m.elapsed - prev;
56
+ prev = m.elapsed;
57
+ return `${m.name}(${delta}ms)`;
58
+ });
59
+ const total = this.marks[this.marks.length - 1].elapsed;
60
+ return `recv -> ${parts.join(' -> ')} = ${total}ms`;
61
+ }
62
+
63
+ /** 返回各 mark 的绝对时间戳 (ms) */
64
+ getAbsoluteMarks(): Record<string, number> {
65
+ const result: Record<string, number> = {};
66
+ for (const m of this.marks) {
67
+ result[m.name] = this.startTime + m.elapsed;
68
+ }
69
+ return result;
70
+ }
71
+
72
+ /** 返回 trace 起始时间 (ms) */
73
+ getStartTime(): number {
74
+ return this.startTime;
75
+ }
76
+
77
+ /** 一次性输出结构化日志 */
78
+ flush(): void {
79
+ const timeline = this.buildTimeline();
80
+
81
+ if (this.error) {
82
+ logger.error(
83
+ `[TRADE FAILED] ${this.orderId} | ${this.pair} ${this.direction} | stage=${this.error.stage} | ${this.error.message}`,
84
+ new Error(this.error.message),
85
+ );
86
+ logger.info(`[TRADE FAILED detail] ${this.orderId}`, {
87
+ timeline,
88
+ error_stage: this.error.stage,
89
+ ...this.data,
90
+ });
91
+ } else {
92
+ logger.info(`[TRADE OK] ${this.orderId} | ${this.pair} ${this.direction}`, {
93
+ timeline,
94
+ ...this.data,
95
+ });
96
+ }
97
+ }
98
+ }
@@ -1,5 +1,5 @@
1
1
  import { OrderMessageType, PriceMessageType, StandardPoolInfoType, StandardSwapDetailType, TradeBroadcastType, TradeResultBalanceChangeType, TradeResultType, TradeTimeFlowType } from "@clonegod/ttd-core"
2
- import { AbstractTransactionResultCheck, getServerInfo, isEmpty, LOCAL_EVENT_NAME, log_error, log_info, log_warn, TradeContext, TRANSACTION_STATE_FAILED, TRANSACTION_STATE_PROCESSING, TRANSACTION_STATE_SUCCESS } from "@clonegod/ttd-core/dist"
2
+ import { AbstractTransactionResultCheck, getServerInfo, isEmpty, LOCAL_EVENT_NAME, log_error, log_info, log_warn, report_data_to_analyze, TradeContext, TRANSACTION_STATE_FAILED, TRANSACTION_STATE_PROCESSING, TRANSACTION_STATE_SUCCESS } from "@clonegod/ttd-core/dist"
3
3
  import { Connection, ParsedTransactionWithMeta, PublicKey } from "@solana/web3.js"
4
4
  import { EventEmitter } from 'events'
5
5
  import { COMMITMENT_LEVEL } from "../common"
@@ -339,6 +339,10 @@ export class TransactionResultChecker extends AbstractTransactionResultCheck {
339
339
  c_id
340
340
  }
341
341
 
342
+ // 对齐 core AbstractTransactionResultCheck.map_swap_result_to_tx_result:本方法是 core 的覆盖拷贝,
343
+ // 之前漏掉了 core 末尾这行上报 → analyze 交易列表一直空。analyze 按 txid 去重,重复上报幂等。
344
+ report_data_to_analyze('TradeResultType', trade_result)
345
+
342
346
  return trade_result
343
347
  }
344
348
 
@@ -1,87 +0,0 @@
1
- import { AppConfig, HttpUtil, log_info, log_warn, SERVICE_PORT, StandardPoolInfoType } from '@clonegod/ttd-core/dist'
2
- import { resolveGrpcProvider } from '../grpc/grpc_provider_registry'
3
-
4
- /**
5
- * 按需订阅 —— 生产端(询价进程调用)。对齐 BSC/BASE/SUI:询价进程通过 config-center 声明所需池子,
6
- * config-center 写 `${chain}:pool:subscriptions` Hash + 广播 `:change`,stream-quote 消费后订阅 Helius。
7
- *
8
- * Solana 适配(与 BSC 的差异):
9
- * - **单 Helius provider,且对齐 SUI 由运维在 analyze Config/RPC 页配(type=grpc, endpoint=laserstream URL, auth_token=API key)**。
10
- * 本 helper **不自动种 provider**;register 用 `resolveGrpcProvider` 解析出 analyze 配的 provider id(无配 fail-loud)。
11
- * - **base58 地址大小写敏感**:依赖 config-center `lower()` 已改为 0x 感知(非 0x 原样保留),不破坏地址。
12
- */
13
-
14
- function configCenterBase(appConfig: AppConfig): string {
15
- const chain = appConfig.env_args.chain_id // SOLANA
16
- let host = String(appConfig.env_args.config_center_host || '127.0.0.1').trim()
17
- // host 可能含端口(127.0.0.1:4000)或不含(127.0.0.1);不含则补 CONFIG_CENTER_HTTP
18
- if (!host.includes(':')) host = `${host}:${SERVICE_PORT.CONFIG_CENTER_HTTP}`
19
- return `http://${host}/${chain}/config`
20
- }
21
-
22
- /** 注册一批池子订阅(每池 POST config-center /quote/register),provider_ids 用 analyze 配的 grpc provider。 */
23
- export async function registerPoolSubscriptions(
24
- appConfig: AppConfig,
25
- pools: StandardPoolInfoType[],
26
- quote_app_id: string,
27
- ): Promise<void> {
28
- // provider 由运维在 analyze Config/RPC 配(type=grpc);解析其 id 供 register 引用,无配 fail-loud
29
- const provider = await resolveGrpcProvider(appConfig.arb_cache.redis_cmd, appConfig.env_args.chain_id)
30
- const provider_ids = [provider.id]
31
-
32
- const base = configCenterBase(appConfig)
33
- let ok = 0
34
- for (const p of pools) {
35
- try {
36
- await HttpUtil.post(`${base}/quote/register`, {
37
- pool_address: p.pool_address,
38
- provider_ids,
39
- quote_app_id,
40
- pair: p.pair,
41
- dex_id: p.dex_id,
42
- pool_name: p.pool_name,
43
- fee_rate: p.fee_rate,
44
- })
45
- ok++
46
- } catch (e: any) {
47
- log_warn(`[pool/subscriptions] register ${p.pool_address} failed: ${e.message}`)
48
- }
49
- }
50
- log_info(`[pool/subscriptions] registered ${ok}/${pools.length} pools by ${quote_app_id} → provider=${provider.id}`)
51
- }
52
-
53
- /** 注销本进程注册的所有池子订阅(按 quote_app_id)。 */
54
- export async function unregisterPoolSubscriptions(appConfig: AppConfig, quote_app_id: string): Promise<void> {
55
- try {
56
- await HttpUtil.post(`${configCenterBase(appConfig)}/quote/unregister`, { quote_app_id })
57
- log_info(`[pool/subscriptions] unregistered all pools by ${quote_app_id}`)
58
- } catch (e: any) {
59
- log_warn(`[pool/subscriptions] unregister failed: ${e.message}`)
60
- }
61
- }
62
-
63
- /**
64
- * 询价进程启动后调用一次:确保 helius provider + 注册全部池子 + 装退出时自动 unregister。
65
- * quote_app_id **必须 = PM2 进程名**(pm2 注入 process.env.name),与 trade-mgt register/reconcile 用的 app_id 一致。
66
- * 否则 trade-mgt 的 60s reconcile 按 PM2 进程名核对,会把本进程自注册的订阅当"孤儿"注销 → 订阅消失、收不到事件。
67
- * **取不到则 fail-loud(绝不兜底)**:塌成 'sol-quote' 这类 PM2 里不存在的名字 = 必被 reconcile 清掉,等于没注册还掩盖问题。
68
- */
69
- export async function attachPoolSubscriptionLifecycle(appConfig: AppConfig, pools: StandardPoolInfoType[]): Promise<void> {
70
- const quote_app_id = process.env.name || String(appConfig.env_args.app_name || '')
71
- if (!quote_app_id) {
72
- throw new Error('[pool/subscriptions] 无法确定 quote_app_id:process.env.name(PM2 进程名) 与 APP_NAME 均为空。'
73
- + '询价进程必须由 PM2/trade-mgt 以确定进程名启动,否则订阅会被 reconcile 当孤儿清除。')
74
- }
75
- await registerPoolSubscriptions(appConfig, pools, quote_app_id)
76
-
77
- let cleaned = false
78
- const cleanup = async (sig: string) => {
79
- if (cleaned) return
80
- cleaned = true
81
- log_info(`[pool/subscriptions] ${sig} → unregister ${quote_app_id}`)
82
- await unregisterPoolSubscriptions(appConfig, quote_app_id)
83
- process.exit(0)
84
- }
85
- process.on('SIGINT', () => void cleanup('SIGINT'))
86
- process.on('SIGTERM', () => void cleanup('SIGTERM'))
87
- }