@clonegod/ttd-bsc-common 3.1.66 → 3.1.68
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/depth/amm_depth_calculator.d.ts +4 -0
- package/dist/quote/depth/amm_depth_calculator.js +21 -4
- package/dist/quote/depth/clmm_depth_calculator.d.ts +2 -0
- package/dist/quote/depth/clmm_depth_calculator.js +13 -2
- package/dist/quote/depth/index.d.ts +8 -6
- package/dist/quote/depth/index.js +149 -35
- package/dist/quote/hook_fee_monitor.d.ts +10 -0
- package/dist/quote/hook_fee_monitor.js +32 -0
- package/dist/quote/index.d.ts +1 -0
- package/dist/quote/index.js +1 -0
- package/dist/quote/pricing/pool_state_initializer.d.ts +2 -0
- package/dist/quote/pricing/pool_state_initializer.js +40 -7
- package/dist/quote/verify/quote_price_verify.d.ts +5 -1
- package/dist/quote/verify/quote_price_verify.js +75 -8
- package/dist/trade/abstract_dex_trade.js +9 -1
- package/dist/trade/check/abstract_tx_result_checker.js +23 -0
- package/dist/types/pool_state.d.ts +4 -3
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
export
|
|
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:
|
|
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):
|
|
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.
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
|
57
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
130
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/quote/index.d.ts
CHANGED
package/dist/quote/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
104
|
+
feeRateBps: state.feeRateBps, feeRatePpm: state.feeRatePpm,
|
|
105
|
+
tickSpacing: state.tickSpacing, tick: state.tick,
|
|
78
106
|
});
|
|
79
107
|
return state;
|
|
80
108
|
}
|
|
@@ -94,11 +122,15 @@ class PoolStateInitializer {
|
|
|
94
122
|
const [sqrtPriceX96, tick, protocolFee, lpFee] = slot0Result;
|
|
95
123
|
const [currency0, currency1, hooks, poolManager, fee, parameters] = poolKeyInfo;
|
|
96
124
|
const { tickSpacing } = decodeParameters(parameters);
|
|
125
|
+
const DYNAMIC_FEE_FLAG = 0x800000;
|
|
126
|
+
const isDynamic = Number(fee) === DYNAMIC_FEE_FLAG;
|
|
127
|
+
const effectiveLpFeePpm = isDynamic ? Number(lpFee) : Number(fee);
|
|
97
128
|
const state = {
|
|
98
129
|
address: poolKey,
|
|
99
130
|
token0: currency0.toLowerCase ? currency0.toLowerCase() : currency0,
|
|
100
131
|
token1: currency1.toLowerCase ? currency1.toLowerCase() : currency1,
|
|
101
|
-
|
|
132
|
+
feeRateBps: (effectiveLpFeePpm + Number(protocolFee)) / 100,
|
|
133
|
+
feeRatePpm: Number(fee),
|
|
102
134
|
tickSpacing,
|
|
103
135
|
tick: Number(tick),
|
|
104
136
|
sqrtPriceX96: sqrtPriceX96.toString(),
|
|
@@ -113,7 +145,8 @@ class PoolStateInitializer {
|
|
|
113
145
|
};
|
|
114
146
|
(0, ttd_core_1.log_info)(`[PoolStateInitializer] Infinity pool initialized: ${poolKey}`, {
|
|
115
147
|
currency0: state.currency0, currency1: state.currency1,
|
|
116
|
-
|
|
148
|
+
feeRateBps: state.feeRateBps, feeRatePpm: state.feeRatePpm,
|
|
149
|
+
tickSpacing: state.tickSpacing, tick: state.tick,
|
|
117
150
|
lpFee: state.lpFee, protocolFee: state.protocolFee,
|
|
118
151
|
});
|
|
119
152
|
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
|
|
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;
|
|
@@ -3,6 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.QuotePriceVerify = void 0;
|
|
4
4
|
const trade_direction_1 = require("../../utils/trade_direction");
|
|
5
5
|
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
6
|
+
function pickMatchingTier(tiers, actualAmountIn) {
|
|
7
|
+
if (!tiers || tiers.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
if (tiers.length === 1)
|
|
10
|
+
return { tier: tiers[0], mode: 'single_tier' };
|
|
11
|
+
const sorted = [...tiers].sort((a, b) => a.amount_in - b.amount_in);
|
|
12
|
+
const top = sorted[sorted.length - 1];
|
|
13
|
+
if (actualAmountIn < sorted[0].amount_in) {
|
|
14
|
+
return { tier: sorted[0], mode: 'extrapolated_low' };
|
|
15
|
+
}
|
|
16
|
+
if (actualAmountIn > top.amount_in) {
|
|
17
|
+
return { tier: top, mode: 'extrapolated_high' };
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
20
|
+
const lower = sorted[i];
|
|
21
|
+
const upper = sorted[i + 1];
|
|
22
|
+
if (actualAmountIn >= lower.amount_in && actualAmountIn <= upper.amount_in) {
|
|
23
|
+
if (actualAmountIn === lower.amount_in)
|
|
24
|
+
return { tier: lower, mode: 'exact' };
|
|
25
|
+
if (actualAmountIn === upper.amount_in)
|
|
26
|
+
return { tier: upper, mode: 'exact' };
|
|
27
|
+
const span = upper.amount_in - lower.amount_in;
|
|
28
|
+
if (span <= 0)
|
|
29
|
+
return { tier: lower, mode: 'exact' };
|
|
30
|
+
const ratio = (actualAmountIn - lower.amount_in) / span;
|
|
31
|
+
const lerp = (a, b) => a + ratio * (b - a);
|
|
32
|
+
return {
|
|
33
|
+
tier: {
|
|
34
|
+
pct: lerp(lower.pct, upper.pct),
|
|
35
|
+
price: lerp(lower.price, upper.price),
|
|
36
|
+
amount: lerp(lower.amount, upper.amount),
|
|
37
|
+
amount_in: actualAmountIn,
|
|
38
|
+
amount_in_usd: lerp(lower.amount_in_usd, upper.amount_in_usd),
|
|
39
|
+
fee: lerp(lower.fee, upper.fee),
|
|
40
|
+
fee_usd: lerp(lower.fee_usd, upper.fee_usd),
|
|
41
|
+
},
|
|
42
|
+
mode: 'interpolated',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { tier: top, mode: 'extrapolated_high' };
|
|
47
|
+
}
|
|
6
48
|
class QuotePriceVerify {
|
|
7
49
|
constructor() {
|
|
8
50
|
this.quoteCache = new Map();
|
|
@@ -10,7 +52,7 @@ class QuotePriceVerify {
|
|
|
10
52
|
get enabled() {
|
|
11
53
|
return process.env.VERIFY_QUOTE_PRICE === 'true';
|
|
12
54
|
}
|
|
13
|
-
cacheQuote(poolAddress, priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd, token0PriceUsd = 0, token1PriceUsd = 0) {
|
|
55
|
+
cacheQuote(poolAddress, priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd, token0PriceUsd = 0, token1PriceUsd = 0, tiers) {
|
|
14
56
|
if (!this.enabled)
|
|
15
57
|
return;
|
|
16
58
|
if (!source)
|
|
@@ -26,6 +68,8 @@ class QuotePriceVerify {
|
|
|
26
68
|
: 0;
|
|
27
69
|
sourceMap.set(source, {
|
|
28
70
|
priceId, source, askPrice, bidPrice,
|
|
71
|
+
askTiers: tiers?.askTiers,
|
|
72
|
+
bidTiers: tiers?.bidTiers,
|
|
29
73
|
referenceBlock: blockNumber,
|
|
30
74
|
verifiedReferenceBlock,
|
|
31
75
|
quoteAmountUsd,
|
|
@@ -55,12 +99,28 @@ class QuotePriceVerify {
|
|
|
55
99
|
continue;
|
|
56
100
|
if (cached.verifiedReferenceBlock === cached.referenceBlock)
|
|
57
101
|
continue;
|
|
58
|
-
const
|
|
102
|
+
const tiers = isBuy ? cached.askTiers : cached.bidTiers;
|
|
103
|
+
let refPrice;
|
|
104
|
+
let tierInfo;
|
|
105
|
+
if (tiers && tiers.length > 0) {
|
|
106
|
+
const match = pickMatchingTier(tiers, swapData.inputAmountUi);
|
|
107
|
+
if (!match)
|
|
108
|
+
continue;
|
|
109
|
+
refPrice = match.tier.price;
|
|
110
|
+
tierInfo = {
|
|
111
|
+
matched_pct: parseFloat(match.tier.pct.toFixed(4)),
|
|
112
|
+
mode: match.mode,
|
|
113
|
+
tier_amount_in: match.tier.amount_in,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
118
|
+
}
|
|
59
119
|
if (refPrice <= 0)
|
|
60
120
|
continue;
|
|
61
121
|
cached.verifiedReferenceBlock = cached.referenceBlock;
|
|
62
122
|
const diff_bps = (refPrice - execPrice) / refPrice * 10000;
|
|
63
|
-
verifiable.push({ source, cached, refPrice, diff_bps });
|
|
123
|
+
verifiable.push({ source, cached, refPrice, diff_bps, tierInfo });
|
|
64
124
|
}
|
|
65
125
|
if (verifiable.length === 0)
|
|
66
126
|
return;
|
|
@@ -73,9 +133,14 @@ class QuotePriceVerify {
|
|
|
73
133
|
bid: r.cached.bidPrice,
|
|
74
134
|
diff_bps: parseFloat(r.diff_bps.toFixed(1)),
|
|
75
135
|
quote_block: r.cached.referenceBlock,
|
|
136
|
+
...(r.tierInfo && {
|
|
137
|
+
matched_pct: r.tierInfo.matched_pct,
|
|
138
|
+
tier_mode: r.tierInfo.mode,
|
|
139
|
+
tier_amount_in: r.tierInfo.tier_amount_in,
|
|
140
|
+
}),
|
|
76
141
|
};
|
|
77
142
|
}
|
|
78
|
-
this.compareAndLog(poolAddress, params.poolName, poolInfo, primary.cached, swapData.inputTokenAddress, swapData.outputTokenAddress, swapData.inputAmountUi, swapData.outputAmountUi, primary.cached.token0PriceUsd, primary.cached.token1PriceUsd, token0Address, blockNumber, txHash, sources);
|
|
143
|
+
this.compareAndLog(poolAddress, params.poolName, poolInfo, primary.cached, swapData.inputTokenAddress, swapData.outputTokenAddress, swapData.inputAmountUi, swapData.outputAmountUi, primary.refPrice, execPrice, primary.cached.token0PriceUsd, primary.cached.token1PriceUsd, token0Address, blockNumber, txHash, sources, primary.tierInfo);
|
|
79
144
|
}
|
|
80
145
|
deriveSwapDirection(params) {
|
|
81
146
|
const { amount0, amount1, token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
@@ -102,7 +167,7 @@ class QuotePriceVerify {
|
|
|
102
167
|
}
|
|
103
168
|
return null;
|
|
104
169
|
}
|
|
105
|
-
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash, sources) {
|
|
170
|
+
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, refPrice, execPriceNum, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash, sources, primaryTierInfo) {
|
|
106
171
|
if (inputAmountUi <= 0 || outputAmountUi <= 0)
|
|
107
172
|
return;
|
|
108
173
|
let swapUsd = 0;
|
|
@@ -117,9 +182,6 @@ class QuotePriceVerify {
|
|
|
117
182
|
const quoteToken = direction.quoteToken;
|
|
118
183
|
const inputIsQuoteToken = inputTokenAddress.toLowerCase() === quoteToken.address.toLowerCase();
|
|
119
184
|
const isBuy = inputIsQuoteToken;
|
|
120
|
-
const execPrice = (0, trade_direction_1.calculateStandardPrice)(inputAmountUi, outputAmountUi, isBuy);
|
|
121
|
-
const execPriceNum = Number(execPrice);
|
|
122
|
-
const refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
123
185
|
const side = isBuy ? 'BUY' : 'SELL';
|
|
124
186
|
if (refPrice <= 0 || execPriceNum <= 0)
|
|
125
187
|
return;
|
|
@@ -164,6 +226,11 @@ class QuotePriceVerify {
|
|
|
164
226
|
status,
|
|
165
227
|
time: Date.now(),
|
|
166
228
|
sources: sources || undefined,
|
|
229
|
+
...(primaryTierInfo && {
|
|
230
|
+
matched_tier_pct: primaryTierInfo.matched_pct,
|
|
231
|
+
matched_tier_mode: primaryTierInfo.mode,
|
|
232
|
+
matched_tier_amount_in: primaryTierInfo.tier_amount_in,
|
|
233
|
+
}),
|
|
167
234
|
});
|
|
168
235
|
}
|
|
169
236
|
catch (_) {
|
|
@@ -199,8 +199,9 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
199
199
|
trace.set('gasType', mainGasFields.type === 2 ? 'EIP1559' : 'Legacy');
|
|
200
200
|
trace.set('tip', transfer_amount_gwei);
|
|
201
201
|
const only_bundle = order_msg.is_dex_maker;
|
|
202
|
-
await this.transactionSender.sendTransaction(signedMainTx, tipTxMap, order_trace_id, pair, only_bundle, trace);
|
|
203
202
|
trace.mark('sent');
|
|
203
|
+
await this.transactionSender.sendTransaction(signedMainTx, tipTxMap, order_trace_id, pair, only_bundle, trace);
|
|
204
|
+
trace.mark('send_complete');
|
|
204
205
|
trace.flush();
|
|
205
206
|
context._execution_marks = trace.getAbsoluteMarks();
|
|
206
207
|
context._execution_start_time = trace.getStartTime();
|
|
@@ -238,6 +239,13 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
238
239
|
if (!txid) {
|
|
239
240
|
trace.markError('exhaust', `failed after ${maxAttempts} attempts`);
|
|
240
241
|
trace.flush();
|
|
242
|
+
const groupId = this.appConfig.trade_runtime?.group?.id;
|
|
243
|
+
ttd_core_1.ALERT_TYPES.TRADE_BUILDER_REJECTED.report({
|
|
244
|
+
identity: caller.address.toLowerCase(),
|
|
245
|
+
scope: { caller_address: caller.address.toLowerCase(), group_id: groupId },
|
|
246
|
+
title: `Trade execution exhausted after ${maxAttempts} attempts (order ${order_trace_id})`,
|
|
247
|
+
detail: { order_trace_id, attempts: maxAttempts, last_nonce: nonce, last_error_nonce: nonce_from_error },
|
|
248
|
+
});
|
|
241
249
|
throw new Error(`交易执行失败,已重试 ${maxAttempts} 次,orderId: ${order_trace_id}`);
|
|
242
250
|
}
|
|
243
251
|
return txid;
|
|
@@ -117,6 +117,12 @@ class AbstractTxResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
117
117
|
}
|
|
118
118
|
else {
|
|
119
119
|
clearInterval(intervalId);
|
|
120
|
+
ttd_core_1.ALERT_TYPES.TRADE_TX_PENDING_TOO_LONG.report({
|
|
121
|
+
identity: String(this.txid).toLowerCase(),
|
|
122
|
+
scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
|
|
123
|
+
title: `Tx pending > ${check_timeout}ms: ${this.txid}`,
|
|
124
|
+
detail: { txid: this.txid, timeout_ms: check_timeout, checks: this.check_count },
|
|
125
|
+
});
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
catch (err) {
|
|
@@ -155,6 +161,23 @@ class AbstractTxResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
155
161
|
}
|
|
156
162
|
else {
|
|
157
163
|
this.event_emitter.emit(ttd_core_1.TRANSACTION_STATE_FAILED, trade_result);
|
|
164
|
+
const status = txReceipt?.status;
|
|
165
|
+
if (status === 0) {
|
|
166
|
+
ttd_core_1.ALERT_TYPES.TRADE_VAULT_REVERT.report({
|
|
167
|
+
identity: String(this.txid).toLowerCase(),
|
|
168
|
+
scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
|
|
169
|
+
title: `Vault revert: tx=${this.txid}`,
|
|
170
|
+
detail: { txid: this.txid, status, source, gas_used: txReceipt?.gasUsed?.toString?.() },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
ttd_core_1.ALERT_TYPES.TRADE_RESULT_MISSING.report({
|
|
175
|
+
identity: String(this.txid).toLowerCase(),
|
|
176
|
+
scope: { pool_address: this.pool_info?.pool_address?.toLowerCase() },
|
|
177
|
+
title: `TradeResult event missing: tx=${this.txid}`,
|
|
178
|
+
detail: { txid: this.txid, status, source, parse_result: trade_result },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
158
181
|
}
|
|
159
182
|
if (source === 'interval') {
|
|
160
183
|
console.log('--------------------- Transaction Result from Polling ---------------------');
|
|
@@ -2,7 +2,7 @@ export interface OnchainPoolData {
|
|
|
2
2
|
address: string;
|
|
3
3
|
token0: string;
|
|
4
4
|
token1: string;
|
|
5
|
-
|
|
5
|
+
feeRateBps: number;
|
|
6
6
|
tickSpacing: number;
|
|
7
7
|
reserve0: string;
|
|
8
8
|
reserve1: string;
|
|
@@ -14,7 +14,7 @@ export interface AmmPoolState {
|
|
|
14
14
|
address: string;
|
|
15
15
|
token0: string;
|
|
16
16
|
token1: string;
|
|
17
|
-
|
|
17
|
+
feeRateBps: number;
|
|
18
18
|
reserve0: string;
|
|
19
19
|
reserve1: string;
|
|
20
20
|
}
|
|
@@ -22,7 +22,8 @@ export interface ClmmPoolState {
|
|
|
22
22
|
address: string;
|
|
23
23
|
token0: string;
|
|
24
24
|
token1: string;
|
|
25
|
-
|
|
25
|
+
feeRateBps: number;
|
|
26
|
+
feeRatePpm: number;
|
|
26
27
|
tickSpacing: number;
|
|
27
28
|
tick: number;
|
|
28
29
|
sqrtPriceX96: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clonegod/ttd-bsc-common",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.68",
|
|
4
4
|
"description": "BSC common library",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"push": "npm run build && npm publish"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@clonegod/ttd-core": "3.1.
|
|
17
|
+
"@clonegod/ttd-core": "3.1.58",
|
|
18
18
|
"axios": "1.15.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"ethers": "^5.8.0",
|