@clonegod/ttd-bsc-common 3.1.74 → 3.1.76

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.
@@ -69,6 +69,31 @@ function grossUpFee(amountInNet, feeRateBps) {
69
69
  return { amountInGross, feeAmount };
70
70
  }
71
71
  const _tickLiquidityLastBuild = new Map();
72
+ const _tierInvariantLastWarn = new Map();
73
+ function assertTierInvariant(poolLabel, side, tiers) {
74
+ if (!tiers || tiers.length === 0)
75
+ return;
76
+ const TOL_REL = 0.0001;
77
+ for (const t of tiers) {
78
+ if (!(t.price > 0) || !(t.amount > 0) || !(t.amount_in > 0))
79
+ continue;
80
+ const expected = side === 'ASK' ? t.amount_in / t.amount : t.amount / t.amount_in;
81
+ if (!(expected > 0))
82
+ continue;
83
+ const relDiff = Math.abs(t.price - expected) / expected;
84
+ if (relDiff > TOL_REL) {
85
+ const key = `${poolLabel}_${side}_${t.pct}`;
86
+ const now = Date.now();
87
+ if ((now - (_tierInvariantLastWarn.get(key) || 0)) < 60000)
88
+ return;
89
+ _tierInvariantLastWarn.set(key, now);
90
+ (0, ttd_core_1.log_warn)(`[tier-invariant] ${poolLabel} ${side} pct=${t.pct} price=${t.price.toFixed(10)} ` +
91
+ `≠ expected ${expected.toFixed(10)} (relDiff=${(relDiff * 10000).toFixed(2)}bps) — ` +
92
+ `tier.price MUST be effective avg price including fee`);
93
+ return;
94
+ }
95
+ }
96
+ }
72
97
  function assembleEntry(tiers) {
73
98
  const defaultTier = tiers.find(t => t.pct === ttd_core_2.DEFAULT_TIER_PCT) ?? tiers[0];
74
99
  return {
@@ -131,9 +156,11 @@ function buildClmmDepth(input) {
131
156
  const bidTickMove = bidResult.targetTick > bidResult.currentTick
132
157
  ? `${bidResult.currentTick} -> ${bidResult.targetTick}`
133
158
  : `${bidResult.targetTick} <- ${bidResult.currentTick}`;
159
+ const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askTargetPrice;
160
+ const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidTargetPrice;
134
161
  askTiers.push({
135
162
  pct,
136
- price: askTargetPrice,
163
+ price: askEffPrice,
137
164
  amount: askResult.amountOut,
138
165
  amount_in: askGross.amountInGross,
139
166
  amount_in_usd: askGross.amountInGross * quotePriceUsd,
@@ -143,7 +170,7 @@ function buildClmmDepth(input) {
143
170
  });
144
171
  bidTiers.push({
145
172
  pct,
146
- price: bidTargetPrice,
173
+ price: bidEffPrice,
147
174
  amount: bidResult.amountOut,
148
175
  amount_in: bidGross.amountInGross,
149
176
  amount_in_usd: bidGross.amountInGross * basePriceUsd,
@@ -152,6 +179,8 @@ function buildClmmDepth(input) {
152
179
  tick_move: bidTickMove,
153
180
  });
154
181
  }
182
+ assertTierInvariant(poolInfo.pool_name || poolAddress, 'ASK', askTiers);
183
+ assertTierInvariant(poolInfo.pool_name || poolAddress, 'BID', bidTiers);
155
184
  const depth = {
156
185
  mid_price: midPrice,
157
186
  fee_rate_bps: feeRateBps,
@@ -231,9 +260,11 @@ function buildAmmDepth(input) {
231
260
  });
232
261
  const askGross = grossUpFee(askResult.amountIn, feeRateBps);
233
262
  const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
263
+ const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askResult.targetPrice;
264
+ const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidResult.targetPrice;
234
265
  askTiers.push({
235
266
  pct,
236
- price: askResult.targetPrice,
267
+ price: askEffPrice,
237
268
  amount: askResult.amountOut,
238
269
  amount_in: askGross.amountInGross,
239
270
  amount_in_usd: askGross.amountInGross * quotePriceUsd,
@@ -242,7 +273,7 @@ function buildAmmDepth(input) {
242
273
  });
243
274
  bidTiers.push({
244
275
  pct,
245
- price: bidResult.targetPrice,
276
+ price: bidEffPrice,
246
277
  amount: bidResult.amountOut,
247
278
  amount_in: bidGross.amountInGross,
248
279
  amount_in_usd: bidGross.amountInGross * basePriceUsd,
@@ -250,6 +281,8 @@ function buildAmmDepth(input) {
250
281
  fee_usd: bidGross.feeAmount * basePriceUsd,
251
282
  });
252
283
  }
284
+ assertTierInvariant(poolInfo.pool_name || 'amm-pool', 'ASK', askTiers);
285
+ assertTierInvariant(poolInfo.pool_name || 'amm-pool', 'BID', bidTiers);
253
286
  return {
254
287
  mid_price: midPrice,
255
288
  fee_rate_bps: feeRateBps,
@@ -1,6 +1,9 @@
1
1
  import { AmmPoolState, ClmmPoolState, InfinityPoolState } from "../../types/pool_state";
2
+ import { StandardPoolInfoType } from '@clonegod/ttd-core';
2
3
  declare function withInitRetry<T>(fn: () => Promise<T>, label: string): Promise<T>;
3
4
  export { withInitRetry };
5
+ declare function assertPoolTokensMatch(poolInfo: StandardPoolInfoType, onchainToken0: string, onchainToken1: string, nativeAddress?: string): void;
6
+ export { assertPoolTokensMatch };
4
7
  export declare class PoolStateInitializer {
5
8
  static initAmmPool(provider: any, poolAddress: string, dexId: string, ethersLib: any): Promise<AmmPoolState>;
6
9
  static initClmmPool(provider: any, poolAddress: string, ethersLib: any, poolAbi?: any[]): Promise<ClmmPoolState>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PoolStateInitializer = void 0;
4
4
  exports.withInitRetry = withInitRetry;
5
+ exports.assertPoolTokensMatch = assertPoolTokensMatch;
5
6
  const ttd_core_1 = require("@clonegod/ttd-core");
6
7
  async function withInitRetry(fn, label) {
7
8
  try {
@@ -27,6 +28,37 @@ async function withInitRetry(fn, label) {
27
28
  }
28
29
  }
29
30
  }
31
+ function assertPoolTokensMatch(poolInfo, onchainToken0, onchainToken1, nativeAddress) {
32
+ const configured = new Set([
33
+ poolInfo.tokenA.address.toLowerCase(),
34
+ poolInfo.tokenB.address.toLowerCase(),
35
+ ]);
36
+ const native = nativeAddress?.toLowerCase();
37
+ const onchain = [onchainToken0.toLowerCase(), onchainToken1.toLowerCase()];
38
+ const missing = onchain.filter(addr => addr !== native && !configured.has(addr));
39
+ if (missing.length === 0)
40
+ return;
41
+ const identity = poolInfo.pool_address.toLowerCase();
42
+ const title = `${poolInfo.pool_name} 配置 token 与链上不一致(疑似同名 symbol 选错地址): ` +
43
+ `config=[${[...configured].join(', ')}] chain=[${onchain.join(', ')}] missing=[${missing.join(', ')}]`;
44
+ ttd_core_1.ALERT_TYPES.QUOTE_POOL_INIT_FAILED.report({
45
+ severity: 'critical',
46
+ identity,
47
+ scope: { dex_id: poolInfo.dex_id, pool_address: identity, pair: poolInfo.pair },
48
+ title,
49
+ detail: { pool_name: poolInfo.pool_name, configured: [...configured], onchain, missing },
50
+ });
51
+ throw new Error(`[pool-token-mismatch] ${title}`);
52
+ }
53
+ function attributeInitError(error, poolId, expectedType) {
54
+ const msg = String(error?.message || error);
55
+ const isRevert = /CALL_EXCEPTION|execution reverted|missing revert data/i.test(msg);
56
+ if (!isRevert)
57
+ return error instanceof Error ? error : new Error(msg);
58
+ return new Error(`[pool-type-mismatch?] ${poolId} 期望是 ${expectedType} 池,但链上特征函数 revert ` +
59
+ `—— 疑似池子类型/DEX_ID 配错(核对该地址链上真实协议:CLMM 有 slot0/tickSpacing,` +
60
+ `AMM 有 getReserves/metadata)。原始错误: ${msg}`);
61
+ }
30
62
  const V2_PAIR_ABI = [
31
63
  'function token0() view returns (address)',
32
64
  'function token1() view returns (address)',
@@ -71,7 +103,7 @@ class PoolStateInitializer {
71
103
  }
72
104
  catch (error) {
73
105
  (0, ttd_core_1.log_error)(`[PoolStateInitializer] Failed to init AMM pool ${poolAddress}:`, error);
74
- throw error;
106
+ throw attributeInitError(error, poolAddress, 'AMM');
75
107
  }
76
108
  }
77
109
  static async initClmmPool(provider, poolAddress, ethersLib, poolAbi) {
@@ -106,7 +138,7 @@ class PoolStateInitializer {
106
138
  }
107
139
  catch (error) {
108
140
  (0, ttd_core_1.log_error)(`[PoolStateInitializer] Failed to init CLMM pool ${poolAddress}:`, error);
109
- throw error;
141
+ throw attributeInitError(error, poolAddress, 'CLMM');
110
142
  }
111
143
  }
112
144
  static async initInfinityPool(provider, poolKey, poolManagerAddress, poolManagerAbi, ethersLib, decodeParameters) {
@@ -147,7 +179,7 @@ class PoolStateInitializer {
147
179
  }
148
180
  catch (error) {
149
181
  (0, ttd_core_1.log_error)(`[PoolStateInitializer] Failed to init Infinity pool ${poolKey}:`, error);
150
- throw error;
182
+ throw attributeInitError(error, poolKey, 'Infinity');
151
183
  }
152
184
  }
153
185
  }
@@ -8,5 +8,5 @@ export declare class CachedTickDataProvider {
8
8
  liquidityNet: string;
9
9
  liquidityGross: string;
10
10
  }>;
11
- nextInitializedTickWithinOneWord(tick: number, lte: boolean, tickSpacing: number): Promise<[number, boolean]>;
11
+ nextInitializedTickWithinOneWord(tick: number, lte: boolean, _sdkTickSpacing: number): Promise<[number, boolean]>;
12
12
  }
@@ -24,7 +24,12 @@ class CachedTickDataProvider {
24
24
  liquidityGross: '0',
25
25
  };
26
26
  }
27
- async nextInitializedTickWithinOneWord(tick, lte, tickSpacing) {
27
+ async nextInitializedTickWithinOneWord(tick, lte, _sdkTickSpacing) {
28
+ const tickSpacing = this.tickSpacing;
29
+ if (!Number.isInteger(tickSpacing) || tickSpacing <= 0) {
30
+ throw new Error(`[CachedTickDataProvider] invalid tickSpacing=${tickSpacing} for ${this.poolAddress} ` +
31
+ `— 必须来自链上 pool.tickSpacing(),不能从 fee 反推`);
32
+ }
28
33
  const zeroForOne = !lte;
29
34
  const result = this.tickCache.getNextInitializedTickWithinOneWord(this.poolAddress, tick, tickSpacing, zeroForOne);
30
35
  if (result)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "3.1.74",
3
+ "version": "3.1.76",
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.68",
17
+ "@clonegod/ttd-core": "3.1.79",
18
18
  "axios": "1.15.0",
19
19
  "dotenv": "^16.4.7",
20
20
  "ethers": "^5.8.0",