@clonegod/ttd-bsc-common 3.0.54 → 3.1.1
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/index.js +9 -2
- package/dist/quote/index.d.ts +1 -1
- package/dist/quote/index.js +1 -1
- package/dist/quote/verify/index.d.ts +2 -0
- package/dist/quote/verify/index.js +5 -0
- package/dist/quote/verify/quote_price_verify.d.ts +42 -0
- package/dist/quote/verify/quote_price_verify.js +144 -0
- package/dist/trade/abstract_dex_trade.js +2 -0
- package/dist/trade/caller_manager.js +44 -21
- package/dist/trade/trade_trace.d.ts +2 -0
- package/dist/trade/trade_trace.js +10 -0
- package/package.json +2 -2
|
@@ -16,14 +16,21 @@ Object.defineProperty(exports, "bigIntSqrt", { enumerable: true, get: function (
|
|
|
16
16
|
var amm_depth_calculator_2 = require("./amm_depth_calculator");
|
|
17
17
|
Object.defineProperty(exports, "calculateAmmDepth", { enumerable: true, get: function () { return amm_depth_calculator_2.calculateAmmDepth; } });
|
|
18
18
|
let _depthBpsLogged = false;
|
|
19
|
+
const DEFAULT_BPS = 20;
|
|
19
20
|
function getDepthBps() {
|
|
20
21
|
const raw = process.env.DEPTH_BPS_LEVELS;
|
|
21
22
|
const levels = raw
|
|
22
23
|
? raw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)
|
|
23
|
-
: [];
|
|
24
|
+
: [DEFAULT_BPS];
|
|
25
|
+
const cexBps = parseInt(process.env.DEPTH_CEX_BPS || String(DEFAULT_BPS));
|
|
26
|
+
if (!levels.includes(cexBps))
|
|
27
|
+
levels.push(cexBps);
|
|
28
|
+
if (!levels.includes(DEFAULT_BPS))
|
|
29
|
+
levels.push(DEFAULT_BPS);
|
|
30
|
+
levels.sort((a, b) => a - b);
|
|
24
31
|
if (!_depthBpsLogged) {
|
|
25
32
|
_depthBpsLogged = true;
|
|
26
|
-
(0, ttd_core_1.log_info)(`[Depth]
|
|
33
|
+
(0, ttd_core_1.log_info)(`[Depth] bpsLevels=${JSON.stringify(levels)}, cexBps=${cexBps}`);
|
|
27
34
|
}
|
|
28
35
|
return levels;
|
|
29
36
|
}
|
package/dist/quote/index.d.ts
CHANGED
package/dist/quote/index.js
CHANGED
|
@@ -18,6 +18,6 @@ __exportStar(require("./event"), exports);
|
|
|
18
18
|
__exportStar(require("./pricing"), exports);
|
|
19
19
|
__exportStar(require("./tick"), exports);
|
|
20
20
|
__exportStar(require("./depth"), exports);
|
|
21
|
-
__exportStar(require("./
|
|
21
|
+
__exportStar(require("./verify"), exports);
|
|
22
22
|
__exportStar(require("./price_feed_handler"), exports);
|
|
23
23
|
__exportStar(require("./quote_amount"), exports);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuotePriceVerify = void 0;
|
|
4
|
+
var quote_price_verify_1 = require("./quote_price_verify");
|
|
5
|
+
Object.defineProperty(exports, "QuotePriceVerify", { enumerable: true, get: function () { return quote_price_verify_1.QuotePriceVerify; } });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface CheckSwapParams {
|
|
2
|
+
poolAddress: string;
|
|
3
|
+
poolName: string;
|
|
4
|
+
blockNumber: number;
|
|
5
|
+
txHash: string;
|
|
6
|
+
amount0: string;
|
|
7
|
+
amount1: string;
|
|
8
|
+
token0Address: string;
|
|
9
|
+
token1Address: string;
|
|
10
|
+
token0Decimals: number;
|
|
11
|
+
token1Decimals: number;
|
|
12
|
+
poolInfo: {
|
|
13
|
+
tokenA: any;
|
|
14
|
+
tokenB: any;
|
|
15
|
+
quote_token: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface CheckSyncParams {
|
|
19
|
+
poolAddress: string;
|
|
20
|
+
poolName: string;
|
|
21
|
+
blockNumber: number;
|
|
22
|
+
reserve0: string;
|
|
23
|
+
reserve1: string;
|
|
24
|
+
token0Address: string;
|
|
25
|
+
token1Address: string;
|
|
26
|
+
token0Decimals: number;
|
|
27
|
+
token1Decimals: number;
|
|
28
|
+
poolInfo: {
|
|
29
|
+
tokenA: any;
|
|
30
|
+
tokenB: any;
|
|
31
|
+
quote_token: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare class QuotePriceVerify {
|
|
35
|
+
private quoteCache;
|
|
36
|
+
private reserveCache;
|
|
37
|
+
private get enabled();
|
|
38
|
+
cacheQuote(poolAddress: string, priceId: string, source: string, askPrice: number, bidPrice: number, blockNumber: number, quoteAmountUsd: number, token0PriceUsd?: number, token1PriceUsd?: number): void;
|
|
39
|
+
checkSwap(params: CheckSwapParams): void;
|
|
40
|
+
checkSync(params: CheckSyncParams): void;
|
|
41
|
+
private compareAndLog;
|
|
42
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuotePriceVerify = void 0;
|
|
4
|
+
const trade_direction_1 = require("../../utils/trade_direction");
|
|
5
|
+
class QuotePriceVerify {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.quoteCache = new Map();
|
|
8
|
+
this.reserveCache = new Map();
|
|
9
|
+
}
|
|
10
|
+
get enabled() {
|
|
11
|
+
return process.env.VERIFY_QUOTE_PRICE === 'true';
|
|
12
|
+
}
|
|
13
|
+
cacheQuote(poolAddress, priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd, token0PriceUsd = 0, token1PriceUsd = 0) {
|
|
14
|
+
if (!this.enabled)
|
|
15
|
+
return;
|
|
16
|
+
this.quoteCache.set(poolAddress, {
|
|
17
|
+
priceId, source, askPrice, bidPrice, blockNumber, quoteAmountUsd,
|
|
18
|
+
verifiedBlock: 0,
|
|
19
|
+
token0PriceUsd, token1PriceUsd,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
checkSwap(params) {
|
|
23
|
+
if (!this.enabled)
|
|
24
|
+
return;
|
|
25
|
+
const { poolAddress, poolName, blockNumber, txHash, amount0, amount1, poolInfo } = params;
|
|
26
|
+
const { token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
27
|
+
const cached = this.quoteCache.get(poolAddress);
|
|
28
|
+
if (!cached)
|
|
29
|
+
return;
|
|
30
|
+
if (blockNumber <= cached.blockNumber)
|
|
31
|
+
return;
|
|
32
|
+
if (blockNumber <= cached.verifiedBlock)
|
|
33
|
+
return;
|
|
34
|
+
cached.verifiedBlock = blockNumber;
|
|
35
|
+
const amt0 = BigInt(amount0);
|
|
36
|
+
const amt1 = BigInt(amount1);
|
|
37
|
+
if (amt0 === 0n && amt1 === 0n)
|
|
38
|
+
return;
|
|
39
|
+
let inputTokenAddress;
|
|
40
|
+
let outputTokenAddress;
|
|
41
|
+
let inputAmountUi;
|
|
42
|
+
let outputAmountUi;
|
|
43
|
+
if (amt0 > 0n && amt1 < 0n) {
|
|
44
|
+
inputTokenAddress = token0Address;
|
|
45
|
+
outputTokenAddress = token1Address;
|
|
46
|
+
inputAmountUi = Number(amt0) / Math.pow(10, token0Decimals);
|
|
47
|
+
outputAmountUi = Number(-amt1) / Math.pow(10, token1Decimals);
|
|
48
|
+
}
|
|
49
|
+
else if (amt0 < 0n && amt1 > 0n) {
|
|
50
|
+
inputTokenAddress = token1Address;
|
|
51
|
+
outputTokenAddress = token0Address;
|
|
52
|
+
inputAmountUi = Number(amt1) / Math.pow(10, token1Decimals);
|
|
53
|
+
outputAmountUi = Number(-amt0) / Math.pow(10, token0Decimals);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, cached.token0PriceUsd, cached.token1PriceUsd, token0Address, blockNumber, txHash);
|
|
59
|
+
}
|
|
60
|
+
checkSync(params) {
|
|
61
|
+
if (!this.enabled)
|
|
62
|
+
return;
|
|
63
|
+
const { poolAddress, poolName, blockNumber, reserve0, reserve1, poolInfo } = params;
|
|
64
|
+
const { token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
65
|
+
const cached = this.quoteCache.get(poolAddress);
|
|
66
|
+
if (!cached)
|
|
67
|
+
return;
|
|
68
|
+
if (blockNumber <= cached.blockNumber)
|
|
69
|
+
return;
|
|
70
|
+
if (blockNumber <= cached.verifiedBlock)
|
|
71
|
+
return;
|
|
72
|
+
const newR0 = BigInt(reserve0);
|
|
73
|
+
const newR1 = BigInt(reserve1);
|
|
74
|
+
const oldReserves = this.reserveCache.get(poolAddress);
|
|
75
|
+
this.reserveCache.set(poolAddress, { reserve0: newR0, reserve1: newR1 });
|
|
76
|
+
if (!oldReserves)
|
|
77
|
+
return;
|
|
78
|
+
const delta0 = newR0 - oldReserves.reserve0;
|
|
79
|
+
const delta1 = newR1 - oldReserves.reserve1;
|
|
80
|
+
if ((delta0 > 0n && delta1 > 0n) || (delta0 < 0n && delta1 < 0n) || (delta0 === 0n && delta1 === 0n)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
cached.verifiedBlock = blockNumber;
|
|
84
|
+
let inputTokenAddress;
|
|
85
|
+
let outputTokenAddress;
|
|
86
|
+
let inputAmountUi;
|
|
87
|
+
let outputAmountUi;
|
|
88
|
+
if (delta0 > 0n && delta1 < 0n) {
|
|
89
|
+
inputTokenAddress = token0Address;
|
|
90
|
+
outputTokenAddress = token1Address;
|
|
91
|
+
inputAmountUi = Number(delta0) / Math.pow(10, token0Decimals);
|
|
92
|
+
outputAmountUi = Number(-delta1) / Math.pow(10, token1Decimals);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
inputTokenAddress = token1Address;
|
|
96
|
+
outputTokenAddress = token0Address;
|
|
97
|
+
inputAmountUi = Number(delta1) / Math.pow(10, token1Decimals);
|
|
98
|
+
outputAmountUi = Number(-delta0) / Math.pow(10, token0Decimals);
|
|
99
|
+
}
|
|
100
|
+
this.compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, cached.token0PriceUsd, cached.token1PriceUsd, token0Address, blockNumber);
|
|
101
|
+
}
|
|
102
|
+
compareAndLog(poolAddress, poolName, poolInfo, cached, inputTokenAddress, outputTokenAddress, inputAmountUi, outputAmountUi, token0PriceUsd, token1PriceUsd, token0Address, swapBlockNumber, txHash) {
|
|
103
|
+
if (inputAmountUi <= 0 || outputAmountUi <= 0)
|
|
104
|
+
return;
|
|
105
|
+
let swapUsd = 0;
|
|
106
|
+
if (token0PriceUsd && token1PriceUsd && token0Address) {
|
|
107
|
+
const inputIsToken0 = inputTokenAddress.toLowerCase() === token0Address.toLowerCase();
|
|
108
|
+
swapUsd = inputIsToken0
|
|
109
|
+
? inputAmountUi * token0PriceUsd
|
|
110
|
+
: inputAmountUi * token1PriceUsd;
|
|
111
|
+
}
|
|
112
|
+
const direction = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
|
|
113
|
+
const baseToken = direction.baseToken;
|
|
114
|
+
const quoteToken = direction.quoteToken;
|
|
115
|
+
const inputIsQuoteToken = inputTokenAddress.toLowerCase() === quoteToken.address.toLowerCase();
|
|
116
|
+
const isBuy = inputIsQuoteToken;
|
|
117
|
+
const execPrice = (0, trade_direction_1.calculateStandardPrice)(inputAmountUi, outputAmountUi, isBuy);
|
|
118
|
+
const execPriceNum = Number(execPrice);
|
|
119
|
+
const refPrice = isBuy ? cached.askPrice : cached.bidPrice;
|
|
120
|
+
const side = isBuy ? 'BUY' : 'SELL';
|
|
121
|
+
if (refPrice <= 0 || execPriceNum <= 0)
|
|
122
|
+
return;
|
|
123
|
+
const diffBps = (execPriceNum - refPrice) / refPrice * 10000;
|
|
124
|
+
const absDiffBps = Math.abs(diffBps);
|
|
125
|
+
const status = absDiffBps < 10 ? '✅' : absDiffBps < 30 ? '⚠️' : '❌';
|
|
126
|
+
const usdStr = swapUsd > 0 ? `$${swapUsd.toFixed(0)}` : '$?';
|
|
127
|
+
const minUsd = cached.quoteAmountUsd * 0.5;
|
|
128
|
+
const maxUsd = cached.quoteAmountUsd * 2;
|
|
129
|
+
const inRange = swapUsd <= 0 || (swapUsd >= minUsd && swapUsd <= maxUsd);
|
|
130
|
+
const rangeTag = inRange ? '' : ' [out]';
|
|
131
|
+
const tradeFlow = isBuy
|
|
132
|
+
? `${inputAmountUi.toFixed(4)} ${quoteToken.symbol} -> ${outputAmountUi.toFixed(4)} ${baseToken.symbol}`
|
|
133
|
+
: `${inputAmountUi.toFixed(4)} ${baseToken.symbol} -> ${outputAmountUi.toFixed(4)} ${quoteToken.symbol}`;
|
|
134
|
+
const quoteSrc = cached.source || '?';
|
|
135
|
+
const quoteTag = `quote[${quoteSrc} blk:${cached.blockNumber}]`;
|
|
136
|
+
const swapBlk = swapBlockNumber || '?';
|
|
137
|
+
const swapTx = txHash ? ` ${txHash.slice(0, 10)}` : '';
|
|
138
|
+
const swapTag = `swap[blk:${swapBlk}${swapTx}]`;
|
|
139
|
+
const priceLabel = isBuy ? 'ask' : 'bid';
|
|
140
|
+
const msg = ` ↳ [Verify] ${side} ${usdStr} (${tradeFlow}) ${quoteTag} ${priceLabel}=${refPrice.toFixed(12)} vs ${swapTag} exec=${execPriceNum.toFixed(12)} diff=${diffBps > 0 ? '+' : ''}${diffBps.toFixed(1)}bps ${status}${rangeTag}`;
|
|
141
|
+
console.log(msg);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.QuotePriceVerify = QuotePriceVerify;
|
|
@@ -180,6 +180,8 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
180
180
|
await this.transactionSender.sendTransaction(signedMainTx, tipTxMap, order_trace_id, pair, only_bundle, trace);
|
|
181
181
|
trace.mark('sent');
|
|
182
182
|
trace.flush();
|
|
183
|
+
context._execution_marks = trace.getAbsoluteMarks();
|
|
184
|
+
context._execution_start_time = trace.getStartTime();
|
|
183
185
|
try {
|
|
184
186
|
base_tx_result_checker_1.TradeResultSubscriber.getInstance().sendNonceWatch(caller.address, txid);
|
|
185
187
|
}
|
|
@@ -62,32 +62,55 @@ class CallerManager {
|
|
|
62
62
|
logger.info(`CallerManager initialized for ${this.config.groupId}: loaded=${allWallets.length}, active=${this.callers.length}, skipped=${skipped.length}`, callerSummary);
|
|
63
63
|
}
|
|
64
64
|
async acquireCaller() {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
const lockKey = `${this.config.chainName}:caller:lock:select`;
|
|
66
|
+
const lockValue = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
67
|
+
const lockExpireSeconds = 1;
|
|
68
|
+
const maxRetries = 20;
|
|
69
|
+
const retryDelayMs = 10;
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
let acquired = false;
|
|
72
|
+
let retries = 0;
|
|
73
|
+
for (; retries < maxRetries; retries++) {
|
|
74
|
+
acquired = await this.redis.acquireLock(lockKey, lockValue, lockExpireSeconds);
|
|
75
|
+
if (acquired)
|
|
76
|
+
break;
|
|
77
|
+
await new Promise(r => setTimeout(r, retryDelayMs));
|
|
78
|
+
}
|
|
79
|
+
if (!acquired) {
|
|
80
|
+
throw new Error(`acquireCaller: failed to acquire lock after ${maxRetries} retries (${Date.now() - startTime}ms)`);
|
|
81
|
+
}
|
|
82
|
+
const lockAcquiredTime = Date.now();
|
|
83
|
+
let callerAddr;
|
|
84
|
+
let selectedIdx;
|
|
85
|
+
try {
|
|
86
|
+
const addresses = this.callers.map(w => w.address.toLowerCase());
|
|
87
|
+
const lastUsedKey = this.getLastUsedRedisKey();
|
|
88
|
+
const lastUsedData = await this.redis.hgetall(lastUsedKey);
|
|
89
|
+
selectedIdx = 0;
|
|
90
|
+
let minLastUsed = Number.MAX_SAFE_INTEGER;
|
|
91
|
+
for (let i = 0; i < this.callers.length; i++) {
|
|
92
|
+
const redisTs = parseInt(lastUsedData?.[addresses[i]] || '0', 10);
|
|
93
|
+
if (redisTs < minLastUsed) {
|
|
94
|
+
minLastUsed = redisTs;
|
|
95
|
+
selectedIdx = i;
|
|
96
|
+
}
|
|
79
97
|
}
|
|
98
|
+
callerAddr = addresses[selectedIdx];
|
|
99
|
+
await this.redis.hsetValue(lastUsedKey, callerAddr, String(Date.now()), 24 * 60 * 60);
|
|
80
100
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
finally {
|
|
102
|
+
this.redis.releaseLock(lockKey, lockValue).catch(() => { });
|
|
103
|
+
}
|
|
104
|
+
const nonceKey = this.getNonceRedisKey();
|
|
105
|
+
const nonceStr = await this.redis.hgetvalue(nonceKey, callerAddr);
|
|
84
106
|
if (nonceStr === null || nonceStr === undefined) {
|
|
85
|
-
throw new Error(`Caller ${
|
|
107
|
+
throw new Error(`Caller ${callerAddr} nonce not found in Redis, stream-trade may not be running`);
|
|
86
108
|
}
|
|
87
109
|
const nonce = parseInt(nonceStr, 10);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
const totalMs = Date.now() - startTime;
|
|
111
|
+
const lockWaitMs = lockAcquiredTime - startTime;
|
|
112
|
+
logger.info(`acquireCaller: ${callerAddr} nonce=${nonce}, lock_wait=${lockWaitMs}ms, total=${totalMs}ms${retries > 0 ? `, retries=${retries}` : ''}`);
|
|
113
|
+
return { wallet: this.callers[selectedIdx], nonce };
|
|
91
114
|
}
|
|
92
115
|
async confirmNonce(address, confirmedNonce) {
|
|
93
116
|
const current = await this.getNonce(address);
|
|
@@ -34,6 +34,16 @@ class TradeTrace {
|
|
|
34
34
|
const total = this.marks[this.marks.length - 1].elapsed;
|
|
35
35
|
return `recv -> ${parts.join(' -> ')} = ${total}ms`;
|
|
36
36
|
}
|
|
37
|
+
getAbsoluteMarks() {
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const m of this.marks) {
|
|
40
|
+
result[m.name] = this.startTime + m.elapsed;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
getStartTime() {
|
|
45
|
+
return this.startTime;
|
|
46
|
+
}
|
|
37
47
|
flush() {
|
|
38
48
|
const timeline = this.buildTimeline();
|
|
39
49
|
if (this.error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clonegod/ttd-bsc-common",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
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.
|
|
17
|
+
"@clonegod/ttd-core": "3.1.1",
|
|
18
18
|
"axios": "^1.12.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"ethers": "^5.8.0",
|