@clonegod/ttd-bsc-common 3.1.65 → 3.1.67

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.
@@ -32,6 +32,7 @@ export declare class BscEnvArgs extends EnvArgs {
32
32
  redis_url: string;
33
33
  skip_pool_list: string;
34
34
  price_feed_skip_dup: boolean;
35
+ price_feed_client_id: string;
35
36
  trade_analyze_port: number;
36
37
  log_max_size_mb: number;
37
38
  log_check_interval_min: number;
@@ -36,6 +36,7 @@ const constants_1 = require("../common/constants");
36
36
  redis_url: { env: 'REDIS_URL', type: 'string', default: 'redis://127.0.0.1:6379', desc: 'Redis 连接 URL' },
37
37
  skip_pool_list: { env: 'SKIP_POOL_LIST', type: 'string', default: '', desc: '跳过的池子名称列表(逗号分隔)' },
38
38
  price_feed_skip_dup: { env: 'PRICE_FEED_SKIP_DUP', type: 'boolean', default: false, desc: 'PriceFeed 去重过滤' },
39
+ price_feed_client_id: { env: 'PRICE_FEED_CLIENT_ID', type: 'string', default: 'BSC_DC', desc: 'PriceFeed 订阅消息里的 client 标识(如 yyNode 区分客户端)' },
39
40
  trade_analyze_port: { env: 'TRADE_ANALYZE_PORT', type: 'number', default: 8004, desc: '上报 analyze 的 HTTP 端口' },
40
41
  log_max_size_mb: { env: 'LOG_MAX_SIZE_MB', type: 'number', default: 20, desc: '日志文件大小上限(MB)' },
41
42
  log_check_interval_min: { env: 'LOG_CHECK_INTERVAL_MIN', type: 'number', default: 10, desc: '日志检查间隔(分钟)' },
@@ -11,5 +11,9 @@ export interface AmmDepthResult {
11
11
  amountOutWei: bigint;
12
12
  amountOut: number;
13
13
  outputDecimals: number;
14
+ amountInWei: bigint;
15
+ amountIn: number;
16
+ inputDecimals: number;
17
+ targetPrice: number;
14
18
  }
15
19
  export declare function calculateAmmDepth(input: AmmDepthInput): AmmDepthResult;
@@ -7,32 +7,49 @@ function calculateAmmDepth(input) {
7
7
  const R0 = BigInt(input.reserve0);
8
8
  const R1 = BigInt(input.reserve1);
9
9
  if (R0 === 0n || R1 === 0n) {
10
- return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0 };
10
+ return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0, amountInWei: 0n, amountIn: 0, inputDecimals: 0, targetPrice: 0 };
11
11
  }
12
12
  const k = R0 * R1;
13
13
  const bps = BigInt(targetBps);
14
14
  const PRECISION = 10n ** 18n;
15
15
  let amountOutWei;
16
+ let amountInWei;
16
17
  let outputDecimals;
18
+ let inputDecimals;
17
19
  if (isBuy) {
18
20
  const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
19
21
  outputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
20
- const numerator = k * 10000n * PRECISION;
21
- const denominator = (10000n + bps) * quoteReserve;
22
+ inputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
22
23
  const baseReserveNew = baseReserve * (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)((10000n + bps) * PRECISION);
24
+ const quoteReserveNew = quoteReserve * (0, clmm_depth_calculator_1.bigIntSqrt)((10000n + bps) * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION);
23
25
  amountOutWei = baseReserve - baseReserveNew;
26
+ amountInWei = quoteReserveNew - quoteReserve;
24
27
  }
25
28
  else {
26
29
  const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
27
30
  outputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
31
+ inputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
28
32
  if (bps >= 10000n) {
29
33
  amountOutWei = quoteReserve;
34
+ amountInWei = baseReserve;
30
35
  }
31
36
  else {
32
37
  const quoteReserveNew = quoteReserve * (0, clmm_depth_calculator_1.bigIntSqrt)((10000n - bps) * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION);
38
+ const baseReserveNew = baseReserve * (0, clmm_depth_calculator_1.bigIntSqrt)(10000n * PRECISION) / (0, clmm_depth_calculator_1.bigIntSqrt)((10000n - bps) * PRECISION);
33
39
  amountOutWei = quoteReserve - quoteReserveNew;
40
+ amountInWei = baseReserveNew - baseReserve;
34
41
  }
35
42
  }
36
43
  const amountOut = Number(amountOutWei) / Math.pow(10, outputDecimals);
