@clonegod/ttd-sol-common 2.0.68 → 2.0.69
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 +19 -0
- package/dist/quote/depth/amm_depth_calculator.js +55 -0
- package/dist/quote/depth/index.d.ts +18 -1
- package/dist/quote/depth/index.js +65 -1
- package/package.json +1 -1
- package/src/quote/depth/amm_depth_calculator.ts +143 -0
- package/src/quote/depth/index.ts +109 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AmmDepthInput {
|
|
2
|
+
reserve0: string;
|
|
3
|
+
reserve1: string;
|
|
4
|
+
targetBps: number;
|
|
5
|
+
isBuy: boolean;
|
|
6
|
+
baseIsToken0: boolean;
|
|
7
|
+
token0Decimals: number;
|
|
8
|
+
token1Decimals: number;
|
|
9
|
+
}
|
|
10
|
+
export interface AmmDepthResult {
|
|
11
|
+
amountOutWei: bigint;
|
|
12
|
+
amountOut: number;
|
|
13
|
+
outputDecimals: number;
|
|
14
|
+
amountInWei: bigint;
|
|
15
|
+
amountIn: number;
|
|
16
|
+
inputDecimals: number;
|
|
17
|
+
targetPrice: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function calculateAmmDepth(input: AmmDepthInput): AmmDepthResult;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateAmmDepth = calculateAmmDepth;
|
|
4
|
+
const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
|
|
5
|
+
function calculateAmmDepth(input) {
|
|
6
|
+
const { targetBps, isBuy, baseIsToken0, token0Decimals, token1Decimals } = input;
|
|
7
|
+
const R0 = BigInt(input.reserve0);
|
|
8
|
+
const R1 = BigInt(input.reserve1);
|
|
9
|
+
if (R0 === 0n || R1 === 0n) {
|
|
10
|
+
return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0, amountInWei: 0n, amountIn: 0, inputDecimals: 0, targetPrice: 0 };
|
|
11
|
+
}
|
|
12
|
+
const k = R0 * R1;
|
|
13
|
+
const bps = BigInt(targetBps);
|
|
14
|
+
const PRECISION = 10n ** 18n;
|
|
15
|
+
let amountOutWei;
|
|
16
|
+
let amountInWei;
|
|
17
|
+
let outputDecimals;
|
|
18
|
+
let inputDecimals;
|
|
19
|
+
if (isBuy) {
|
|
20
|
+
const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
|
|
21
|
+
outputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
|
|
22
|
+
inputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
|
|
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);
|
|
25
|
+
amountOutWei = baseReserve - baseReserveNew;
|
|
26
|
+
amountInWei = quoteReserveNew - quoteReserve;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
|
|
30
|
+
outputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
|
|
31
|
+
inputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
|
|
32
|
+
if (bps >= 10000n) {
|
|
33
|
+
amountOutWei = quoteReserve;
|
|
34
|
+
amountInWei = baseReserve;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
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);
|
|
39
|
+
amountOutWei = quoteReserve - quoteReserveNew;
|
|
40
|
+
amountInWei = baseReserveNew - baseReserve;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const amountOut = Number(amountOutWei) / Math.pow(10, 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 };
|
|
55
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { StandardPoolInfoType } from '@clonegod/ttd-core/dist';
|
|
2
|
-
import { QuoteDepthOutput } from '@clonegod/ttd-core/dist';
|
|
2
|
+
import { QuoteTier, QuoteEntry, QuoteDepthOutput } from '@clonegod/ttd-core/dist';
|
|
3
3
|
import { DepthTick } from './clmm_depth_calculator';
|
|
4
4
|
export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt, swapExactInClmm } from './clmm_depth_calculator';
|
|
5
5
|
export type { DepthTick, ClmmDepthInput, DepthResult, SwapExactInInput, SwapExactInResult } from './clmm_depth_calculator';
|
|
6
|
+
export { calculateAmmDepth } from './amm_depth_calculator';
|
|
7
|
+
export type { AmmDepthInput, AmmDepthResult } from './amm_depth_calculator';
|
|
8
|
+
export declare function grossUpFee(amountInNet: number, feeRateBps: number): {
|
|
9
|
+
amountInGross: number;
|
|
10
|
+
feeAmount: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function assembleEntry(tiers: QuoteTier[]): QuoteEntry;
|
|
6
13
|
export interface BuildClmmDepthInput {
|
|
7
14
|
poolInfo: StandardPoolInfoType;
|
|
8
15
|
poolAddress: string;
|
|
@@ -18,3 +25,13 @@ export interface BuildClmmDepthInput {
|
|
|
18
25
|
feeRateBps: number;
|
|
19
26
|
}
|
|
20
27
|
export declare function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | undefined;
|
|
28
|
+
export interface BuildAmmDepthInput {
|
|
29
|
+
poolInfo: StandardPoolInfoType;
|
|
30
|
+
reserve0: string;
|
|
31
|
+
reserve1: string;
|
|
32
|
+
baseIsToken0: boolean;
|
|
33
|
+
basePriceUsd: number;
|
|
34
|
+
quotePriceUsd: number;
|
|
35
|
+
feeRateBps: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function buildAmmDepth(input: BuildAmmDepthInput): QuoteDepthOutput | undefined;
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.swapExactInClmm = exports.bigIntSqrt = exports.computeTargetSqrtX64 = exports.priceFromSqrtX64 = exports.calculateClmmDepth = void 0;
|
|
3
|
+
exports.calculateAmmDepth = exports.swapExactInClmm = exports.bigIntSqrt = exports.computeTargetSqrtX64 = exports.priceFromSqrtX64 = exports.calculateClmmDepth = void 0;
|
|
4
|
+
exports.grossUpFee = grossUpFee;
|
|
5
|
+
exports.assembleEntry = assembleEntry;
|
|
4
6
|
exports.buildClmmDepth = buildClmmDepth;
|
|
7
|
+
exports.buildAmmDepth = buildAmmDepth;
|
|
5
8
|
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
6
9
|
const dist_2 = require("@clonegod/ttd-core/dist");
|
|
7
10
|
const trade_direction_1 = require("../../utils/trade_direction");
|
|
8
11
|
const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
|
|
12
|
+
const amm_depth_calculator_1 = require("./amm_depth_calculator");
|
|
9
13
|
var clmm_depth_calculator_2 = require("./clmm_depth_calculator");
|
|
10
14
|
Object.defineProperty(exports, "calculateClmmDepth", { enumerable: true, get: function () { return clmm_depth_calculator_2.calculateClmmDepth; } });
|
|
11
15
|
Object.defineProperty(exports, "priceFromSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.priceFromSqrtX64; } });
|
|
12
16
|
Object.defineProperty(exports, "computeTargetSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.computeTargetSqrtX64; } });
|
|
13
17
|
Object.defineProperty(exports, "bigIntSqrt", { enumerable: true, get: function () { return clmm_depth_calculator_2.bigIntSqrt; } });
|
|
14
18
|
Object.defineProperty(exports, "swapExactInClmm", { enumerable: true, get: function () { return clmm_depth_calculator_2.swapExactInClmm; } });
|
|
19
|
+
var amm_depth_calculator_2 = require("./amm_depth_calculator");
|
|
20
|
+
Object.defineProperty(exports, "calculateAmmDepth", { enumerable: true, get: function () { return amm_depth_calculator_2.calculateAmmDepth; } });
|
|
15
21
|
let _depthPctLogged = false;
|
|
16
22
|
function logDepthLevelsOnce() {
|
|
17
23
|
if (_depthPctLogged)
|
|
@@ -98,3 +104,61 @@ function buildClmmDepth(input) {
|
|
|
98
104
|
return undefined;
|
|
99
105
|
}
|
|
100
106
|
}
|
|
107
|
+
function buildAmmDepth(input) {
|
|
108
|
+
logDepthLevelsOnce();
|
|
109
|
+
const pctLevels = (0, dist_2.getDepthPricePctLevels)();
|
|
110
|
+
if (pctLevels.length === 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
const { poolInfo, reserve0, reserve1, baseIsToken0, basePriceUsd, quotePriceUsd, feeRateBps } = input;
|
|
113
|
+
if (feeRateBps == null || feeRateBps < 0) {
|
|
114
|
+
(0, dist_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM: invalid feeRateBps=${feeRateBps}, skip`, '');
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const dir = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
|
|
119
|
+
const baseToken = dir.baseToken;
|
|
120
|
+
const quoteToken = dir.quoteToken;
|
|
121
|
+
const baseReserveUi = Number(BigInt(baseIsToken0 ? reserve0 : reserve1)) / Math.pow(10, baseToken.decimals);
|
|
122
|
+
const quoteReserveUi = Number(BigInt(baseIsToken0 ? reserve1 : reserve0)) / Math.pow(10, quoteToken.decimals);
|
|
123
|
+
const midPrice = quoteReserveUi / baseReserveUi;
|
|
124
|
+
const askTiers = [];
|
|
125
|
+
const bidTiers = [];
|
|
126
|
+
for (const pct of pctLevels) {
|
|
127
|
+
const bps = Math.round(pct * 100);
|
|
128
|
+
const askResult = (0, amm_depth_calculator_1.calculateAmmDepth)({
|
|
129
|
+
reserve0, reserve1, targetBps: bps, isBuy: true, baseIsToken0,
|
|
130
|
+
token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
|
|
131
|
+
token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
|
|
132
|
+
});
|
|
133
|
+
const bidResult = (0, amm_depth_calculator_1.calculateAmmDepth)({
|
|
134
|
+
reserve0, reserve1, targetBps: bps, isBuy: false, baseIsToken0,
|
|
135
|
+
token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
|
|
136
|
+
token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
|
|
137
|
+
});
|
|
138
|
+
const askGross = grossUpFee(askResult.amountIn, feeRateBps);
|
|
139
|
+
const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
|
|
140
|
+
const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askResult.targetPrice;
|
|
141
|
+
const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidResult.targetPrice;
|
|
142
|
+
askTiers.push({
|
|
143
|
+
pct, price: askEffPrice, amount: askResult.amountOut,
|
|
144
|
+
amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
|
|
145
|
+
fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd,
|
|
146
|
+
});
|
|
147
|
+
bidTiers.push({
|
|
148
|
+
pct, price: bidEffPrice, amount: bidResult.amountOut,
|
|
149
|
+
amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
|
|
150
|
+
fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
mid_price: midPrice,
|
|
155
|
+
fee_rate_bps: feeRateBps,
|
|
156
|
+
ask: assembleEntry(askTiers),
|
|
157
|
+
bid: assembleEntry(bidTiers),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
(0, dist_1.log_debug)(`[Depth] ${poolInfo.pool_name} AMM depth failed: ${error.message}`, '');
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AMM 深度计算器
|
|
3
|
+
*
|
|
4
|
+
* 给定 reserves + 价格偏移(bps),用 x*y=k 闭合公式计算可交易量。
|
|
5
|
+
* 一次乘除,零遍历。
|
|
6
|
+
*
|
|
7
|
+
* 与 CLMM 一样,给定价格范围的输出量与 fee 无关(fee 只影响需要多少 input)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { bigIntSqrt } from './clmm_depth_calculator';
|
|
11
|
+
|
|
12
|
+
export interface AmmDepthInput {
|
|
13
|
+
reserve0: string; // token0 reserve(wei string)
|
|
14
|
+
reserve1: string; // token1 reserve(wei string)
|
|
15
|
+
targetBps: number; // 价格偏移(如 15, 30)
|
|
16
|
+
isBuy: boolean; // true=买入 base token(token0), false=卖出 base token
|
|
17
|
+
/** base token 是 pool 的 token0 还是 token1 */
|
|
18
|
+
baseIsToken0: boolean;
|
|
19
|
+
token0Decimals: number;
|
|
20
|
+
token1Decimals: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AmmDepthResult {
|
|
24
|
+
/** 输出 token 数量(wei) */
|
|
25
|
+
amountOutWei: bigint;
|
|
26
|
+
/** 输出 token 数量(UI 单位) */
|
|
27
|
+
amountOut: number;
|
|
28
|
+
/** 输出的 decimals */
|
|
29
|
+
outputDecimals: number;
|
|
30
|
+
/** 输入 token 数量(wei,不含 fee;池子曲线纯输入) */
|
|
31
|
+
amountInWei: bigint;
|
|
32
|
+
/** 输入 token 数量(UI 单位,不含 fee) */
|
|
33
|
+
amountIn: number;
|
|
34
|
+
/** 输入的 decimals */
|
|
35
|
+
inputDecimals: number;
|
|
36
|
+
/** 推到的目标价(quote per base,UI 单位) */
|
|
37
|
+
targetPrice: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 计算 AMM 池子在给定价格偏移范围内的深度
|
|
42
|
+
*
|
|
43
|
+
* 公式推导(以标准 token0/token1 为例):
|
|
44
|
+
* k = R0 * R1
|
|
45
|
+
* P = R1 / R0(token0 相对 token1 的价格)
|
|
46
|
+
*
|
|
47
|
+
* Ask(买入 token0,价格上涨):
|
|
48
|
+
* P_target = P * (1 + bps/10000)
|
|
49
|
+
* R0_new = sqrt(k / P_target)
|
|
50
|
+
* depth_token0 = R0 - R0_new
|
|
51
|
+
*
|
|
52
|
+
* Bid(卖出 token0,价格下跌):
|
|
53
|
+
* P_target = P * (1 - bps/10000)
|
|
54
|
+
* R1_new = sqrt(k * P_target)
|
|
55
|
+
* depth_token1 = R1 - R1_new
|
|
56
|
+
*
|
|
57
|
+
* 注意:pair 的 base/quote 可能与 pool 的 token0/token1 顺序不同,
|
|
58
|
+
* 通过 baseIsToken0 参数处理。
|
|
59
|
+
*/
|
|
60
|
+
export function calculateAmmDepth(input: AmmDepthInput): AmmDepthResult {
|
|
61
|
+
const { targetBps, isBuy, baseIsToken0, token0Decimals, token1Decimals } = input;
|
|
62
|
+
const R0 = BigInt(input.reserve0);
|
|
63
|
+
const R1 = BigInt(input.reserve1);
|
|
64
|
+
|
|
65
|
+
if (R0 === 0n || R1 === 0n) {
|
|
66
|
+
return { amountOutWei: 0n, amountOut: 0, outputDecimals: 0, amountInWei: 0n, amountIn: 0, inputDecimals: 0, targetPrice: 0 };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const k = R0 * R1;
|
|
70
|
+
const bps = BigInt(targetBps);
|
|
71
|
+
// 精度放大因子
|
|
72
|
+
const PRECISION = 10n ** 18n;
|
|
73
|
+
|
|
74
|
+
let amountOutWei: bigint;
|
|
75
|
+
let amountInWei: bigint;
|
|
76
|
+
let outputDecimals: number;
|
|
77
|
+
let inputDecimals: number;
|
|
78
|
+
|
|
79
|
+
if (isBuy) {
|
|
80
|
+
// 买入 base token → 价格上涨 → 输出 base token
|
|
81
|
+
// base token 从池子中减少
|
|
82
|
+
|
|
83
|
+
// P = quoteReserve / baseReserve
|
|
84
|
+
// P_target = P * (1 + bps/10000) = P * (10000 + bps) / 10000
|
|
85
|
+
// baseReserve_new = sqrt(k / P_target) = sqrt(k * baseReserve / quoteReserve * 10000 / (10000 + bps))
|
|
86
|
+
//
|
|
87
|
+
// 简化:baseReserve_new = sqrt(k * 10000 / ((10000 + bps) * P))
|
|
88
|
+
// = sqrt(k * baseReserve * 10000 / (quoteReserve * (10000 + bps)))
|
|
89
|
+
|
|
90
|
+
const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
|
|
91
|
+
outputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
|
|
92
|
+
inputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
|
|
93
|
+
|
|
94
|
+
// baseReserve_new = baseReserve * sqrt(10000 / (10000+bps))
|
|
95
|
+
// quoteReserve_new = k / baseReserve_new = quoteReserve * sqrt((10000+bps) / 10000)
|
|
96
|
+
// amountOut(base) = baseReserve - baseReserve_new
|
|
97
|
+
// amountIn(quote, not fee-adjusted) = quoteReserve_new - quoteReserve
|
|
98
|
+
const baseReserveNew = baseReserve * bigIntSqrt(10000n * PRECISION) / bigIntSqrt((10000n + bps) * PRECISION);
|
|
99
|
+
const quoteReserveNew = quoteReserve * bigIntSqrt((10000n + bps) * PRECISION) / bigIntSqrt(10000n * PRECISION);
|
|
100
|
+
amountOutWei = baseReserve - baseReserveNew;
|
|
101
|
+
amountInWei = quoteReserveNew - quoteReserve;
|
|
102
|
+
|
|
103
|
+
} else {
|
|
104
|
+
// 卖出 base token → 价格下跌 → 输出 quote token
|
|
105
|
+
// quote token 从池子中减少
|
|
106
|
+
|
|
107
|
+
const [baseReserve, quoteReserve] = baseIsToken0 ? [R0, R1] : [R1, R0];
|
|
108
|
+
outputDecimals = baseIsToken0 ? token1Decimals : token0Decimals;
|
|
109
|
+
inputDecimals = baseIsToken0 ? token0Decimals : token1Decimals;
|
|
110
|
+
|
|
111
|
+
// quoteReserve_new = quoteReserve * sqrt((10000-bps) / 10000)
|
|
112
|
+
// baseReserve_new = k / quoteReserve_new = baseReserve * sqrt(10000 / (10000-bps))
|
|
113
|
+
// amountOut(quote) = quoteReserve - quoteReserve_new
|
|
114
|
+
// amountIn(base, not fee-adjusted) = baseReserve_new - baseReserve
|
|
115
|
+
if (bps >= 10000n) {
|
|
116
|
+
// 100% 偏移 → 所有 quote token;amountIn 取一个有限上界(baseReserve)
|
|
117
|
+
amountOutWei = quoteReserve;
|
|
118
|
+
amountInWei = baseReserve; // 极端情况下池子 base 耗尽
|
|
119
|
+
} else {
|
|
120
|
+
const quoteReserveNew = quoteReserve * bigIntSqrt((10000n - bps) * PRECISION) / bigIntSqrt(10000n * PRECISION);
|
|
121
|
+
const baseReserveNew = baseReserve * bigIntSqrt(10000n * PRECISION) / bigIntSqrt((10000n - bps) * PRECISION);
|
|
122
|
+
amountOutWei = quoteReserve - quoteReserveNew;
|
|
123
|
+
amountInWei = baseReserveNew - baseReserve;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const amountOut = Number(amountOutWei) / Math.pow(10, outputDecimals);
|
|
128
|
+
const amountIn = Number(amountInWei) / Math.pow(10, inputDecimals);
|
|
129
|
+
|
|
130
|
+
// 计算目标价(quote per base,UI 单位)
|
|
131
|
+
// 推到目标价后的池子状态:基于 P_target = P * (1 ± bps/10000)
|
|
132
|
+
const baseDec = baseIsToken0 ? token0Decimals : token1Decimals;
|
|
133
|
+
const quoteDec = baseIsToken0 ? token1Decimals : token0Decimals;
|
|
134
|
+
const baseReserveUi = Number(baseIsToken0 ? R0 : R1) / Math.pow(10, baseDec);
|
|
135
|
+
const quoteReserveUi = Number(baseIsToken0 ? R1 : R0) / Math.pow(10, quoteDec);
|
|
136
|
+
const midPrice = quoteReserveUi / baseReserveUi;
|
|
137
|
+
const bpsNum = Number(targetBps);
|
|
138
|
+
const targetPrice = isBuy
|
|
139
|
+
? midPrice * (10000 + bpsNum) / 10000
|
|
140
|
+
: midPrice * (10000 - bpsNum) / 10000;
|
|
141
|
+
|
|
142
|
+
return { amountOutWei, amountOut, outputDecimals, amountInWei, amountIn, inputDecimals, targetPrice };
|
|
143
|
+
}
|
package/src/quote/depth/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* 深度计算模块(Solana,tier 版)
|
|
3
3
|
*
|
|
4
4
|
* port 自 SUI `ttd-sui-common/src/quote/depth/index.ts`(同源 BSC),差异(有理由的偏离):
|
|
5
|
-
* ·
|
|
6
|
-
* AMM/
|
|
5
|
+
* · **CLMM + AMM**:三个 CLMM(Raydium/Orca/ByReal)全是 Uniswap V3 风格集中流动性 X64 → buildClmmDepth;
|
|
6
|
+
* 两个 AMM(Raydium AMM/CPMM)是 x·y=k 恒定乘积 → buildAmmDepth(reserve 上闭式);
|
|
7
|
+
* DAMMv2(CP-AMM 单 sqrtPrice 区间)复用 buildClmmDepth(ticks=[]);DLMM bin 深度待做。
|
|
7
8
|
* · **X64**:调用 X64 版 clmm_depth_calculator(见同目录,逐字照搬 SUI)。
|
|
8
9
|
* · **tick + sqrtPriceX64 由调用方物化**:各 DEX 把本地 tickArray cache 的已初始化 tick 配
|
|
9
10
|
* **共享 TickMath**(`../tick/clmm_tick_math` getSqrtPriceX64FromTick)算的 sqrtPriceX64,
|
|
@@ -24,9 +25,12 @@ import {
|
|
|
24
25
|
} from '@clonegod/ttd-core/dist';
|
|
25
26
|
import { resolveTradeDirection } from '../../utils/trade_direction';
|
|
26
27
|
import { DepthTick, calculateClmmDepth, priceFromSqrtX64 } from './clmm_depth_calculator';
|
|
28
|
+
import { calculateAmmDepth } from './amm_depth_calculator';
|
|
27
29
|
|
|
28
30
|
export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt, swapExactInClmm } from './clmm_depth_calculator';
|
|
29
31
|
export type { DepthTick, ClmmDepthInput, DepthResult, SwapExactInInput, SwapExactInResult } from './clmm_depth_calculator';
|
|
32
|
+
export { calculateAmmDepth } from './amm_depth_calculator';
|
|
33
|
+
export type { AmmDepthInput, AmmDepthResult } from './amm_depth_calculator';
|
|
30
34
|
|
|
31
35
|
// ========== 启动日志一次性输出 ==========
|
|
32
36
|
let _depthPctLogged = false;
|
|
@@ -40,8 +44,10 @@ function logDepthLevelsOnce(): void {
|
|
|
40
44
|
/**
|
|
41
45
|
* 池子曲线纯输入 → 用户实付(含费):
|
|
42
46
|
* amountInGross = amountInNet / (1 - feeRate);feeAmount = amountInGross - amountInNet
|
|
47
|
+
*
|
|
48
|
+
* 导出供 DEX 专有 depth 构建复用(如 DLMM 离散 bin 深度,在各自包内但 tier 语义统一)。
|
|
43
49
|
*/
|
|
44
|
-
function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: number; feeAmount: number } {
|
|
50
|
+
export function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: number; feeAmount: number } {
|
|
45
51
|
if (feeRateBps <= 0 || amountInNet <= 0) return { amountInGross: amountInNet, feeAmount: 0 };
|
|
46
52
|
const feeRatio = feeRateBps / 10000;
|
|
47
53
|
const amountInGross = amountInNet / (1 - feeRatio);
|
|
@@ -49,7 +55,8 @@ function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: n
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
// ========== 把多档 tier 装配成 QuoteEntry(主档镜像顶层)==========
|
|
52
|
-
|
|
58
|
+
/** 导出供 DEX 专有 depth 构建复用(tier 语义统一)。 */
|
|
59
|
+
export function assembleEntry(tiers: QuoteTier[]): QuoteEntry {
|
|
53
60
|
const def = tiers.find(t => t.pct === DEFAULT_TIER_PCT) ?? tiers[0];
|
|
54
61
|
return {
|
|
55
62
|
price: def.price, amount: def.amount, amount_in: def.amount_in, amount_in_usd: def.amount_in_usd,
|
|
@@ -165,3 +172,101 @@ export function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | u
|
|
|
165
172
|
return undefined;
|
|
166
173
|
}
|
|
167
174
|
}
|
|
175
|
+
|
|
176
|
+
// ========== AMM 通用深度构建 ==========
|
|
177
|
+
//
|
|
178
|
+
// port 自 BSC `arb-chain-bsc/.../quote/depth/index.ts` buildAmmDepth;差异(有理由的偏离):
|
|
179
|
+
// · **baseIsToken0 由调用方传**(用链上 mintA/mintB 判定),不在此用地址 lowercase 比较——
|
|
180
|
+
// Solana 地址 base58 大小写敏感,不可 lowercase(与 buildClmmDepth 一致)。
|
|
181
|
+
// · reserve0/reserve1 = 链上 token0(mintA)/token1(mintB) 的 reserve(wei string)。
|
|
182
|
+
|
|
183
|
+
export interface BuildAmmDepthInput {
|
|
184
|
+
poolInfo: StandardPoolInfoType;
|
|
185
|
+
/** token0(=链上 mintA) 的 reserve(wei string) */
|
|
186
|
+
reserve0: string;
|
|
187
|
+
/** token1(=链上 mintB) 的 reserve(wei string) */
|
|
188
|
+
reserve1: string;
|
|
189
|
+
/** base token 是否为链上 token0(=mintA);调用方用链上 mint 判定(base58 大小写敏感) */
|
|
190
|
+
baseIsToken0: boolean;
|
|
191
|
+
basePriceUsd: number;
|
|
192
|
+
quotePriceUsd: number;
|
|
193
|
+
/**
|
|
194
|
+
* 池子 effective fee rate (bps),必填。各 DEX 从配置 fee_rate(market-data 强制整数 ≥1)传入。
|
|
195
|
+
* 错则 verify 跨源 diff 系统性偏移会暴露。
|
|
196
|
+
*/
|
|
197
|
+
feeRateBps: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 通用 AMM 深度构建:多档(默认 5 档)ask/bid 深度,从内存 reserve 零额外 RPC 计算(x·y=k 闭式)。
|
|
202
|
+
*/
|
|
203
|
+
export function buildAmmDepth(input: BuildAmmDepthInput): QuoteDepthOutput | undefined {
|
|
204
|
+
logDepthLevelsOnce();
|
|
205
|
+
const pctLevels = getDepthPricePctLevels();
|
|
206
|
+
if (pctLevels.length === 0) return undefined;
|
|
207
|
+
|
|
208
|
+
const { poolInfo, reserve0, reserve1, baseIsToken0, basePriceUsd, quotePriceUsd, feeRateBps } = input;
|
|
209
|
+
if (feeRateBps == null || feeRateBps < 0) {
|
|
210
|
+
log_debug(`[Depth] ${poolInfo.pool_name} AMM: invalid feeRateBps=${feeRateBps}, skip`, '');
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const dir = resolveTradeDirection(poolInfo, true);
|
|
216
|
+
const baseToken = dir.baseToken;
|
|
217
|
+
const quoteToken = dir.quoteToken;
|
|
218
|
+
|
|
219
|
+
// 中间价:从 reserve 比值算(UI 单位)
|
|
220
|
+
const baseReserveUi = Number(BigInt(baseIsToken0 ? reserve0 : reserve1)) / Math.pow(10, baseToken.decimals);
|
|
221
|
+
const quoteReserveUi = Number(BigInt(baseIsToken0 ? reserve1 : reserve0)) / Math.pow(10, quoteToken.decimals);
|
|
222
|
+
const midPrice = quoteReserveUi / baseReserveUi;
|
|
223
|
+
|
|
224
|
+
const askTiers: QuoteTier[] = [];
|
|
225
|
+
const bidTiers: QuoteTier[] = [];
|
|
226
|
+
|
|
227
|
+
for (const pct of pctLevels) {
|
|
228
|
+
const bps = Math.round(pct * 100);
|
|
229
|
+
|
|
230
|
+
const askResult = calculateAmmDepth({
|
|
231
|
+
reserve0, reserve1, targetBps: bps, isBuy: true, baseIsToken0,
|
|
232
|
+
token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
|
|
233
|
+
token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
|
|
234
|
+
});
|
|
235
|
+
const bidResult = calculateAmmDepth({
|
|
236
|
+
reserve0, reserve1, targetBps: bps, isBuy: false, baseIsToken0,
|
|
237
|
+
token0Decimals: baseIsToken0 ? baseToken.decimals : quoteToken.decimals,
|
|
238
|
+
token1Decimals: baseIsToken0 ? quoteToken.decimals : baseToken.decimals,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Ask: amount_in 是 quote token;Bid: amount_in 是 base token
|
|
242
|
+
const askGross = grossUpFee(askResult.amountIn, feeRateBps);
|
|
243
|
+
const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
|
|
244
|
+
|
|
245
|
+
// 【强制·原则2】tier.price = effective avg price including fee = amount_in_gross / amount_out
|
|
246
|
+
// 跟 chain swap exec_price 严格同语义;禁止改回 marginal / 不含 fee 形态(G4 事故根因)。
|
|
247
|
+
const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askResult.targetPrice;
|
|
248
|
+
const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidResult.targetPrice;
|
|
249
|
+
|
|
250
|
+
askTiers.push({
|
|
251
|
+
pct, price: askEffPrice, amount: askResult.amountOut,
|
|
252
|
+
amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
|
|
253
|
+
fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd,
|
|
254
|
+
});
|
|
255
|
+
bidTiers.push({
|
|
256
|
+
pct, price: bidEffPrice, amount: bidResult.amountOut,
|
|
257
|
+
amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
|
|
258
|
+
fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
mid_price: midPrice,
|
|
264
|
+
fee_rate_bps: feeRateBps,
|
|
265
|
+
ask: assembleEntry(askTiers),
|
|
266
|
+
bid: assembleEntry(bidTiers),
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
log_debug(`[Depth] ${poolInfo.pool_name} AMM depth failed: ${(error as Error).message}`, '');
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
}
|