@clonegod/ttd-base-common 1.0.25 → 1.1.1
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/appconfig/BaseQuoteAppConfig.d.ts +10 -0
- package/dist/appconfig/BaseQuoteAppConfig.js +36 -0
- package/dist/appconfig/BaseTradeAppConfig.d.ts +7 -0
- package/dist/appconfig/BaseTradeAppConfig.js +13 -0
- package/dist/appconfig/base_dex_env_args.d.ts +5 -0
- package/dist/appconfig/base_dex_env_args.js +68 -0
- package/dist/appconfig/base_env_args.d.ts +82 -0
- package/dist/appconfig/base_env_args.js +91 -0
- package/dist/appconfig/ensure_core_env.d.ts +1 -0
- package/dist/appconfig/ensure_core_env.js +18 -0
- package/dist/appconfig/index.d.ts +5 -0
- package/dist/appconfig/index.js +21 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/quote/depth/amm_depth_calculator.d.ts +19 -0
- package/dist/quote/depth/amm_depth_calculator.js +55 -0
- package/dist/quote/depth/clmm_depth_calculator.d.ts +28 -0
- package/dist/quote/depth/clmm_depth_calculator.js +176 -0
- package/dist/quote/depth/index.d.ts +51 -0
- package/dist/quote/depth/index.js +264 -0
- package/dist/quote/depth/tick_liquidity_snapshot.d.ts +58 -0
- package/dist/quote/depth/tick_liquidity_snapshot.js +143 -0
- package/dist/quote/event/index.d.ts +1 -0
- package/dist/quote/event/index.js +1 -0
- package/dist/quote/event/pool_event_listener.d.ts +5 -3
- package/dist/quote/event/pool_event_listener.js +128 -150
- package/dist/quote/event/swap_debouncer.d.ts +22 -0
- package/dist/quote/event/swap_debouncer.js +80 -0
- package/dist/quote/get_base_token_price.d.ts +6 -0
- package/dist/quote/get_base_token_price.js +90 -0
- package/dist/quote/index.d.ts +7 -0
- package/dist/quote/index.js +7 -0
- package/dist/quote/preload_token_prices.d.ts +2 -0
- package/dist/quote/preload_token_prices.js +37 -0
- package/dist/quote/price_feed_handler.d.ts +15 -0
- package/dist/quote/price_feed_handler.js +56 -0
- package/dist/quote/pricing/fee_helpers.d.ts +13 -0
- package/dist/quote/pricing/fee_helpers.js +68 -0
- package/dist/quote/pricing/index.d.ts +3 -1
- package/dist/quote/pricing/index.js +3 -1
- package/dist/quote/pricing/pool_state_initializer.d.ts +12 -0
- package/dist/quote/pricing/pool_state_initializer.js +191 -0
- package/dist/quote/pricing/sdk_token_factory.d.ts +2 -0
- package/dist/quote/pricing/sdk_token_factory.js +21 -0
- package/dist/quote/quote_amount.d.ts +4 -0
- package/dist/quote/quote_amount.js +24 -0
- package/dist/quote/tick/cached_tick_data_provider.d.ts +12 -0
- package/dist/quote/tick/cached_tick_data_provider.js +45 -0
- package/dist/quote/tick/clmm_tick_cache.d.ts +42 -0
- package/dist/quote/tick/clmm_tick_cache.js +236 -0
- package/dist/quote/tick/index.d.ts +4 -0
- package/dist/{ws → quote/tick}/index.js +4 -2
- package/dist/quote/tick/state_view_tick_loader.d.ts +17 -0
- package/dist/quote/tick/state_view_tick_loader.js +136 -0
- package/dist/quote/tick/tick_lens_loaders.d.ts +24 -0
- package/dist/quote/tick/tick_lens_loaders.js +158 -0
- package/dist/quote/verify/index.d.ts +2 -0
- package/dist/quote/verify/index.js +5 -0
- package/dist/quote/verify/quote_price_verify.d.ts +30 -0
- package/dist/quote/verify/quote_price_verify.js +240 -0
- package/dist/redis/redis_client.d.ts +3 -2
- package/dist/redis/redis_client.js +86 -116
- package/dist/send-tx/constants.d.ts +2 -0
- package/dist/send-tx/constants.js +6 -0
- package/dist/send-tx/index.d.ts +2 -0
- package/dist/{config → send-tx}/index.js +2 -1
- package/dist/send-tx/types.d.ts +4 -0
- package/dist/send-tx/types.js +2 -0
- package/dist/trade/abstract_dex_trade.d.ts +43 -21
- package/dist/trade/abstract_dex_trade.js +347 -133
- package/dist/trade/caller_manager.d.ts +31 -0
- package/dist/trade/caller_manager.js +202 -0
- package/dist/trade/check/abstract_tx_result_checker.d.ts +28 -0
- package/dist/trade/check/abstract_tx_result_checker.js +192 -0
- package/dist/trade/check/index.d.ts +1 -1
- package/dist/trade/check/index.js +1 -1
- package/dist/trade/index.d.ts +2 -2
- package/dist/trade/index.js +2 -2
- package/dist/trade/parse/base_parser.d.ts +1 -2
- package/dist/trade/parse/base_parser.js +36 -36
- package/dist/trade/trade_trace.d.ts +17 -0
- package/dist/trade/trade_trace.js +65 -0
- package/dist/types/config_types.d.ts +3 -3
- package/dist/types/event_types.d.ts +3 -3
- package/dist/types/pool_state.d.ts +140 -13
- package/dist/utils/ethers_compat.d.ts +13 -0
- package/dist/utils/ethers_compat.js +18 -0
- package/dist/utils/fast_signer.d.ts +1 -0
- package/dist/utils/fast_signer.js +87 -0
- package/dist/utils/gas_helper.d.ts +2 -2
- package/dist/utils/gas_helper.js +48 -60
- package/dist/utils/index.d.ts +5 -2
- package/dist/utils/index.js +6 -2
- package/dist/utils/pool_filter.d.ts +8 -0
- package/dist/utils/pool_filter.js +38 -0
- package/dist/utils/trade_direction.d.ts +14 -0
- package/dist/utils/trade_direction.js +23 -0
- package/package.json +2 -2
- package/dist/config/base_env_args.d.ts +0 -11
- package/dist/config/base_env_args.js +0 -19
- package/dist/config/index.d.ts +0 -1
- package/dist/quote/event/verify_clmm_swap_event.d.ts +0 -1
- package/dist/quote/event/verify_clmm_swap_event.js +0 -178
- package/dist/quote/pricing/token_price_cache.d.ts +0 -10
- package/dist/quote/pricing/token_price_cache.js +0 -40
- package/dist/trade/abstract_dex_trade_plus.d.ts +0 -43
- package/dist/trade/abstract_dex_trade_plus.js +0 -421
- package/dist/trade/check/tx_websocket_manager.d.ts +0 -23
- package/dist/trade/check/tx_websocket_manager.js +0 -119
- package/dist/trade/send/alchemy_base.d.ts +0 -5
- package/dist/trade/send/alchemy_base.js +0 -48
- package/dist/trade/send/ankr_base.d.ts +0 -5
- package/dist/trade/send/ankr_base.js +0 -48
- package/dist/trade/send/base_rpc.d.ts +0 -5
- package/dist/trade/send/base_rpc.js +0 -48
- package/dist/trade/send/blockpi_base.d.ts +0 -5
- package/dist/trade/send/blockpi_base.js +0 -48
- package/dist/trade/send/bloxroute_base.d.ts +0 -11
- package/dist/trade/send/bloxroute_base.js +0 -115
- package/dist/trade/send/chainstack_base.d.ts +0 -5
- package/dist/trade/send/chainstack_base.js +0 -48
- package/dist/trade/send/drpc_base.d.ts +0 -5
- package/dist/trade/send/drpc_base.js +0 -48
- package/dist/trade/send/getblock_base.d.ts +0 -5
- package/dist/trade/send/getblock_base.js +0 -48
- package/dist/trade/send/index.d.ts +0 -15
- package/dist/trade/send/index.js +0 -33
- package/dist/trade/send/infura_base.d.ts +0 -5
- package/dist/trade/send/infura_base.js +0 -48
- package/dist/trade/send/moralis_base.d.ts +0 -5
- package/dist/trade/send/moralis_base.js +0 -48
- package/dist/trade/send/onerpc_base.d.ts +0 -5
- package/dist/trade/send/onerpc_base.js +0 -48
- package/dist/trade/send/quicknode_base.d.ts +0 -5
- package/dist/trade/send/quicknode_base.js +0 -48
- package/dist/trade/send/send_tx.d.ts +0 -17
- package/dist/trade/send/send_tx.js +0 -163
- package/dist/ws/event_filter.d.ts +0 -8
- package/dist/ws/event_filter.js +0 -36
- package/dist/ws/index.d.ts +0 -2
- package/dist/ws/subscribe_v2_events.d.ts +0 -14
- package/dist/ws/subscribe_v2_events.js +0 -174
- package/dist/ws/subscribe_v3_events.d.ts +0 -14
- package/dist/ws/subscribe_v3_events.js +0 -174
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTickLiquiditySnapshot = buildTickLiquiditySnapshot;
|
|
4
|
+
const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
|
|
5
|
+
const Q96 = 1n << 96n;
|
|
6
|
+
const DEFAULT_RANGE_BPS = 200;
|
|
7
|
+
function buildTickLiquiditySnapshot(input) {
|
|
8
|
+
const { poolAddress, pair, protocolId, tickCache, currentTick, tickSpacing, token0, token1, baseIsToken0, token0PriceUsd, token1PriceUsd, } = input;
|
|
9
|
+
const currentSqrtPriceX96 = BigInt(input.sqrtPriceX96);
|
|
10
|
+
const currentLiquidity = BigInt(input.liquidity);
|
|
11
|
+
const rangeBps = input.rangeBps ?? DEFAULT_RANGE_BPS;
|
|
12
|
+
if (currentLiquidity === 0n)
|
|
13
|
+
return undefined;
|
|
14
|
+
const allTickIndices = tickCache.getCachedTickIndices(poolAddress);
|
|
15
|
+
if (allTickIndices.length === 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
const upperSqrtPrice = (0, clmm_depth_calculator_1.computeTargetSqrtPrice)(currentSqrtPriceX96, rangeBps, false);
|
|
18
|
+
const lowerSqrtPrice = (0, clmm_depth_calculator_1.computeTargetSqrtPrice)(currentSqrtPriceX96, rangeBps, true);
|
|
19
|
+
const upperTick = sqrtPriceToTick(upperSqrtPrice);
|
|
20
|
+
const lowerTick = sqrtPriceToTick(lowerSqrtPrice);
|
|
21
|
+
const ticksInRange = allTickIndices.filter(t => t >= lowerTick && t <= upperTick);
|
|
22
|
+
if (ticksInRange.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
const liquidityMap = computeLiquidityAtTicks(ticksInRange, currentTick, currentLiquidity, tickCache, poolAddress);
|
|
25
|
+
const tickDataPoints = [];
|
|
26
|
+
let totalTvlUsd = 0;
|
|
27
|
+
for (let i = 0; i < ticksInRange.length - 1; i++) {
|
|
28
|
+
const tickLower = ticksInRange[i];
|
|
29
|
+
const tickUpper = ticksInRange[i + 1];
|
|
30
|
+
const liq = liquidityMap.get(tickLower) ?? 0n;
|
|
31
|
+
if (liq <= 0n) {
|
|
32
|
+
const price0 = tickToPrice(tickLower, token0.decimals, token1.decimals);
|
|
33
|
+
const price1 = price0 > 0 ? 1 / price0 : 0;
|
|
34
|
+
tickDataPoints.push({
|
|
35
|
+
tick: tickLower,
|
|
36
|
+
price0, price1,
|
|
37
|
+
liquidity: '0',
|
|
38
|
+
amount0: 0, amount1: 0, tvlUsd: 0,
|
|
39
|
+
buyable: 0, sellable: 0,
|
|
40
|
+
});
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const sqrtPriceLower = (0, clmm_depth_calculator_1.getSqrtRatioAtTick)(tickLower);
|
|
44
|
+
const sqrtPriceUpper = (0, clmm_depth_calculator_1.getSqrtRatioAtTick)(tickUpper);
|
|
45
|
+
const { amount0, amount1 } = computeTokenAmounts(sqrtPriceLower, sqrtPriceUpper, currentSqrtPriceX96, liq, token0.decimals, token1.decimals);
|
|
46
|
+
const tvlUsd = amount0 * token0PriceUsd + amount1 * token1PriceUsd;
|
|
47
|
+
totalTvlUsd += tvlUsd;
|
|
48
|
+
const { buyable, sellable } = computeBuySell(sqrtPriceLower, sqrtPriceUpper, liq, token0.decimals, token1.decimals, baseIsToken0);
|
|
49
|
+
const price0 = tickToPrice(tickLower, token0.decimals, token1.decimals);
|
|
50
|
+
const price1 = price0 > 0 ? 1 / price0 : 0;
|
|
51
|
+
tickDataPoints.push({
|
|
52
|
+
tick: tickLower,
|
|
53
|
+
price0, price1,
|
|
54
|
+
liquidity: liq.toString(),
|
|
55
|
+
amount0, amount1, tvlUsd,
|
|
56
|
+
buyable, sellable,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const currentPriceFloat = Number(currentSqrtPriceX96) / Number(Q96);
|
|
60
|
+
const currentPrice = (currentPriceFloat * currentPriceFloat) *
|
|
61
|
+
Math.pow(10, token0.decimals - token1.decimals);
|
|
62
|
+
return {
|
|
63
|
+
poolAddress,
|
|
64
|
+
pair,
|
|
65
|
+
protocolId,
|
|
66
|
+
currentTick,
|
|
67
|
+
tickSpacing,
|
|
68
|
+
currentPrice,
|
|
69
|
+
token0,
|
|
70
|
+
token1,
|
|
71
|
+
ticks: tickDataPoints,
|
|
72
|
+
totalTvlUsd,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function computeLiquidityAtTicks(sortedTicks, currentTick, currentLiquidity, tickCache, poolAddress) {
|
|
77
|
+
const result = new Map();
|
|
78
|
+
let splitIdx = sortedTicks.findIndex(t => t > currentTick);
|
|
79
|
+
if (splitIdx === -1)
|
|
80
|
+
splitIdx = sortedTicks.length;
|
|
81
|
+
let liq = currentLiquidity;
|
|
82
|
+
for (let i = splitIdx; i < sortedTicks.length; i++) {
|
|
83
|
+
const tick = sortedTicks[i];
|
|
84
|
+
const netLiq = tickCache.getTickLiquidityNet(poolAddress, tick) ?? 0n;
|
|
85
|
+
liq = liq + netLiq;
|
|
86
|
+
result.set(tick, liq > 0n ? liq : 0n);
|
|
87
|
+
}
|
|
88
|
+
liq = currentLiquidity;
|
|
89
|
+
for (let i = splitIdx - 1; i >= 0; i--) {
|
|
90
|
+
const tick = sortedTicks[i];
|
|
91
|
+
result.set(tick, liq > 0n ? liq : 0n);
|
|
92
|
+
const netLiq = tickCache.getTickLiquidityNet(poolAddress, tick) ?? 0n;
|
|
93
|
+
liq = liq - netLiq;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
function computeTokenAmounts(sqrtPriceLower, sqrtPriceUpper, currentSqrtPrice, liquidity, decimals0, decimals1) {
|
|
98
|
+
let amount0Wei = 0n;
|
|
99
|
+
let amount1Wei = 0n;
|
|
100
|
+
if (currentSqrtPrice <= sqrtPriceLower) {
|
|
101
|
+
amount0Wei = calcToken0Amount(sqrtPriceLower, sqrtPriceUpper, liquidity);
|
|
102
|
+
}
|
|
103
|
+
else if (currentSqrtPrice >= sqrtPriceUpper) {
|
|
104
|
+
amount1Wei = calcToken1Amount(sqrtPriceLower, sqrtPriceUpper, liquidity);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
amount0Wei = calcToken0Amount(currentSqrtPrice, sqrtPriceUpper, liquidity);
|
|
108
|
+
amount1Wei = calcToken1Amount(sqrtPriceLower, currentSqrtPrice, liquidity);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
amount0: Number(amount0Wei) / Math.pow(10, decimals0),
|
|
112
|
+
amount1: Number(amount1Wei) / Math.pow(10, decimals1),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function computeBuySell(sqrtPriceLower, sqrtPriceUpper, liquidity, decimals0, decimals1, baseIsToken0) {
|
|
116
|
+
const token0Out = Number(calcToken0Amount(sqrtPriceLower, sqrtPriceUpper, liquidity)) / Math.pow(10, decimals0);
|
|
117
|
+
const token1Out = Number(calcToken1Amount(sqrtPriceLower, sqrtPriceUpper, liquidity)) / Math.pow(10, decimals1);
|
|
118
|
+
if (baseIsToken0) {
|
|
119
|
+
return { buyable: token0Out, sellable: token1Out };
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return { buyable: token1Out, sellable: token0Out };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function calcToken0Amount(sqrtPriceLower, sqrtPriceUpper, liquidity) {
|
|
126
|
+
if (sqrtPriceLower >= sqrtPriceUpper || liquidity === 0n)
|
|
127
|
+
return 0n;
|
|
128
|
+
const diff = sqrtPriceUpper - sqrtPriceLower;
|
|
129
|
+
return liquidity * diff * Q96 / (sqrtPriceLower * sqrtPriceUpper);
|
|
130
|
+
}
|
|
131
|
+
function calcToken1Amount(sqrtPriceLower, sqrtPriceUpper, liquidity) {
|
|
132
|
+
if (sqrtPriceLower >= sqrtPriceUpper || liquidity === 0n)
|
|
133
|
+
return 0n;
|
|
134
|
+
const diff = sqrtPriceUpper - sqrtPriceLower;
|
|
135
|
+
return liquidity * diff / Q96;
|
|
136
|
+
}
|
|
137
|
+
function sqrtPriceToTick(sqrtPriceX96) {
|
|
138
|
+
const sqrtPriceFloat = Number(sqrtPriceX96) / Number(Q96);
|
|
139
|
+
return Math.floor(Math.log(sqrtPriceFloat * sqrtPriceFloat) / Math.log(1.0001));
|
|
140
|
+
}
|
|
141
|
+
function tickToPrice(tick, decimals0, decimals1) {
|
|
142
|
+
return Math.pow(1.0001, tick) * Math.pow(10, decimals0 - decimals1);
|
|
143
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { StandardPoolInfoType } from '@clonegod/ttd-core';
|
|
2
|
-
import { AppConfig } from '@clonegod/ttd-core
|
|
2
|
+
import { AppConfig } from '@clonegod/ttd-core';
|
|
3
3
|
export interface BlockUpdateEvent {
|
|
4
4
|
blockNumber: number;
|
|
5
|
-
previousBlockNumber
|
|
6
|
-
|
|
5
|
+
previousBlockNumber?: number;
|
|
6
|
+
blockTimestamp?: number;
|
|
7
|
+
blockTimestampMs?: number;
|
|
8
|
+
recvBlockTime: number;
|
|
7
9
|
}
|
|
8
10
|
export interface ConnectionDiagnostics {
|
|
9
11
|
isConnected: boolean;
|
|
@@ -1,17 +1,8 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.PoolEventListener = void 0;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
4
|
+
const ethers_compat_1 = require("../../utils/ethers_compat");
|
|
5
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
15
6
|
const common_1 = require("../../common");
|
|
16
7
|
const CONFIG = {
|
|
17
8
|
MAX_RETRIES: 3,
|
|
@@ -44,81 +35,73 @@ class PoolEventListener {
|
|
|
44
35
|
this.appConfig = appConfig;
|
|
45
36
|
this.ws_endpoint = ws_endpoint;
|
|
46
37
|
}
|
|
47
|
-
init(poolList) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
yield this.connect();
|
|
54
|
-
});
|
|
38
|
+
async init(poolList) {
|
|
39
|
+
this.poolList = poolList.filter(pool => ethers_compat_1.ethersCompat.isAddress(pool.pool_address));
|
|
40
|
+
if (this.poolList.length !== poolList.length) {
|
|
41
|
+
(0, ttd_core_1.log_warn)(`Found ${poolList.length - this.poolList.length} invalid pool addresses, filtered out`, '');
|
|
42
|
+
}
|
|
43
|
+
await this.connect();
|
|
55
44
|
}
|
|
56
|
-
start() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
(0, dist_1.log_info)('Block listener started successfully');
|
|
66
|
-
});
|
|
45
|
+
async start() {
|
|
46
|
+
(0, ttd_core_1.log_info)(`Starting block listener...`);
|
|
47
|
+
if (!this.isConnected || !this.wsProvider) {
|
|
48
|
+
(0, ttd_core_1.log_warn)('WebSocket not connected, cannot start listener', '');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await this.startBlockListener();
|
|
52
|
+
this.isStarted = true;
|
|
53
|
+
(0, ttd_core_1.log_info)('Block listener started successfully');
|
|
67
54
|
}
|
|
68
|
-
stop() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(0, dist_1.log_info)(`Block listener stopped`);
|
|
75
|
-
});
|
|
55
|
+
async stop() {
|
|
56
|
+
(0, ttd_core_1.log_info)(`Stopping block listener...`);
|
|
57
|
+
this.stopBlockListener();
|
|
58
|
+
this.cleanup();
|
|
59
|
+
this.isStarted = false;
|
|
60
|
+
(0, ttd_core_1.log_info)(`Block listener stopped`);
|
|
76
61
|
}
|
|
77
|
-
connect() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
yield this.startBlockListener();
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
62
|
+
async connect() {
|
|
63
|
+
for (let i = 0; i < CONFIG.MAX_RETRIES; i++) {
|
|
64
|
+
try {
|
|
65
|
+
(0, ttd_core_1.log_info)(`Connecting to WebSocket: ${this.ws_endpoint} (Attempt ${i + 1}/${CONFIG.MAX_RETRIES})`);
|
|
66
|
+
this.wsProvider = new ethers_compat_1.ethersCompat.WebSocketProvider(this.ws_endpoint);
|
|
67
|
+
const wsPromise = this.wsProvider.ready;
|
|
68
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
69
|
+
setTimeout(() => reject(new Error('WebSocket connection timeout')), CONFIG.CONNECTION_TIMEOUT);
|
|
70
|
+
});
|
|
71
|
+
await Promise.race([wsPromise, timeoutPromise]);
|
|
72
|
+
this.isConnected = true;
|
|
73
|
+
this.reconnectAttempts = 0;
|
|
74
|
+
(0, ttd_core_1.log_info)(`WebSocket connected: ${this.ws_endpoint}`);
|
|
75
|
+
this.setupWebSocketListeners();
|
|
76
|
+
if (this.isStarted) {
|
|
77
|
+
await this.startBlockListener();
|
|
96
78
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
(0, dist_1.log_info)(`Waiting ${delay / 1000} seconds before retry...`);
|
|
104
|
-
yield new Promise(resolve => setTimeout(resolve, delay));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
(0, ttd_core_1.log_error)(`WebSocket connection failed (Attempt ${i + 1}/${CONFIG.MAX_RETRIES}):`, error);
|
|
83
|
+
if (i === CONFIG.MAX_RETRIES - 1) {
|
|
84
|
+
throw error;
|
|
105
85
|
}
|
|
86
|
+
const delay = CONFIG.RETRY_DELAY_MULTIPLIER * (i + 1);
|
|
87
|
+
(0, ttd_core_1.log_info)(`Waiting ${delay / 1000} seconds before retry...`);
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
106
89
|
}
|
|
107
|
-
}
|
|
90
|
+
}
|
|
108
91
|
}
|
|
109
92
|
setupWebSocketListeners() {
|
|
110
93
|
if (!this.wsProvider)
|
|
111
94
|
return;
|
|
112
95
|
this.wsProvider.on('error', (err) => {
|
|
113
|
-
(0,
|
|
96
|
+
(0, ttd_core_1.log_error)(`WebSocket error:`, err, '');
|
|
114
97
|
this.handleConnectionIssue();
|
|
115
98
|
});
|
|
116
99
|
this.wsProvider._websocket.on('close', (code, reason) => {
|
|
117
|
-
(0,
|
|
100
|
+
(0, ttd_core_1.log_warn)(`WebSocket connection closed, code: ${code}, reason: ${reason || 'unknown'}`);
|
|
118
101
|
this.handleConnectionIssue();
|
|
119
102
|
});
|
|
120
103
|
this.wsProvider._websocket.on('open', () => {
|
|
121
|
-
(0,
|
|
104
|
+
(0, ttd_core_1.log_info)('WebSocket connection opened');
|
|
122
105
|
this.isConnected = true;
|
|
123
106
|
this.reconnectAttempts = 0;
|
|
124
107
|
});
|
|
@@ -128,7 +111,7 @@ class PoolEventListener {
|
|
|
128
111
|
if (this.heartbeatInterval) {
|
|
129
112
|
clearInterval(this.heartbeatInterval);
|
|
130
113
|
}
|
|
131
|
-
this.heartbeatInterval = setInterval(() =>
|
|
114
|
+
this.heartbeatInterval = setInterval(async () => {
|
|
132
115
|
try {
|
|
133
116
|
if (!this.wsProvider || !this.isConnected) {
|
|
134
117
|
this.handleConnectionIssue();
|
|
@@ -138,89 +121,85 @@ class PoolEventListener {
|
|
|
138
121
|
this.lastHeartbeatResponse = Date.now();
|
|
139
122
|
const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatResponse;
|
|
140
123
|
if (timeSinceLastHeartbeat > CONFIG.HEARTBEAT_TIMEOUT) {
|
|
141
|
-
(0,
|
|
124
|
+
(0, ttd_core_1.log_warn)(`No heartbeat response for ${timeSinceLastHeartbeat / 1000} seconds`);
|
|
142
125
|
this.handleConnectionIssue();
|
|
143
126
|
}
|
|
144
127
|
}
|
|
145
128
|
catch (error) {
|
|
146
|
-
(0,
|
|
129
|
+
(0, ttd_core_1.log_error)('Heartbeat check failed:', error);
|
|
147
130
|
this.handleConnectionIssue();
|
|
148
131
|
}
|
|
149
|
-
}
|
|
132
|
+
}, CONFIG.HEARTBEAT_INTERVAL);
|
|
150
133
|
}
|
|
151
|
-
handleConnectionIssue() {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
yield (0, dist_1.sleep)(delay);
|
|
174
|
-
yield this.connect();
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
(0, dist_1.log_error)('Reconnection attempt failed:', error);
|
|
178
|
-
setTimeout(() => this.handleConnectionIssue(), CONFIG.BASE_RECONNECT_DELAY);
|
|
179
|
-
}
|
|
180
|
-
finally {
|
|
181
|
-
this.isReconnecting = false;
|
|
134
|
+
async handleConnectionIssue() {
|
|
135
|
+
if (this.isReconnecting) {
|
|
136
|
+
(0, ttd_core_1.log_info)('Already attempting to reconnect, skipping...');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.isReconnecting = true;
|
|
140
|
+
this.isConnected = false;
|
|
141
|
+
try {
|
|
142
|
+
this.stopBlockListener();
|
|
143
|
+
this.cleanup();
|
|
144
|
+
this.reconnectAttempts++;
|
|
145
|
+
const delay = Math.min(CONFIG.BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), CONFIG.MAX_RECONNECT_DELAY);
|
|
146
|
+
(0, ttd_core_1.log_info)(`Attempting to reconnect ${this.reconnectAttempts}/${CONFIG.MAX_RECONNECT_ATTEMPTS}, delay ${delay}ms, node: ${this.ws_endpoint}`);
|
|
147
|
+
if (this.reconnectAttempts > CONFIG.MAX_RECONNECT_ATTEMPTS) {
|
|
148
|
+
(0, ttd_core_1.log_error)(`Reconnection failed, reached maximum attempts (${CONFIG.MAX_RECONNECT_ATTEMPTS}), exiting`, new Error('Max reconnect attempts failed'), '');
|
|
149
|
+
this.appConfig.emit(common_1.EVENT_NAMES.WS_CONNECTION_FAILED, {
|
|
150
|
+
endpoint: this.ws_endpoint,
|
|
151
|
+
attempts: CONFIG.MAX_RECONNECT_ATTEMPTS,
|
|
152
|
+
});
|
|
153
|
+
process.exit(1);
|
|
182
154
|
}
|
|
183
|
-
|
|
155
|
+
await (0, ttd_core_1.sleep)(delay);
|
|
156
|
+
await this.connect();
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
(0, ttd_core_1.log_error)('Reconnection attempt failed:', error);
|
|
160
|
+
setTimeout(() => this.handleConnectionIssue(), CONFIG.BASE_RECONNECT_DELAY);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
this.isReconnecting = false;
|
|
164
|
+
}
|
|
184
165
|
}
|
|
185
|
-
startBlockListener() {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
});
|
|
166
|
+
async startBlockListener() {
|
|
167
|
+
if (!this.wsProvider || !this.isConnected) {
|
|
168
|
+
(0, ttd_core_1.log_warn)(`Cannot start block listener: WebSocket not connected`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const blockNumber = await this.wsProvider.getBlockNumber();
|
|
173
|
+
this.lastProcessedBlockNumber = blockNumber;
|
|
174
|
+
(0, ttd_core_1.log_info)(`Initializing block listener, current block: ${blockNumber}`);
|
|
175
|
+
this.wsProvider.on('block', async (blockNumber) => {
|
|
176
|
+
try {
|
|
177
|
+
if (!this.isConnected || !this.isBlockListenerActive)
|
|
178
|
+
return;
|
|
179
|
+
this.lastMessageTime = Date.now();
|
|
180
|
+
(0, ttd_core_1.log_info)(`------- Block: ${blockNumber} -------`);
|
|
181
|
+
const previousBlockNumber = this.lastProcessedBlockNumber;
|
|
182
|
+
this.lastProcessedBlockNumber = blockNumber;
|
|
183
|
+
const blockUpdateEvent = {
|
|
184
|
+
blockNumber,
|
|
185
|
+
previousBlockNumber,
|
|
186
|
+
recvBlockTime: Date.now(),
|
|
187
|
+
};
|
|
188
|
+
this.appConfig.emit(common_1.EVENT_NAMES.BLOCK_UPDATE, blockUpdateEvent);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
(0, ttd_core_1.log_error)(`Error processing block event:`, error, '');
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
this.startMessageMonitor();
|
|
195
|
+
this.startDiagnosticMonitor();
|
|
196
|
+
this.isBlockListenerActive = true;
|
|
197
|
+
(0, ttd_core_1.log_info)(`Block listener started using WebSocket event subscription`);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
(0, ttd_core_1.log_error)(`Failed to start block listener:`, error, '');
|
|
201
|
+
this.isBlockListenerActive = false;
|
|
202
|
+
}
|
|
224
203
|
}
|
|
225
204
|
startMessageMonitor() {
|
|
226
205
|
if (this.messageMonitorInterval) {
|
|
@@ -229,7 +208,7 @@ class PoolEventListener {
|
|
|
229
208
|
this.messageMonitorInterval = setInterval(() => {
|
|
230
209
|
const timeSinceLastMessage = Date.now() - this.lastMessageTime;
|
|
231
210
|
if (timeSinceLastMessage > CONFIG.MESSAGE_TIMEOUT) {
|
|
232
|
-
(0,
|
|
211
|
+
(0, ttd_core_1.log_warn)(`No messages received for ${CONFIG.MESSAGE_TIMEOUT / 1000} seconds, preparing to resubscribe`);
|
|
233
212
|
this.handleConnectionIssue();
|
|
234
213
|
}
|
|
235
214
|
}, CONFIG.MESSAGE_CHECK_INTERVAL);
|
|
@@ -244,7 +223,7 @@ class PoolEventListener {
|
|
|
244
223
|
}, CONFIG.DIAGNOSTIC_INTERVAL);
|
|
245
224
|
}
|
|
246
225
|
logDiagnostics(diagnostics) {
|
|
247
|
-
(0,
|
|
226
|
+
(0, ttd_core_1.log_info)(`WebSocket Connection Diagnostics:
|
|
248
227
|
Connection Status: ${diagnostics.isConnected ? 'Connected' : 'Disconnected'}
|
|
249
228
|
Reconnection Status: ${diagnostics.isReconnecting ? 'Reconnecting' : 'Not Reconnecting'}
|
|
250
229
|
Listener Status: ${diagnostics.isStarted ? 'Started' : 'Not Started'}
|
|
@@ -279,7 +258,7 @@ class PoolEventListener {
|
|
|
279
258
|
clearInterval(this.diagnosticInterval);
|
|
280
259
|
this.diagnosticInterval = null;
|
|
281
260
|
}
|
|
282
|
-
(0,
|
|
261
|
+
(0, ttd_core_1.log_info)(`Block listener stopped`);
|
|
283
262
|
}
|
|
284
263
|
}
|
|
285
264
|
cleanup() {
|
|
@@ -291,7 +270,7 @@ class PoolEventListener {
|
|
|
291
270
|
}
|
|
292
271
|
}
|
|
293
272
|
catch (error) {
|
|
294
|
-
(0,
|
|
273
|
+
(0, ttd_core_1.log_error)(`Failed to cleanup WebSocket resources:`, error, '');
|
|
295
274
|
}
|
|
296
275
|
this.wsProvider = null;
|
|
297
276
|
}
|
|
@@ -311,8 +290,7 @@ class PoolEventListener {
|
|
|
311
290
|
this.isReconnecting = false;
|
|
312
291
|
}
|
|
313
292
|
isWSConnected() {
|
|
314
|
-
|
|
315
|
-
return this.isConnected && ((_b = (_a = this.wsProvider) === null || _a === void 0 ? void 0 : _a._websocket) === null || _b === void 0 ? void 0 : _b.readyState) === 1;
|
|
293
|
+
return this.isConnected && this.wsProvider?._websocket?.readyState === 1;
|
|
316
294
|
}
|
|
317
295
|
getConnectionDiagnostics() {
|
|
318
296
|
return {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class SwapDebouncer<TEvent extends {
|
|
2
|
+
pool_address: string;
|
|
3
|
+
type: string;
|
|
4
|
+
data: {
|
|
5
|
+
blockNumber: number;
|
|
6
|
+
};
|
|
7
|
+
}, TResult = void> {
|
|
8
|
+
private debounceMs;
|
|
9
|
+
private onCalculate;
|
|
10
|
+
private onPublish;
|
|
11
|
+
private debouncedTypes;
|
|
12
|
+
private windows;
|
|
13
|
+
private latestBlock;
|
|
14
|
+
constructor(config: {
|
|
15
|
+
onCalculate: (event: TEvent) => TResult | Promise<TResult>;
|
|
16
|
+
onPublish: (event: TEvent, result: TResult) => void;
|
|
17
|
+
debouncedEventTypes?: string[];
|
|
18
|
+
});
|
|
19
|
+
onEvent(event: TEvent): Promise<void>;
|
|
20
|
+
flush(): void;
|
|
21
|
+
private startWindow;
|
|
22
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SwapDebouncer = void 0;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
class SwapDebouncer {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.windows = new Map();
|
|
8
|
+
this.latestBlock = new Map();
|
|
9
|
+
this.debounceMs = parseInt(process.env.SWAP_DEBOUNCE_MS || '3', 10);
|
|
10
|
+
this.onCalculate = config.onCalculate;
|
|
11
|
+
this.onPublish = config.onPublish;
|
|
12
|
+
this.debouncedTypes = new Set((config.debouncedEventTypes || ['swap']).map(t => t.toLowerCase()));
|
|
13
|
+
}
|
|
14
|
+
async onEvent(event) {
|
|
15
|
+
const poolAddress = event.pool_address;
|
|
16
|
+
const blockNumber = event.data?.blockNumber || 0;
|
|
17
|
+
if (blockNumber > 0) {
|
|
18
|
+
const lastBlock = this.latestBlock.get(poolAddress) || 0;
|
|
19
|
+
if (blockNumber < lastBlock) {
|
|
20
|
+
(0, ttd_core_1.log_warn)(`SwapDebouncer: stale block dropped, pool=${poolAddress}, event block=${blockNumber}, latest=${lastBlock}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.latestBlock.set(poolAddress, blockNumber);
|
|
24
|
+
}
|
|
25
|
+
const eventType = event.type?.toLowerCase();
|
|
26
|
+
if (!this.debouncedTypes.has(eventType)) {
|
|
27
|
+
const result = await this.onCalculate(event);
|
|
28
|
+
this.onPublish(event, result);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const window = this.windows.get(poolAddress);
|
|
32
|
+
const result = await this.onCalculate(event);
|
|
33
|
+
if (window) {
|
|
34
|
+
if (blockNumber > window.blockNumber) {
|
|
35
|
+
if (!window.expired) {
|
|
36
|
+
clearTimeout(window.timer);
|
|
37
|
+
this.onPublish(window.latestEvent, window.latestResult);
|
|
38
|
+
}
|
|
39
|
+
this.windows.delete(poolAddress);
|
|
40
|
+
this.startWindow(poolAddress, event, result, blockNumber);
|
|
41
|
+
}
|
|
42
|
+
else if (window.expired) {
|
|
43
|
+
this.onPublish(event, result);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
window.latestEvent = event;
|
|
47
|
+
window.latestResult = result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.startWindow(poolAddress, event, result, blockNumber);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
flush() {
|
|
55
|
+
for (const [_, window] of this.windows) {
|
|
56
|
+
if (!window.expired) {
|
|
57
|
+
clearTimeout(window.timer);
|
|
58
|
+
this.onPublish(window.latestEvent, window.latestResult);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.windows.clear();
|
|
62
|
+
}
|
|
63
|
+
startWindow(poolAddress, event, result, blockNumber) {
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
const w = this.windows.get(poolAddress);
|
|
66
|
+
if (w) {
|
|
67
|
+
w.expired = true;
|
|
68
|
+
this.onPublish(w.latestEvent, w.latestResult);
|
|
69
|
+
}
|
|
70
|
+
}, this.debounceMs);
|
|
71
|
+
this.windows.set(poolAddress, {
|
|
72
|
+
blockNumber,
|
|
73
|
+
latestEvent: event,
|
|
74
|
+
latestResult: result,
|
|
75
|
+
timer,
|
|
76
|
+
expired: false,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.SwapDebouncer = SwapDebouncer;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FormattedTokenPrice } from '@clonegod/ttd-core';
|
|
2
|
+
type PriceSource = 'force_fetch' | 'cache_only' | 'cache_first';
|
|
3
|
+
export declare function get_base_token_price_info(addresses: string[], opts?: {
|
|
4
|
+
source?: PriceSource;
|
|
5
|
+
}): Promise<Map<string, FormattedTokenPrice>>;
|
|
6
|
+
export {};
|