37
- return { amountOutWei, amountOut, outputDecimals };
44
+ const amountIn = Number(amountInWei) / Math.pow(10, inputDecimals);
45
+ const baseDec = baseIsToken0 ? token0Decimals : token1Decimals;
46
+ const quoteDec = baseIsToken0 ? token1Decimals : token0Decimals;
47
+ const baseReserveUi = Number(baseIsToken0 ? R0 : R1) / Math.pow(10, baseDec);
48
+ const quoteReserveUi = Number(baseIsToken0 ? R1 : R0) / Math.pow(10, quoteDec);
49
+ const midPrice = quoteReserveUi / baseReserveUi;
50
+ const bpsNum = Number(targetBps);
51
+ const targetPrice = isBuy
52
+ ? midPrice * (10000 + bpsNum) / 10000
53
+ : midPrice * (10000 - bpsNum) / 10000;
54
+ return { amountOutWei, amountOut, outputDecimals, amountInWei, amountIn, inputDecimals, targetPrice };
38
55
  }
@@ -19,8 +19,10 @@ export interface DepthResult {
19
19
  amountOut: number;
20
20
  currentTick: number;
21
21
  targetTick: number;
22
+ targetSqrtPriceX96: bigint;
22
23
  }
23
24
  export declare function calculateClmmDepth(input: ClmmDepthInput): DepthResult;
25
+ export declare function priceFromSqrtX96(sqrtPriceX96: bigint, baseDecimals: number, quoteDecimals: number, baseIsToken0: boolean): number;
24
26
  declare function computeTargetSqrtPrice(currentSqrtPriceX96: bigint, bps: number, zeroForOne: boolean): bigint;
25
27
  declare function bigIntSqrt(n: bigint): bigint;
26
28
  export { getSqrtRatioAtTick, computeTargetSqrtPrice, bigIntSqrt };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.calculateClmmDepth = calculateClmmDepth;
4
+ exports.priceFromSqrtX96 = priceFromSqrtX96;
4
5
  exports.getSqrtRatioAtTick = getSqrtRatioAtTick;
5
6
  exports.computeTargetSqrtPrice = computeTargetSqrtPrice;
6
7
  exports.bigIntSqrt = bigIntSqrt;
