@clonegod/ttd-sol-common 2.0.67 → 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.
Files changed (94) hide show
  1. package/dist/appconfig/SolanaQuoteAppConfig.d.ts +9 -0
  2. package/dist/appconfig/SolanaQuoteAppConfig.js +29 -0
  3. package/dist/appconfig/SolanaTradeAppConfig.d.ts +13 -0
  4. package/dist/{config → appconfig}/SolanaTradeAppConfig.js +25 -0
  5. package/dist/appconfig/ensure_core_env.d.ts +1 -0
  6. package/dist/appconfig/ensure_core_env.js +18 -0
  7. package/dist/appconfig/index.d.ts +5 -0
  8. package/dist/appconfig/index.js +21 -0
  9. package/dist/appconfig/sol_dex_env_args.d.ts +5 -0
  10. package/dist/appconfig/sol_dex_env_args.js +29 -0
  11. package/dist/appconfig/sol_env_args.d.ts +17 -0
  12. package/dist/appconfig/sol_env_args.js +37 -0
  13. package/dist/common/get_wallet_token_account.js +13 -16
  14. package/dist/grpc/grpc_provider_registry.d.ts +14 -0
  15. package/dist/grpc/grpc_provider_registry.js +70 -0
  16. package/dist/grpc/index.d.ts +1 -0
  17. package/dist/grpc/index.js +17 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +3 -0
  20. package/dist/quote/abstract_dex_quote.d.ts +68 -0
  21. package/dist/quote/abstract_dex_quote.js +208 -0
  22. package/dist/quote/chain_ops.d.ts +18 -0
  23. package/dist/quote/chain_ops.js +66 -0
  24. package/dist/quote/depth/amm_depth_calculator.d.ts +19 -0
  25. package/dist/quote/depth/amm_depth_calculator.js +55 -0
  26. package/dist/quote/depth/clmm_depth_calculator.d.ts +42 -0
  27. package/dist/quote/depth/clmm_depth_calculator.js +173 -0
  28. package/dist/quote/depth/index.d.ts +37 -0
  29. package/dist/quote/depth/index.js +164 -0
  30. package/dist/quote/index.d.ts +9 -0
  31. package/dist/quote/index.js +9 -0
  32. package/dist/quote/pool_event.d.ts +20 -0
  33. package/dist/quote/pool_event.js +22 -0
  34. package/dist/quote/pool_subscription_registry.d.ts +4 -0
  35. package/dist/quote/pool_subscription_registry.js +62 -0
  36. package/dist/quote/quote_amount.d.ts +4 -0
  37. package/dist/quote/quote_amount.js +24 -0
  38. package/dist/quote/quote_trace.d.ts +16 -0
  39. package/dist/quote/quote_trace.js +40 -0
  40. package/dist/quote/tick/clmm_tick_math.d.ts +5 -0
  41. package/dist/quote/tick/clmm_tick_math.js +61 -0
  42. package/dist/quote/tick/index.d.ts +1 -0
  43. package/dist/{config → quote/tick}/index.js +1 -1
  44. package/dist/quote/verify/index.d.ts +1 -0
  45. package/dist/quote/verify/index.js +17 -0
  46. package/dist/quote/verify/quote_price_verify.d.ts +30 -0
  47. package/dist/quote/verify/quote_price_verify.js +247 -0
  48. package/dist/trade/index.d.ts +0 -1
  49. package/dist/trade/index.js +0 -1
  50. package/dist/trade/tx_builder.d.ts +1 -1
  51. package/dist/trade/tx_result_parse.js +1 -0
  52. package/dist/types/index.d.ts +9 -1
  53. package/dist/types/index.js +2 -0
  54. package/dist/utils/index.d.ts +1 -0
  55. package/dist/utils/index.js +17 -0
  56. package/dist/utils/trade_direction.d.ts +14 -0
  57. package/dist/utils/trade_direction.js +23 -0
  58. package/package.json +4 -4
  59. package/src/appconfig/SolanaQuoteAppConfig.ts +55 -0
  60. package/src/appconfig/SolanaTradeAppConfig.ts +117 -0
  61. package/src/appconfig/ensure_core_env.ts +28 -0
  62. package/src/appconfig/index.ts +5 -0
  63. package/src/appconfig/sol_dex_env_args.ts +52 -0
  64. package/src/appconfig/sol_env_args.ts +79 -0
  65. package/src/common/get_wallet_token_account.ts +27 -33
  66. package/src/grpc/grpc_provider_registry.ts +103 -0
  67. package/src/grpc/index.ts +1 -0
  68. package/src/index.ts +3 -0
  69. package/src/quote/abstract_dex_quote.ts +337 -0
  70. package/src/quote/chain_ops.ts +91 -0
  71. package/src/quote/depth/amm_depth_calculator.ts +143 -0
  72. package/src/quote/depth/clmm_depth_calculator.ts +321 -0
  73. package/src/quote/depth/index.ts +272 -0
  74. package/src/quote/index.ts +9 -0
  75. package/src/quote/pool_event.ts +82 -0
  76. package/src/quote/pool_subscription_registry.ts +81 -0
  77. package/src/quote/quote_amount.ts +37 -0
  78. package/src/quote/quote_trace.ts +56 -0
  79. package/src/quote/tick/clmm_tick_math.ts +77 -0
  80. package/src/quote/tick/index.ts +1 -0
  81. package/src/quote/verify/index.ts +1 -0
  82. package/src/quote/verify/quote_price_verify.ts +508 -0
  83. package/src/trade/index.ts +0 -1
  84. package/src/trade/tx_builder.ts +1 -1
  85. package/src/trade/tx_result_parse.ts +1 -0
  86. package/src/types/index.ts +20 -2
  87. package/src/utils/index.ts +1 -0
  88. package/src/utils/trade_direction.ts +68 -0
  89. package/dist/config/SolanaTradeAppConfig.d.ts +0 -10
  90. package/dist/config/index.d.ts +0 -1
  91. package/dist/trade/SolanaTradeAppConfig.d.ts +0 -8
  92. package/dist/trade/SolanaTradeAppConfig.js +0 -26
  93. package/src/config/SolanaTradeAppConfig.ts +0 -70
  94. package/src/config/index.ts +0 -2
