@clonegod/ttd-bsc-common 3.1.76 → 3.1.78
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/appconfig/bsc_dex_env_args.js +4 -1
- package/dist/appconfig/bsc_env_args.d.ts +4 -1
- package/dist/appconfig/bsc_env_args.js +4 -1
- package/dist/common/abi.d.ts +1 -29
- package/dist/common/abi.js +11 -48
- package/dist/common/constants.d.ts +0 -6
- package/dist/common/constants.js +1 -17
- package/dist/quote/abstract_dex_quote.d.ts +59 -0
- package/dist/quote/abstract_dex_quote.js +214 -0
- package/dist/quote/chain_ops.d.ts +11 -0
- package/dist/quote/chain_ops.js +2 -0
- package/dist/quote/depth/index.js +0 -29
- package/dist/quote/evm_chain_ops.d.ts +30 -0
- package/dist/quote/evm_chain_ops.js +49 -0
- package/dist/quote/index.d.ts +4 -0
- package/dist/quote/index.js +4 -0
- package/dist/quote/quote_trace.d.ts +16 -0
- package/dist/quote/quote_trace.js +40 -0
- package/dist/quote/verify/quote_price_verify.d.ts +1 -1
- package/dist/quote/verify/quote_price_verify.js +3 -2
- package/dist/trade/abstract_dex_trade.js +6 -6
- package/dist/trade/caller_manager.js +6 -3
- package/package.json +3 -3
|
@@ -30,6 +30,10 @@ class BscDexEnvArgs extends bsc_env_args_1.BscEnvArgs {
|
|
|
30
30
|
this.gas_price_gwei = cfg.gas_price_gwei;
|
|
31
31
|
this.gas_limit = cfg.gas_limit;
|
|
32
32
|
this.tip_amount_gwei = cfg.tip_amount_gwei;
|
|
33
|
+
this.gas_max_price_gwei = cfg.gas_max_price_gwei;
|
|
34
|
+
this.gas_max_fee_per_gas_gwei = cfg.gas_max_fee_per_gas_gwei;
|
|
35
|
+
this.gas_max_priority_fee_gwei = cfg.gas_max_priority_fee_gwei;
|
|
36
|
+
this.gas_max_tip_amount_gwei = cfg.gas_max_tip_amount_gwei;
|
|
33
37
|
this.pancake_executor_id = cfg.pancake_executor_id;
|
|
34
38
|
this.uniswap_executor_id = cfg.uniswap_executor_id;
|
|
35
39
|
this.caller_select_strategy = cfg.caller_select_strategy;
|
|
@@ -41,7 +45,6 @@ class BscDexEnvArgs extends bsc_env_args_1.BscEnvArgs {
|
|
|
41
45
|
this.send_tx_default_rpc = cfg.send_tx_default_rpc;
|
|
42
46
|
this.send_tx_blockrazor_private = cfg.send_tx_blockrazor_private;
|
|
43
47
|
this.send_tx_48club_private = cfg.send_tx_48club_private;
|
|
44
|
-
this.bsc_rpc_endpoint = cfg.bsc_rpc_endpoint;
|
|
45
48
|
this.blockrazor_rpc_url = cfg.blockrazor_rpc_url;
|
|
46
49
|
this.blockrazor_auth_token = cfg.blockrazor_auth_token;
|
|
47
50
|
this._48club_rpc_url = cfg._48club_rpc_url;
|
|
@@ -3,6 +3,10 @@ export declare class BscEnvArgs extends EnvArgs {
|
|
|
3
3
|
gas_price_gwei: number;
|
|
4
4
|
gas_limit: number;
|
|
5
5
|
tip_amount_gwei: number;
|
|
6
|
+
gas_max_price_gwei: number;
|
|
7
|
+
gas_max_fee_per_gas_gwei: number;
|
|
8
|
+
gas_max_priority_fee_gwei: number;
|
|
9
|
+
gas_max_tip_amount_gwei: number;
|
|
6
10
|
pancake_executor_id: string;
|
|
7
11
|
uniswap_executor_id: string;
|
|
8
12
|
caller_select_strategy: string;
|
|
@@ -14,7 +18,6 @@ export declare class BscEnvArgs extends EnvArgs {
|
|
|
14
18
|
send_tx_default_rpc: boolean;
|
|
15
19
|
send_tx_blockrazor_private: boolean;
|
|
16
20
|
send_tx_48club_private: boolean;
|
|
17
|
-
bsc_rpc_endpoint: string;
|
|
18
21
|
blockrazor_rpc_url: string;
|
|
19
22
|
blockrazor_auth_token: string;
|
|
20
23
|
_48club_rpc_url: string;
|
|
@@ -7,6 +7,10 @@ const constants_1 = require("../common/constants");
|
|
|
7
7
|
gas_price_gwei: { env: 'GAS_PRICE_GWEI', type: 'number', default: 1, desc: '交易 gas 价格(Gwei)' },
|
|
8
8
|
gas_limit: { env: 'GAS_LIMIT', type: 'number', default: 300000, desc: '交易 gas 上限' },
|
|
9
9
|
tip_amount_gwei: { env: 'TIP_AMOUNT_GWEI', type: 'number', default: 10000, desc: 'Builder tip(Gwei)' },
|
|
10
|
+
gas_max_price_gwei: { env: 'GAS_MAX_PRICE_GWEI', type: 'number', default: 10, desc: 'Legacy gasPrice 上限(Gwei)' },
|
|
11
|
+
gas_max_fee_per_gas_gwei: { env: 'GAS_MAX_FEE_PER_GAS_GWEI', type: 'number', default: 10, desc: 'EIP-1559 maxFeePerGas 上限(Gwei)' },
|
|
12
|
+
gas_max_priority_fee_gwei: { env: 'GAS_MAX_PRIORITY_FEE_GWEI', type: 'number', default: 3, desc: 'EIP-1559 maxPriorityFeePerGas 上限(Gwei)' },
|
|
13
|
+
gas_max_tip_amount_gwei: { env: 'GAS_MAX_TIP_AMOUNT_GWEI', type: 'number', default: 500000, desc: 'Builder tip 上限(Gwei)' },
|
|
10
14
|
pancake_executor_id: { env: 'PANCAKE_EXECUTOR_ID', type: 'string', default: 'PANCAKE', desc: 'Vault 中 Pancake executor 注册名(keccak256 后作为 bytes32)' },
|
|
11
15
|
uniswap_executor_id: { env: 'UNISWAP_EXECUTOR_ID', type: 'string', default: 'UNISWAP', desc: 'Vault 中 Uniswap executor 注册名(keccak256 后作为 bytes32)' },
|
|
12
16
|
caller_select_strategy: { env: 'CALLER_SELECT_STRATEGY', type: 'string', default: 'lock', desc: 'CallerManager LRU 选择策略:lock=Redis 分布式锁(默认) | lua_cas=Lua 原子脚本(单 RTT,无锁重试)' },
|
|
@@ -18,7 +22,6 @@ const constants_1 = require("../common/constants");
|
|
|
18
22
|
send_tx_default_rpc: { env: 'SEND_TX_DEFAULT_RPC', type: 'boolean', default: false, desc: '默认 RPC 私有发送(HTTP,trader 直发)' },
|
|
19
23
|
send_tx_blockrazor_private: { env: 'SEND_TX_BLOCKRAZOR_PRIVATE', type: 'boolean', default: false, desc: 'BlockRazor 私有发送(HTTP,trader 直发)' },
|
|
20
24
|
send_tx_48club_private: { env: 'SEND_TX_48CLUB_PRIVATE', type: 'boolean', default: false, desc: '48Club 私有发送(HTTP,trader 直发)' },
|
|
21
|
-
bsc_rpc_endpoint: { env: 'BSC_RPC_ENDPOINT', type: 'string', default: '', desc: 'BSC RPC 端点(default_rpc HTTP 直发用)' },
|
|
22
25
|
blockrazor_rpc_url: { env: 'BLOCKRAZOR_RPC_URL', type: 'string', default: 'https://rpc.blockrazor.builders', desc: 'BlockRazor RPC URL(HTTP 直发用)' },
|
|
23
26
|
blockrazor_auth_token: { env: 'BLOCKRAZOR_AUTH_TOKEN', type: 'string', default: '', sensitive: true, desc: 'BlockRazor 认证 Token(HTTP 直发用)' },
|
|
24
27
|
_48club_rpc_url: { env: '_48CLUB_RPC_URL', type: 'string', default: 'https://puissant-builder.48.club/', desc: '48Club RPC URL(HTTP 直发用)' },
|
package/dist/common/abi.d.ts
CHANGED
|
@@ -1,31 +1,3 @@
|
|
|
1
1
|
export declare const ERC20_ABI: string[];
|
|
2
|
-
export declare const PANCAKE_PAIR_FULL_ABI: ({
|
|
3
|
-
anonymous: boolean;
|
|
4
|
-
inputs: {
|
|
5
|
-
indexed: boolean;
|
|
6
|
-
internalType: string;
|
|
7
|
-
name: string;
|
|
8
|
-
type: string;
|
|
9
|
-
}[];
|
|
10
|
-
name: string;
|
|
11
|
-
type: string;
|
|
12
|
-
constant?: undefined;
|
|
13
|
-
outputs?: undefined;
|
|
14
|
-
payable?: undefined;
|
|
15
|
-
stateMutability?: undefined;
|
|
16
|
-
} | {
|
|
17
|
-
constant: boolean;
|
|
18
|
-
inputs: any[];
|
|
19
|
-
name: string;
|
|
20
|
-
outputs: {
|
|
21
|
-
internalType: string;
|
|
22
|
-
name: string;
|
|
23
|
-
type: string;
|
|
24
|
-
}[];
|
|
25
|
-
payable: boolean;
|
|
26
|
-
stateMutability: string;
|
|
27
|
-
type: string;
|
|
28
|
-
anonymous?: undefined;
|
|
29
|
-
})[];
|
|
30
2
|
export declare const PANCAKE_V2_ROUTER_ABI: string[];
|
|
31
|
-
export declare const
|
|
3
|
+
export declare const PancakeV3PoolABI: string[];
|
package/dist/common/abi.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.PancakeV3PoolABI = exports.PANCAKE_V2_ROUTER_ABI = exports.ERC20_ABI = void 0;
|
|
4
4
|
exports.ERC20_ABI = [
|
|
5
5
|
'function decimals() view returns (uint8)',
|
|
6
6
|
'function symbol() view returns (string)',
|
|
@@ -12,57 +12,20 @@ exports.ERC20_ABI = [
|
|
|
12
12
|
'function approve(address spender, uint256 amount) returns (bool)',
|
|
13
13
|
'function transferFrom(address sender, address recipient, uint256 amount) returns (bool)'
|
|
14
14
|
];
|
|
15
|
-
exports.PANCAKE_PAIR_FULL_ABI = [
|
|
16
|
-
{
|
|
17
|
-
"anonymous": false,
|
|
18
|
-
"inputs": [
|
|
19
|
-
{ "indexed": false, "internalType": "uint112", "name": "reserve0", "type": "uint112" },
|
|
20
|
-
{ "indexed": false, "internalType": "uint112", "name": "reserve1", "type": "uint112" }
|
|
21
|
-
],
|
|
22
|
-
"name": "Sync",
|
|
23
|
-
"type": "event"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"constant": true,
|
|
27
|
-
"inputs": [],
|
|
28
|
-
"name": "token0",
|
|
29
|
-
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
|
30
|
-
"payable": false,
|
|
31
|
-
"stateMutability": "view",
|
|
32
|
-
"type": "function"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"constant": true,
|
|
36
|
-
"inputs": [],
|
|
37
|
-
"name": "token1",
|
|
38
|
-
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
|
39
|
-
"payable": false,
|
|
40
|
-
"stateMutability": "view",
|
|
41
|
-
"type": "function"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"constant": true,
|
|
45
|
-
"inputs": [],
|
|
46
|
-
"name": "getReserves",
|
|
47
|
-
"outputs": [
|
|
48
|
-
{ "internalType": "uint112", "name": "_reserve0", "type": "uint112" },
|
|
49
|
-
{ "internalType": "uint112", "name": "_reserve1", "type": "uint112" },
|
|
50
|
-
{ "internalType": "uint32", "name": "_blockTimestampLast", "type": "uint32" }
|
|
51
|
-
],
|
|
52
|
-
"payable": false,
|
|
53
|
-
"stateMutability": "view",
|
|
54
|
-
"type": "function"
|
|
55
|
-
}
|
|
56
|
-
];
|
|
57
15
|
exports.PANCAKE_V2_ROUTER_ABI = [
|
|
58
16
|
'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)',
|
|
59
17
|
'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)',
|
|
60
18
|
'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)',
|
|
61
19
|
'function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)'
|
|
62
20
|
];
|
|
63
|
-
exports.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
21
|
+
exports.PancakeV3PoolABI = [
|
|
22
|
+
"function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)",
|
|
23
|
+
"function liquidity() external view returns (uint128)",
|
|
24
|
+
"function tickSpacing() external view returns (int24)",
|
|
25
|
+
"function factory() external view returns (address)",
|
|
26
|
+
"function token0() external view returns (address)",
|
|
27
|
+
"function token1() external view returns (address)",
|
|
28
|
+
"function fee() external view returns (uint24)",
|
|
29
|
+
"function ticks(int24 tick) external view returns (uint128 liquidityGross, int128 liquidityNet, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized)",
|
|
30
|
+
"function tickBitmap(int16 wordPosition) external view returns (uint256)",
|
|
68
31
|
];
|
|
@@ -18,9 +18,3 @@ export declare const EVENT_NAMES: {
|
|
|
18
18
|
readonly QUOTE_TRIGGER: "quote_trigger";
|
|
19
19
|
readonly WS_CONNECTION_FAILED: "ws_connection_failed";
|
|
20
20
|
};
|
|
21
|
-
export declare const EVENT_SIGNATURES: {
|
|
22
|
-
SWAP_RAW: string;
|
|
23
|
-
MINT: string;
|
|
24
|
-
BURN: string;
|
|
25
|
-
};
|
|
26
|
-
export declare const PancakeV3PoolABI: string[];
|
package/dist/common/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.EVENT_NAMES = exports.PANCAKE_UNIVERSAL_ROUTER_ADDRESS = exports.PANCAKE_PERMIT2_ADDRESS = exports.PANCAKE_SMART_ROUTER_ADDRESS = exports.PANCAKE_V3_ROUTER_ADDRESS = exports.PANCAKE_V3_QUOTER_ADDRESS = exports.PANCAKE_V2_ROUTER_ADDRESS = exports.Q96 = exports.NATIVE_BNB_ADDRESS = exports.WBNB_ADDRESS = exports.BSC_CHAIN_ID = void 0;
|
|
4
4
|
exports.BSC_CHAIN_ID = 56;
|
|
5
5
|
exports.WBNB_ADDRESS = '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c';
|
|
6
6
|
exports.NATIVE_BNB_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
@@ -21,19 +21,3 @@ exports.EVENT_NAMES = {
|
|
|
21
21
|
QUOTE_TRIGGER: 'quote_trigger',
|
|
22
22
|
WS_CONNECTION_FAILED: 'ws_connection_failed',
|
|
23
23
|
};
|
|
24
|
-
exports.EVENT_SIGNATURES = {
|
|
25
|
-
SWAP_RAW: "Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint128 protocolFeesToken0, uint128 protocolFeesToken1)",
|
|
26
|
-
MINT: "event Mint(address sender, address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)",
|
|
27
|
-
BURN: "event Burn(address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)"
|
|
28
|
-
};
|
|
29
|
-
exports.PancakeV3PoolABI = [
|
|
30
|
-
"function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)",
|
|
31
|
-
"function liquidity() external view returns (uint128)",
|
|
32
|
-
"function tickSpacing() external view returns (int24)",
|
|
33
|
-
"function factory() external view returns (address)",
|
|
34
|
-
"function token0() external view returns (address)",
|
|
35
|
-
"function token1() external view returns (address)",
|
|
36
|
-
"function fee() external view returns (uint24)",
|
|
37
|
-
"function ticks(int24 tick) external view returns (uint128 liquidityGross, int128 liquidityNet, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized)",
|
|
38
|
-
"function tickBitmap(int16 wordPosition) external view returns (uint256)",
|
|
39
|
-
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AppConfig, DEX_ID, QuoteResultType, StandardPoolInfoType, QuoteDepthOutput } from '@clonegod/ttd-core';
|
|
2
|
+
import { ChainOps } from './chain_ops';
|
|
3
|
+
import { QuotePriceVerify, CheckSwapParams } from './verify/quote_price_verify';
|
|
4
|
+
import { QuoteTrace } from './quote_trace';
|
|
5
|
+
export interface PoolInitSummary {
|
|
6
|
+
protocol_type: string;
|
|
7
|
+
fee_bps: number;
|
|
8
|
+
tick_spacing?: number;
|
|
9
|
+
token0: string;
|
|
10
|
+
token1: string;
|
|
11
|
+
verifyContract?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface QuoteBundle {
|
|
14
|
+
poolInfo: StandardPoolInfoType;
|
|
15
|
+
quote_amount_usd: number;
|
|
16
|
+
streamTimestamp: number;
|
|
17
|
+
quoteStartTime: number;
|
|
18
|
+
blockNumber: number;
|
|
19
|
+
txIndex?: number;
|
|
20
|
+
askQuote: QuoteResultType;
|
|
21
|
+
bidQuote: QuoteResultType;
|
|
22
|
+
txid: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
depth?: QuoteDepthOutput;
|
|
25
|
+
priceMap?: Map<string, {
|
|
26
|
+
price: string;
|
|
27
|
+
} | undefined>;
|
|
28
|
+
trace?: QuoteTrace;
|
|
29
|
+
}
|
|
30
|
+
export declare abstract class AbstractDexQuote<C extends ChainOps = ChainOps> {
|
|
31
|
+
protected appConfig: AppConfig;
|
|
32
|
+
protected chain: C;
|
|
33
|
+
protected poolInfoMap: Map<string, StandardPoolInfoType>;
|
|
34
|
+
private poolLastQuoteTimeMap;
|
|
35
|
+
private lastPublished;
|
|
36
|
+
protected quotePriceVerify: QuotePriceVerify;
|
|
37
|
+
private readonly MIN_QUOTE_INTERVAL_MS;
|
|
38
|
+
constructor(appConfig: AppConfig, chain: C);
|
|
39
|
+
protected abstract readonly dexId: DEX_ID;
|
|
40
|
+
protected abstract readonly debouncedEventTypes: string[];
|
|
41
|
+
protected abstract loadPool(poolInfo: StandardPoolInfoType): Promise<PoolInitSummary>;
|
|
42
|
+
protected abstract quoteV1(poolInfo: StandardPoolInfoType, isBuy: boolean): Promise<QuoteResultType>;
|
|
43
|
+
protected abstract quoteV2(poolInfo: StandardPoolInfoType, isBuy: boolean): Promise<QuoteResultType>;
|
|
44
|
+
protected abstract refreshStateFromEvent(poolInfo: StandardPoolInfoType, eventData: any): void | Promise<void>;
|
|
45
|
+
protected abstract calculateDepth(poolInfo: StandardPoolInfoType, poolAddress: string, priceMap: Map<string, {
|
|
46
|
+
price: string;
|
|
47
|
+
} | undefined>): Promise<QuoteDepthOutput | undefined>;
|
|
48
|
+
protected abstract buildSwapVerify(poolInfo: StandardPoolInfoType, eventData: any): CheckSwapParams | null;
|
|
49
|
+
init(poolList: StandardPoolInfoType[]): Promise<void>;
|
|
50
|
+
protected beforeLoadPools(): Promise<void>;
|
|
51
|
+
private registerEventHandlers;
|
|
52
|
+
private assertValidBlockNumber;
|
|
53
|
+
private isStaleUpdate;
|
|
54
|
+
private handleBlockUpdateEvent;
|
|
55
|
+
private calculateQuote;
|
|
56
|
+
private calculateQuoteForPool;
|
|
57
|
+
protected publishQuote(result: QuoteBundle): void;
|
|
58
|
+
private do_quote_v3;
|
|
59
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AbstractDexQuote = void 0;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
const quote_price_verify_1 = require("./verify/quote_price_verify");
|
|
6
|
+
const price_feed_handler_1 = require("./price_feed_handler");
|
|
7
|
+
const quote_amount_1 = require("./quote_amount");
|
|
8
|
+
const quote_trace_1 = require("./quote_trace");
|
|
9
|
+
class AbstractDexQuote {
|
|
10
|
+
constructor(appConfig, chain) {
|
|
11
|
+
this.poolInfoMap = new Map();
|
|
12
|
+
this.poolLastQuoteTimeMap = new Map();
|
|
13
|
+
this.lastPublished = new Map();
|
|
14
|
+
this.quotePriceVerify = new quote_price_verify_1.QuotePriceVerify();
|
|
15
|
+
this.MIN_QUOTE_INTERVAL_MS = Math.max(3000, parseInt(process.env.MIN_QUOTE_INTERVAL_MS || '10000', 10) || 10000);
|
|
16
|
+
this.appConfig = appConfig;
|
|
17
|
+
this.chain = chain;
|
|
18
|
+
}
|
|
19
|
+
async init(poolList) {
|
|
20
|
+
(0, ttd_core_1.log_info)(`初始化 ${this.dexId} Quote,共 ${poolList.length} 个池...`);
|
|
21
|
+
for (const poolInfo of poolList) {
|
|
22
|
+
if (!this.chain.isValidPoolAddress(poolInfo.pool_address)) {
|
|
23
|
+
(0, ttd_core_1.log_warn)(`跳过无效的池地址: ${poolInfo.pool_address}`, '');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
this.poolInfoMap.set(poolInfo.pool_address, poolInfo);
|
|
27
|
+
}
|
|
28
|
+
await this.beforeLoadPools();
|
|
29
|
+
await Promise.all(Array.from(this.poolInfoMap.values()).map(async (p) => {
|
|
30
|
+
const summary = await this.loadPool(p);
|
|
31
|
+
if (summary.verifyContract)
|
|
32
|
+
await this.chain.assertContractDeployed(summary.verifyContract);
|
|
33
|
+
(0, ttd_core_1.log_info)(`[QUOTE INIT] ${p.pool_name} (${p.pool_address}) dex=${this.dexId}`, { ...summary });
|
|
34
|
+
}));
|
|
35
|
+
this.registerEventHandlers();
|
|
36
|
+
(0, ttd_core_1.log_info)(`${this.dexId} Quote 初始化完成,${this.poolInfoMap.size} 个有效池`);
|
|
37
|
+
}
|
|
38
|
+
async beforeLoadPools() { }
|
|
39
|
+
registerEventHandlers() {
|
|
40
|
+
this.chain.subscribeNewBlock((raw) => this.handleBlockUpdateEvent(raw));
|
|
41
|
+
this.chain.subscribePoolEvents(this.dexId, Array.from(this.poolInfoMap.values()), async (evt) => {
|
|
42
|
+
if (evt.type === 'subscribed')
|
|
43
|
+
return;
|
|
44
|
+
if (!evt.data) {
|
|
45
|
+
(0, ttd_core_1.log_warn)(`[${this.dexId}] pool event: no data`, evt);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const { bundle, verifyLog } = await this.calculateQuote(evt);
|
|
49
|
+
if (bundle)
|
|
50
|
+
this.publishQuote(bundle);
|
|
51
|
+
if (verifyLog)
|
|
52
|
+
(0, ttd_core_1.log_info)(verifyLog);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
assertValidBlockNumber(blockNumber, ctx) {
|
|
56
|
+
if (!Number.isInteger(blockNumber) || blockNumber <= 0) {
|
|
57
|
+
throw new Error(`[block-number] ${ctx}: 非法 blockNumber=${blockNumber},每条数据源必须携带真实区块号`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
isStaleUpdate(poolAddr, block, txIndex) {
|
|
61
|
+
const last = this.lastPublished.get(poolAddr);
|
|
62
|
+
return !!last && (block < last.block || (block === last.block && txIndex <= last.txIndex));
|
|
63
|
+
}
|
|
64
|
+
async handleBlockUpdateEvent(eventData) {
|
|
65
|
+
const { blockNumber, recvBlockTime } = JSON.parse(eventData);
|
|
66
|
+
this.assertValidBlockNumber(blockNumber, `${this.dexId} block-event`);
|
|
67
|
+
for (const poolInfo of this.poolInfoMap.values()) {
|
|
68
|
+
const poolAddress = poolInfo.pool_address;
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const last = this.poolLastQuoteTimeMap.get(poolAddress) || 0;
|
|
71
|
+
if (now - last >= this.MIN_QUOTE_INTERVAL_MS) {
|
|
72
|
+
this.poolLastQuoteTimeMap.set(poolAddress, now);
|
|
73
|
+
const result = await this.calculateQuoteForPool(poolInfo, recvBlockTime, blockNumber);
|
|
74
|
+
if (result)
|
|
75
|
+
this.publishQuote(result);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async calculateQuote(eventData) {
|
|
80
|
+
const poolInfo = this.poolInfoMap.get(eventData.pool_address);
|
|
81
|
+
if (!poolInfo)
|
|
82
|
+
return { bundle: null };
|
|
83
|
+
const eventType = eventData.type?.toLowerCase();
|
|
84
|
+
if (eventType === 'pricefeed')
|
|
85
|
+
return { bundle: this.do_quote_v3(poolInfo, eventData) };
|
|
86
|
+
let verifyLog;
|
|
87
|
+
if (eventType === 'swap') {
|
|
88
|
+
const v = this.buildSwapVerify(poolInfo, eventData);
|
|
89
|
+
if (v)
|
|
90
|
+
verifyLog = this.quotePriceVerify.checkSwap(v) || undefined;
|
|
91
|
+
if (!this.debouncedEventTypes.includes('swap'))
|
|
92
|
+
return { bundle: null, verifyLog };
|
|
93
|
+
}
|
|
94
|
+
const blockNumber = eventData.data?.blockNumber;
|
|
95
|
+
this.assertValidBlockNumber(blockNumber, `${this.dexId} ${poolInfo.pool_name} ${eventType}`);
|
|
96
|
+
await this.refreshStateFromEvent(poolInfo, eventData);
|
|
97
|
+
const txIndex = eventData.data?.transactionIndex;
|
|
98
|
+
if (Number.isInteger(txIndex) && this.isStaleUpdate(poolInfo.pool_address, blockNumber, txIndex))
|
|
99
|
+
return { bundle: null, verifyLog };
|
|
100
|
+
const bundle = await this.calculateQuoteForPool(poolInfo, eventData.event_time, blockNumber, eventData);
|
|
101
|
+
return { bundle, verifyLog };
|
|
102
|
+
}
|
|
103
|
+
async calculateQuoteForPool(poolInfo, streamTimestamp, blockNumber, eventData) {
|
|
104
|
+
const { pool_address } = poolInfo;
|
|
105
|
+
const eventDriven = !!eventData && this.debouncedEventTypes.includes(eventData.type?.toLowerCase());
|
|
106
|
+
const source = eventDriven ? `${eventData.provider_id || 'local'}:v2` : 'rpc:v1';
|
|
107
|
+
const trace = new quote_trace_1.QuoteTrace(poolInfo.pool_name, pool_address, blockNumber, source);
|
|
108
|
+
trace.mark('trigger');
|
|
109
|
+
let stage = 'compute';
|
|
110
|
+
try {
|
|
111
|
+
const quote_amount_usd = (0, quote_amount_1.getQuoteAmountUsd)(poolInfo);
|
|
112
|
+
const quoteStartTime = Date.now();
|
|
113
|
+
const txid = eventData ? (eventData.data?.transactionHash || 'block') : 'block';
|
|
114
|
+
const txIndex = eventData?.data?.transactionIndex;
|
|
115
|
+
let askQuote, bidQuote;
|
|
116
|
+
if (eventDriven) {
|
|
117
|
+
;
|
|
118
|
+
[askQuote, bidQuote] = await Promise.all([this.quoteV2(poolInfo, true), this.quoteV2(poolInfo, false)]);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
;
|
|
122
|
+
[askQuote, bidQuote] = await Promise.all([this.quoteV1(poolInfo, true), this.quoteV1(poolInfo, false)]);
|
|
123
|
+
}
|
|
124
|
+
trace.mark('compute');
|
|
125
|
+
const askN = Number(askQuote.price), bidN = Number(bidQuote.price);
|
|
126
|
+
if (askN > 0 && bidN > 0 && askN < bidN) {
|
|
127
|
+
throw new Error(`ask(${askN}) < bid(${bidN}) — 异常价差,丢弃该报价`);
|
|
128
|
+
}
|
|
129
|
+
this.poolLastQuoteTimeMap.set(pool_address, Date.now());
|
|
130
|
+
stage = 'depth';
|
|
131
|
+
const t0a = poolInfo.tokenA?.address, t1a = poolInfo.tokenB?.address;
|
|
132
|
+
const priceMap = (t0a && t1a) ? await this.chain.loadTokenPrices([t0a, t1a]) : new Map();
|
|
133
|
+
const depth = await this.calculateDepth(poolInfo, pool_address, priceMap);
|
|
134
|
+
trace.mark('depth');
|
|
135
|
+
trace.set('ask', askQuote.price);
|
|
136
|
+
trace.set('bid', bidQuote.price);
|
|
137
|
+
trace.set('tiers', depth?.ask?.tiers?.length ?? 0);
|
|
138
|
+
trace.set('quote_amount_usd', quote_amount_usd);
|
|
139
|
+
trace.set('txid', txid);
|
|
140
|
+
return { poolInfo, quote_amount_usd, streamTimestamp, quoteStartTime, blockNumber, txIndex, askQuote, bidQuote, txid, source, depth, priceMap, trace };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
trace.markError(stage, error instanceof Error ? error.message : String(error));
|
|
144
|
+
trace.flush();
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
publishQuote(result) {
|
|
149
|
+
const poolAddr = result.poolInfo.pool_address;
|
|
150
|
+
const isV2 = result.source?.endsWith(':v2');
|
|
151
|
+
let shouldPush = true;
|
|
152
|
+
if (result.source?.endsWith(':v1')) {
|
|
153
|
+
shouldPush = true;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const incomingTx = result.txIndex;
|
|
157
|
+
if (!Number.isInteger(incomingTx)) {
|
|
158
|
+
throw new Error(`[publish] ${result.source} ${poolAddr}: 缺/非法 txIndex=${incomingTx},事件源必须提供块内序号`);
|
|
159
|
+
}
|
|
160
|
+
const tx = incomingTx;
|
|
161
|
+
shouldPush = !this.isStaleUpdate(poolAddr, result.blockNumber, tx);
|
|
162
|
+
if (shouldPush)
|
|
163
|
+
this.lastPublished.set(poolAddr, { block: result.blockNumber, txIndex: tx });
|
|
164
|
+
}
|
|
165
|
+
if (shouldPush) {
|
|
166
|
+
const push = result.source?.includes(':v3') ? this.appConfig.env_args.use_pricefeed_for_orderbook : true;
|
|
167
|
+
(0, ttd_core_1.on_quote_response)({
|
|
168
|
+
appConfig: this.appConfig, poolInfo: result.poolInfo, quoteAmountUsd: result.quote_amount_usd,
|
|
169
|
+
streamTime: result.streamTimestamp, quoteStartTime: result.quoteStartTime, blockNumber: result.blockNumber,
|
|
170
|
+
quotes: [result.askQuote, result.bidQuote], txid: result.txid, pushPriceMessage: push,
|
|
171
|
+
source: result.source, depth: result.depth,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
result.trace?.set('published', shouldPush);
|
|
175
|
+
result.trace?.mark('publish');
|
|
176
|
+
const candidateTiers = isV2 && result.depth ? {
|
|
177
|
+
ask_tiers: result.depth.ask.tiers.map(t => ({ pct: t.pct, price: t.price, amount: t.amount, amount_in: t.amount_in })),
|
|
178
|
+
bid_tiers: result.depth.bid.tiers.map(t => ({ pct: t.pct, price: t.price, amount: t.amount, amount_in: t.amount_in })),
|
|
179
|
+
} : {};
|
|
180
|
+
(0, ttd_core_1.report_quote_candidate)({
|
|
181
|
+
pool_address: poolAddr, pair: result.poolInfo.pair, dex_id: result.poolInfo.dex_id,
|
|
182
|
+
source: result.source || '', block_number: result.blockNumber,
|
|
183
|
+
ask_price: result.askQuote.price, bid_price: result.bidQuote.price, ...candidateTiers,
|
|
184
|
+
});
|
|
185
|
+
const quoteId = result.txid?.slice(0, 10) || `blk:${result.blockNumber}`;
|
|
186
|
+
const tiersArg = isV2 ? { askTiers: result.depth?.ask?.tiers, bidTiers: result.depth?.bid?.tiers } : undefined;
|
|
187
|
+
const t0a = result.poolInfo.tokenA?.address, t1a = result.poolInfo.tokenB?.address;
|
|
188
|
+
const p0 = t0a ? Number(result.priceMap?.get(t0a.toLowerCase())?.price) || 0 : 0;
|
|
189
|
+
const p1 = t1a ? Number(result.priceMap?.get(t1a.toLowerCase())?.price) || 0 : 0;
|
|
190
|
+
this.quotePriceVerify.cacheQuote(poolAddr, quoteId, result.source || '', Number(result.askQuote.price), Number(result.bidQuote.price), result.blockNumber, result.quote_amount_usd, p0, p1, tiersArg);
|
|
191
|
+
result.trace?.mark('verify');
|
|
192
|
+
result.trace?.flush();
|
|
193
|
+
}
|
|
194
|
+
do_quote_v3(poolInfo, eventData) {
|
|
195
|
+
this.assertValidBlockNumber(eventData.data?.blockNumber, `${this.dexId} ${poolInfo.pool_name} pricefeed`);
|
|
196
|
+
const result = (0, price_feed_handler_1.buildQuoteFromPriceFeed)(poolInfo, eventData.data);
|
|
197
|
+
if (!result)
|
|
198
|
+
return null;
|
|
199
|
+
const source = `${eventData.provider_id || 'PriceFeed'}:v3`;
|
|
200
|
+
const trace = new quote_trace_1.QuoteTrace(poolInfo.pool_name, poolInfo.pool_address, eventData.data.blockNumber, source);
|
|
201
|
+
trace.mark('trigger');
|
|
202
|
+
trace.mark('compute');
|
|
203
|
+
trace.set('ask', result.askQuote.price);
|
|
204
|
+
trace.set('bid', result.bidQuote.price);
|
|
205
|
+
return {
|
|
206
|
+
poolInfo, quote_amount_usd: poolInfo.quote_amount_usd, streamTimestamp: eventData.event_time,
|
|
207
|
+
quoteStartTime: Date.now(), blockNumber: eventData.data.blockNumber,
|
|
208
|
+
txIndex: eventData.data?.transactionIndex,
|
|
209
|
+
askQuote: result.askQuote, bidQuote: result.bidQuote,
|
|
210
|
+
txid: `PriceFeed - ${eventData.provider_id}`, source, trace,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.AbstractDexQuote = AbstractDexQuote;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CHAIN_ID, DEX_ID, StandardPoolInfoType } from '@clonegod/ttd-core';
|
|
2
|
+
export interface ChainOps {
|
|
3
|
+
readonly chainId: CHAIN_ID;
|
|
4
|
+
isValidPoolAddress(addr: string): boolean;
|
|
5
|
+
assertContractDeployed(contractAddress: string): Promise<void>;
|
|
6
|
+
loadTokenPrices(addresses: string[]): Promise<Map<string, {
|
|
7
|
+
price: string;
|
|
8
|
+
} | undefined>>;
|
|
9
|
+
subscribeNewBlock(handler: (raw: string) => void): void;
|
|
10
|
+
subscribePoolEvents(dexId: DEX_ID, pools: StandardPoolInfoType[], handler: (evt: any) => void): void;
|
|
11
|
+
}
|
|
@@ -69,31 +69,6 @@ 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
|
-
}
|
|
97
72
|
function assembleEntry(tiers) {
|
|
98
73
|
const defaultTier = tiers.find(t => t.pct === ttd_core_2.DEFAULT_TIER_PCT) ?? tiers[0];
|
|
99
74
|
return {
|
|
@@ -179,8 +154,6 @@ function buildClmmDepth(input) {
|
|
|
179
154
|
tick_move: bidTickMove,
|
|
180
155
|
});
|
|
181
156
|
}
|
|
182
|
-
assertTierInvariant(poolInfo.pool_name || poolAddress, 'ASK', askTiers);
|
|
183
|
-
assertTierInvariant(poolInfo.pool_name || poolAddress, 'BID', bidTiers);
|
|
184
157
|
const depth = {
|
|
185
158
|
mid_price: midPrice,
|
|
186
159
|
fee_rate_bps: feeRateBps,
|
|
@@ -281,8 +254,6 @@ function buildAmmDepth(input) {
|
|
|
281
254
|
fee_usd: bidGross.feeAmount * basePriceUsd,
|
|
282
255
|
});
|
|
283
256
|
}
|
|
284
|
-
assertTierInvariant(poolInfo.pool_name || 'amm-pool', 'ASK', askTiers);
|
|
285
|
-
assertTierInvariant(poolInfo.pool_name || 'amm-pool', 'BID', bidTiers);
|
|
286
257
|
return {
|
|
287
258
|
mid_price: midPrice,
|
|
288
259
|
fee_rate_bps: feeRateBps,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import { CHAIN_ID, DEX_ID, StandardPoolInfoType } from '@clonegod/ttd-core';
|
|
3
|
+
import { ChainOps } from './chain_ops';
|
|
4
|
+
type TokenPriceMap = Map<string, {
|
|
5
|
+
price: string;
|
|
6
|
+
} | undefined>;
|
|
7
|
+
interface NewBlockSubscriber {
|
|
8
|
+
subscribe_new_block(chainId: CHAIN_ID, handler: (raw: string) => any): void;
|
|
9
|
+
}
|
|
10
|
+
export interface EvmChainOpsArgs {
|
|
11
|
+
provider: ethers.providers.Provider;
|
|
12
|
+
chainId: CHAIN_ID;
|
|
13
|
+
loadTokenPrices: (addresses: string[]) => Promise<TokenPriceMap>;
|
|
14
|
+
streamQuoteWsHost: string;
|
|
15
|
+
newBlockSubscriber: NewBlockSubscriber;
|
|
16
|
+
}
|
|
17
|
+
export declare class EvmChainOps implements ChainOps {
|
|
18
|
+
readonly provider: ethers.providers.Provider;
|
|
19
|
+
readonly chainId: CHAIN_ID;
|
|
20
|
+
private readonly priceFn;
|
|
21
|
+
private readonly streamQuoteWsHost;
|
|
22
|
+
private readonly newBlockSubscriber;
|
|
23
|
+
constructor(args: EvmChainOpsArgs);
|
|
24
|
+
isValidPoolAddress(addr: string): boolean;
|
|
25
|
+
assertContractDeployed(contractAddress: string): Promise<void>;
|
|
26
|
+
loadTokenPrices(addresses: string[]): Promise<TokenPriceMap>;
|
|
27
|
+
subscribeNewBlock(handler: (raw: string) => void): void;
|
|
28
|
+
subscribePoolEvents(dexId: DEX_ID, pools: StandardPoolInfoType[], handler: (evt: any) => void): void;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvmChainOps = void 0;
|
|
4
|
+
const ethers_1 = require("ethers");
|
|
5
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
6
|
+
class EvmChainOps {
|
|
7
|
+
constructor(args) {
|
|
8
|
+
this.provider = args.provider;
|
|
9
|
+
this.chainId = args.chainId;
|
|
10
|
+
this.priceFn = args.loadTokenPrices;
|
|
11
|
+
this.streamQuoteWsHost = args.streamQuoteWsHost;
|
|
12
|
+
this.newBlockSubscriber = args.newBlockSubscriber;
|
|
13
|
+
}
|
|
14
|
+
isValidPoolAddress(addr) {
|
|
15
|
+
return ethers_1.ethers.utils.isAddress(addr) || /^0x[0-9a-fA-F]{64}$/.test(addr);
|
|
16
|
+
}
|
|
17
|
+
async assertContractDeployed(contractAddress) {
|
|
18
|
+
if (!contractAddress)
|
|
19
|
+
return;
|
|
20
|
+
const code = await this.provider.getCode(contractAddress);
|
|
21
|
+
if (!code || code === '0x') {
|
|
22
|
+
throw new Error(`[contract-deployed] EVM 合约无 code: ${contractAddress}(地址写错 / 抄了别的链?)`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
loadTokenPrices(addresses) {
|
|
26
|
+
return this.priceFn(addresses);
|
|
27
|
+
}
|
|
28
|
+
subscribeNewBlock(handler) {
|
|
29
|
+
this.newBlockSubscriber.subscribe_new_block(this.chainId, handler);
|
|
30
|
+
}
|
|
31
|
+
subscribePoolEvents(dexId, pools, handler) {
|
|
32
|
+
const ws_url = `ws://${this.streamQuoteWsHost}:${ttd_core_1.SERVICE_PORT.STREAM_QUOTE_WS}`;
|
|
33
|
+
const ws_client = new ttd_core_1.WebSocketClient(ws_url);
|
|
34
|
+
ws_client.onOpen(() => {
|
|
35
|
+
pools.forEach((poolInfo) => {
|
|
36
|
+
ws_client.send(JSON.stringify({
|
|
37
|
+
dex_id: dexId,
|
|
38
|
+
pool_name: poolInfo.pool_name,
|
|
39
|
+
pair: poolInfo.pair,
|
|
40
|
+
pool_address: poolInfo.pool_address,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
(0, ttd_core_1.log_info)(`已订阅 stream-quote 池事件, dex=${dexId}, pools: ${pools.length}`);
|
|
44
|
+
});
|
|
45
|
+
ws_client.onMessage((evt) => handler(evt));
|
|
46
|
+
ws_client.connect();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.EvmChainOps = EvmChainOps;
|
package/dist/quote/index.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ export * from './verify';
|
|
|
6
6
|
export * from './price_feed_handler';
|
|
7
7
|
export * from './quote_amount';
|
|
8
8
|
export * from './preload_token_prices';
|
|
9
|
+
export * from './abstract_dex_quote';
|
|
10
|
+
export * from './chain_ops';
|
|
11
|
+
export * from './evm_chain_ops';
|
|
12
|
+
export * from './quote_trace';
|
package/dist/quote/index.js
CHANGED
|
@@ -22,3 +22,7 @@ __exportStar(require("./verify"), exports);
|
|
|
22
22
|
__exportStar(require("./price_feed_handler"), exports);
|
|
23
23
|
__exportStar(require("./quote_amount"), exports);
|
|
24
24
|
__exportStar(require("./preload_token_prices"), exports);
|
|
25
|
+
__exportStar(require("./abstract_dex_quote"), exports);
|
|
26
|
+
__exportStar(require("./chain_ops"), exports);
|
|
27
|
+
__exportStar(require("./evm_chain_ops"), exports);
|
|
28
|
+
__exportStar(require("./quote_trace"), exports);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class QuoteTrace {
|
|
2
|
+
readonly poolName: string;
|
|
3
|
+
readonly poolAddress: string;
|
|
4
|
+
blockNumber: number;
|
|
5
|
+
source: string;
|
|
6
|
+
private startTime;
|
|
7
|
+
private marks;
|
|
8
|
+
private data;
|
|
9
|
+
private error;
|
|
10
|
+
constructor(poolName: string, poolAddress: string, blockNumber?: number, source?: string);
|
|
11
|
+
mark(name: string): void;
|
|
12
|
+
set(key: string, value: any): void;
|
|
13
|
+
markError(stage: string, message: string): void;
|
|
14
|
+
private buildTimeline;
|
|
15
|
+
flush(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuoteTrace = void 0;
|
|
4
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
const logger = (0, ttd_core_1.createLogger)(__filename);
|
|
6
|
+
class QuoteTrace {
|
|
7
|
+
constructor(poolName, poolAddress, blockNumber = 0, source = '') {
|
|
8
|
+
this.poolName = poolName;
|
|
9
|
+
this.poolAddress = poolAddress;
|
|
10
|
+
this.blockNumber = blockNumber;
|
|
11
|
+
this.source = source;
|
|
12
|
+
this.startTime = Date.now();
|
|
13
|
+
this.marks = [];
|
|
14
|
+
this.data = {};
|
|
15
|
+
this.error = null;
|
|
16
|
+
}
|
|
17
|
+
mark(name) {
|
|
18
|
+
this.marks.push({ name, elapsed: Date.now() - this.startTime });
|
|
19
|
+
}
|
|
20
|
+
set(key, value) { this.data[key] = value; }
|
|
21
|
+
markError(stage, message) { this.error = { stage, message }; }
|
|
22
|
+
buildTimeline() {
|
|
23
|
+
if (this.marks.length === 0)
|
|
24
|
+
return '(no marks)';
|
|
25
|
+
let prev = 0;
|
|
26
|
+
const parts = this.marks.map(m => { const d = m.elapsed - prev; prev = m.elapsed; return `${m.name}(${d}ms)`; });
|
|
27
|
+
return `${parts.join(' -> ')} = ${this.marks[this.marks.length - 1].elapsed}ms`;
|
|
28
|
+
}
|
|
29
|
+
flush() {
|
|
30
|
+
const timeline = this.buildTimeline();
|
|
31
|
+
if (this.error) {
|
|
32
|
+
logger.error(`[QUOTE FAILED] ${this.poolName} | stage=${this.error.stage} | source=${this.source} | ${this.error.message}`, new Error(this.error.message));
|
|
33
|
+
logger.info(`[QUOTE FAILED detail] ${this.poolName}`, { timeline, error_stage: this.error.stage, source: this.source, ...this.data });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
logger.debug(`[QUOTE OK] ${this.poolName} blk=${this.blockNumber} source=${this.source}`, { timeline, ...this.data });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.QuoteTrace = QuoteTrace;
|
|
@@ -24,7 +24,7 @@ export declare class QuotePriceVerify {
|
|
|
24
24
|
askTiers?: QuoteTier[];
|
|
25
25
|
bidTiers?: QuoteTier[];
|
|
26
26
|
}): void;
|
|
27
|
-
checkSwap(params: CheckSwapParams): void;
|
|
27
|
+
checkSwap(params: CheckSwapParams): string | void;
|
|
28
28
|
private deriveSwapDirection;
|
|
29
29
|
private compareAndLog;
|
|
30
30
|
}
|
|
@@ -131,6 +131,7 @@ class QuotePriceVerify {
|
|
|
131
131
|
sources[r.source] = {
|
|
132
132
|
ask: r.cached.askPrice,
|
|
133
133
|
bid: r.cached.bidPrice,
|
|
134
|
+
matched_ref_price: r.refPrice,
|
|
134
135
|
diff_bps: parseFloat(r.diff_bps.toFixed(1)),
|
|
135
136
|
quote_block: r.cached.referenceBlock,
|
|
136
137
|
...(r.tierInfo && {
|
|
@@ -140,7 +141,7 @@ class QuotePriceVerify {
|
|
|
140
141
|
}),
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
|
-
this.compareAndLog(poolAddress, params.poolName, poolInfo, primary.cached, swapData.inputTokenAddress, swapData.outputTokenAddress, swapData.inputAmountUi, swapData.outputAmountUi, primary.refPrice, execPrice, primary.cached.token0PriceUsd, primary.cached.token1PriceUsd, token0Address, blockNumber, txHash, sources, primary.tierInfo);
|
|
144
|
+
return this.compareAndLog(poolAddress, params.poolName, poolInfo, primary.cached, swapData.inputTokenAddress, swapData.outputTokenAddress, swapData.inputAmountUi, swapData.outputAmountUi, primary.refPrice, execPrice, primary.cached.token0PriceUsd, primary.cached.token1PriceUsd, token0Address, blockNumber, txHash, sources, primary.tierInfo);
|
|
144
145
|
}
|
|
145
146
|
deriveSwapDirection(params) {
|
|
146
147
|
const { amount0, amount1, token0Address, token1Address, token0Decimals, token1Decimals } = params;
|
|
@@ -202,7 +203,6 @@ class QuotePriceVerify {
|
|
|
202
203
|
const swapTag = `swap[blk:${swapBlk}${swapTx}]`;
|
|
203
204
|
const priceLabel = isBuy ? 'ask' : 'bid';
|
|
204
205
|
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}`;
|
|
205
|
-
(0, ttd_core_1.log_info)(msg);
|
|
206
206
|
try {
|
|
207
207
|
(0, ttd_core_1.report_data_to_analyze)('QuoteVerify', {
|
|
208
208
|
pool_address: poolAddress,
|
|
@@ -235,6 +235,7 @@ class QuotePriceVerify {
|
|
|
235
235
|
}
|
|
236
236
|
catch (_) {
|
|
237
237
|
}
|
|
238
|
+
return msg;
|
|
238
239
|
}
|
|
239
240
|
}
|
|
240
241
|
exports.QuotePriceVerify = QuotePriceVerify;
|
|
@@ -64,8 +64,8 @@ function buildTradeConfig(envArgs) {
|
|
|
64
64
|
return {
|
|
65
65
|
vaultAddress: vaultWallet.public_key,
|
|
66
66
|
executorIds: {
|
|
67
|
-
pancake: ethers_compat_1.ethersCompat.id(envArgs.pancake_executor_id),
|
|
68
|
-
uniswap: ethers_compat_1.ethersCompat.id(envArgs.uniswap_executor_id),
|
|
67
|
+
pancake: ethers_compat_1.ethersCompat.id(envArgs.pancake_executor_id.toUpperCase()),
|
|
68
|
+
uniswap: ethers_compat_1.ethersCompat.id(envArgs.uniswap_executor_id.toUpperCase()),
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
}
|
|
@@ -93,11 +93,11 @@ class AbstractDexTrade extends ttd_core_1.AbastrcatTrade {
|
|
|
93
93
|
gasOptions: {
|
|
94
94
|
gasLimit: env.gas_limit,
|
|
95
95
|
defaultGasPriceGwei: env.gas_price_gwei,
|
|
96
|
-
maxGasPriceGwei:
|
|
97
|
-
maxFeePerGasGwei:
|
|
98
|
-
maxPriorityFeePerGasGwei:
|
|
96
|
+
maxGasPriceGwei: env.gas_max_price_gwei,
|
|
97
|
+
maxFeePerGasGwei: env.gas_max_fee_per_gas_gwei,
|
|
98
|
+
maxPriorityFeePerGasGwei: env.gas_max_priority_fee_gwei,
|
|
99
99
|
defaultTipAmountGwei: env.tip_amount_gwei,
|
|
100
|
-
maxTipAmountGwei:
|
|
100
|
+
maxTipAmountGwei: env.gas_max_tip_amount_gwei,
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
103
|
this.tradeConfig = buildTradeConfig(env);
|
|
@@ -155,10 +155,13 @@ class CallerManager {
|
|
|
155
155
|
async readNonce(callerAddr) {
|
|
156
156
|
const nonceKey = this.getNonceRedisKey();
|
|
157
157
|
const nonceStr = await this.redis.hget(nonceKey, callerAddr);
|
|
158
|
-
if (nonceStr
|
|
159
|
-
|
|
158
|
+
if (nonceStr !== null && nonceStr !== undefined) {
|
|
159
|
+
return parseInt(nonceStr, 10);
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
const chainNonce = await this.config.provider.getTransactionCount(callerAddr, 'pending');
|
|
162
|
+
await this.setNonce(callerAddr, chainNonce);
|
|
163
|
+
logger.warn(`readNonce: ${callerAddr} 不在 Redis,已回链自愈 nonce=${chainNonce}(新 caller / 未成交 / TTL 过期)`);
|
|
164
|
+
return chainNonce;
|
|
162
165
|
}
|
|
163
166
|
async confirmNonce(address, confirmedNonce) {
|
|
164
167
|
const current = await this.getNonce(address);
|
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.78",
|
|
4
4
|
"description": "BSC common library",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"push": "npm run build && npm publish"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@clonegod/ttd-core": "3.1.
|
|
18
|
-
"axios": "1.
|
|
17
|
+
"@clonegod/ttd-core": "3.1.82",
|
|
18
|
+
"axios": "1.17.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"ethers": "^5.8.0",
|
|
21
21
|
"secp256k1": "^5.0.1"
|