@@ -60,7 +61,7 @@ function calculateClmmDepth(input) {
60
61
  const currentSqrtPriceX96 = BigInt(input.sqrtPriceX96);
61
62
  let liquidity = BigInt(input.liquidity);
62
63
  if (liquidity === 0n) {
63
- return { amountInWei: 0n, amountIn: 0, amountOutWei: 0n, amountOut: 0, currentTick, targetTick: currentTick };
64
+ return { amountInWei: 0n, amountIn: 0, amountOutWei: 0n, amountOut: 0, currentTick, targetTick: currentTick, targetSqrtPriceX96: currentSqrtPriceX96 };
64
65
  }
65
66
  const targetSqrtPriceX96 = computeTargetSqrtPrice(currentSqrtPriceX96, targetBps, zeroForOne);
66
67
  const allTicks = tickCache.getCachedTickIndices(poolAddress);
@@ -94,7 +95,17 @@ function calculateClmmDepth(input) {
94
95
  const amountOut = Number(totalOutput) / Math.pow(10, outputDecimals);
95
96
  const sqrtPriceFloat = Number(targetSqrtPriceX96) / Number(Q96);
96
97
  const targetTick = Math.floor(Math.log(sqrtPriceFloat * sqrtPriceFloat) / Math.log(1.0001));
97
- return { amountInWei: totalInput, amountIn, amountOutWei: totalOutput, amountOut, currentTick, targetTick };
98
+ return { amountInWei: totalInput, amountIn, amountOutWei: totalOutput, amountOut, currentTick, targetTick, targetSqrtPriceX96 };
99
+ }
100
+ function priceFromSqrtX96(sqrtPriceX96, baseDecimals, quoteDecimals, baseIsToken0) {
101
+ const sp = Number(sqrtPriceX96) / Number(Q96);
102
+ const priceToken1PerToken0 = sp * sp;
103
+ if (baseIsToken0) {
104
+ return priceToken1PerToken0 * Math.pow(10, baseDecimals - quoteDecimals);
105
+ }
106
+ else {
107
+ return (1 / priceToken1PerToken0) * Math.pow(10, baseDecimals - quoteDecimals);
108
+ }
98
109
  }
99
110
  function computeTargetSqrtPrice(currentSqrtPriceX96, bps, zeroForOne) {
100
111
  const PRECISION = 10n ** 18n;
@@ -1,14 +1,14 @@
1
+ import { QuoteDepthOutput } from '@clonegod/ttd-core';
1
2
  import { ClmmTickCache } from '../tick/clmm_tick_cache';
2
- export { calculateClmmDepth, getSqrtRatioAtTick, computeTargetSqrtPrice, bigIntSqrt } from './clmm_depth_calculator';
3
+ export { calculateClmmDepth, getSqrtRatioAtTick, computeTargetSqrtPrice, bigIntSqrt, priceFromSqrtX96 } from './clmm_depth_calculator';
3
4
  export type { ClmmDepthInput, DepthResult } from './clmm_depth_calculator';
4
5
  export { calculateAmmDepth } from './amm_depth_calculator';
5
6
  export type { AmmDepthInput, AmmDepthResult } from './amm_depth_calculator';
6
7
  export { buildTickLiquiditySnapshot } from './tick_liquidity_snapshot';
7
8
  export type { TickLiquiditySnapshot, TickLiquiditySnapshotInput, TickDataPoint } from './tick_liquidity_snapshot';
8
9
  import { TickLiquiditySnapshot } from './tick_liquidity_snapshot';
9
- import { PoolDepthData } from '@clonegod/ttd-core';
10
- export type { PoolDepthData } from '@clonegod/ttd-core';
11
- export declare function getDepthBps(): number[];
10
+ export declare function logFeeChainConfigOnce(poolName: string, poolAddress: string, configFeeBps: number, chainFeeBps: number, dexId?: string, pair?: string): void;
11
+ export declare function clearFeeMatchLogged(poolAddress: string): void;
12
12
  export interface BuildClmmDepthInput {
13
13
  poolInfo: {
14
14
  pool_name: string;
@@ -27,9 +27,10 @@ export interface BuildClmmDepthInput {
27
27
  };
28
28
  basePriceUsd: number;
29
29
  quotePriceUsd: number;
30
+ feeRateBps: number;
30
31
  }
31
32
  export interface ClmmDepthResult {
32
- depth: PoolDepthData;
33
+ depth: QuoteDepthOutput;
33
34
  tickLiquidity?: TickLiquiditySnapshot;
34
35
  }
35
36
  export declare function buildClmmDepth(input: BuildClmmDepthInput): ClmmDepthResult | undefined;
@@ -45,5 +46,6 @@ export interface BuildAmmDepthInput {
45
46
  token0Address: string;
46
47
  basePriceUsd: number;
47
48
  quotePriceUsd: number;
49
+ feeRateBps: number;
48
50
  }
49
- export declare function buildAmmDepth(input: BuildAmmDepthInput): PoolDepthData | undefined;
51
+ export declare function buildAmmDepth(input: BuildAmmDepthInput): QuoteDepthOutput | undefined;
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildTickLiquiditySnapshot = exports.calculateAmmDepth = exports.bigIntSqrt = exports.computeTargetSqrtPrice = exports.getSqrtRatioAtTick = exports.calculateClmmDepth = void 0;
4
- exports.getDepthBps = getDepthBps;
3
+ exports.buildTickLiquiditySnapshot = exports.calculateAmmDepth = exports.priceFromSqrtX96 = exports.bigIntSqrt = exports.computeTargetSqrtPrice = exports.getSqrtRatioAtTick = exports.calculateClmmDepth = void 0;
4
+ exports.logFeeChainConfigOnce = logFeeChainConfigOnce;
5
+ exports.clearFeeMatchLogged = clearFeeMatchLogged;
5
6
  exports.buildClmmDepth = buildClmmDepth;
6
7
  exports.buildAmmDepth = buildAmmDepth;
7
8
  const ttd_core_1 = require("@clonegod/ttd-core");
9
+ const ttd_core_2 = require("@clonegod/ttd-core");
8
10
  const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
9
11
  const amm_depth_calculator_1 = require("./amm_depth_calculator");
10
12
  const trade_direction_1 = require("../../utils/trade_direction");
@@ -13,39 +15,85 @@ Object.defineProperty(exports, "calculateClmmDepth", { enumerable: true, get: fu
13
15
  Object.defineProperty(exports, "getSqrtRatioAtTick", { enumerable: true, get: function () { return clmm_depth_calculator_2.getSqrtRatioAtTick; } });
14
16
  Object.defineProperty(exports, "computeTargetSqrtPrice", { enumerable: true, get: function () { return clmm_depth_calculator_2.computeTargetSqrtPrice; } });
15
17
  Object.defineProperty(exports, "bigIntSqrt", { enumerable: true, get: function () { return clmm_depth_calculator_2.bigIntSqrt; } });
18
+ Object.defineProperty(exports, "priceFromSqrtX96", { enumerable: true, get: function () { return clmm_depth_calculator_2.priceFromSqrtX96; } });
16
19
  var amm_depth_calculator_2 = require("./amm_depth_calculator");
17
20
  Object.defineProperty(exports, "calculateAmmDepth", { enumerable: true, get: function () { return amm_depth_calculator_2.calculateAmmDepth; } });
18
21
  var tick_liquidity_snapshot_1 = require("./tick_liquidity_snapshot");
19
22
  Object.defineProperty(exports, "buildTickLiquiditySnapshot", { enumerable: true, get: function () { return tick_liquidity_snapshot_1.buildTickLiquiditySnapshot; } });
20
23
  const tick_liquidity_snapshot_2 = require("./tick_liquidity_snapshot");
21
24
  const clmm_tick_cache_1 = require("../tick/clmm_tick_cache");
22
- let _depthBpsLogged = false;
23
- const DEFAULT_BPS = 20;
24
- function getDepthBps() {
25
- const raw = process.env.DEPTH_BPS_LEVELS;
26
- const levels = raw
27
- ? raw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)
28
- : [DEFAULT_BPS];
29
- const cexBps = parseInt(process.env.DEPTH_CEX_BPS || String(DEFAULT_BPS));
30
- if (!levels.includes(cexBps))
31
- levels.push(cexBps);
32
- if (!levels.includes(DEFAULT_BPS))
33
- levels.push(DEFAULT_BPS);
34
- levels.sort((a, b) => a - b);
35
- if (!_depthBpsLogged) {
36
- _depthBpsLogged = true;
37
- (0, ttd_core_1.log_info)(`[Depth] bpsLevels=${JSON.stringify(levels)}, cexBps=${cexBps}`);
25
+ let _depthPctLogged = false;
26
+ function logDepthLevelsOnce() {
27
+ if (_depthPctLogged)
28
+ return;
29
+ _depthPctLogged = true;
30
+ const pcts = (0, ttd_core_2.getDepthPricePctLevels)();
31
+ (0, ttd_core_1.log_info)(`[Depth] pctLevels=${JSON.stringify(pcts)}, default=${ttd_core_2.DEFAULT_TIER_PCT}`);
32
+ }
33
+ const _feeMatchLoggedPools = new Set();
34
+ function logFeeChainConfigOnce(poolName, poolAddress, configFeeBps, chainFeeBps, dexId, pair) {
35
+ if (_feeMatchLoggedPools.has(poolAddress))
36
+ return;
37
+ _feeMatchLoggedPools.add(poolAddress);
38
+ const diff = Math.abs(configFeeBps - chainFeeBps);
39
+ const drifted = diff >= 0.01;
40
+ if (drifted) {
41
+ (0, ttd_core_1.log_warn)(`[Fee] ${poolName} 配置漂移: config=${configFeeBps}bps ≠ chain=${chainFeeBps}bps (差 ${diff.toFixed(2)}bps) — 已用链上值,请运维检查池子配置`);
42
+ }
43
+ else {
44
+ (0, ttd_core_1.log_info)(`[Fee] ${poolName} config=${configFeeBps}bps == chain=${chainFeeBps}bps ✓`);
38
45
  }
39
- return levels;
46
+ const addrLower = poolAddress.toLowerCase();
47
+ ttd_core_2.ALERT_TYPES.QUOTE_FEE_CHAIN_DRIFT.reportIfChanged(addrLower, drifted, () => ({
48
+ identity: addrLower,
49
+ scope: { dex_id: dexId, pool_address: addrLower, pair },
50
+ title: `${poolName} fee 漂移 cfg=${configFeeBps}bps chain=${chainFeeBps}bps`,
51
+ detail: {
52
+ pool_name: poolName,
53
+ config_fee_bps: configFeeBps,
54
+ chain_fee_bps: chainFeeBps,
55
+ diff_bps: diff,
56
+ },
57
+ }));
58
+ }
59
+ function clearFeeMatchLogged(poolAddress) {
60
+ _feeMatchLoggedPools.delete(poolAddress);
61
+ }
62
+ function grossUpFee(amountInNet, feeRateBps) {
63
+ if (feeRateBps <= 0 || amountInNet <= 0) {
64
+ return { amountInGross: amountInNet, feeAmount: 0 };
65
+ }
66
+ const feeRatio = feeRateBps / 10000;
67
+ const amountInGross = amountInNet / (1 - feeRatio);
68
+ const feeAmount = amountInGross - amountInNet;
69
+ return { amountInGross, feeAmount };
40
70
  }
41
71
  const _tickLiquidityLastBuild = new Map();
72
+ function assembleEntry(tiers) {
73
+ const defaultTier = tiers.find(t => t.pct === ttd_core_2.DEFAULT_TIER_PCT) ?? tiers[0];
74
+ return {
75
+ price: defaultTier.price,
76
+ amount: defaultTier.amount,
77
+ amount_in: defaultTier.amount_in,
78
+ amount_in_usd: defaultTier.amount_in_usd,
79
+ fee: defaultTier.fee,
80
+ fee_usd: defaultTier.fee_usd,
81
+ tick_move: defaultTier.tick_move,
82
+ tiers,
83
+ };
84
+ }
42
85
  function buildClmmDepth(input) {
43
- const bpsLevels = getDepthBps();
44
- if (bpsLevels.length === 0)
86
+ logDepthLevelsOnce();
87
+ const pctLevels = (0, ttd_core_2.getDepthPricePctLevels)();
88
+ if (pctLevels.length === 0)
45
89
  return undefined;
46
- const { poolInfo, poolAddress, tickCache, poolState, basePriceUsd, quotePriceUsd } = input;
90
+ const { poolInfo, poolAddress, tickCache, poolState, basePriceUsd, quotePriceUsd, feeRateBps } = input;
47
91
  if (BigInt(poolState.liquidity) === 0n)
48
92
  return undefined;
93
+ if (feeRateBps == null || feeRateBps < 0) {
94
+ (0, ttd_core_1.log_debug)(`[Depth] ${poolInfo.pool_name} CLMM: invalid feeRateBps=${feeRateBps}, skip`, '');
95
+ return undefined;
96
+ }
49
97
  try {
50
98
  const askDirection = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
51
99
  const bidDirection = (0, trade_direction_1.resolveTradeDirection)(poolInfo, false);
@@ -53,8 +101,12 @@ function buildClmmDepth(input) {
53
101
  const bidZeroForOne = bidDirection.inputToken.address.toLowerCase() === poolState.token0.toLowerCase();
54
102
  const baseToken = askDirection.baseToken;
55
103
  const quoteToken = askDirection.quoteToken;
56
- const depth = { ask: {}, bid: {} };
57
- for (const bps of bpsLevels) {
104
+ const baseIsToken0 = baseToken.address.toLowerCase() === poolState.token0.toLowerCase();
105
+ const midPrice = (0, clmm_depth_calculator_1.priceFromSqrtX96)(BigInt(poolState.sqrtPriceX96), baseToken.decimals, quoteToken.decimals, baseIsToken0);
106
+ const askTiers = [];
107
+ const bidTiers = [];
108
+ for (const pct of pctLevels) {
109
+ const bps = Math.round(pct * 100);
58
110
  const askResult = (0, clmm_depth_calculator_1.calculateClmmDepth)({
59
111
  poolAddress, tickCache,
60
112
  currentTick: poolState.tick, sqrtPriceX96: poolState.sqrtPriceX96,
@@ -69,15 +121,43 @@ function buildClmmDepth(input) {
69
121
  zeroForOne: bidZeroForOne, targetBps: bps,
70
122
  inputDecimals: baseToken.decimals, outputDecimals: quoteToken.decimals,
71
123
  });
124
+ const askTargetPrice = (0, clmm_depth_calculator_1.priceFromSqrtX96)(askResult.targetSqrtPriceX96, baseToken.decimals, quoteToken.decimals, baseIsToken0);
125
+ const bidTargetPrice = (0, clmm_depth_calculator_1.priceFromSqrtX96)(bidResult.targetSqrtPriceX96, baseToken.decimals, quoteToken.decimals, baseIsToken0);
126
+ const askGross = grossUpFee(askResult.amountIn, feeRateBps);
127
+ const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
72
128
  const askTickMove = askResult.targetTick < askResult.currentTick
73
129
  ? `${askResult.targetTick} <- ${askResult.currentTick}`
74
130
  : `${askResult.currentTick} -> ${askResult.targetTick}`;
75
131
  const bidTickMove = bidResult.targetTick > bidResult.currentTick
76
132
  ? `${bidResult.currentTick} -> ${bidResult.targetTick}`
77
133
  : `${bidResult.targetTick} <- ${bidResult.currentTick}`;
78
- depth.ask[bps] = { amount: askResult.amountOut, amountUsd: askResult.amountOut * basePriceUsd, amountIn: askResult.amountIn, amountInUsd: askResult.amountIn * quotePriceUsd, tickMove: askTickMove };
79
- depth.bid[bps] = { amount: bidResult.amountOut, amountUsd: bidResult.amountOut * quotePriceUsd, amountIn: bidResult.amountIn, amountInUsd: bidResult.amountIn * basePriceUsd, tickMove: bidTickMove };
134
+ askTiers.push({
135
+ pct,
136
+ price: askTargetPrice,
137
+ amount: askResult.amountOut,
138
+ amount_in: askGross.amountInGross,
139
+ amount_in_usd: askGross.amountInGross * quotePriceUsd,
140
+ fee: askGross.feeAmount,
141
+ fee_usd: askGross.feeAmount * quotePriceUsd,
142
+ tick_move: askTickMove,
143
+ });
144
+ bidTiers.push({
145
+ pct,
146
+ price: bidTargetPrice,
147
+ amount: bidResult.amountOut,
148
+ amount_in: bidGross.amountInGross,
149
+ amount_in_usd: bidGross.amountInGross * basePriceUsd,
150
+ fee: bidGross.feeAmount,
151
+ fee_usd: bidGross.feeAmount * basePriceUsd,
152
+ tick_move: bidTickMove,
153
+ });
80
154
  }
155
+ const depth = {
156
+ mid_price: midPrice,
157
+ fee_rate_bps: feeRateBps,
158
+ ask: assembleEntry(askTiers),
159
+ bid: assembleEntry(bidTiers),
160
+ };
81
161
  let tickLiquidity;
82
162
  const now = Date.now();
83
163
  const lastBuild = _tickLiquidityLastBuild.get(poolAddress) || 0;
@@ -86,7 +166,6 @@ function buildClmmDepth(input) {
86
166
  }
87
167
  _tickLiquidityLastBuild.set(poolAddress, now);
88
168
  try {
89
- const baseIsToken0 = baseToken.address.toLowerCase() === poolState.token0.toLowerCase();
90
169
  const tokenAIsToken0 = poolInfo.tokenA?.address?.toLowerCase() === poolState.token0.toLowerCase();
91
170
  const token0Info = tokenAIsToken0 ? poolInfo.tokenA : poolInfo.tokenB;
92
171
  const token1Info = tokenAIsToken0 ? poolInfo.tokenB : poolInfo.tokenA;
@@ -117,17 +196,29 @@ function buildClmmDepth(input) {
117
196
  }
118
197
  }
119
198
  function buildAmmDepth(input) {
120
- const bpsLevels = getDepthBps();
121
- if (bpsLevels.length === 0)
199
+ logDepthLevelsOnce();
200
+ const pctLevels = (0, ttd_core_2.getDepthPricePctLevels)();
201
+ if (pctLevels.length === 0)
202
+ return undefined;
203
+ const { poolInfo, reserve0, reserve1, token0Address, basePriceUsd, quotePriceUsd, feeRateBps } = input;
204
+ if (feeRateBps == null || feeRateBps < 0) {
205
+ (0, ttd_core_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM: invalid feeRateBps=${feeRateBps}, skip`, '');
122
206
  return undefined;
123
- const { poolInfo, reserve0, reserve1, token0Address, basePriceUsd, quotePriceUsd } = input;
207
+ }
124
208
  try {
125
209
  const askDirection = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
126
210
  const baseToken = askDirection.baseToken;
127
211
  const quoteToken = askDirection.quoteToken;
128
212
  const baseIsToken0 = baseToken.address.toLowerCase() === token0Address.toLowerCase();
129
- const depth = { ask: {}, bid: {} };
130
- for (const bps of bpsLevels) {
213
+ const baseDec = baseIsToken0 ? baseToken.decimals : quoteToken.decimals;
214
+ const quoteDec = baseIsToken0 ? quoteToken.decimals : baseToken.decimals;
215
+ const baseReserveUi = Number(BigInt(baseIsToken0 ? reserve0 : reserve1)) / Math.pow(10, baseToken.decimals);
216
+ const quoteReserveUi = Number(BigInt(baseIsToken0 ? reserve1 : reserve0)) / Math.pow(10, quoteToken.decimals);
217
+ const midPrice = quoteReserveUi / baseReserveUi;
218
+ const askTiers = [];
219
+ const bidTiers = [];
220
+ for (const pct of pctLevels) {
221
+ const bps = Math.round(pct * 100);
131
222
  const askResult = (0, amm_depth_calculator_1.calculateAmmDepth)({
132
223
  reserve0, reserve1, targetBps: bps, isBuy: true, baseIsToken0,
133
224
  token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
@@ -138,10 +229,33 @@ function buildAmmDepth(input) {
138
229
  token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
139
230
  token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
140
231
  });
141
- depth.ask[bps] = { amount: askResult.amountOut, amountUsd: askResult.amountOut * basePriceUsd, amountIn: 0, amountInUsd: 0 };
142
- depth.bid[bps] = { amount: bidResult.amountOut, amountUsd: bidResult.amountOut * quotePriceUsd, amountIn: 0, amountInUsd: 0 };
232
+ const askGross = grossUpFee(askResult.amountIn, feeRateBps);
233
+ const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
234
+ askTiers.push({
235
+ pct,
236
+ price: askResult.targetPrice,
237
+ amount: askResult.amountOut,
238
+ amount_in: askGross.amountInGross,
239
+ amount_in_usd: askGross.amountInGross * quotePriceUsd,
240
+ fee: askGross.feeAmount,
241
+ fee_usd: askGross.feeAmount * quotePriceUsd,
242
+ });
243
+ bidTiers.push({
244
+ pct,
245
+ price: bidResult.targetPrice,
246
+ amount: bidResult.amountOut,
247
+ amount_in: bidGross.amountInGross,
248
+ amount_in_usd: bidGross.amountInGross * basePriceUsd,
249
+ fee: bidGross.feeAmount,
250
+ fee_usd: bidGross.feeAmount * basePriceUsd,
251
+ });
143
252
  }
144
- return depth;
253
+ return {
254
+ mid_price: midPrice,
255
+ fee_rate_bps: feeRateBps,
256
+ ask: assembleEntry(askTiers),
257
+ bid: assembleEntry(bidTiers),
258
+ };
145
259
  }
146
260
  catch (error) {
147
261
  (0, ttd_core_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM depth failed: ${error.message}`, '');
@@ -0,0 +1,10 @@
1
+ export declare function reportHookFeeChangeIfChanged(opts: {
2
+ pool_address: string;
3
+ dex_id: string;
4
+ pool_name?: string;
5
+ block_number: number;
6
+ prevLpFee: number | undefined;
7
+ prevProtocolFee: number | undefined;
8
+ newLpFee: number;
9
+ newProtocolFee: number;
10
+ }): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportHookFeeChangeIfChanged = reportHookFeeChangeIfChanged;
4
+ const ttd_core_1 = require("@clonegod/ttd-core");
5
+ function reportHookFeeChangeIfChanged(opts) {
6
+ const { pool_address, dex_id, pool_name, block_number, prevLpFee, prevProtocolFee, newLpFee, newProtocolFee } = opts;
7
+ if (prevLpFee == null)
8
+ return;
9
+ const prevTotal = prevLpFee + (prevProtocolFee ?? 0);
10
+ const newTotal = newLpFee + newProtocolFee;
11
+ if (prevTotal === newTotal)
12
+ return;
13
+ const addr = pool_address.toLowerCase();
14
+ const prevBps = (prevTotal / 100).toFixed(2);
15
+ const newBps = (newTotal / 100).toFixed(2);
16
+ ttd_core_1.ALERT_TYPES.QUOTE_HOOK_FEE_CHANGE.reportIfChanged(addr, true, () => ({
17
+ identity: addr,
18
+ scope: { dex_id, pool_address: addr },
19
+ title: `${pool_name || addr.slice(0, 10)} hook fee ${prevBps}bps → ${newBps}bps`,
20
+ detail: {
21
+ pool_name: pool_name || '',
22
+ block_number,
23
+ prev_lp_fee_ppm: prevLpFee,
24
+ prev_protocol_fee_ppm: prevProtocolFee ?? 0,
25
+ prev_total_ppm: prevTotal,
26
+ new_lp_fee_ppm: newLpFee,
27
+ new_protocol_fee_ppm: newProtocolFee,
28
+ new_total_ppm: newTotal,
29
+ delta_ppm: newTotal - prevTotal,
30
+ },
31
+ }));
32
+ }
@@ -6,3 +6,4 @@ export * from './verify';
6
6
  export * from './price_feed_handler';
7
7
  export * from './quote_amount';
8
8
  export * from './preload_token_prices';
9
+ export * from './hook_fee_monitor';
@@ -22,3 +22,4 @@ __exportStar(require("./verify"), exports);
22
22
  __exportStar(require("./price_feed_handler"), exports);
23
23
  __exportStar(require("./quote_amount"), exports);
24
24
  __exportStar(require("./preload_token_prices"), exports);
25
+ __exportStar(require("./hook_fee_monitor"), exports);
@@ -1,4 +1,6 @@
1
1
  import { AmmPoolState, ClmmPoolState, InfinityPoolState } from "../../types/pool_state";
2
+ declare function withInitRetry<T>(fn: () => Promise<T>, label: string): Promise<T>;
3
+ export { withInitRetry };
2
4
  export declare class PoolStateInitializer {
3
5
  static initAmmPool(provider: any, poolAddress: string, dexId: string, ethersLib: any): Promise<AmmPoolState>;
4
6
  static initClmmPool(provider: any, poolAddress: string, ethersLib: any, poolAbi?: any[]): Promise<ClmmPoolState>;
@@ -1,7 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PoolStateInitializer = void 0;
4
+ exports.withInitRetry = withInitRetry;
4
5
  const ttd_core_1 = require("@clonegod/ttd-core");
6
+ async function withInitRetry(fn, label) {
7
+ try {
8
+ return await fn();
9
+ }
10
+ catch (err) {
11
+ (0, ttd_core_1.log_warn)(`[PoolStateInitializer] ${label} attempt 1 failed: ${err?.message || err}, retrying once...`);
12
+ try {
13
+ return await fn();
14
+ }
15
+ catch (err2) {
16
+ (0, ttd_core_1.log_error)(`[PoolStateInitializer] ${label} retry also failed`, err2 instanceof Error ? err2 : new Error(String(err2)));
17
+ const addrMatch = label.match(/0x[0-9a-fA-F]+/);
18
+ const identity = (addrMatch?.[0] || label).toLowerCase();
19
+ ttd_core_1.ALERT_TYPES.QUOTE_POOL_INIT_FAILED.report({
20
+ severity: 'critical',
21
+ identity,
22
+ scope: { pool_address: addrMatch ? identity : undefined },
23
+ title: `Pool init failed: ${label} — ${err2?.message || err2}`,
24
+ detail: { label, error: String(err2?.message || err2), stack: err2?.stack },
25
+ });
26
+ throw err2;
27
+ }
28
+ }
29
+ }
5
30
  const V2_PAIR_ABI = [
6
31
  'function token0() view returns (address)',
7
32
  'function token1() view returns (address)',
@@ -30,17 +55,17 @@ class PoolStateInitializer {
30
55
  poolContract.token0(),
31
56
  poolContract.token1(),
32
57
  ]);
33
- const fee = AMM_FEE_MAP[dexId] || AMM_FEE_MAP[dexId.replace('-', '_')] || 30;
58
+ const feeRateBps = AMM_FEE_MAP[dexId] || AMM_FEE_MAP[dexId.replace('-', '_')] || 30;
34
59
  const state = {
35
60
  address: poolAddress,
36
61
  token0: token0.toLowerCase(),
37
62
  token1: token1.toLowerCase(),
38
- fee,
63
+ feeRateBps,
39
64
  reserve0: reserves[0].toString(),
40
65
  reserve1: reserves[1].toString(),
41
66
  };
42
67
  (0, ttd_core_1.log_info)(`[PoolStateInitializer] AMM pool initialized: ${poolAddress}`, {
43
- token0: state.token0, token1: state.token1, fee: state.fee,
68
+ token0: state.token0, token1: state.token1, feeRateBps: state.feeRateBps,
44
69
  });
45
70
  return state;
46
71
  }
@@ -62,11 +87,13 @@ class PoolStateInitializer {
62
87
  poolContract.liquidity(),
63
88
  ]);
64
89
  const [sqrtPriceX96, tick] = slot0;
90
+ const feeRatePpm = Number(fee);
65
91
  const state = {
66
92
  address: poolAddress,
67
93
  token0: token0.toLowerCase(),
68
94
  token1: token1.toLowerCase(),
69
- fee: Number(fee),
95
+ feeRateBps: feeRatePpm / 100,
96
+ feeRatePpm,
70
97
  tickSpacing: Number(tickSpacing),
71
98
  tick: Number(tick),
72
99
  sqrtPriceX96: sqrtPriceX96.toString(),
@@ -74,7 +101,8 @@ class PoolStateInitializer {
74
101
  };
75
102
  (0, ttd_core_1.log_info)(`[PoolStateInitializer] CLMM pool initialized: ${poolAddress}`, {
76
103
  token0: state.token0, token1: state.token1,
77
- fee: state.fee, tickSpacing: state.tickSpacing, tick: state.tick,
104
+ feeRateBps: state.feeRateBps, feeRatePpm: state.feeRatePpm,
105
+ tickSpacing: state.tickSpacing, tick: state.tick,
78
106
  });
79
107
  return state;
80
108
  }
@@ -98,7 +126,8 @@ class PoolStateInitializer {
98
126
  address: poolKey,
99
127
  token0: currency0.toLowerCase ? currency0.toLowerCase() : currency0,
100
128
  token1: currency1.toLowerCase ? currency1.toLowerCase() : currency1,
101
- fee: Number(fee),
129
+ feeRateBps: (Number(lpFee) + Number(protocolFee)) / 100,
130
+ feeRatePpm: Number(fee),
102
131
  tickSpacing,
103
132
  tick: Number(tick),
104
133
  sqrtPriceX96: sqrtPriceX96.toString(),
@@ -113,7 +142,8 @@ class PoolStateInitializer {
113
142
  };
114
143
  (0, ttd_core_1.log_info)(`[PoolStateInitializer] Infinity pool initialized: ${poolKey}`, {
115
144
  currency0: state.currency0, currency1: state.currency1,
116
- fee: state.fee, tickSpacing: state.tickSpacing, tick: state.tick,
145
+ feeRateBps: state.feeRateBps, feeRatePpm: state.feeRatePpm,
146
+ tickSpacing: state.tickSpacing, tick: state.tick,
117
147
  lpFee: state.lpFee, protocolFee: state.protocolFee,
118
148
  });
119
149
  return state;
@@ -1,3 +1,4 @@
1
+ import { QuoteTier } from '@clonegod/ttd-core';
1
2
  export interface CheckSwapParams {
2
3
  poolAddress: string;
3
4
  poolName: string;
@@ -19,7 +20,10 @@ export interface CheckSwapParams {
19
20
  export declare class QuotePriceVerify {
20
21
  private quoteCache;
21
22
  private get enabled();
22
- cacheQuote(poolAddress: string, priceId: string, source: string, askPrice: number, bidPrice: number, blockNumber: number, quoteAmountUsd: number, token0PriceUsd?: number, token1PriceUsd?: number): void;
23
+ cacheQuote(poolAddress: string, priceId: string, source: string, askPrice: number, bidPrice: number, blockNumber: number, quoteAmountUsd: number, token0PriceUsd?: number, token1PriceUsd?: number, tiers?: {
24
+ askTiers?: QuoteTier[];
25
+ bidTiers?: QuoteTier[];
26
+ }): void;
23
27
  checkSwap(params: CheckSwapParams): void;
24
28
  private deriveSwapDirection;
25
29
  private compareAndLog;