@clonegod/ttd-sol-common 2.0.78 → 2.0.80
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/quote/abstract_dex_quote.d.ts +2 -1
- package/dist/quote/abstract_dex_quote.js +3 -3
- package/dist/quote/index.d.ts +0 -1
- package/dist/quote/index.js +0 -1
- package/dist/quote/pool_subscription_registry.js +7 -0
- package/dist/quote/verify/quote_price_verify.d.ts +1 -2
- package/dist/quote/verify/quote_price_verify.js +3 -6
- package/package.json +1 -1
- package/src/quote/abstract_dex_quote.ts +14 -10
- package/src/quote/index.ts +0 -1
- package/src/quote/verify/quote_price_verify.ts +18 -24
- package/src/quote/pool_subscription_registry.ts +0 -87
|
@@ -50,7 +50,8 @@ export declare abstract class AbstractDexQuote<C extends SolanaChainOps = Solana
|
|
|
50
50
|
protected calculateDepth(_poolInfo: StandardPoolInfoType, _poolAddress: string, _priceMap: Map<string, {
|
|
51
51
|
price: string;
|
|
52
52
|
} | undefined>): Promise<QuoteDepthOutput | undefined>;
|
|
53
|
-
protected
|
|
53
|
+
protected getEffectiveFeeBps(poolInfo: StandardPoolInfoType): number;
|
|
54
|
+
protected getOutOfVaultFeeBps(_poolInfo: StandardPoolInfoType): number;
|
|
54
55
|
protected isStateConsistentForQuote(_poolInfo: StandardPoolInfoType): boolean;
|
|
55
56
|
protected accountSlots: Map<string, number>;
|
|
56
57
|
protected recordAccountSlot(accountAddr: string, slot: number): void;
|
|
@@ -15,13 +15,14 @@ class AbstractDexQuote {
|
|
|
15
15
|
this.latestBlockSlot = 0;
|
|
16
16
|
this.MIN_QUOTE_INTERVAL_MS = Math.max(3000, parseInt(process.env.MIN_QUOTE_INTERVAL_MS || '10000', 10) || 10000);
|
|
17
17
|
this.consistencyPolicy = 'snapshot';
|
|
18
|
-
this.feeInVault = true;
|
|
19
18
|
this.accountSlots = new Map();
|
|
20
19
|
this.appConfig = appConfig;
|
|
21
20
|
this.chain = chain;
|
|
22
21
|
}
|
|
23
22
|
async refreshStateFromEvent(_poolInfo, _evt) { }
|
|
24
23
|
async calculateDepth(_poolInfo, _poolAddress, _priceMap) { return undefined; }
|
|
24
|
+
getEffectiveFeeBps(poolInfo) { return Number(poolInfo.fee_rate) || 0; }
|
|
25
|
+
getOutOfVaultFeeBps(_poolInfo) { return 0; }
|
|
25
26
|
isStateConsistentForQuote(_poolInfo) { return true; }
|
|
26
27
|
recordAccountSlot(accountAddr, slot) {
|
|
27
28
|
this.accountSlots.set(accountAddr, slot);
|
|
@@ -72,8 +73,7 @@ class AbstractDexQuote {
|
|
|
72
73
|
token1Decimals: Number(poolInfo.tokenB?.decimals ?? 0),
|
|
73
74
|
poolInfo: { tokenA: poolInfo.tokenA, tokenB: poolInfo.tokenB, quote_token: poolInfo.quote_token },
|
|
74
75
|
swapperDeltaConvention: false,
|
|
75
|
-
|
|
76
|
-
feeRateBps: poolInfo.fee_rate,
|
|
76
|
+
outOfVaultFeeBps: this.getOutOfVaultFeeBps(poolInfo),
|
|
77
77
|
};
|
|
78
78
|
const verifyLog = this.quotePriceVerify.checkSwap(params);
|
|
79
79
|
if (verifyLog)
|
package/dist/quote/index.d.ts
CHANGED
package/dist/quote/index.js
CHANGED
|
@@ -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);
|
|
@@ -114,9 +114,6 @@ class QuotePriceVerify {
|
|
|
114
114
|
if (!match)
|
|
115
115
|
continue;
|
|
116
116
|
refPrice = match.tier.price;
|
|
117
|
-
if (params.feeExclusiveExec && match.tier.amount_in > 0) {
|
|
118
|
-
refPrice = stripFeeExclusive(refPrice, isBuy, match.tier.fee / match.tier.amount_in);
|
|
119
|
-
}
|
|
120
117
|
tierInfo = {
|
|
121
118
|
matched_pct: parseFloat(match.tier.pct.toFixed(4)),
|
|
122
119
|
mode: match.mode,
|
|
@@ -127,9 +124,9 @@ class QuotePriceVerify {
|
|
|
127
124
|
}
|
|
128
125
|
else {
|
|
129
126
|
refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
}
|
|
128
|
+
if (params.outOfVaultFeeBps && params.outOfVaultFeeBps > 0) {
|
|
129
|
+
refPrice = stripFeeExclusive(refPrice, isBuy, params.outOfVaultFeeBps / 10000);
|
|
133
130
|
}
|
|
134
131
|
if (refPrice <= 0)
|
|
135
132
|
continue;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ import { QuoteTrace } from './quote_trace'
|
|
|
17
17
|
// · 无 v3 PriceFeed(同 SUI,省 do_quote_v3)。
|
|
18
18
|
// · verify(报价偏差)走**独立的 swap-verify 通道**(非账户快照):stream-quote 从真实交易的
|
|
19
19
|
// pre/postTokenBalances 解码订阅池子 vault Δ(DEX 无关)→ WS push → handleSwapVerify → checkSwap。
|
|
20
|
-
// exec_price = |Δquote/Δbase|(pool-side vault Δ),fee-exclusive
|
|
20
|
+
// exec_price = |Δquote/Δbase|(pool-side vault Δ),fee-exclusive 对账(费路由出池的部分由 getOutOfVaultFeeBps 提供,方向感知剥掉)。
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* 流动性一致性策略(设计文档 §2.2 定稿:3 订阅原型)。各 DEX 子类按协议特性声明:
|
|
@@ -114,12 +114,17 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
114
114
|
/** 深度(tier 化);fee 来自链上 load。priceMap 由基类预取。默认 undefined(step 3 各 DEX 逐档实现)。 */
|
|
115
115
|
protected async calculateDepth(_poolInfo: StandardPoolInfoType, _poolAddress: string, _priceMap: Map<string, { price: string } | undefined>): Promise<QuoteDepthOutput | undefined> { return undefined }
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* - false(pumpswap:protocol/creator fee 路由出池到独立账户):vault Δ 不含费 → checkSwap 把参考价剥成 fee-exclusive。
|
|
120
|
-
* 详见 quote_price_verify CheckSwapParams.feeExclusiveExec。
|
|
117
|
+
* depth gross-up 用的**总费率**(bps)。默认配置 fee_rate;动态费 DEX(pumpswap 市值档位 / DLMM 波动费)覆盖,
|
|
118
|
+
* 每事件按真实链上 feeConfig 重算 → 报价自适应费率变化。
|
|
121
119
|
*/
|
|
122
|
-
protected
|
|
120
|
+
protected getEffectiveFeeBps(poolInfo: StandardPoolInfoType): number { return Number(poolInfo.fee_rate) || 0 }
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* verify 剥费用的**出池费率**(bps)= 路由出 vault 的费(vault Δ exec 不含的部分)。
|
|
124
|
+
* - 0(默认):费全留池(Raydium/Orca/Meteora 等)→ vault Δ 与含费报价同口径,不剥。
|
|
125
|
+
* - >0:pumpswap(protocol+creator,动态按市值档位)等覆盖。详见 CheckSwapParams.outOfVaultFeeBps。
|
|
126
|
+
*/
|
|
127
|
+
protected getOutOfVaultFeeBps(_poolInfo: StandardPoolInfoType): number { return 0 }
|
|
123
128
|
|
|
124
129
|
/**
|
|
125
130
|
* slot 门控钩子(仅 consistencyPolicy='slot-gate' 生效):判断本池"走单触碰的多账户"状态是否 slot 一致。
|
|
@@ -181,7 +186,7 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
181
186
|
|
|
182
187
|
/**
|
|
183
188
|
* 消费 stream-quote 推来的 swap-verify(pool-side vault Δ)→ checkSwap 对账缓存报价 → 打 [Verify]。
|
|
184
|
-
* exec_price 由 |Δquote/Δbase|
|
|
189
|
+
* exec_price 由 |Δquote/Δbase| 算;剥费用 getOutOfVaultFeeBps(详见 CheckSwapParams.outOfVaultFeeBps)。
|
|
185
190
|
*/
|
|
186
191
|
private handleSwapVerify(sv: SolanaSwapVerifyEventData): void {
|
|
187
192
|
const poolInfo = this.poolInfoMap.get(sv.pool_address)
|
|
@@ -198,9 +203,8 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
198
203
|
token0Decimals: Number(poolInfo.tokenA?.decimals ?? 0),
|
|
199
204
|
token1Decimals: Number(poolInfo.tokenB?.decimals ?? 0),
|
|
200
205
|
poolInfo: { tokenA: poolInfo.tokenA, tokenB: poolInfo.tokenB, quote_token: poolInfo.quote_token },
|
|
201
|
-
swapperDeltaConvention: false,
|
|
202
|
-
|
|
203
|
-
feeRateBps: poolInfo.fee_rate, // market-data 强制整数 bps
|
|
206
|
+
swapperDeltaConvention: false, // amount0/1 是 pool-side vault Δ
|
|
207
|
+
outOfVaultFeeBps: this.getOutOfVaultFeeBps(poolInfo), // 出池费(默认 0=全留池不剥;pumpswap 动态)
|
|
204
208
|
}
|
|
205
209
|
const verifyLog = this.quotePriceVerify.checkSwap(params)
|
|
206
210
|
if (verifyLog) log_info(verifyLog)
|
package/src/quote/index.ts
CHANGED
|
@@ -120,12 +120,13 @@ function pickMatchingTier(tiers: QuoteTier[], actualAmountIn: number): TierMatch
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* 把含费报价剥成 fee-exclusive(与 vault Δ exec 同口径),r = **出池费率**(路由出 vault 的部分)。
|
|
124
124
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* -
|
|
128
|
-
*
|
|
125
|
+
* vault Δ exec 不含"出池费"(如 pumpswap 的 protocol+creator,转给独立账户),但**含**留池的 lp 费。
|
|
126
|
+
* 报价(ask/bid)是 swapper 含全费的成交价。两者差一个"出池费",且方向**不对称**:
|
|
127
|
+
* - ask(买基础币) = exec / (1 − r) → ask_excl = ask × (1 − r)
|
|
128
|
+
* - bid(卖基础币) = exec × (1 − r) → bid_excl = bid / (1 − r) ← 是除,不是乘!
|
|
129
|
+
* 不论出池费记输入侧还是输出侧,上式一阶成立(lp 留池的二阶项可忽略)。
|
|
129
130
|
* ask/bid 同为 quote/base 口径(calculateStandardPrice),仅方向决定撑开符号。
|
|
130
131
|
*/
|
|
131
132
|
function stripFeeExclusive(refPrice: number, isBuy: boolean, r: number): number {
|
|
@@ -157,18 +158,14 @@ export interface CheckSwapParams {
|
|
|
157
158
|
*/
|
|
158
159
|
swapperDeltaConvention?: boolean;
|
|
159
160
|
/**
|
|
160
|
-
*
|
|
161
|
+
* **出池费率**(bps)= 路由出 vault 的费(pumpswap=protocol+creator,**动态**按市值档位)。
|
|
161
162
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* -
|
|
165
|
-
*
|
|
166
|
-
* 为 true 时:refPrice ×= (amount_in − fee)/amount_in(tier 模式,用该档自身的含费/费额);
|
|
167
|
-
* scalar 兜底用 (1 − feeRateBps/1e4) 一阶近似。
|
|
163
|
+
* exec_price 由 pool vault Δ 算,**不含出池费、含留池 lp 费**。报价含全费 → 两者差出池费。
|
|
164
|
+
* 故 verify 把报价按出池费剥成 vault 口径(方向感知,见 stripFeeExclusive)。
|
|
165
|
+
* - 0 / 缺省:费全留池(Raydium/Orca/Meteora 等),vault Δ 与含费报价同口径 → 不剥。
|
|
166
|
+
* - >0:pumpswap 等,DEX 子类经 getOutOfVaultFeeBps 动态提供(每事件按真实 feeConfig 算)。
|
|
168
167
|
*/
|
|
169
|
-
|
|
170
|
-
/** 池子费率(bps);feeExclusiveExec 且 scalar 兜底(无 tier)时用作一阶剥费因子。 */
|
|
171
|
-
feeRateBps?: number;
|
|
168
|
+
outOfVaultFeeBps?: number;
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
// ========== 主类 ==========
|
|
@@ -303,11 +300,6 @@ export class QuotePriceVerify {
|
|
|
303
300
|
const match = pickMatchingTier(tiers, swapData.inputAmountUi);
|
|
304
301
|
if (!match) continue;
|
|
305
302
|
refPrice = match.tier.price;
|
|
306
|
-
// fee-exclusive 对账:把含费 tier 价剥成 fee-exclusive,与 fee-out-of-vault DEX(pumpswap)的 vault Δ 同口径。
|
|
307
|
-
// r = 该档费率(fee/amount_in,amount_in 含费)。
|
|
308
|
-
if (params.feeExclusiveExec && match.tier.amount_in > 0) {
|
|
309
|
-
refPrice = stripFeeExclusive(refPrice, isBuy, match.tier.fee / match.tier.amount_in);
|
|
310
|
-
}
|
|
311
303
|
tierInfo = {
|
|
312
304
|
matched_pct: parseFloat(match.tier.pct.toFixed(4)),
|
|
313
305
|
mode: match.mode,
|
|
@@ -318,10 +310,12 @@ export class QuotePriceVerify {
|
|
|
318
310
|
} else {
|
|
319
311
|
// scalar fallback:旧 DEX 包未传 tiers 时使用
|
|
320
312
|
refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// fee-exclusive 对账:剥掉"出池费"(vault Δ exec 不含的部分)→ 报价与 vault Δ 同口径。
|
|
316
|
+
// tier / scalar 统一处理:出池费是定比(与档位/大小无关),方向感知(买×卖÷)。
|
|
317
|
+
if (params.outOfVaultFeeBps && params.outOfVaultFeeBps > 0) {
|
|
318
|
+
refPrice = stripFeeExclusive(refPrice, isBuy, params.outOfVaultFeeBps / 10000);
|
|
325
319
|
}
|
|
326
320
|
|
|
327
321
|
if (refPrice <= 0) continue;
|
|
@@ -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
|
-
}
|