@clonegod/ttd-sol-common 2.0.77 → 2.0.79
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/abstract_dex_quote.d.ts +2 -1
- package/dist/quote/abstract_dex_quote.js +3 -3
- package/dist/quote/verify/quote_price_verify.d.ts +1 -2
- package/dist/quote/verify/quote_price_verify.js +3 -6
- package/package.json +2 -2
- package/src/quote/abstract_dex_quote.ts +14 -10
- package/src/quote/verify/quote_price_verify.ts +18 -24
|
@@ -50,7 +50,8 @@ export declare abstract class AbstractDexQuote<C extends SolanaChainOps = Solana
|
|
|
50
50
|
protected calculateDepth(_poolInfo: StandardPoolInfoType, _poolAddress: string, _priceMap: Map<string, {
|
|
51
51
|
price: string;
|
|
52
52
|
} | undefined>): Promise<QuoteDepthOutput | undefined>;
|
|
53
|
-
protected
|
|
53
|
+
protected getEffectiveFeeBps(poolInfo: StandardPoolInfoType): number;
|
|
54
|
+
protected getOutOfVaultFeeBps(_poolInfo: StandardPoolInfoType): number;
|
|
54
55
|
protected isStateConsistentForQuote(_poolInfo: StandardPoolInfoType): boolean;
|
|
55
56
|
protected accountSlots: Map<string, number>;
|
|
56
57
|
protected recordAccountSlot(accountAddr: string, slot: number): void;
|
|
@@ -15,13 +15,14 @@ class AbstractDexQuote {
|
|
|
15
15
|
this.latestBlockSlot = 0;
|
|
16
16
|
this.MIN_QUOTE_INTERVAL_MS = Math.max(3000, parseInt(process.env.MIN_QUOTE_INTERVAL_MS || '10000', 10) || 10000);
|
|
17
17
|
this.consistencyPolicy = 'snapshot';
|
|
18
|
-
this.feeInVault = true;
|
|
19
18
|
this.accountSlots = new Map();
|
|
20
19
|
this.appConfig = appConfig;
|
|
21
20
|
this.chain = chain;
|
|
22
21
|
}
|
|
23
22
|
async refreshStateFromEvent(_poolInfo, _evt) { }
|
|
24
23
|
async calculateDepth(_poolInfo, _poolAddress, _priceMap) { return undefined; }
|
|
24
|
+
getEffectiveFeeBps(poolInfo) { return Number(poolInfo.fee_rate) || 0; }
|
|
25
|
+
getOutOfVaultFeeBps(_poolInfo) { return 0; }
|
|
25
26
|
isStateConsistentForQuote(_poolInfo) { return true; }
|
|
26
27
|
recordAccountSlot(accountAddr, slot) {
|
|
27
28
|
this.accountSlots.set(accountAddr, slot);
|
|
@@ -72,8 +73,7 @@ class AbstractDexQuote {
|
|
|
72
73
|
token1Decimals: Number(poolInfo.tokenB?.decimals ?? 0),
|
|
73
74
|
poolInfo: { tokenA: poolInfo.tokenA, tokenB: poolInfo.tokenB, quote_token: poolInfo.quote_token },
|
|
74
75
|
swapperDeltaConvention: false,
|
|
75
|
-
|
|
76
|
-
feeRateBps: poolInfo.fee_rate,
|
|
76
|
+
outOfVaultFeeBps: this.getOutOfVaultFeeBps(poolInfo),
|
|
77
77
|
};
|
|
78
78
|
const verifyLog = this.quotePriceVerify.checkSwap(params);
|
|
79
79
|
if (verifyLog)
|
|
@@ -114,9 +114,6 @@ class QuotePriceVerify {
|
|
|
114
114
|
if (!match)
|
|
115
115
|
continue;
|
|
116
116
|
refPrice = match.tier.price;
|
|
117
|
-
if (params.feeExclusiveExec && match.tier.amount_in > 0) {
|
|
118
|
-
refPrice = stripFeeExclusive(refPrice, isBuy, match.tier.fee / match.tier.amount_in);
|
|
119
|
-
}
|
|
120
117
|
tierInfo = {
|
|
121
118
|
matched_pct: parseFloat(match.tier.pct.toFixed(4)),
|
|
122
119
|
mode: match.mode,
|
|
@@ -127,9 +124,9 @@ class QuotePriceVerify {
|
|
|
127
124
|
}
|
|
128
125
|
else {
|
|
129
126
|
refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
}
|
|
128
|
+
if (params.outOfVaultFeeBps && params.outOfVaultFeeBps > 0) {
|
|
129
|
+
refPrice = stripFeeExclusive(refPrice, isBuy, params.outOfVaultFeeBps / 10000);
|
|
133
130
|
}
|
|
134
131
|
if (refPrice <= 0)
|
|
135
132
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clonegod/ttd-sol-common",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.79",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"push": "npm run build && npm publish"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@clonegod/ttd-core": "3.1.
|
|
16
|
+
"@clonegod/ttd-core": "3.1.90",
|
|
17
17
|
"@solana/web3.js": "1.91.6",
|
|
18
18
|
"rpc-websockets": "7.10.0",
|
|
19
19
|
"axios": "1.17.0",
|
|
@@ -17,7 +17,7 @@ import { QuoteTrace } from './quote_trace'
|
|
|
17
17
|
// · 无 v3 PriceFeed(同 SUI,省 do_quote_v3)。
|
|
18
18
|
// · verify(报价偏差)走**独立的 swap-verify 通道**(非账户快照):stream-quote 从真实交易的
|
|
19
19
|
// pre/postTokenBalances 解码订阅池子 vault Δ(DEX 无关)→ WS push → handleSwapVerify → checkSwap。
|
|
20
|
-
// exec_price = |Δquote/Δbase|(pool-side vault Δ),fee-exclusive
|
|
20
|
+
// exec_price = |Δquote/Δbase|(pool-side vault Δ),fee-exclusive 对账(费路由出池的部分由 getOutOfVaultFeeBps 提供,方向感知剥掉)。
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* 流动性一致性策略(设计文档 §2.2 定稿:3 订阅原型)。各 DEX 子类按协议特性声明:
|
|
@@ -114,12 +114,17 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
114
114
|
/** 深度(tier 化);fee 来自链上 load。priceMap 由基类预取。默认 undefined(step 3 各 DEX 逐档实现)。 */
|
|
115
115
|
protected async calculateDepth(_poolInfo: StandardPoolInfoType, _poolAddress: string, _priceMap: Map<string, { price: string } | undefined>): Promise<QuoteDepthOutput | undefined> { return undefined }
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* - false(pumpswap:protocol/creator fee 路由出池到独立账户):vault Δ 不含费 → checkSwap 把参考价剥成 fee-exclusive。
|
|
120
|
-
* 详见 quote_price_verify CheckSwapParams.feeExclusiveExec。
|
|
117
|
+
* depth gross-up 用的**总费率**(bps)。默认配置 fee_rate;动态费 DEX(pumpswap 市值档位 / DLMM 波动费)覆盖,
|
|
118
|
+
* 每事件按真实链上 feeConfig 重算 → 报价自适应费率变化。
|
|
121
119
|
*/
|
|
122
|
-
protected
|
|
120
|
+
protected getEffectiveFeeBps(poolInfo: StandardPoolInfoType): number { return Number(poolInfo.fee_rate) || 0 }
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* verify 剥费用的**出池费率**(bps)= 路由出 vault 的费(vault Δ exec 不含的部分)。
|
|
124
|
+
* - 0(默认):费全留池(Raydium/Orca/Meteora 等)→ vault Δ 与含费报价同口径,不剥。
|
|
125
|
+
* - >0:pumpswap(protocol+creator,动态按市值档位)等覆盖。详见 CheckSwapParams.outOfVaultFeeBps。
|
|
126
|
+
*/
|
|
127
|
+
protected getOutOfVaultFeeBps(_poolInfo: StandardPoolInfoType): number { return 0 }
|
|
123
128
|
|
|
124
129
|
/**
|
|
125
130
|
* slot 门控钩子(仅 consistencyPolicy='slot-gate' 生效):判断本池"走单触碰的多账户"状态是否 slot 一致。
|
|
@@ -181,7 +186,7 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
181
186
|
|
|
182
187
|
/**
|
|
183
188
|
* 消费 stream-quote 推来的 swap-verify(pool-side vault Δ)→ checkSwap 对账缓存报价 → 打 [Verify]。
|
|
184
|
-
* exec_price 由 |Δquote/Δbase|
|
|
189
|
+
* exec_price 由 |Δquote/Δbase| 算;剥费用 getOutOfVaultFeeBps(详见 CheckSwapParams.outOfVaultFeeBps)。
|
|
185
190
|
*/
|
|
186
191
|
private handleSwapVerify(sv: SolanaSwapVerifyEventData): void {
|
|
187
192
|
const poolInfo = this.poolInfoMap.get(sv.pool_address)
|
|
@@ -198,9 +203,8 @@ export abstract class AbstractDexQuote<C extends SolanaChainOps = SolanaChainOps
|
|
|
198
203
|
token0Decimals: Number(poolInfo.tokenA?.decimals ?? 0),
|
|
199
204
|
token1Decimals: Number(poolInfo.tokenB?.decimals ?? 0),
|
|
200
205
|
poolInfo: { tokenA: poolInfo.tokenA, tokenB: poolInfo.tokenB, quote_token: poolInfo.quote_token },
|
|
201
|
-
swapperDeltaConvention: false,
|
|
202
|
-
|
|
203
|
-
feeRateBps: poolInfo.fee_rate, // market-data 强制整数 bps
|
|
206
|
+
swapperDeltaConvention: false, // amount0/1 是 pool-side vault Δ
|
|
207
|
+
outOfVaultFeeBps: this.getOutOfVaultFeeBps(poolInfo), // 出池费(默认 0=全留池不剥;pumpswap 动态)
|
|
204
208
|
}
|
|
205
209
|
const verifyLog = this.quotePriceVerify.checkSwap(params)
|
|
206
210
|
if (verifyLog) log_info(verifyLog)
|
|
@@ -120,12 +120,13 @@ function pickMatchingTier(tiers: QuoteTier[], actualAmountIn: number): TierMatch
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* 把含费报价剥成 fee-exclusive(与 vault Δ exec 同口径),r = **出池费率**(路由出 vault 的部分)。
|
|
124
124
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* -
|
|
128
|
-
*
|
|
125
|
+
* vault Δ exec 不含"出池费"(如 pumpswap 的 protocol+creator,转给独立账户),但**含**留池的 lp 费。
|
|
126
|
+
* 报价(ask/bid)是 swapper 含全费的成交价。两者差一个"出池费",且方向**不对称**:
|
|
127
|
+
* - ask(买基础币) = exec / (1 − r) → ask_excl = ask × (1 − r)
|
|
128
|
+
* - bid(卖基础币) = exec × (1 − r) → bid_excl = bid / (1 − r) ← 是除,不是乘!
|
|
129
|
+
* 不论出池费记输入侧还是输出侧,上式一阶成立(lp 留池的二阶项可忽略)。
|
|
129
130
|
* ask/bid 同为 quote/base 口径(calculateStandardPrice),仅方向决定撑开符号。
|
|
130
131
|
*/
|
|
131
132
|
function stripFeeExclusive(refPrice: number, isBuy: boolean, r: number): number {
|
|
@@ -157,18 +158,14 @@ export interface CheckSwapParams {
|
|
|
157
158
|
*/
|
|
158
159
|
swapperDeltaConvention?: boolean;
|
|
159
160
|
/**
|
|
160
|
-
*
|
|
161
|
+
* **出池费率**(bps)= 路由出 vault 的费(pumpswap=protocol+creator,**动态**按市值档位)。
|
|
161
162
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* -
|
|
165
|
-
*
|
|
166
|
-
* 为 true 时:refPrice ×= (amount_in − fee)/amount_in(tier 模式,用该档自身的含费/费额);
|
|
167
|
-
* scalar 兜底用 (1 − feeRateBps/1e4) 一阶近似。
|
|
163
|
+
* exec_price 由 pool vault Δ 算,**不含出池费、含留池 lp 费**。报价含全费 → 两者差出池费。
|
|
164
|
+
* 故 verify 把报价按出池费剥成 vault 口径(方向感知,见 stripFeeExclusive)。
|
|
165
|
+
* - 0 / 缺省:费全留池(Raydium/Orca/Meteora 等),vault Δ 与含费报价同口径 → 不剥。
|
|
166
|
+
* - >0:pumpswap 等,DEX 子类经 getOutOfVaultFeeBps 动态提供(每事件按真实 feeConfig 算)。
|
|
168
167
|
*/
|
|
169
|
-
|
|
170
|
-
/** 池子费率(bps);feeExclusiveExec 且 scalar 兜底(无 tier)时用作一阶剥费因子。 */
|
|
171
|
-
feeRateBps?: number;
|
|
168
|
+
outOfVaultFeeBps?: number;
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
// ========== 主类 ==========
|
|
@@ -303,11 +300,6 @@ export class QuotePriceVerify {
|
|
|
303
300
|
const match = pickMatchingTier(tiers, swapData.inputAmountUi);
|
|
304
301
|
if (!match) continue;
|
|
305
302
|
refPrice = match.tier.price;
|
|
306
|
-
// fee-exclusive 对账:把含费 tier 价剥成 fee-exclusive,与 fee-out-of-vault DEX(pumpswap)的 vault Δ 同口径。
|
|
307
|
-
// r = 该档费率(fee/amount_in,amount_in 含费)。
|
|
308
|
-
if (params.feeExclusiveExec && match.tier.amount_in > 0) {
|
|
309
|
-
refPrice = stripFeeExclusive(refPrice, isBuy, match.tier.fee / match.tier.amount_in);
|
|
310
|
-
}
|
|
311
303
|
tierInfo = {
|
|
312
304
|
matched_pct: parseFloat(match.tier.pct.toFixed(4)),
|
|
313
305
|
mode: match.mode,
|
|
@@ -318,10 +310,12 @@ export class QuotePriceVerify {
|
|
|
318
310
|
} else {
|
|
319
311
|
// scalar fallback:旧 DEX 包未传 tiers 时使用
|
|
320
312
|
refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// fee-exclusive 对账:剥掉"出池费"(vault Δ exec 不含的部分)→ 报价与 vault Δ 同口径。
|
|
316
|
+
// tier / scalar 统一处理:出池费是定比(与档位/大小无关),方向感知(买×卖÷)。
|
|
317
|
+
if (params.outOfVaultFeeBps && params.outOfVaultFeeBps > 0) {
|
|
318
|
+
refPrice = stripFeeExclusive(refPrice, isBuy, params.outOfVaultFeeBps / 10000);
|
|
325
319
|
}
|
|
326
320
|
|
|
327
321
|
if (refPrice <= 0) continue;
|