@clonegod/ttd-bsc-common 3.1.63 → 3.1.65
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/index.d.ts +1 -0
- package/dist/quote/index.js +1 -0
- package/dist/quote/preload_token_prices.d.ts +2 -0
- package/dist/quote/preload_token_prices.js +36 -0
- package/dist/quote/price_feed_handler.js +9 -21
- package/dist/quote/verify/index.d.ts +1 -1
- package/dist/quote/verify/quote_price_verify.d.ts +1 -20
- package/dist/quote/verify/quote_price_verify.js +71 -82
- package/package.json +2 -2
package/dist/quote/index.d.ts
CHANGED
package/dist/quote/index.js
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preloadTokenPrices = preloadTokenPrices;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
async function preloadTokenPrices(pool_list, label = 'Quote') {
|
|
6
|
+
if (!pool_list || pool_list.length === 0)
|
|
7
|
+
return;
|
|
8
|
+
const addrSet = new Set();
|
|
9
|
+
for (const pool of pool_list) {
|
|
10
|
+
if (pool.tokenA?.address)
|
|
11
|
+
addrSet.add(pool.tokenA.address.toLowerCase());
|
|
12
|
+
if (pool.tokenB?.address)
|
|
13
|
+
addrSet.add(pool.tokenB.address.toLowerCase());
|
|
14
|
+
}
|
|
15
|
+
const addresses = Array.from(addrSet);
|
|
16
|
+
if (addresses.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const priceMap = await (0, ttd_core_1.get_bsc_token_price_info)(addresses, { source: 'force_fetch' });
|
|
20
|
+
const got = [];
|
|
21
|
+
const miss = [];
|
|
22
|
+
for (const a of addresses) {
|
|
23
|
+
if (priceMap.get(a)?.price)
|
|
24
|
+
got.push(a);
|
|
25
|
+
else
|
|
26
|
+
miss.push(a);
|
|
27
|
+
}
|
|
28
|
+
(0, ttd_core_1.log_info)(`[${label}] preloadTokenPrices done, total=${addresses.length}, got=${got.length}, miss=${miss.length}`);
|
|
29
|
+
if (miss.length > 0) {
|
|
30
|
+
(0, ttd_core_1.log_warn)(`[${label}] preloadTokenPrices missing: ${miss.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
(0, ttd_core_1.log_warn)(`[${label}] preloadTokenPrices error (non-fatal): ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -11,45 +11,33 @@ function buildQuoteFromPriceFeed(poolInfo, data) {
|
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
const normSymbol = (s) => {
|
|
14
|
-
const u = s.toUpperCase();
|
|
14
|
+
const u = (s || '').toUpperCase();
|
|
15
15
|
return u === 'WBNB' ? 'BNB' : u;
|
|
16
16
|
};
|
|
17
|
-
let askPrice;
|
|
18
|
-
let bidPrice;
|
|
19
|
-
let baseToken;
|
|
20
|
-
let quoteToken;
|
|
21
17
|
const tokenAIsQuote = normSymbol(tokenA.symbol) === normSymbol(quoteTokenSymbol);
|
|
22
18
|
const tokenBIsQuote = normSymbol(tokenB.symbol) === normSymbol(quoteTokenSymbol);
|
|
23
19
|
if (!tokenAIsQuote && !tokenBIsQuote) {
|
|
24
|
-
(0, ttd_core_1.log_warn)(`[PriceFeed] quote_token ${quoteTokenSymbol}
|
|
20
|
+
(0, ttd_core_1.log_warn)(`[PriceFeed] quote_token "${quoteTokenSymbol}" 在池子配置里找不到 (tokenA=${tokenA.symbol}, tokenB=${tokenB.symbol}, pool=${poolInfo.pool_name})`);
|
|
25
21
|
return null;
|
|
26
22
|
}
|
|
23
|
+
let askPrice;
|
|
24
|
+
let bidPrice;
|
|
25
|
+
let baseToken;
|
|
26
|
+
let quoteToken;
|
|
27
27
|
if (tokenAIsQuote) {
|
|
28
28
|
quoteToken = tokenA;
|
|
29
29
|
baseToken = tokenB;
|
|
30
|
+
askPrice = data.askToken1InToken0;
|
|
31
|
+
bidPrice = data.bidToken1InToken0;
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
32
34
|
quoteToken = tokenB;
|
|
33
35
|
baseToken = tokenA;
|
|
34
|
-
}
|
|
35
|
-
const feedToken0 = normSymbol(data.token0Symbol || '');
|
|
36
|
-
const feedToken1 = normSymbol(data.token1Symbol || '');
|
|
37
|
-
const baseSymbolNorm = normSymbol(baseToken.symbol);
|
|
38
|
-
const quoteSymbolNorm = normSymbol(quoteToken.symbol);
|
|
39
|
-
if (normSymbol(feedToken0) === quoteSymbolNorm) {
|
|
40
|
-
askPrice = data.askToken1InToken0;
|
|
41
|
-
bidPrice = data.bidToken1InToken0;
|
|
42
|
-
}
|
|
43
|
-
else if (normSymbol(feedToken1) === quoteSymbolNorm) {
|
|
44
36
|
askPrice = data.askToken0InToken1;
|
|
45
37
|
bidPrice = data.bidToken0InToken1;
|
|
46
38
|
}
|
|
47
|
-
else {
|
|
48
|
-
(0, ttd_core_1.log_warn)(`[PriceFeed] Cannot match quote_token symbol: quote=${quoteSymbolNorm}, feed=[${feedToken0}/${feedToken1}], pool=${poolInfo.pool_name}`);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
39
|
if (!askPrice || !bidPrice) {
|
|
52
|
-
(0, ttd_core_1.log_warn)(`[PriceFeed] Missing price
|
|
40
|
+
(0, ttd_core_1.log_warn)(`[PriceFeed] Missing price for pool=${poolInfo.pool_name}, direction=${tokenAIsQuote ? 'token1InToken0' : 'token0InToken1'}, feedSymbols=[${data.token0Symbol}/${data.token1Symbol}]`);
|
|
53
41
|
return null;
|
|
54
42
|
}
|
|
55
43
|
const makeQuoteResult = (price, input, output) => ({
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { QuotePriceVerify } from './quote_price_verify';
|
|
2
|
-
export type { CheckSwapParams
|
|
2
|
+
export type { CheckSwapParams } from './quote_price_verify';
|
|
@@ -16,30 +16,11 @@ export interface CheckSwapParams {
|
|
|
16
16
|
};
|
|
17
17
|
swapperDeltaConvention?: boolean;
|
|
18
18
|
}
|
|
19
|
-
export interface CheckSyncParams {
|
|
20
|
-
poolAddress: string;
|
|
21
|
-
poolName: string;
|
|
22
|
-
blockNumber: number;
|
|
23
|
-
reserve0: string;
|
|
24
|
-
reserve1: string;
|
|
25
|
-
token0Address: string;
|
|
26
|
-
token1Address: string;
|
|
27
|
-
token0Decimals: number;
|
|
28
|
-
token1Decimals: number;
|
|
29
|
-
poolInfo: {
|
|
30
|
-
tokenA: any;
|
|
31
|
-
tokenB: any;
|
|
32
|
-
quote_token: string;
|
|
33
|
-
};
|
|
34
|
-
txHash?: string;
|
|
35
|
-
}
|
|
36
19
|
export declare class QuotePriceVerify {
|
|
37
20
|
private quoteCache;
|
|
38
|
-
private reserveCache;
|
|
39
21
|
private get enabled();
|
|
40
|
-
seedReserves(poolAddress: string, reserve0: string, reserve1: string): void;
|
|
41
22
|
cacheQuote(poolAddress: string, priceId: string, source: string, askPrice: number, bidPrice: number, blockNumber: number, quoteAmountUsd: number, token0PriceUsd?: number, token1PriceUsd?: number): void;
|
|
42
23
|
checkSwap(params: CheckSwapParams): void;
|
|
43
|
-
|
|
24
|
+
private deriveSwapDirection;
|
|
44
25
|
private compareAndLog;
|
|
45
26
|
}
|
|
@@ -6,29 +6,28 @@ const ttd_core_1 = require("@clonegod/ttd-core");
|
|
|
6
6
|
class QuotePriceVerify {
|
|
7
7
|
constructor() {
|
|
8
8
|
this.quoteCache = new Map();
|
|
9
|
-
this.reserveCache = new Map();
|
|
10
9
|
}
|
|
11
10
|
get enabled() {
|
|
12
11
|
return process.env.VERIFY_QUOTE_PRICE === 'true';
|
|
13
12
|
}
|
|
14
|
-
seedReserves(poolAddress, reserve0, reserve1) {
|
|
15
|
-
if (!this.enabled)
|
|
16
|
-
return;
|
|
17
|
-
try {
|
|
18
|
-
this.reserveCache.set(poolAddress, {
|
|
19
|
-
reserve0: BigInt(reserve0),
|
|
20
|
-
reserve1: BigInt(reserve1),
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
catch { }
|
|
24
|
-
}
|
|
25
13
|
cacheQuote(poolAddress, priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd, token0PriceUsd = 0, token1PriceUsd = 0) {
|
|
26
14
|
if (!this.enabled)
|
|
27
15
|
return;
|
|
28
|
-
|
|
16
|
+
if (!source)
|
|
17
|
+
return;
|
|
18
|
+
let sourceMap = this.quoteCache.get(poolAddress);
|
|
19
|
+
if (!sourceMap) {
|
|
20
|
+
sourceMap = new Map();
|
|
21
|
+
this.quoteCache.set(poolAddress, sourceMap);
|
|
22
|
+
}
|
|
23
|
+
const existing = sourceMap.get(source);
|
|
24
|
+
const verifiedReferenceBlock = (existing && existing.referenceBlock === blockNumber)
|
|
25
|
+
? existing.verifiedReferenceBlock
|
|
26
|
+
: 0;
|
|
27
|
+
sourceMap.set(source, {
|
|
29
28
|
priceId, source, askPrice, bidPrice,
|
|
30
29
|
referenceBlock: blockNumber,
|
|
31
|
-
verifiedReferenceBlock
|
|
30
|
+
verifiedReferenceBlock,
|
|
32
31
|
quoteAmountUsd,
|
|
33
32
|
token0PriceUsd, token1PriceUsd,
|
|
34
33
|
});
|
|
@@ -36,85 +35,74 @@ class QuotePriceVerify {
|
|
|
36
35
|
checkSwap(params) {
|
|
37
36
|
if (!this.enabled)
|
|
38
37
|
return;
|
|
39
|
-
const { poolAddress,
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
if (!cached)
|
|
43
|
-
return;
|
|
44
|
-
if (blockNumber !== cached.referenceBlock + 1)
|
|
38
|
+
const { poolAddress, blockNumber, txHash, poolInfo, token0Address } = params;
|
|
39
|
+
const sourceMap = this.quoteCache.get(poolAddress);
|
|
40
|
+
if (!sourceMap || sourceMap.size === 0)
|
|
45
41
|
return;
|
|
46
|
-
|
|
42
|
+
const swapData = this.deriveSwapDirection(params);
|
|
43
|
+
if (!swapData)
|
|
47
44
|
return;
|
|
48
|
-
|
|
45
|
+
const direction = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
|
|
46
|
+
const inputIsQuoteToken = swapData.inputTokenAddress.toLowerCase() === direction.quoteToken.address.toLowerCase();
|
|
47
|
+
const isBuy = inputIsQuoteToken;
|
|
48
|
+
const execPriceStr = (0, trade_direction_1.calculateStandardPrice)(swapData.inputAmountUi, swapData.outputAmountUi, isBuy);
|
|
49
|
+
const execPrice = Number(execPriceStr);
|
|
50
|
+
if (execPrice <= 0)
|
|
51
|
+
return;
|
|
52
|
+
const verifiable = [];
|
|
53
|
+
for (const [source, cached] of sourceMap) {
|
|
54
|
+
if (blockNumber <= cached.referenceBlock)
|
|
55
|
+
continue;
|
|
56
|
+
if (cached.verifiedReferenceBlock === cached.referenceBlock)
|
|
57
|
+
continue;
|
|
58
|
+
const refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
59
|
+
if (refPrice <= 0)
|
|
60
|
+
continue;
|
|
61
|
+
cached.verifiedReferenceBlock = cached.referenceBlock;
|
|
62
|
+
const diff_bps = (refPrice - execPrice) / refPrice * 10000;
|
|
63
|
+
verifiable.push({ source, cached, refPrice, diff_bps });
|
|
64
|
+
}
|
|
65
|
+
if (verifiable.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
verifiable.sort((a, b) => b.cached.referenceBlock - a.cached.referenceBlock);
|
|
68
|
+
const primary = verifiable[0];
|
|
69
|
+
const sources = {};
|
|
70
|
+
for (const r of verifiable) {
|
|
71
|
+
sources[r.source] = {
|
|
72
|
+
ask: r.cached.askPrice,
|
|
73
|
+
bid: r.cached.bidPrice,
|
|
74
|
+
diff_bps: parseFloat(r.diff_bps.toFixed(1)),
|
|
75
|
+
quote_block: r.cached.referenceBlock,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
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);
|
|
79
|
+
}
|
|
80
|
+
deriveSwapDirection(params) {
|
|
81
|
+
const { amount0, amount1, token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
49
82
|
const sign = params.swapperDeltaConvention ? -1n : 1n;
|
|
50
83
|
const amt0 = BigInt(amount0) * sign;
|
|
51
84
|
const amt1 = BigInt(amount1) * sign;
|
|
52
85
|
if (amt0 === 0n && amt1 === 0n)
|
|
53
|
-
return;
|
|
54
|
-
let inputTokenAddress;
|
|
55
|
-
let outputTokenAddress;
|
|
56
|
-
let inputAmountUi;
|
|
57
|
-
let outputAmountUi;
|
|
86
|
+
return null;
|
|
58
87
|
if (amt0 > 0n && amt1 < 0n) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
return {
|
|
89
|
+
inputTokenAddress: token0Address,
|
|
90
|
+
outputTokenAddress: token1Address,
|
|
91
|
+
inputAmountUi: Number(amt0) / Math.pow(10, token0Decimals),
|
|
92
|
+
outputAmountUi: Number(-amt1) / Math.pow(10, token1Decimals),
|
|
93
|
+
};
|
|
63
94
|
}
|
|
64
95
|
else if (amt0 < 0n && amt1 > 0n) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
this.compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, cached.token0PriceUsd, cached.token1PriceUsd, token0Address, blockNumber, txHash);
|
|
74
|
-
}
|
|
75
|
-
checkSync(params) {
|
|
76
|
-
if (!this.enabled)
|
|
77
|
-
return;
|
|
78
|
-
const { poolAddress, poolName, blockNumber, reserve0, reserve1, poolInfo, txHash } = params;
|
|
79
|
-
const { token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
80
|
-
const cached = this.quoteCache.get(poolAddress);
|
|
81
|
-
if (!cached)
|
|
82
|
-
return;
|
|
83
|
-
if (blockNumber !== cached.referenceBlock + 1)
|
|
84
|
-
return;
|
|
85
|
-
if (cached.verifiedReferenceBlock === cached.referenceBlock)
|
|
86
|
-
return;
|
|
87
|
-
const newR0 = BigInt(reserve0);
|
|
88
|
-
const newR1 = BigInt(reserve1);
|
|
89
|
-
const oldReserves = this.reserveCache.get(poolAddress);
|
|
90
|
-
this.reserveCache.set(poolAddress, { reserve0: newR0, reserve1: newR1 });
|
|
91
|
-
if (!oldReserves)
|
|
92
|
-
return;
|
|
93
|
-
const delta0 = newR0 - oldReserves.reserve0;
|
|
94
|
-
const delta1 = newR1 - oldReserves.reserve1;
|
|
95
|
-
if ((delta0 > 0n && delta1 > 0n) || (delta0 < 0n && delta1 < 0n) || (delta0 === 0n && delta1 === 0n)) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
cached.verifiedReferenceBlock = cached.referenceBlock;
|
|
99
|
-
let inputTokenAddress;
|
|
100
|
-
let outputTokenAddress;
|
|
101
|
-
let inputAmountUi;
|
|
102
|
-
let outputAmountUi;
|
|
103
|
-
if (delta0 > 0n && delta1 < 0n) {
|
|
104
|
-
inputTokenAddress = token0Address;
|
|
105
|
-
outputTokenAddress = token1Address;
|
|
106
|
-
inputAmountUi = Number(delta0) / Math.pow(10, token0Decimals);
|
|
107
|
-
outputAmountUi = Number(-delta1) / Math.pow(10, token1Decimals);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
inputTokenAddress = token1Address;
|
|
111
|
-
outputTokenAddress = token0Address;
|
|
112
|
-
inputAmountUi = Number(delta1) / Math.pow(10, token1Decimals);
|
|
113
|
-
outputAmountUi = Number(-delta0) / Math.pow(10, token0Decimals);
|
|
96
|
+
return {
|
|
97
|
+
inputTokenAddress: token1Address,
|
|
98
|
+
outputTokenAddress: token0Address,
|
|
99
|
+
inputAmountUi: Number(amt1) / Math.pow(10, token1Decimals),
|
|
100
|
+
outputAmountUi: Number(-amt0) / Math.pow(10, token0Decimals),
|
|
101
|
+
};
|
|
114
102
|
}
|
|
115
|
-
|
|
103
|
+
return null;
|
|
116
104
|
}
|
|
117
|
-
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash) {
|
|
105
|
+
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash, sources) {
|
|
118
106
|
if (inputAmountUi <= 0 || outputAmountUi <= 0)
|
|
119
107
|
return;
|
|
120
108
|
let swapUsd = 0;
|
|
@@ -175,6 +163,7 @@ class QuotePriceVerify {
|
|
|
175
163
|
output_symbol: isBuy ? baseToken.symbol : quoteToken.symbol,
|
|
176
164
|
status,
|
|
177
165
|
time: Date.now(),
|
|
166
|
+
sources: sources || undefined,
|
|
178
167
|
});
|
|
179
168
|
}
|
|
180
169
|
catch (_) {
|
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.65",
|
|
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.55",
|
|
18
18
|
"axios": "1.15.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"ethers": "^5.8.0",
|