@@ -0,0 +1,321 @@
1
+ /**
2
+ * CLMM 深度计算器(SUI X64 版)
3
+ *
4
+ * port 自 BSC `ttd-bsc-common/src/quote/depth/clmm_depth_calculator.ts`,差异(有理由的偏离):
5
+ * · **sqrtPrice 表示 Q96 → Q64**:SUI CLMM(Cetus/Bluefin/Magma/Momentum,integer-mate V3 家族)用 X64
6
+ * 定点(sqrtPriceX64 = sqrt(price) × 2^64),故常量 Q96 改 Q64,amount0/1 公式同构换底。
7
+ * · **不内建 TickMath**:各 DEX 的 tick→sqrtPrice 由其 SDK 提供(Cetus TickMath.tickIndexToSqrtPriceX64 等),
8
+ * 调用方把已算好的 sqrtPriceX64 随 tick 传入 → 本模块零 SDK 依赖、零 TickMath 移植。
9
+ *
10
+ * 给定价格偏移(bps),计算把现价推到 mid × (1 ± bps/10000) 范围内可成交的 token 数量。
11
+ * 复用与 swap 引擎相同的 CLMM 数学,但终止条件不同:到达目标 sqrtPrice 即停(非 amountIn 用完)。
12
+ *
13
+ * 重要:深度(output amount)与 fee 无关 —— fee 只影响需要多少 input,不影响给定价格范围能产出多少 output。
14
+ * fee gross-up 在 buildClmmDepth 层做(与 BSC 一致)。
15
+ */
16
+
17
+ const Q64 = 1n << 64n;
18
+
19
+ // ========== 深度计算核心 ==========
20
+
21
+ /** 已初始化 tick(调用方从 SuiClmmTickCache 物化,sqrtPriceX64 由 DEX SDK TickMath 算)。 */
22
+ export interface DepthTick {
23
+ index: number;
24
+ /** 有符号 net liquidity(正=position 下界,负=上界) */
25
+ liquidityNet: bigint;
26
+ /** 该 tick 的 sqrtPriceX64(Q64.64) */
27
+ sqrtPriceX64: bigint;
28
+ }
29
+
30
+ export interface ClmmDepthInput {
31
+ /** 当前 sqrtPriceX64(Q64.64) */
32
+ sqrtPriceX64: bigint;
33
+ currentTick: number;
34
+ /** 当前 active liquidity */
35
+ liquidity: bigint;
36
+ /** 全量已初始化 tick(含 sqrtPriceX64,无需预排序) */
37
+ ticks: DepthTick[];
38
+ /** swap 方向(aToB / zeroForOne,由 resolveTradeDirection 决定) */
39
+ zeroForOne: boolean;
40
+ /** 目标价格偏移(如 10, 20) */
41
+ targetBps: number;
42
+ inputDecimals: number;
43
+ outputDecimals: number;
44
+ }
45
+
46
+ export interface DepthResult {
47
+ amountInWei: bigint;
48
+ amountIn: number;
49
+ amountOutWei: bigint;
50
+ amountOut: number;
51
+ currentTick: number;
52
+ targetTick: number;
53
+ /** 目标 sqrtPriceX64(Q64.64,用于精确反算 targetPrice) */
54
+ targetSqrtPriceX64: bigint;
55
+ }
56
+
57
+ /**
58
+ * 计算 CLMM 池子在给定价格偏移范围内的深度。
59
+ * @returns 在 targetBps 价格范围内可获得的 output token 数量(不含 fee)
60
+ */
61
+ export function calculateClmmDepth(input: ClmmDepthInput): DepthResult {
62
+ const { currentTick, zeroForOne, targetBps, inputDecimals, outputDecimals } = input;
63
+
64
+ const currentSqrtPriceX64 = input.sqrtPriceX64;
65
+ let liquidity = input.liquidity;
66
+
67
+ if (liquidity <= 0n) {
68
+ return { amountInWei: 0n, amountIn: 0, amountOutWei: 0n, amountOut: 0, currentTick, targetTick: currentTick, targetSqrtPriceX64: currentSqrtPriceX64 };
69
+ }
70
+
71
+ // 1. 目标 sqrtPriceX64
72
+ const targetSqrtPriceX64 = computeTargetSqrtX64(currentSqrtPriceX64, targetBps, zeroForOne);
73
+
74
+ // 2. 现价 → 目标价之间的已初始化 tick(按方向排序)
75
+ const ticksToTraverse = getTicksBetween(input.ticks, currentTick, zeroForOne);
76
+
77
+ // 3. 逐段累加输入量和输出量
78
+ let totalInput = 0n;
79
+ let totalOutput = 0n;
80
+ let sqrtPriceCursor = currentSqrtPriceX64;
81
+
82
+ for (const tick of ticksToTraverse) {
83
+ const sqrtPriceAtTick = tick.sqrtPriceX64;
84
+
85
+ // 已越过目标价 → 停(本段在步骤 4 收尾)
86
+ if (zeroForOne && sqrtPriceAtTick <= targetSqrtPriceX64) break;
87
+ if (!zeroForOne && sqrtPriceAtTick >= targetSqrtPriceX64) break;
88
+
89
+ if (liquidity > 0n) {
90
+ totalInput += calcInputForRange(sqrtPriceCursor, sqrtPriceAtTick, liquidity, zeroForOne);
91
+ totalOutput += calcOutputForRange(sqrtPriceCursor, sqrtPriceAtTick, liquidity, zeroForOne);
92
+ }
93
+
94
+ // 跨越 tick:有符号 net 直接加减(zeroForOne 价格下行 -net,反向 +net)
95
+ let liquidityNet = tick.liquidityNet;
96
+ if (zeroForOne) liquidityNet = -liquidityNet;
97
+ liquidity = liquidity + liquidityNet;
98
+ if (liquidity < 0n) liquidity = 0n;
99
+
100
+ sqrtPriceCursor = sqrtPriceAtTick;
101
+ }
102
+
103
+ // 4. 末段:最后一个 tick → 目标价
104
+ if (liquidity > 0n && sqrtPriceCursor !== targetSqrtPriceX64) {
105
+ totalInput += calcInputForRange(sqrtPriceCursor, targetSqrtPriceX64, liquidity, zeroForOne);
106
+ totalOutput += calcOutputForRange(sqrtPriceCursor, targetSqrtPriceX64, liquidity, zeroForOne);
107
+ }
108
+
109
+ const amountIn = Number(totalInput) / Math.pow(10, inputDecimals);
110
+ const amountOut = Number(totalOutput) / Math.pow(10, outputDecimals);
111
+
112
+ // 5. 从 targetSqrtPriceX64 反推 targetTick(仅 tick_move 展示用)
113
+ // tick ≈ 2 · log(sqrtPriceX64 / 2^64) / log(1.0001)
114
+ const sqrtPriceFloat = Number(targetSqrtPriceX64) / Number(Q64);
115
+ const targetTick = Math.floor(Math.log(sqrtPriceFloat * sqrtPriceFloat) / Math.log(1.0001));
116
+
117
+ return { amountInWei: totalInput, amountIn, amountOutWei: totalOutput, amountOut, currentTick, targetTick, targetSqrtPriceX64 };
118
+ }
119
+
120
+ /**
121
+ * 从 sqrtPriceX64 + decimals 反算价格(quote per base,UI 单位)。
122
+ *
123
+ * sqrtPriceX64 = sqrt(rawToken1 / rawToken0) × 2^64
124
+ * priceToken1PerToken0(raw) = (sqrtPriceX64 / 2^64)^2
125
+ * UI price = quote/base = (raw 比值) × 10^(baseDecimals - quoteDecimals)
126
+ * base 为 token1 时取倒数。
127
+ */
128
+ export function priceFromSqrtX64(
129
+ sqrtPriceX64: bigint,
130
+ baseDecimals: number,
131
+ quoteDecimals: number,
132
+ baseIsToken0: boolean,
133
+ ): number {
134
+ const sp = Number(sqrtPriceX64) / Number(Q64);
135
+ const priceToken1PerToken0 = sp * sp;
136
+ if (baseIsToken0) {
137
+ return priceToken1PerToken0 * Math.pow(10, baseDecimals - quoteDecimals);
138
+ } else {
139
+ return (1 / priceToken1PerToken0) * Math.pow(10, baseDecimals - quoteDecimals);
140
+ }
141
+ }
142
+
143
+ // ========== 内部工具 ==========
144
+
145
+ /**
146
+ * 计算目标 sqrtPriceX64:
147
+ * price_new = price × (1 ± bps/10000) → sqrtPrice_new = sqrtPrice × sqrt(1 ± bps/10000)
148
+ * 用高精度整数 sqrt(与表示无关,故 X64/X96 通用)。
149
+ */
150
+ export function computeTargetSqrtX64(currentSqrtPriceX64: bigint, bps: number, zeroForOne: boolean): bigint {
151
+ const PRECISION = 10n ** 18n;
152
+ const bpsBigInt = BigInt(bps);
153
+ // zeroForOne 价格下降 → 10000 - bps;反向 → 10000 + bps
154
+ const factor = zeroForOne ? 10000n - bpsBigInt : 10000n + bpsBigInt;
155
+ const sqrtFactor = bigIntSqrt(factor * PRECISION);
156
+ const sqrtBase = bigIntSqrt(10000n * PRECISION);
157
+ return currentSqrtPriceX64 * sqrtFactor / sqrtBase;
158
+ }
159
+
160
+ /** 整数平方根(Newton 法)。 */
161
+ export function bigIntSqrt(n: bigint): bigint {
162
+ if (n < 0n) throw new Error('sqrt of negative');
163
+ if (n === 0n) return 0n;
164
+ if (n <= 3n) return 1n;
165
+ let x = n;
166
+ let y = (x + 1n) / 2n;
167
+ while (y < x) {
168
+ x = y;
169
+ y = (x + n / x) / 2n;
170
+ }
171
+ return x;
172
+ }
173
+
174
+ /** 现价 tick 与目标价之间的已初始化 tick(按 swap 方向排序)。 */
175
+ function getTicksBetween(ticks: DepthTick[], currentTick: number, zeroForOne: boolean): DepthTick[] {
176
+ if (zeroForOne) {
177
+ // 价格下降:tick ≤ currentTick,降序(从现价往下走)
178
+ return ticks.filter(t => t.index <= currentTick).sort((a, b) => b.index - a.index);
179
+ } else {
180
+ // 价格上升:tick > currentTick,升序
181
+ return ticks.filter(t => t.index > currentTick).sort((a, b) => a.index - b.index);
182
+ }
183
+ }
184
+
185
+ // ========== amountIn 驱动的跨 tick swap(SDK-free,供 quoteV2 真跨 tick)==========
186
+
187
+ /**
188
+ * 给定一段流动性内,把 amountIn 全部吃进去后到达的 nextSqrtPriceX64(canonical Uniswap V3,纯 bigint)。
189
+ * zeroForOne=true(输入 token0,价格下行):
190
+ * sqrtNext = L·sqrt·Q64 / (L·Q64 + amountIn·sqrt)
191
+ * zeroForOne=false(输入 token1,价格上行):
192
+ * sqrtNext = sqrt + amountIn·Q64 / L
193
+ * 与 byreal/raydium SqrtPriceMath.getNextSqrtPriceX64FromInput 同公式(此处用 amountIn 已扣 fee 的净额)。
194
+ */
195
+ function nextSqrtFromAmountIn(sqrtPriceX64: bigint, liquidity: bigint, amountIn: bigint, zeroForOne: boolean): bigint {
196
+ if (amountIn <= 0n || liquidity <= 0n) return sqrtPriceX64;
197
+ if (zeroForOne) {
198
+ const numerator = liquidity * sqrtPriceX64 * Q64;
199
+ const denominator = liquidity * Q64 + amountIn * sqrtPriceX64;
200
+ return numerator / denominator;
201
+ } else {
202
+ return sqrtPriceX64 + (amountIn * Q64) / liquidity;
203
+ }
204
+ }
205
+
206
+ export interface SwapExactInInput {
207
+ sqrtPriceX64: bigint;
208
+ currentTick: number;
209
+ liquidity: bigint;
210
+ ticks: DepthTick[];
211
+ zeroForOne: boolean;
212
+ /** 净输入量(已扣 fee 的链上整数 wei) */
213
+ amountInNetWei: bigint;
214
+ }
215
+
216
+ export interface SwapExactInResult {
217
+ amountInConsumedWei: bigint;
218
+ amountOutWei: bigint;
219
+ endSqrtPriceX64: bigint;
220
+ }
221
+
222
+ /**
223
+ * amountIn 驱动的跨 tick swap:把 amountInNetWei 顺方向吃进,逐 tick 累加 output(SDK-free,与深度同一套 X64 数学)。
224
+ * 终止条件:amountIn 用完(区别于 calculateClmmDepth 的"到达目标价")。流动性耗尽(无更多 tick)则到此为止。
225
+ *
226
+ * 与链上 swap 引擎同源:calcInputForRange/calcOutputForRange 段内累加 + 有符号 liquidityNet 跨 tick + 末段
227
+ * nextSqrtFromAmountIn 反算 partial。**fee 在调用方扣**(传净额),与 buildClmmDepth gross-up 口径一致。
228
+ */
229
+ export function swapExactInClmm(input: SwapExactInInput): SwapExactInResult {
230
+ const { currentTick, zeroForOne } = input;
231
+ let liquidity = input.liquidity;
232
+ let sqrtCursor = input.sqrtPriceX64;
233
+ let remaining = input.amountInNetWei;
234
+ let totalOut = 0n;
235
+ let consumed = 0n;
236
+
237
+ if (liquidity <= 0n || remaining <= 0n) {
238
+ return { amountInConsumedWei: 0n, amountOutWei: 0n, endSqrtPriceX64: sqrtCursor };
239
+ }
240
+
241
+ const ticksToTraverse = getTicksBetween(input.ticks, currentTick, zeroForOne);
242
+
243
+ for (const tick of ticksToTraverse) {
244
+ if (remaining <= 0n) break;
245
+ const sqrtAtTick = tick.sqrtPriceX64;
246
+
247
+ if (liquidity > 0n) {
248
+ // 当前段(cursor → tick 边界)最多能吃多少 input
249
+ const segMaxIn = calcInputForRange(sqrtCursor, sqrtAtTick, liquidity, zeroForOne);
250
+ if (remaining < segMaxIn) {
251
+ // input 在本段内耗尽 → 反算 partial nextSqrt + partial output
252
+ const nextSqrt = nextSqrtFromAmountIn(sqrtCursor, liquidity, remaining, zeroForOne);
253
+ totalOut += calcOutputForRange(sqrtCursor, nextSqrt, liquidity, zeroForOne);
254
+ consumed += remaining;
255
+ remaining = 0n;
256
+ sqrtCursor = nextSqrt;
257
+ break;
258
+ }
259
+ // 吃满本段到 tick 边界
260
+ totalOut += calcOutputForRange(sqrtCursor, sqrtAtTick, liquidity, zeroForOne);
261
+ consumed += segMaxIn;
262
+ remaining -= segMaxIn;
263
+ }
264
+ sqrtCursor = sqrtAtTick;
265
+
266
+ // 跨 tick:有符号 net(zeroForOne 价格下行取负)
267
+ let liquidityNet = tick.liquidityNet;
268
+ if (zeroForOne) liquidityNet = -liquidityNet;
269
+ liquidity = liquidity + liquidityNet;
270
+ if (liquidity < 0n) liquidity = 0n;
271
+ }
272
+
273
+ // 末段:无更多 tick 但仍有 input 且有流动性 → 在当前流动性内继续吃完剩余(无边界)
274
+ if (remaining > 0n && liquidity > 0n) {
275
+ const nextSqrt = nextSqrtFromAmountIn(sqrtCursor, liquidity, remaining, zeroForOne);
276
+ totalOut += calcOutputForRange(sqrtCursor, nextSqrt, liquidity, zeroForOne);
277
+ consumed += remaining;
278
+ sqrtCursor = nextSqrt;
279
+ remaining = 0n;
280
+ }
281
+
282
+ return { amountInConsumedWei: consumed, amountOutWei: totalOut, endSqrtPriceX64: sqrtCursor };
283
+ }
284
+
285
+ /**
286
+ * 一段价格区间内的输入 token 数量(不含 fee)。
287
+ * zeroForOne=true → 输入 token0 → amount0 公式
288
+ * zeroForOne=false → 输入 token1 → amount1 公式
289
+ */
290
+ function calcInputForRange(sqrtPriceA: bigint, sqrtPriceB: bigint, liquidity: bigint, zeroForOne: boolean): bigint {
291
+ const lower = sqrtPriceA < sqrtPriceB ? sqrtPriceA : sqrtPriceB;
292
+ const upper = sqrtPriceA < sqrtPriceB ? sqrtPriceB : sqrtPriceA;
293
+ const diff = upper - lower;
294
+ if (diff === 0n || liquidity === 0n) return 0n;
295
+ if (zeroForOne) {
296
+ // amount0 = L · (upper - lower) · 2^64 / (lower · upper)
297
+ return liquidity * diff * Q64 / (lower * upper);
298
+ } else {
299
+ // amount1 = L · (upper - lower) / 2^64
300
+ return liquidity * diff / Q64;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * 一段价格区间内的输出 token 数量。
306
+ * zeroForOne=true → 输出 token1 → amount1 公式
307
+ * zeroForOne=false → 输出 token0 → amount0 公式
308
+ */
309
+ function calcOutputForRange(sqrtPriceA: bigint, sqrtPriceB: bigint, liquidity: bigint, zeroForOne: boolean): bigint {
310
+ const lower = sqrtPriceA < sqrtPriceB ? sqrtPriceA : sqrtPriceB;
311
+ const upper = sqrtPriceA < sqrtPriceB ? sqrtPriceB : sqrtPriceA;
312
+ const diff = upper - lower;
313
+ if (diff === 0n || liquidity === 0n) return 0n;
314
+ if (zeroForOne) {
315
+ // 输出 token1: amount1 = L · (upper - lower) / 2^64
316
+ return liquidity * diff / Q64;
317
+ } else {
318
+ // 输出 token0: amount0 = L · (upper - lower) · 2^64 / (lower · upper)
319
+ return liquidity * diff * Q64 / (lower * upper);
320
+ }
321
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * 深度计算模块(Solana,tier 版)
3
+ *
4
+ * port 自 SUI `ttd-sui-common/src/quote/depth/index.ts`(同源 BSC),差异(有理由的偏离):
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 深度待做。
8
+ * · **X64**:调用 X64 版 clmm_depth_calculator(见同目录,逐字照搬 SUI)。
9
+ * · **tick + sqrtPriceX64 由调用方物化**:各 DEX 把本地 tickArray cache 的已初始化 tick 配
10
+ * **共享 TickMath**(`../tick/clmm_tick_math` getSqrtPriceX64FromTick)算的 sqrtPriceX64,
11
+ * 组装成 DepthTick[] 传入 —— 三个 CLMM 同源 Uniswap V3,故共享同一份 TickMath。
12
+ * · **方向**:调用方传 baseIsToken0(用链上 mintA/mintB 判定 base 是否为 token0=mintA),
13
+ * ask(买 base,输入 quote)zeroForOne = !baseIsToken0;bid(卖 base,输入 base)zeroForOne = baseIsToken0。
14
+ *
15
+ * 输出语义(与 BSC/SUI 一致):
16
+ * amount = 推到 target price 时获得的 base token(不受 fee 影响)
17
+ * amount_in = 推到 target price 时需投入的 quote token(含费 gross up)
18
+ * fee = amount_in × fee_rate_bps/10000
19
+ */
20
+
21
+ import { log_debug, log_info, StandardPoolInfoType } from '@clonegod/ttd-core/dist';
22
+ import {
23
+ QuoteTier, QuoteEntry, QuoteDepthOutput,
24
+ getDepthPricePctLevels, DEFAULT_TIER_PCT,
25
+ } from '@clonegod/ttd-core/dist';
26
+ import { resolveTradeDirection } from '../../utils/trade_direction';
27
+ import { DepthTick, calculateClmmDepth, priceFromSqrtX64 } from './clmm_depth_calculator';
28
+ import { calculateAmmDepth } from './amm_depth_calculator';
29
+
30
+ export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt, swapExactInClmm } from './clmm_depth_calculator';
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';
34
+
35
+ // ========== 启动日志一次性输出 ==========
36
+ let _depthPctLogged = false;
37
+ function logDepthLevelsOnce(): void {
38
+ if (_depthPctLogged) return;
39
+ _depthPctLogged = true;
40
+ log_info(`[Depth] pctLevels=${JSON.stringify(getDepthPricePctLevels())}, default=${DEFAULT_TIER_PCT}`);
41
+ }
42
+
43
+ // ========== Fee gross-up ==========
44
+ /**
45
+ * 池子曲线纯输入 → 用户实付(含费):
46
+ * amountInGross = amountInNet / (1 - feeRate);feeAmount = amountInGross - amountInNet
47
+ *
48
+ * 导出供 DEX 专有 depth 构建复用(如 DLMM 离散 bin 深度,在各自包内但 tier 语义统一)。
49
+ */
50
+ export function grossUpFee(amountInNet: number, feeRateBps: number): { amountInGross: number; feeAmount: number } {
51
+ if (feeRateBps <= 0 || amountInNet <= 0) return { amountInGross: amountInNet, feeAmount: 0 };
52
+ const feeRatio = feeRateBps / 10000;
53
+ const amountInGross = amountInNet / (1 - feeRatio);
54
+ return { amountInGross, feeAmount: amountInGross - amountInNet };
55
+ }
56
+
57
+ // ========== 把多档 tier 装配成 QuoteEntry(主档镜像顶层)==========
58
+ /** 导出供 DEX 专有 depth 构建复用(tier 语义统一)。 */
59
+ export function assembleEntry(tiers: QuoteTier[]): QuoteEntry {
60
+ const def = tiers.find(t => t.pct === DEFAULT_TIER_PCT) ?? tiers[0];
61
+ return {
62
+ price: def.price, amount: def.amount, amount_in: def.amount_in, amount_in_usd: def.amount_in_usd,
63
+ fee: def.fee, fee_usd: def.fee_usd, tick_move: def.tick_move, tiers,
64
+ };
65
+ }
66
+
67
+ // ========== CLMM 通用深度构建 ==========
68
+
69
+ export interface BuildClmmDepthInput {
70
+ poolInfo: StandardPoolInfoType;
71
+ poolAddress: string;
72
+ /** 池子动态态(v2 维护的本地 cache 或 v1 现读) */
73
+ poolState: {
74
+ currentSqrtPriceX64: bigint;
75
+ currentTick: number;
76
+ liquidity: bigint;
77
+ /** base token 是否为链上 token0(=mintA);调用方用链上 mint 判定 */
78
+ baseIsToken0: boolean;
79
+ };
80
+ /** 全量已初始化 tick(含 sqrtPriceX64),调用方从本地 tickArray cache + 共享 TickMath 物化 */
81
+ ticks: DepthTick[];
82
+ basePriceUsd: number;
83
+ quotePriceUsd: number;
84
+ /**
85
+ * 池子 effective fee rate (bps),调用方从链上 feeRate 换算(各 DEX 单位不同)。
86
+ * 不接受 fallback 到配置漂移值之外的兜底——错则 verify 跨源 diff 系统性偏移会暴露。
87
+ */
88
+ feeRateBps: number;
89
+ }
90
+
91
+ /**
92
+ * 通用 CLMM 深度构建:多档(默认 5 档)ask/bid 深度,从内存 tick 数据零额外 RPC 计算。
93
+ */
94
+ export function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | undefined {
95
+ logDepthLevelsOnce();
96
+ const pctLevels = getDepthPricePctLevels();
97
+ if (pctLevels.length === 0) return undefined;
98
+
99
+ const { poolInfo, poolState, ticks, basePriceUsd, quotePriceUsd, feeRateBps } = input;
100
+ if (poolState.liquidity <= 0n) return undefined;
101
+ if (feeRateBps == null || feeRateBps < 0) {
102
+ log_debug(`[Depth] ${poolInfo.pool_name} CLMM: invalid feeRateBps=${feeRateBps}, skip`, '');
103
+ return undefined;
104
+ }
105
+
106
+ try {
107
+ const dir = resolveTradeDirection(poolInfo, true);
108
+ const baseToken = dir.baseToken;
109
+ const quoteToken = dir.quoteToken;
110
+ const baseIsToken0 = poolState.baseIsToken0;
111
+ // ask 买 base(输入 quote):quote 为 token0 ⟺ base 为 token1 ⟺ !baseIsToken0
112
+ const askZeroForOne = !baseIsToken0;
113
+ // bid 卖 base(输入 base):base 为 token0 ⟺ baseIsToken0
114
+ const bidZeroForOne = baseIsToken0;
115
+
116
+ const midPrice = priceFromSqrtX64(poolState.currentSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
117
+
118
+ const askTiers: QuoteTier[] = [];
119
+ const bidTiers: QuoteTier[] = [];
120
+
121
+ for (const pct of pctLevels) {
122
+ const bps = Math.round(pct * 100); // 0.1% → 10bp
123
+
124
+ const askResult = calculateClmmDepth({
125
+ sqrtPriceX64: poolState.currentSqrtPriceX64, currentTick: poolState.currentTick,
126
+ liquidity: poolState.liquidity, ticks, zeroForOne: askZeroForOne, targetBps: bps,
127
+ inputDecimals: quoteToken.decimals, outputDecimals: baseToken.decimals,
128
+ });
129
+ const bidResult = calculateClmmDepth({
130
+ sqrtPriceX64: poolState.currentSqrtPriceX64, currentTick: poolState.currentTick,
131
+ liquidity: poolState.liquidity, ticks, zeroForOne: bidZeroForOne, targetBps: bps,
132
+ inputDecimals: baseToken.decimals, outputDecimals: quoteToken.decimals,
133
+ });
134
+
135
+ const askTargetPrice = priceFromSqrtX64(askResult.targetSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
136
+ const bidTargetPrice = priceFromSqrtX64(bidResult.targetSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
137
+
138
+ // Ask: amount_in 是 quote token(gross up),amount_out 是 base token
139
+ const askGross = grossUpFee(askResult.amountIn, feeRateBps);
140
+ // Bid: amount_in 是 base token(gross up),amount_out 是 quote token
141
+ const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
142
+
143
+ const askTickMove = `${askResult.targetTick} <- ${askResult.currentTick}`;
144
+ const bidTickMove = `${bidResult.currentTick} -> ${bidResult.targetTick}`;
145
+
146
+ // 【强制·原则2】tier.price = effective average price including fee = amount_in_gross / amount_out
147
+ // 跟链上 swap exec_price 严格同语义;禁止改回 marginal / 不含 fee 形态(G4 事故根因)。
148
+ // 详见 arb-common/docs/20260602-dex-quote-implementation-standard.md 原则2;verify 跨源对账兜底。
149
+ const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askTargetPrice;
150
+ const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidTargetPrice;
151
+
152
+ askTiers.push({
153
+ pct, price: askEffPrice, amount: askResult.amountOut,
154
+ amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
155
+ fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd, tick_move: askTickMove,
156
+ });
157
+ bidTiers.push({
158
+ pct, price: bidEffPrice, amount: bidResult.amountOut,
159
+ amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
160
+ fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd, tick_move: bidTickMove,
161
+ });
162
+ }
163
+
164
+ return {
165
+ mid_price: midPrice,
166
+ fee_rate_bps: feeRateBps,
167
+ ask: assembleEntry(askTiers),
168
+ bid: assembleEntry(bidTiers),
169
+ };
170
+ } catch (error) {
171
+ log_debug(`[Depth] ${poolInfo.pool_name} CLMM depth failed: ${(error as Error).message}`, '');
172
+ return undefined;
173
+ }
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
+ }
@@ -1 +1,10 @@
1
1
  export * from './pricing'
2
+ export * from './pool_subscription_registry'
3
+ export * from './pool_event'
4
+ export * from './chain_ops'
5
+ export * from './abstract_dex_quote'
6
+ export * from './quote_trace'
7
+ export * from './quote_amount'
8
+ export * from './verify'
9
+ export * from './depth'
10
+ export * from './tick'