@clonegod/ttd-bsc-common 3.1.77 → 3.1.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.
@@ -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 直发用)' },
@@ -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 PANCAKE_V3_ROUTER_ABI: string[];
3
+ export declare const PancakeV3PoolABI: string[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PANCAKE_V3_ROUTER_ABI = exports.PANCAKE_V2_ROUTER_ABI = exports.PANCAKE_PAIR_FULL_ABI = exports.ERC20_ABI = void 0;
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.PANCAKE_V3_ROUTER_ABI = [
64
- 'function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)',
65
- 'function exactInput(tuple(bytes path, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum)) external payable returns (uint256 amountOut)',
66
- 'function exactOutputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountIn)',
67
- 'function exactOutput(tuple(bytes path, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum)) external payable returns (uint256 amountIn)'
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[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PancakeV3PoolABI = exports.EVENT_SIGNATURES = 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;
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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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;
@@ -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';
@@ -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
  }
@@ -7,26 +7,26 @@ function pickMatchingTier(tiers, actualAmountIn) {
7
7
  if (!tiers || tiers.length === 0)
8
8
  return null;
9
9
  if (tiers.length === 1)
10
- return { tier: tiers[0], mode: 'single_tier' };
10
+ return { tier: tiers[0], mode: 'single_tier', lo_pct: tiers[0].pct, hi_pct: tiers[0].pct };
11
11
  const sorted = [...tiers].sort((a, b) => a.amount_in - b.amount_in);
12
12
  const top = sorted[sorted.length - 1];
13
13
  if (actualAmountIn < sorted[0].amount_in) {
14
- return { tier: sorted[0], mode: 'extrapolated_low' };
14
+ return { tier: sorted[0], mode: 'extrapolated_low', lo_pct: null, hi_pct: sorted[0].pct };
15
15
  }
16
16
  if (actualAmountIn > top.amount_in) {
17
- return { tier: top, mode: 'extrapolated_high' };
17
+ return { tier: top, mode: 'extrapolated_high', lo_pct: top.pct, hi_pct: null };
18
18
  }
19
19
  for (let i = 0; i < sorted.length - 1; i++) {
20
20
  const lower = sorted[i];
21
21
  const upper = sorted[i + 1];
22
22
  if (actualAmountIn >= lower.amount_in && actualAmountIn <= upper.amount_in) {
23
23
  if (actualAmountIn === lower.amount_in)
24
- return { tier: lower, mode: 'exact' };
24
+ return { tier: lower, mode: 'exact', lo_pct: lower.pct, hi_pct: lower.pct };
25
25
  if (actualAmountIn === upper.amount_in)
26
- return { tier: upper, mode: 'exact' };
26
+ return { tier: upper, mode: 'exact', lo_pct: upper.pct, hi_pct: upper.pct };
27
27
  const span = upper.amount_in - lower.amount_in;
28
28
  if (span <= 0)
29
- return { tier: lower, mode: 'exact' };
29
+ return { tier: lower, mode: 'exact', lo_pct: lower.pct, hi_pct: lower.pct };
30
30
  const ratio = (actualAmountIn - lower.amount_in) / span;
31
31
  const lerp = (a, b) => a + ratio * (b - a);
32
32
  return {
@@ -40,10 +40,12 @@ function pickMatchingTier(tiers, actualAmountIn) {
40
40
  fee_usd: lerp(lower.fee_usd, upper.fee_usd),
41
41
  },
42
42
  mode: 'interpolated',
43
+ lo_pct: lower.pct,
44
+ hi_pct: upper.pct,
43
45
  };
44
46
  }
45
47
  }
46
- return { tier: top, mode: 'extrapolated_high' };
48
+ return { tier: top, mode: 'extrapolated_high', lo_pct: top.pct, hi_pct: null };
47
49
  }
48
50
  class QuotePriceVerify {
49
51
  constructor() {
@@ -111,6 +113,8 @@ class QuotePriceVerify {
111
113
  matched_pct: parseFloat(match.tier.pct.toFixed(4)),
112
114
  mode: match.mode,
113
115
  tier_amount_in: match.tier.amount_in,
116
+ lo_pct: match.lo_pct,
117
+ hi_pct: match.hi_pct,
114
118
  };
115
119
  }
116
120
  else {
@@ -131,6 +135,7 @@ class QuotePriceVerify {
131
135
  sources[r.source] = {
132
136
  ask: r.cached.askPrice,
133
137
  bid: r.cached.bidPrice,
138
+ matched_ref_price: r.refPrice,
134
139
  diff_bps: parseFloat(r.diff_bps.toFixed(1)),
135
140
  quote_block: r.cached.referenceBlock,
136
141
  ...(r.tierInfo && {
@@ -140,7 +145,7 @@ class QuotePriceVerify {
140
145
  }),
141
146
  };
142
147
  }
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);
148
+ 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
149
  }
145
150
  deriveSwapDirection(params) {
146
151
  const { amount0, amount1, token0Address, token1Address, token0Decimals, token1Decimals } = params;
@@ -202,7 +207,6 @@ class QuotePriceVerify {
202
207
  const swapTag = `swap[blk:${swapBlk}${swapTx}]`;
203
208
  const priceLabel = isBuy ? 'ask' : 'bid';
204
209
  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
210
  try {
207
211
  (0, ttd_core_1.report_data_to_analyze)('QuoteVerify', {
208
212
  pool_address: poolAddress,
@@ -230,11 +234,14 @@ class QuotePriceVerify {
230
234
  matched_tier_pct: primaryTierInfo.matched_pct,
231
235
  matched_tier_mode: primaryTierInfo.mode,
232
236
  matched_tier_amount_in: primaryTierInfo.tier_amount_in,
237
+ matched_tier_lo_pct: primaryTierInfo.lo_pct,
238
+ matched_tier_hi_pct: primaryTierInfo.hi_pct,
233
239
  }),
234
240
  });
235
241
  }
236
242
  catch (_) {
237
243
  }
244
+ return msg;
238
245
  }
239
246
  }
240
247
  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: 10,
97
- maxFeePerGasGwei: 10,
98
- maxPriorityFeePerGasGwei: 3,
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: 500000,
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 === null || nonceStr === undefined) {
159
- throw new Error(`Caller ${callerAddr} nonce not found in Redis, stream-trade may not be running`);
158
+ if (nonceStr !== null && nonceStr !== undefined) {
159
+ return parseInt(nonceStr, 10);
160
160
  }
161
- return parseInt(nonceStr, 10);
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.77",
3
+ "version": "3.1.79",
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.79",
18
- "axios": "1.15.0",
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"