@clonegod/ttd-sui-common 2.0.5 → 2.0.7
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/grpc/grpc-connection.js +3 -1
- package/dist/grpc/grpc_provider_registry.d.ts +2 -2
- package/dist/grpc/grpc_provider_registry.js +13 -3
- package/dist/quote/abstract_dex_quote.js +3 -0
- package/dist/quote/depth/clmm_depth_calculator.d.ts +28 -0
- package/dist/quote/depth/clmm_depth_calculator.js +114 -0
- package/dist/quote/depth/index.d.ts +24 -0
- package/dist/quote/depth/index.js +99 -0
- package/dist/quote/index.d.ts +1 -0
- package/dist/quote/index.js +1 -0
- package/dist/trade/abstract_sui_dex_trade.js +1 -1
- package/dist/trade/check/tx_result_checker.d.ts +1 -0
- package/dist/trade/check/tx_result_checker.js +20 -0
- package/dist/trade/executor/central_executor.d.ts +16 -1
- package/dist/trade/executor/central_executor.js +252 -59
- package/dist/trade/executor/coin_cache.d.ts +7 -1
- package/dist/trade/executor/coin_cache.js +44 -2
- package/dist/trade/executor/core_channel.js +3 -0
- package/dist/trade/parse/sui_tx_parser.js +10 -2
- package/dist/trade/tx_result_channel.d.ts +3 -0
- package/package.json +1 -1
|
@@ -64,9 +64,11 @@ class GrpcConnection {
|
|
|
64
64
|
'grpc.max_reconnect_backoff_ms': 30000,
|
|
65
65
|
'grpc.enable_retries': 1,
|
|
66
66
|
'grpc.retry_buffer_size': 256 * 1024,
|
|
67
|
+
'grpc-node.flow_control_window': 8 * 1024 * 1024,
|
|
68
|
+
'grpc-node.max_session_memory': 64,
|
|
69
|
+
'grpc.default_compression_algorithm': 2,
|
|
67
70
|
'grpc.http2.max_frame_size': 16384,
|
|
68
71
|
'grpc.http2.max_header_list_size': 16384,
|
|
69
|
-
'grpc.http2.initial_window_size': 65535,
|
|
70
72
|
}; }
|
|
71
73
|
getChannel() {
|
|
72
74
|
if (!this.channel) {
|
|
@@ -9,6 +9,6 @@ interface RedisLike {
|
|
|
9
9
|
hgetall(key: string): Promise<Record<string, string> | null>;
|
|
10
10
|
subscribe(channel: string, listener: (message: string) => void): Promise<void> | void;
|
|
11
11
|
}
|
|
12
|
-
export declare function resolveGrpcProvider(redis: RedisLike, chainId: string): Promise<GrpcProviderConfig>;
|
|
13
|
-
export declare function watchGrpcProviderChange(redis: RedisLike, chainId: string, active: GrpcProviderConfig, onChange: (next: GrpcProviderConfig | null) => void): void;
|
|
12
|
+
export declare function resolveGrpcProvider(redis: RedisLike, chainId: string, preferId?: string): Promise<GrpcProviderConfig>;
|
|
13
|
+
export declare function watchGrpcProviderChange(redis: RedisLike, chainId: string, active: GrpcProviderConfig, onChange: (next: GrpcProviderConfig | null) => void, preferId?: string): void;
|
|
14
14
|
export {};
|
|
@@ -11,7 +11,7 @@ function getRpcProvidersKey(chainId) {
|
|
|
11
11
|
function getRpcConfigChangeChannel(chainId) {
|
|
12
12
|
return `${chainId.toLowerCase()}:rpc:config:change`;
|
|
13
13
|
}
|
|
14
|
-
async function resolveGrpcProvider(redis, chainId) {
|
|
14
|
+
async function resolveGrpcProvider(redis, chainId, preferId) {
|
|
15
15
|
const key = getRpcProvidersKey(chainId);
|
|
16
16
|
const map = (await redis.hgetall(key)) || {};
|
|
17
17
|
const candidates = [];
|
|
@@ -28,6 +28,16 @@ async function resolveGrpcProvider(redis, chainId) {
|
|
|
28
28
|
if (candidates.length === 0) {
|
|
29
29
|
throw new Error(`[grpc-registry] ${key} 无 enabled 的 grpc provider —— 请在 trade-analyze Config/RPC 页新增 type=grpc 的 Provider(grpc_endpoint 填 host:port,auth_token 填 x-token)`);
|
|
30
30
|
}
|
|
31
|
+
if (preferId) {
|
|
32
|
+
const preferred = candidates.find(c => c.id === preferId);
|
|
33
|
+
if (!preferred) {
|
|
34
|
+
throw new Error(`[grpc-registry] 指定的 grpc provider '${preferId}' 不存在或未 enabled(${key} 可用: ${candidates.map(c => c.id).join(',')})`);
|
|
35
|
+
}
|
|
36
|
+
if (!preferred.auth_token)
|
|
37
|
+
(0, dist_1.log_warn)(`[grpc-registry] provider ${preferred.id} auth_token 为空,按无鉴权端点连接`);
|
|
38
|
+
(0, dist_1.log_info)(`[grpc-registry] 使用指定 provider: ${preferred.id}`);
|
|
39
|
+
return { id: preferred.id, endpoint: preferred.grpc_endpoint, token: preferred.auth_token || '' };
|
|
40
|
+
}
|
|
31
41
|
candidates.sort((a, b) => (b.default_for_quote === true ? 1 : 0) - (a.default_for_quote === true ? 1 : 0)
|
|
32
42
|
|| a.id.localeCompare(b.id));
|
|
33
43
|
const picked = candidates[0];
|
|
@@ -39,13 +49,13 @@ async function resolveGrpcProvider(redis, chainId) {
|
|
|
39
49
|
}
|
|
40
50
|
return { id: picked.id, endpoint: picked.grpc_endpoint, token: picked.auth_token || '' };
|
|
41
51
|
}
|
|
42
|
-
function watchGrpcProviderChange(redis, chainId, active, onChange) {
|
|
52
|
+
function watchGrpcProviderChange(redis, chainId, active, onChange, preferId) {
|
|
43
53
|
const channel = getRpcConfigChangeChannel(chainId);
|
|
44
54
|
redis.subscribe(channel, (message) => {
|
|
45
55
|
void (async () => {
|
|
46
56
|
let next;
|
|
47
57
|
try {
|
|
48
|
-
next = await resolveGrpcProvider(redis, chainId);
|
|
58
|
+
next = await resolveGrpcProvider(redis, chainId, preferId);
|
|
49
59
|
}
|
|
50
60
|
catch {
|
|
51
61
|
next = null;
|
|
@@ -173,6 +173,9 @@ class AbstractDexQuote {
|
|
|
173
173
|
streamTime: result.streamTimestamp, quoteStartTime: result.quoteStartTime, blockNumber: result.blockNumber,
|
|
174
174
|
quotes: [result.askQuote, result.bidQuote], txid: result.txid, source: result.source, depth: result.depth,
|
|
175
175
|
});
|
|
176
|
+
if (isV2) {
|
|
177
|
+
(0, dist_1.log_info)(`[QUOTE OK v2] ${result.poolInfo.pool_name} ask=${result.askQuote.price} bid=${result.bidQuote.price} blk=${result.blockNumber} tx=${result.txIndex} ${result.txid}`);
|
|
178
|
+
}
|
|
176
179
|
}
|
|
177
180
|
result.trace?.set('published', shouldPush);
|
|
178
181
|
result.trace?.mark('publish');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DepthTick {
|
|
2
|
+
index: number;
|
|
3
|
+
liquidityNet: bigint;
|
|
4
|
+
sqrtPriceX64: bigint;
|
|
5
|
+
}
|
|
6
|
+
export interface ClmmDepthInput {
|
|
7
|
+
sqrtPriceX64: bigint;
|
|
8
|
+
currentTick: number;
|
|
9
|
+
liquidity: bigint;
|
|
10
|
+
ticks: DepthTick[];
|
|
11
|
+
zeroForOne: boolean;
|
|
12
|
+
targetBps: number;
|
|
13
|
+
inputDecimals: number;
|
|
14
|
+
outputDecimals: number;
|
|
15
|
+
}
|
|
16
|
+
export interface DepthResult {
|
|
17
|
+
amountInWei: bigint;
|
|
18
|
+
amountIn: number;
|
|
19
|
+
amountOutWei: bigint;
|
|
20
|
+
amountOut: number;
|
|
21
|
+
currentTick: number;
|
|
22
|
+
targetTick: number;
|
|
23
|
+
targetSqrtPriceX64: bigint;
|
|
24
|
+
}
|
|
25
|
+
export declare function calculateClmmDepth(input: ClmmDepthInput): DepthResult;
|
|
26
|
+
export declare function priceFromSqrtX64(sqrtPriceX64: bigint, baseDecimals: number, quoteDecimals: number, baseIsToken0: boolean): number;
|
|
27
|
+
export declare function computeTargetSqrtX64(currentSqrtPriceX64: bigint, bps: number, zeroForOne: boolean): bigint;
|
|
28
|
+
export declare function bigIntSqrt(n: bigint): bigint;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateClmmDepth = calculateClmmDepth;
|
|
4
|
+
exports.priceFromSqrtX64 = priceFromSqrtX64;
|
|
5
|
+
exports.computeTargetSqrtX64 = computeTargetSqrtX64;
|
|
6
|
+
exports.bigIntSqrt = bigIntSqrt;
|
|
7
|
+
const Q64 = 1n << 64n;
|
|
8
|
+
function calculateClmmDepth(input) {
|
|
9
|
+
const { currentTick, zeroForOne, targetBps, inputDecimals, outputDecimals } = input;
|
|
10
|
+
const currentSqrtPriceX64 = input.sqrtPriceX64;
|
|
11
|
+
let liquidity = input.liquidity;
|
|
12
|
+
if (liquidity <= 0n) {
|
|
13
|
+
return { amountInWei: 0n, amountIn: 0, amountOutWei: 0n, amountOut: 0, currentTick, targetTick: currentTick, targetSqrtPriceX64: currentSqrtPriceX64 };
|
|
14
|
+
}
|
|
15
|
+
const targetSqrtPriceX64 = computeTargetSqrtX64(currentSqrtPriceX64, targetBps, zeroForOne);
|
|
16
|
+
const ticksToTraverse = getTicksBetween(input.ticks, currentTick, zeroForOne);
|
|
17
|
+
let totalInput = 0n;
|
|
18
|
+
let totalOutput = 0n;
|
|
19
|
+
let sqrtPriceCursor = currentSqrtPriceX64;
|
|
20
|
+
for (const tick of ticksToTraverse) {
|
|
21
|
+
const sqrtPriceAtTick = tick.sqrtPriceX64;
|
|
22
|
+
if (zeroForOne && sqrtPriceAtTick <= targetSqrtPriceX64)
|
|
23
|
+
break;
|
|
24
|
+
if (!zeroForOne && sqrtPriceAtTick >= targetSqrtPriceX64)
|
|
25
|
+
break;
|
|
26
|
+
if (liquidity > 0n) {
|
|
27
|
+
totalInput += calcInputForRange(sqrtPriceCursor, sqrtPriceAtTick, liquidity, zeroForOne);
|
|
28
|
+
totalOutput += calcOutputForRange(sqrtPriceCursor, sqrtPriceAtTick, liquidity, zeroForOne);
|
|
29
|
+
}
|
|
30
|
+
let liquidityNet = tick.liquidityNet;
|
|
31
|
+
if (zeroForOne)
|
|
32
|
+
liquidityNet = -liquidityNet;
|
|
33
|
+
liquidity = liquidity + liquidityNet;
|
|
34
|
+
if (liquidity < 0n)
|
|
35
|
+
liquidity = 0n;
|
|
36
|
+
sqrtPriceCursor = sqrtPriceAtTick;
|
|
37
|
+
}
|
|
38
|
+
if (liquidity > 0n && sqrtPriceCursor !== targetSqrtPriceX64) {
|
|
39
|
+
totalInput += calcInputForRange(sqrtPriceCursor, targetSqrtPriceX64, liquidity, zeroForOne);
|
|
40
|
+
totalOutput += calcOutputForRange(sqrtPriceCursor, targetSqrtPriceX64, liquidity, zeroForOne);
|
|
41
|
+
}
|
|
42
|
+
const amountIn = Number(totalInput) / Math.pow(10, inputDecimals);
|
|
43
|
+
const amountOut = Number(totalOutput) / Math.pow(10, outputDecimals);
|
|
44
|
+
const sqrtPriceFloat = Number(targetSqrtPriceX64) / Number(Q64);
|
|
45
|
+
const targetTick = Math.floor(Math.log(sqrtPriceFloat * sqrtPriceFloat) / Math.log(1.0001));
|
|
46
|
+
return { amountInWei: totalInput, amountIn, amountOutWei: totalOutput, amountOut, currentTick, targetTick, targetSqrtPriceX64 };
|
|
47
|
+
}
|
|
48
|
+
function priceFromSqrtX64(sqrtPriceX64, baseDecimals, quoteDecimals, baseIsToken0) {
|
|
49
|
+
const sp = Number(sqrtPriceX64) / Number(Q64);
|
|
50
|
+
const priceToken1PerToken0 = sp * sp;
|
|
51
|
+
if (baseIsToken0) {
|
|
52
|
+
return priceToken1PerToken0 * Math.pow(10, baseDecimals - quoteDecimals);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
return (1 / priceToken1PerToken0) * Math.pow(10, baseDecimals - quoteDecimals);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function computeTargetSqrtX64(currentSqrtPriceX64, bps, zeroForOne) {
|
|
59
|
+
const PRECISION = 10n ** 18n;
|
|
60
|
+
const bpsBigInt = BigInt(bps);
|
|
61
|
+
const factor = zeroForOne ? 10000n - bpsBigInt : 10000n + bpsBigInt;
|
|
62
|
+
const sqrtFactor = bigIntSqrt(factor * PRECISION);
|
|
63
|
+
const sqrtBase = bigIntSqrt(10000n * PRECISION);
|
|
64
|
+
return currentSqrtPriceX64 * sqrtFactor / sqrtBase;
|
|
65
|
+
}
|
|
66
|
+
function bigIntSqrt(n) {
|
|
67
|
+
if (n < 0n)
|
|
68
|
+
throw new Error('sqrt of negative');
|
|
69
|
+
if (n === 0n)
|
|
70
|
+
return 0n;
|
|
71
|
+
if (n <= 3n)
|
|
72
|
+
return 1n;
|
|
73
|
+
let x = n;
|
|
74
|
+
let y = (x + 1n) / 2n;
|
|
75
|
+
while (y < x) {
|
|
76
|
+
x = y;
|
|
77
|
+
y = (x + n / x) / 2n;
|
|
78
|
+
}
|
|
79
|
+
return x;
|
|
80
|
+
}
|
|
81
|
+
function getTicksBetween(ticks, currentTick, zeroForOne) {
|
|
82
|
+
if (zeroForOne) {
|
|
83
|
+
return ticks.filter(t => t.index <= currentTick).sort((a, b) => b.index - a.index);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return ticks.filter(t => t.index > currentTick).sort((a, b) => a.index - b.index);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function calcInputForRange(sqrtPriceA, sqrtPriceB, liquidity, zeroForOne) {
|
|
90
|
+
const lower = sqrtPriceA < sqrtPriceB ? sqrtPriceA : sqrtPriceB;
|
|
91
|
+
const upper = sqrtPriceA < sqrtPriceB ? sqrtPriceB : sqrtPriceA;
|
|
92
|
+
const diff = upper - lower;
|
|
93
|
+
if (diff === 0n || liquidity === 0n)
|
|
94
|
+
return 0n;
|
|
95
|
+
if (zeroForOne) {
|
|
96
|
+
return liquidity * diff * Q64 / (lower * upper);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return liquidity * diff / Q64;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function calcOutputForRange(sqrtPriceA, sqrtPriceB, liquidity, zeroForOne) {
|
|
103
|
+
const lower = sqrtPriceA < sqrtPriceB ? sqrtPriceA : sqrtPriceB;
|
|
104
|
+
const upper = sqrtPriceA < sqrtPriceB ? sqrtPriceB : sqrtPriceA;
|
|
105
|
+
const diff = upper - lower;
|
|
106
|
+
if (diff === 0n || liquidity === 0n)
|
|
107
|
+
return 0n;
|
|
108
|
+
if (zeroForOne) {
|
|
109
|
+
return liquidity * diff / Q64;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return liquidity * diff * Q64 / (lower * upper);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { QuoteDepthOutput } from '@clonegod/ttd-core';
|
|
2
|
+
import { DepthTick } from './clmm_depth_calculator';
|
|
3
|
+
export { calculateClmmDepth, priceFromSqrtX64, computeTargetSqrtX64, bigIntSqrt } from './clmm_depth_calculator';
|
|
4
|
+
export type { DepthTick, ClmmDepthInput, DepthResult } from './clmm_depth_calculator';
|
|
5
|
+
export interface BuildClmmDepthInput {
|
|
6
|
+
poolInfo: {
|
|
7
|
+
pool_name: string;
|
|
8
|
+
tokenA: any;
|
|
9
|
+
tokenB: any;
|
|
10
|
+
quote_token: string;
|
|
11
|
+
};
|
|
12
|
+
poolAddress: string;
|
|
13
|
+
poolState: {
|
|
14
|
+
currentSqrtPriceX64: bigint;
|
|
15
|
+
currentTick: number;
|
|
16
|
+
liquidity: bigint;
|
|
17
|
+
baseIsToken0: boolean;
|
|
18
|
+
};
|
|
19
|
+
ticks: DepthTick[];
|
|
20
|
+
basePriceUsd: number;
|
|
21
|
+
quotePriceUsd: number;
|
|
22
|
+
feeRateBps: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildClmmDepth(input: BuildClmmDepthInput): QuoteDepthOutput | undefined;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bigIntSqrt = exports.computeTargetSqrtX64 = exports.priceFromSqrtX64 = exports.calculateClmmDepth = void 0;
|
|
4
|
+
exports.buildClmmDepth = buildClmmDepth;
|
|
5
|
+
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
6
|
+
const ttd_core_2 = require("@clonegod/ttd-core");
|
|
7
|
+
const trade_direction_1 = require("../../utils/trade_direction");
|
|
8
|
+
const clmm_depth_calculator_1 = require("./clmm_depth_calculator");
|
|
9
|
+
var clmm_depth_calculator_2 = require("./clmm_depth_calculator");
|
|
10
|
+
Object.defineProperty(exports, "calculateClmmDepth", { enumerable: true, get: function () { return clmm_depth_calculator_2.calculateClmmDepth; } });
|
|
11
|
+
Object.defineProperty(exports, "priceFromSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.priceFromSqrtX64; } });
|
|
12
|
+
Object.defineProperty(exports, "computeTargetSqrtX64", { enumerable: true, get: function () { return clmm_depth_calculator_2.computeTargetSqrtX64; } });
|
|
13
|
+
Object.defineProperty(exports, "bigIntSqrt", { enumerable: true, get: function () { return clmm_depth_calculator_2.bigIntSqrt; } });
|
|
14
|
+
let _depthPctLogged = false;
|
|
15
|
+
function logDepthLevelsOnce() {
|
|
16
|
+
if (_depthPctLogged)
|
|
17
|
+
return;
|
|
18
|
+
_depthPctLogged = true;
|
|
19
|
+
(0, ttd_core_1.log_info)(`[Depth] pctLevels=${JSON.stringify((0, ttd_core_2.getDepthPricePctLevels)())}, default=${ttd_core_2.DEFAULT_TIER_PCT}`);
|
|
20
|
+
}
|
|
21
|
+
function grossUpFee(amountInNet, feeRateBps) {
|
|
22
|
+
if (feeRateBps <= 0 || amountInNet <= 0)
|
|
23
|
+
return { amountInGross: amountInNet, feeAmount: 0 };
|
|
24
|
+
const feeRatio = feeRateBps / 10000;
|
|
25
|
+
const amountInGross = amountInNet / (1 - feeRatio);
|
|
26
|
+
return { amountInGross, feeAmount: amountInGross - amountInNet };
|
|
27
|
+
}
|
|
28
|
+
function assembleEntry(tiers) {
|
|
29
|
+
const def = tiers.find(t => t.pct === ttd_core_2.DEFAULT_TIER_PCT) ?? tiers[0];
|
|
30
|
+
return {
|
|
31
|
+
price: def.price, amount: def.amount, amount_in: def.amount_in, amount_in_usd: def.amount_in_usd,
|
|
32
|
+
fee: def.fee, fee_usd: def.fee_usd, tick_move: def.tick_move, tiers,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildClmmDepth(input) {
|
|
36
|
+
logDepthLevelsOnce();
|
|
37
|
+
const pctLevels = (0, ttd_core_2.getDepthPricePctLevels)();
|
|
38
|
+
if (pctLevels.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
const { poolInfo, poolState, ticks, basePriceUsd, quotePriceUsd, feeRateBps } = input;
|
|
41
|
+
if (poolState.liquidity <= 0n)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (feeRateBps == null || feeRateBps < 0) {
|
|
44
|
+
(0, ttd_core_1.log_debug)(`[Depth] ${poolInfo.pool_name} CLMM: invalid feeRateBps=${feeRateBps}, skip`, '');
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const dir = (0, trade_direction_1.resolveTradeDirection)(poolInfo, true);
|
|
49
|
+
const baseToken = dir.baseToken;
|
|
50
|
+
const quoteToken = dir.quoteToken;
|
|
51
|
+
const baseIsToken0 = poolState.baseIsToken0;
|
|
52
|
+
const askZeroForOne = !baseIsToken0;
|
|
53
|
+
const bidZeroForOne = baseIsToken0;
|
|
54
|
+
const midPrice = (0, clmm_depth_calculator_1.priceFromSqrtX64)(poolState.currentSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
|
|
55
|
+
const askTiers = [];
|
|
56
|
+
const bidTiers = [];
|
|
57
|
+
for (const pct of pctLevels) {
|
|
58
|
+
const bps = Math.round(pct * 100);
|
|
59
|
+
const askResult = (0, clmm_depth_calculator_1.calculateClmmDepth)({
|
|
60
|
+
sqrtPriceX64: poolState.currentSqrtPriceX64, currentTick: poolState.currentTick,
|
|
61
|
+
liquidity: poolState.liquidity, ticks, zeroForOne: askZeroForOne, targetBps: bps,
|
|
62
|
+
inputDecimals: quoteToken.decimals, outputDecimals: baseToken.decimals,
|
|
63
|
+
});
|
|
64
|
+
const bidResult = (0, clmm_depth_calculator_1.calculateClmmDepth)({
|
|
65
|
+
sqrtPriceX64: poolState.currentSqrtPriceX64, currentTick: poolState.currentTick,
|
|
66
|
+
liquidity: poolState.liquidity, ticks, zeroForOne: bidZeroForOne, targetBps: bps,
|
|
67
|
+
inputDecimals: baseToken.decimals, outputDecimals: quoteToken.decimals,
|
|
68
|
+
});
|
|
69
|
+
const askTargetPrice = (0, clmm_depth_calculator_1.priceFromSqrtX64)(askResult.targetSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
|
|
70
|
+
const bidTargetPrice = (0, clmm_depth_calculator_1.priceFromSqrtX64)(bidResult.targetSqrtPriceX64, baseToken.decimals, quoteToken.decimals, baseIsToken0);
|
|
71
|
+
const askGross = grossUpFee(askResult.amountIn, feeRateBps);
|
|
72
|
+
const bidGross = grossUpFee(bidResult.amountIn, feeRateBps);
|
|
73
|
+
const askTickMove = `${askResult.targetTick} <- ${askResult.currentTick}`;
|
|
74
|
+
const bidTickMove = `${bidResult.currentTick} -> ${bidResult.targetTick}`;
|
|
75
|
+
const askEffPrice = askResult.amountOut > 0 ? askGross.amountInGross / askResult.amountOut : askTargetPrice;
|
|
76
|
+
const bidEffPrice = bidGross.amountInGross > 0 ? bidResult.amountOut / bidGross.amountInGross : bidTargetPrice;
|
|
77
|
+
askTiers.push({
|
|
78
|
+
pct, price: askEffPrice, amount: askResult.amountOut,
|
|
79
|
+
amount_in: askGross.amountInGross, amount_in_usd: askGross.amountInGross * quotePriceUsd,
|
|
80
|
+
fee: askGross.feeAmount, fee_usd: askGross.feeAmount * quotePriceUsd, tick_move: askTickMove,
|
|
81
|
+
});
|
|
82
|
+
bidTiers.push({
|
|
83
|
+
pct, price: bidEffPrice, amount: bidResult.amountOut,
|
|
84
|
+
amount_in: bidGross.amountInGross, amount_in_usd: bidGross.amountInGross * basePriceUsd,
|
|
85
|
+
fee: bidGross.feeAmount, fee_usd: bidGross.feeAmount * basePriceUsd, tick_move: bidTickMove,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
mid_price: midPrice,
|
|
90
|
+
fee_rate_bps: feeRateBps,
|
|
91
|
+
ask: assembleEntry(askTiers),
|
|
92
|
+
bid: assembleEntry(bidTiers),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
(0, ttd_core_1.log_debug)(`[Depth] ${poolInfo.pool_name} CLMM depth failed: ${error.message}`, '');
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/quote/index.d.ts
CHANGED
package/dist/quote/index.js
CHANGED
|
@@ -21,4 +21,5 @@ __exportStar(require("./quote_trace"), exports);
|
|
|
21
21
|
__exportStar(require("./quote_amount"), exports);
|
|
22
22
|
__exportStar(require("./verify"), exports);
|
|
23
23
|
__exportStar(require("./tick"), exports);
|
|
24
|
+
__exportStar(require("./depth"), exports);
|
|
24
25
|
__exportStar(require("./abstract_dex_quote"), exports);
|
|
@@ -46,7 +46,7 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
|
|
|
46
46
|
try {
|
|
47
47
|
const msg = JSON.parse(message);
|
|
48
48
|
if (msg.digest)
|
|
49
|
-
this.appConfig.emit(`SUI_TX_RESULT_${msg.digest}`, msg.digest);
|
|
49
|
+
this.appConfig.emit(`SUI_TX_RESULT_${msg.digest}`, msg.receipt ?? msg.digest);
|
|
50
50
|
}
|
|
51
51
|
catch (e) {
|
|
52
52
|
(0, dist_1.log_warn)(`[trade] bad tx result msg`, message);
|
|
@@ -10,5 +10,6 @@ export declare class TransactionResultChecker extends AbstractTransactionResultC
|
|
|
10
10
|
private isValidTransactionResult;
|
|
11
11
|
check_tx_result_interval(): Promise<void>;
|
|
12
12
|
on_subscibe_transaction(): void;
|
|
13
|
+
private ensureCheckpoint;
|
|
13
14
|
private processTransactionResult;
|
|
14
15
|
}
|
|
@@ -59,6 +59,7 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
59
59
|
this.event_emitter.once(`SUI_TX_RESULT_${this.txid}`, async (response) => {
|
|
60
60
|
(0, dist_1.log_info)(`receive tx result notification, txid=${this.txid}`);
|
|
61
61
|
if (response.transaction) {
|
|
62
|
+
await this.ensureCheckpoint(response);
|
|
62
63
|
this.processTransactionResult(response, 'grpc');
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
@@ -86,6 +87,25 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
86
87
|
}
|
|
87
88
|
});
|
|
88
89
|
}
|
|
90
|
+
async ensureCheckpoint(receipt) {
|
|
91
|
+
if (receipt.transaction?.checkpoint)
|
|
92
|
+
return;
|
|
93
|
+
for (let i = 0; i < 10; i++) {
|
|
94
|
+
try {
|
|
95
|
+
const r = await this.leadgerService.getTransaction(this.txid, ['checkpoint', 'timestamp']);
|
|
96
|
+
const t = r?.transaction;
|
|
97
|
+
if (t?.checkpoint) {
|
|
98
|
+
receipt.transaction.checkpoint = t.checkpoint;
|
|
99
|
+
if (t.timestamp)
|
|
100
|
+
receipt.transaction.timestamp = t.timestamp;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
await (0, dist_1.sleep)(200);
|
|
106
|
+
}
|
|
107
|
+
(0, dist_1.log_warn)(`ensureCheckpoint: 补查超窗仍无 checkpoint 号, txid=${this.txid}(block_number 将为 0)`);
|
|
108
|
+
}
|
|
89
109
|
async processTransactionResult(txReceipt, source) {
|
|
90
110
|
if (this.trade_result_already_processed) {
|
|
91
111
|
(0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by ${source} check!`);
|
|
@@ -30,7 +30,11 @@ export interface CentralExecutorOptions {
|
|
|
30
30
|
digest: string;
|
|
31
31
|
success: boolean;
|
|
32
32
|
error?: string;
|
|
33
|
+
receipt?: {
|
|
34
|
+
transaction: any;
|
|
35
|
+
};
|
|
33
36
|
}) => void;
|
|
37
|
+
tradeCoinTypes?: () => Promise<string[]>;
|
|
34
38
|
}
|
|
35
39
|
type TxResponse = SuiClientTypes.Transaction<{
|
|
36
40
|
effects: true;
|
|
@@ -46,12 +50,20 @@ export declare class CentralExecutor {
|
|
|
46
50
|
private tradeWallets;
|
|
47
51
|
private sharedRefCache;
|
|
48
52
|
private seq;
|
|
53
|
+
private readonly tradeCoinTypesProvider?;
|
|
49
54
|
constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
|
|
50
55
|
init(): Promise<void>;
|
|
51
|
-
reconcileCoins(wallet: string, coinType: string): Promise<void>;
|
|
56
|
+
reconcileCoins(wallet: string, coinType: string, quietIfUnchanged?: boolean): Promise<void>;
|
|
52
57
|
rebalanceWalletFunds(): Promise<void>;
|
|
53
58
|
private rebalanceOne;
|
|
59
|
+
private decimalsCache;
|
|
60
|
+
private objectReader?;
|
|
61
|
+
private getCoinDecimalsCached;
|
|
62
|
+
private maintainCoinObjects;
|
|
54
63
|
private execMaintenance;
|
|
64
|
+
private poolGenericsCache;
|
|
65
|
+
private getPoolGenerics;
|
|
66
|
+
private canonicalizeReq;
|
|
55
67
|
private getSharedRefCached;
|
|
56
68
|
private chainIdentifier;
|
|
57
69
|
private epochCache;
|
|
@@ -64,8 +76,11 @@ export declare class CentralExecutor {
|
|
|
64
76
|
private registerShared;
|
|
65
77
|
submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
|
|
66
78
|
private broadcastAndCommit;
|
|
79
|
+
private toCheckerReceipt;
|
|
67
80
|
private reconcileAfterFailure;
|
|
68
81
|
simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
|
|
82
|
+
private minSplitFor;
|
|
83
|
+
private postTradeRebalance;
|
|
69
84
|
private buildSwapTx;
|
|
70
85
|
private onSuccess;
|
|
71
86
|
get coinCache(): InProcessCoinCache;
|
|
@@ -2,12 +2,35 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CentralExecutor = void 0;
|
|
4
4
|
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
const grpc_connection_1 = require("../../grpc/grpc-connection");
|
|
6
|
+
const sui_object_reader_1 = require("../../grpc/sui_object_reader");
|
|
7
|
+
const format_1 = require("../../utils/format");
|
|
5
8
|
const ed25519_1 = require("@mysten/sui/keypairs/ed25519");
|
|
6
9
|
const transactions_1 = require("@mysten/sui/transactions");
|
|
7
10
|
const coin_cache_1 = require("./coin_cache");
|
|
8
11
|
const effects_1 = require("./effects");
|
|
9
12
|
const swap_1 = require("../swap");
|
|
10
13
|
const constants_1 = require("../../constants");
|
|
14
|
+
function splitTopLevelGenerics(s) {
|
|
15
|
+
const out = [];
|
|
16
|
+
let depth = 0, cur = '';
|
|
17
|
+
for (const ch of s) {
|
|
18
|
+
if (ch === '<')
|
|
19
|
+
depth++;
|
|
20
|
+
else if (ch === '>')
|
|
21
|
+
depth--;
|
|
22
|
+
if (ch === ',' && depth === 0) {
|
|
23
|
+
out.push(cur.trim());
|
|
24
|
+
cur = '';
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
cur += ch;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (cur.trim())
|
|
31
|
+
out.push(cur.trim());
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
11
34
|
class CentralExecutor {
|
|
12
35
|
constructor(core, opts = {}) {
|
|
13
36
|
this.core = core;
|
|
@@ -15,11 +38,14 @@ class CentralExecutor {
|
|
|
15
38
|
this.tradeWallets = new Map();
|
|
16
39
|
this.sharedRefCache = new Map();
|
|
17
40
|
this.seq = 0;
|
|
41
|
+
this.decimalsCache = new Map();
|
|
42
|
+
this.poolGenericsCache = new Map();
|
|
18
43
|
this.chainIdentifier = '';
|
|
19
44
|
this.epochCache = null;
|
|
20
45
|
this.gasBudget = opts.gasBudget ?? BigInt(process.env.SUI_GAS_BUDGET || '50000000');
|
|
21
46
|
this.reconcileAfterTx = opts.reconcileAfterTx ?? true;
|
|
22
47
|
this.onBroadcastResult = opts.onBroadcastResult;
|
|
48
|
+
this.tradeCoinTypesProvider = opts.tradeCoinTypes;
|
|
23
49
|
}
|
|
24
50
|
async init() {
|
|
25
51
|
const tradeIds = (process.env.SUI_WALLET_GROUP_IDS || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -34,18 +60,22 @@ class CentralExecutor {
|
|
|
34
60
|
}
|
|
35
61
|
(0, dist_1.log_info)(`[executor] init: trade=${[...this.tradeWallets.keys()].join(',')} (单钱包模式: gas=各自地址余额), gasBudget=${this.gasBudget}`);
|
|
36
62
|
}
|
|
37
|
-
async reconcileCoins(wallet, coinType) {
|
|
63
|
+
async reconcileCoins(wallet, coinType, quietIfUnchanged = false) {
|
|
38
64
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
66
|
+
const epoch = this.cache.mutationEpoch(wallet, coinType);
|
|
67
|
+
const refs = [];
|
|
68
|
+
let cursor = undefined;
|
|
69
|
+
do {
|
|
70
|
+
const page = await this.core.listCoins({ owner: wallet, coinType, cursor });
|
|
71
|
+
for (const c of page.objects) {
|
|
72
|
+
refs.push({ objectId: c.objectId, version: c.version, digest: c.digest, balance: c.balance });
|
|
73
|
+
}
|
|
74
|
+
cursor = page.hasNextPage ? page.cursor : null;
|
|
75
|
+
} while (cursor);
|
|
76
|
+
if (this.cache.reconcile(wallet, coinType, refs, epoch, quietIfUnchanged))
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
49
79
|
}
|
|
50
80
|
catch (e) {
|
|
51
81
|
(0, dist_1.log_error)(`[executor] reconcileCoins 失败 ${wallet} ${coinType}`, e);
|
|
@@ -66,11 +96,10 @@ class CentralExecutor {
|
|
|
66
96
|
const balanceMin = BigInt(process.env.SUI_GAS_BALANCE_MIN || '500000000');
|
|
67
97
|
const balanceTarget = BigInt(process.env.SUI_GAS_BALANCE_TARGET || (balanceMin * 2n).toString());
|
|
68
98
|
const coinTarget = Number(process.env.SUI_INPUT_COIN_TARGET || 3);
|
|
69
|
-
const coinMax = Number(process.env.SUI_INPUT_COIN_MAX ||
|
|
70
|
-
const minSplit = BigInt(process.env.SUI_INPUT_COIN_MIN_SPLIT || '100000000');
|
|
99
|
+
const coinMax = Number(process.env.SUI_INPUT_COIN_MAX || 5);
|
|
71
100
|
const chunk = BigInt(process.env.SUI_REDEEM_MIN_CHUNK || '100000000');
|
|
72
101
|
const budget = BigInt(process.env.SUI_MAINTAIN_GAS_BUDGET || '20000000');
|
|
73
|
-
await this.reconcileCoins(addr, SUI);
|
|
102
|
+
await this.reconcileCoins(addr, SUI, true);
|
|
74
103
|
if (this.cache.hasInflight(addr, SUI))
|
|
75
104
|
return;
|
|
76
105
|
const b = await this.core.getBalance({ owner: addr, coinType: '0x2::sui::SUI' });
|
|
@@ -87,8 +116,7 @@ class CentralExecutor {
|
|
|
87
116
|
const tx = new transactions_1.Transaction();
|
|
88
117
|
tx.setSender(addr);
|
|
89
118
|
const [dep] = tx.splitCoins(tx.gas, [tx.pure.u64(need)]);
|
|
90
|
-
|
|
91
|
-
tx.moveCall({ target: '0x2::balance::send_funds', typeArguments: ['0x2::sui::SUI'], arguments: [bal, tx.pure.address(addr)] });
|
|
119
|
+
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: ['0x2::sui::SUI'], arguments: [dep, tx.pure.address(addr)] });
|
|
92
120
|
tx.setGasPayment([{ objectId: src.objectId, version: src.version, digest: src.digest }]);
|
|
93
121
|
await this.execMaintenance(tx, addr, kp, budget, `deposit ${need} → balance`);
|
|
94
122
|
return;
|
|
@@ -106,42 +134,136 @@ class CentralExecutor {
|
|
|
106
134
|
await this.execMaintenance(tx, addr, kp, budget, `redeem 超额 ${excess} → coin`, rawClient);
|
|
107
135
|
return;
|
|
108
136
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
tx.setGasPayment([]);
|
|
118
|
-
tx.setExpiration(await this.getValidDuringExpiration());
|
|
119
|
-
await this.execMaintenance(tx, addr, kp, budget, `split ${big.objectId.slice(0, 10)}… 对半(${coins.length}→${coins.length + 1} 个)`);
|
|
120
|
-
return;
|
|
137
|
+
let tradeTypes = [];
|
|
138
|
+
if (this.tradeCoinTypesProvider) {
|
|
139
|
+
try {
|
|
140
|
+
tradeTypes = (await this.tradeCoinTypesProvider()).map(t => (0, format_1.normalizeSuiTokenAddress)(t));
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
(0, dist_1.log_warn)(`[executor] 读交易币种清单失败,本 tick 只维护 SUI: ${e.message}`);
|
|
144
|
+
}
|
|
121
145
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
const types = [SUI, ...new Set(tradeTypes.filter(t => t !== SUI))];
|
|
147
|
+
for (const t of types) {
|
|
148
|
+
if (t !== SUI)
|
|
149
|
+
await this.reconcileCoins(addr, t, true);
|
|
150
|
+
if (this.cache.hasInflight(addr, t))
|
|
151
|
+
continue;
|
|
152
|
+
let minSplitT;
|
|
153
|
+
try {
|
|
154
|
+
minSplitT = await this.minSplitFor(t);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
(0, dist_1.log_warn)(`[executor] 读 ${t} decimals 失败,本 tick 跳过该币种维护: ${e.message}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const acted = await this.maintainCoinObjects(addr, kp, t, { coinTarget, coinMax, budget, minSplit: minSplitT });
|
|
161
|
+
if (acted)
|
|
162
|
+
return;
|
|
133
163
|
}
|
|
134
164
|
}
|
|
135
|
-
async
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
async getCoinDecimalsCached(coinType) {
|
|
166
|
+
let d = this.decimalsCache.get(coinType);
|
|
167
|
+
if (d == null) {
|
|
168
|
+
if (!this.objectReader) {
|
|
169
|
+
const { grpc_endpoint, grpc_token } = (0, dist_1.getCoreEnv)();
|
|
170
|
+
if (!grpc_endpoint)
|
|
171
|
+
throw new Error('GRPC_ENDPOINT 未配置(读 CoinMetadata decimals 需要)');
|
|
172
|
+
this.objectReader = new sui_object_reader_1.SuiObjectReader(grpc_connection_1.GrpcConnection.getInstance(grpc_endpoint, grpc_token));
|
|
173
|
+
}
|
|
174
|
+
d = await this.objectReader.getCoinDecimals(coinType);
|
|
175
|
+
this.decimalsCache.set(coinType, d);
|
|
176
|
+
}
|
|
177
|
+
return d;
|
|
178
|
+
}
|
|
179
|
+
async maintainCoinObjects(addr, kp, coinType, opts) {
|
|
180
|
+
const short = coinType.split('::').pop();
|
|
181
|
+
const zeroMergeMin = Number(process.env.SUI_ZERO_COIN_MERGE_THRESHOLD || 3);
|
|
182
|
+
const coins = this.cache.snapshot(addr, coinType).coins;
|
|
183
|
+
const valued = coins.filter(c => BigInt(c.balance) > 0n);
|
|
184
|
+
const zeros = coins.filter(c => BigInt(c.balance) === 0n);
|
|
185
|
+
if (coins.length === 0)
|
|
186
|
+
return false;
|
|
187
|
+
const total = valued.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
188
|
+
const maxPieces = opts.minSplit > 0n ? total / opts.minSplit : 0n;
|
|
189
|
+
const pieces = Math.max(1, Number(maxPieces > BigInt(opts.coinTarget) ? BigInt(opts.coinTarget) : maxPieces));
|
|
190
|
+
const needSplit = valued.length > 0 && valued.length < opts.coinTarget && pieces > valued.length;
|
|
191
|
+
const needZeroPurge = zeros.length >= zeroMergeMin;
|
|
192
|
+
const needDefrag = coins.length > opts.coinMax;
|
|
193
|
+
if (!needSplit && !needZeroPurge && !needDefrag)
|
|
194
|
+
return false;
|
|
195
|
+
const tag = this.nextTag('maint');
|
|
196
|
+
if (!this.cache.reserve(addr, coinType, coins.map(c => c.objectId), tag))
|
|
197
|
+
return false;
|
|
198
|
+
const primaryRef = valued[0] ?? coins[0];
|
|
199
|
+
const tx = new transactions_1.Transaction();
|
|
200
|
+
tx.setSender(addr);
|
|
201
|
+
const primary = tx.object(transactions_1.Inputs.ObjectRef(primaryRef));
|
|
202
|
+
const others = coins.filter(c => c.objectId !== primaryRef.objectId);
|
|
203
|
+
if (others.length)
|
|
204
|
+
tx.mergeCoins(primary, others.map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
205
|
+
if (pieces > 1) {
|
|
206
|
+
const per = total / BigInt(pieces);
|
|
207
|
+
const splits = tx.splitCoins(primary, Array.from({ length: pieces - 1 }, () => tx.pure.u64(per)));
|
|
208
|
+
tx.transferObjects(Array.from({ length: pieces - 1 }, (_, i) => splits[i]), tx.pure.address(addr));
|
|
209
|
+
}
|
|
210
|
+
tx.setGasPayment([]);
|
|
211
|
+
tx.setExpiration(await this.getValidDuringExpiration());
|
|
212
|
+
await this.execMaintenance(tx, addr, kp, opts.budget, `整理 ${short}:${valued.length} 值 + ${zeros.length} 壳 → ${pieces} 份均分(每份≈${(total / BigInt(pieces)).toString()})`, undefined, coinType, tag);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
async execMaintenance(tx, addr, kp, budget, label, buildClient, coinType = constants_1.SUI_TOKEN_ADDRESS.LONG, reserveTag) {
|
|
216
|
+
try {
|
|
217
|
+
tx.setGasBudget(budget);
|
|
218
|
+
tx.setGasPrice(await this.getGasPrice());
|
|
219
|
+
const bytes = buildClient ? await tx.build({ client: buildClient }) : await tx.build();
|
|
220
|
+
const { signature } = await kp.signTransaction(bytes);
|
|
221
|
+
const resp = await this.core.executeTransaction({ transaction: bytes, signatures: [signature], include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
222
|
+
const tr = resp.Transaction;
|
|
223
|
+
const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
|
|
224
|
+
(0, dist_1.log_info)(`[executor] rebalance ${label} ${success ? 'OK' : 'FAILED'} wallet=${addr.slice(0, 10)}… digest=${tr.digest}${error ? ' err=' + error : ''}`);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
if (reserveTag)
|
|
228
|
+
this.cache.abort(reserveTag, true);
|
|
229
|
+
await this.reconcileCoins(addr, coinType);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async getPoolGenerics(poolId) {
|
|
233
|
+
const hit = this.poolGenericsCache.get(poolId);
|
|
234
|
+
if (hit !== undefined)
|
|
235
|
+
return hit;
|
|
236
|
+
const res = await this.core.getObjects({ objectIds: [poolId] });
|
|
237
|
+
const obj = res.objects[0];
|
|
238
|
+
if (obj instanceof Error)
|
|
239
|
+
throw new Error(`[executor] getObjects(${poolId}) 失败: ${obj.message}`);
|
|
240
|
+
const t = obj.type ?? obj.objectType;
|
|
241
|
+
let out = null;
|
|
242
|
+
const m = t?.match(/<(.+)>$/);
|
|
243
|
+
if (m) {
|
|
244
|
+
const parts = splitTopLevelGenerics(m[1]);
|
|
245
|
+
if (parts.length === 2) {
|
|
246
|
+
out = { typeA: (0, format_1.normalizeSuiTokenAddress)(parts[0]), typeB: (0, format_1.normalizeSuiTokenAddress)(parts[1]) };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
this.poolGenericsCache.set(poolId, out);
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
async canonicalizeReq(req) {
|
|
253
|
+
const g = await this.getPoolGenerics(req.poolId);
|
|
254
|
+
if (!g)
|
|
255
|
+
return req;
|
|
256
|
+
const a = (0, format_1.normalizeSuiTokenAddress)(req.coinTypeA);
|
|
257
|
+
const b = (0, format_1.normalizeSuiTokenAddress)(req.coinTypeB);
|
|
258
|
+
if (a === g.typeA && b === g.typeB)
|
|
259
|
+
return req;
|
|
260
|
+
if (a === g.typeB && b === g.typeA) {
|
|
261
|
+
if (req.sqrtPriceLimit !== 0n)
|
|
262
|
+
throw new Error(`[executor] pool ${req.poolId} token 序翻转时不支持显式 sqrtPriceLimit`);
|
|
263
|
+
(0, dist_1.log_info)(`[executor] pool ${req.poolId.slice(0, 10)}… 配置 token 序与链上相反,已翻转(a2b ${req.a2b}→${!req.a2b})`);
|
|
264
|
+
return { ...req, coinTypeA: b, coinTypeB: a, a2b: !req.a2b };
|
|
265
|
+
}
|
|
266
|
+
throw new Error(`[executor] pool token 与链上不符: 链上 <${g.typeA}, ${g.typeB}> vs 请求 <${a}, ${b}>(配置可能指错池子)`);
|
|
145
267
|
}
|
|
146
268
|
async getSharedRefCached(objectId) {
|
|
147
269
|
let isv = this.sharedRefCache.get(objectId);
|
|
@@ -160,7 +282,13 @@ class CentralExecutor {
|
|
|
160
282
|
}
|
|
161
283
|
async getValidDuringExpiration() {
|
|
162
284
|
if (!this.chainIdentifier) {
|
|
163
|
-
|
|
285
|
+
try {
|
|
286
|
+
this.chainIdentifier = (await this.core.getChainIdentifier()).chainIdentifier;
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
this.chainIdentifier = process.env.SUI_CHAIN_IDENTIFIER || '35834a8a';
|
|
290
|
+
(0, dist_1.log_warn)(`[executor] getChainIdentifier 失败(${e.message}),使用兜底 chain id ${this.chainIdentifier}`);
|
|
291
|
+
}
|
|
164
292
|
}
|
|
165
293
|
const now = Date.now();
|
|
166
294
|
if (!this.epochCache || now - this.epochCache.ts > 300_000) {
|
|
@@ -208,16 +336,30 @@ class CentralExecutor {
|
|
|
208
336
|
tx.object(transactions_1.Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable }));
|
|
209
337
|
}
|
|
210
338
|
async submitSwap(req) {
|
|
339
|
+
const t0 = Date.now();
|
|
340
|
+
req = await this.canonicalizeReq(req);
|
|
211
341
|
const inType = this.inputCoinType(req);
|
|
212
|
-
|
|
342
|
+
let wallet;
|
|
343
|
+
try {
|
|
344
|
+
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
(0, dist_1.log_warn)(`[executor] ${inType} 选钱包失败(${e.message}),按需 reconcile 后重试`);
|
|
348
|
+
await Promise.all([...this.tradeWallets.keys()].map(w => this.reconcileCoins(w, inType)));
|
|
349
|
+
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
350
|
+
}
|
|
213
351
|
const inputTag = this.nextTag('in');
|
|
214
352
|
const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
|
|
353
|
+
const tAcquire = Date.now();
|
|
354
|
+
(0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 10)}… ${inputRes.coins.length} 个 ${inType.split('::').pop()} (选钱包+占币 ${tAcquire - t0}ms)`);
|
|
215
355
|
try {
|
|
216
356
|
const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
|
|
357
|
+
const tBuild = Date.now();
|
|
217
358
|
const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
218
359
|
const digest = await tx.getDigest();
|
|
360
|
+
const tSign = Date.now();
|
|
219
361
|
void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, inType, this.outputCoinType(req), inputRes.coins, inputTag);
|
|
220
|
-
(0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn}`);
|
|
362
|
+
(0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn} cost={build:${tBuild - tAcquire}ms, sign+digest:${tSign - tBuild}ms, total:${tSign - t0}ms}`);
|
|
221
363
|
return { digest, submitted: true };
|
|
222
364
|
}
|
|
223
365
|
catch (e) {
|
|
@@ -227,6 +369,7 @@ class CentralExecutor {
|
|
|
227
369
|
}
|
|
228
370
|
}
|
|
229
371
|
async broadcastAndCommit(txBytes, signatures, digest, wallet, inType, outType, inputCoins, inputTag) {
|
|
372
|
+
const tBroadcast = Date.now();
|
|
230
373
|
try {
|
|
231
374
|
const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
232
375
|
const tr = resp.Transaction;
|
|
@@ -241,25 +384,50 @@ class CentralExecutor {
|
|
|
241
384
|
if (!success) {
|
|
242
385
|
this.cache.abort(inputTag, true);
|
|
243
386
|
this.reconcileAfterFailure(wallet, inType, outType);
|
|
244
|
-
(0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error}`);
|
|
245
|
-
this.onBroadcastResult?.({ digest, success: false, error });
|
|
387
|
+
(0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error} broadcast=${Date.now() - tBroadcast}ms`);
|
|
388
|
+
this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
|
|
246
389
|
return;
|
|
247
390
|
}
|
|
248
391
|
await this.onSuccess(tr, wallet, inType, outType, inputCoins, inputTag);
|
|
249
|
-
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest}`);
|
|
250
|
-
this.onBroadcastResult?.({ digest, success: true });
|
|
392
|
+
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
|
|
393
|
+
this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
|
|
251
394
|
}
|
|
252
395
|
catch (e) {
|
|
253
396
|
this.cache.abort(inputTag, true);
|
|
254
397
|
this.reconcileAfterFailure(wallet, inType, outType);
|
|
255
|
-
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest}`, e);
|
|
398
|
+
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`, e);
|
|
256
399
|
}
|
|
257
400
|
}
|
|
401
|
+
toCheckerReceipt(tr, digest) {
|
|
402
|
+
const eff = tr.effects;
|
|
403
|
+
const status = eff?.status?.success
|
|
404
|
+
? { success: true }
|
|
405
|
+
: { success: false, error: { kind: eff?.status?.error?.message ?? JSON.stringify(eff?.status?.error ?? 'unknown') } };
|
|
406
|
+
const g = eff?.gasUsed ?? {};
|
|
407
|
+
return {
|
|
408
|
+
transaction: {
|
|
409
|
+
digest,
|
|
410
|
+
effects: {
|
|
411
|
+
status,
|
|
412
|
+
gas_used: {
|
|
413
|
+
computation_cost: g.computationCost,
|
|
414
|
+
storage_cost: g.storageCost,
|
|
415
|
+
storage_rebate: g.storageRebate,
|
|
416
|
+
non_refundable_storage_fee: g.nonRefundableStorageFee,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
balance_changes: (tr.balanceChanges ?? []).map((b) => ({
|
|
420
|
+
address: b.address, coin_type: b.coinType, amount: b.amount,
|
|
421
|
+
})),
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
258
425
|
reconcileAfterFailure(wallet, inType, outType) {
|
|
259
426
|
for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
|
|
260
427
|
void this.reconcileCoins(wallet, t);
|
|
261
428
|
}
|
|
262
429
|
async simulateSwap(req) {
|
|
430
|
+
req = await this.canonicalizeReq(req);
|
|
263
431
|
const inType = this.inputCoinType(req);
|
|
264
432
|
const wallet = req.walletAddress ?? this.chooseTradeWallet(req, inType, req.amountIn);
|
|
265
433
|
const { coins } = this.cache.snapshot(wallet, inType);
|
|
@@ -282,6 +450,32 @@ class CentralExecutor {
|
|
|
282
450
|
}
|
|
283
451
|
return res.Transaction;
|
|
284
452
|
}
|
|
453
|
+
async minSplitFor(coinType) {
|
|
454
|
+
if (coinType === constants_1.SUI_TOKEN_ADDRESS.LONG) {
|
|
455
|
+
return BigInt(process.env.SUI_INPUT_COIN_MIN_SPLIT || '100000000');
|
|
456
|
+
}
|
|
457
|
+
const d = await this.getCoinDecimalsCached(coinType);
|
|
458
|
+
return 10n ** BigInt(d) / 10n;
|
|
459
|
+
}
|
|
460
|
+
async postTradeRebalance(wallet, inType, outType) {
|
|
461
|
+
const kp = this.tradeWallets.get(wallet);
|
|
462
|
+
if (!kp)
|
|
463
|
+
return;
|
|
464
|
+
const coinTarget = Number(process.env.SUI_INPUT_COIN_TARGET || 3);
|
|
465
|
+
const coinMax = Number(process.env.SUI_INPUT_COIN_MAX || 5);
|
|
466
|
+
const budget = BigInt(process.env.SUI_MAINTAIN_GAS_BUDGET || '20000000');
|
|
467
|
+
for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG])) {
|
|
468
|
+
try {
|
|
469
|
+
await this.reconcileCoins(wallet, t);
|
|
470
|
+
if (this.cache.hasInflight(wallet, t))
|
|
471
|
+
continue;
|
|
472
|
+
await this.maintainCoinObjects(wallet, kp, t, { coinTarget, coinMax, budget, minSplit: await this.minSplitFor(t) });
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
(0, dist_1.log_warn)(`[executor] postTradeRebalance ${t} 失败(交 30s tick 兜底): ${e.message}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
285
479
|
async buildSwapTx(req, wallet, inputCoins) {
|
|
286
480
|
const tx = new transactions_1.Transaction();
|
|
287
481
|
tx.setSender(wallet);
|
|
@@ -314,8 +508,7 @@ class CentralExecutor {
|
|
|
314
508
|
const tradeChanges = (0, effects_1.extractCoinChangesFromCore)(tr.effects, objectTypes, { owners: new Set([wallet]), coinTypeById });
|
|
315
509
|
this.cache.commit(inputTag, tradeChanges);
|
|
316
510
|
if (this.reconcileAfterTx) {
|
|
317
|
-
|
|
318
|
-
void this.reconcileCoins(wallet, t);
|
|
511
|
+
void this.postTradeRebalance(wallet, inType, outType);
|
|
319
512
|
}
|
|
320
513
|
}
|
|
321
514
|
get coinCache() { return this.cache; }
|
|
@@ -2,16 +2,22 @@ import { CoinRef, CoinReservation, CoinObjectChange } from '../coin/types';
|
|
|
2
2
|
export declare class InProcessCoinCache {
|
|
3
3
|
private available;
|
|
4
4
|
private inflight;
|
|
5
|
+
private epochs;
|
|
6
|
+
private epochKey;
|
|
7
|
+
private bumpEpoch;
|
|
8
|
+
mutationEpoch(wallet: string, coinType: string): number;
|
|
5
9
|
private walletMap;
|
|
6
10
|
private list;
|
|
7
11
|
private sortDesc;
|
|
8
12
|
seed(wallet: string, coinType: string, coins: CoinRef[]): void;
|
|
9
|
-
reconcile(wallet: string, coinType: string, freshCoins: CoinRef[]):
|
|
13
|
+
reconcile(wallet: string, coinType: string, freshCoins: CoinRef[], epochAtStart?: number, quietIfUnchanged?: boolean): boolean;
|
|
10
14
|
acquire(wallet: string, coinType: string, amount: bigint, txTag: string, maxCoins?: number): CoinReservation;
|
|
15
|
+
reserve(wallet: string, coinType: string, objectIds: string[], txTag: string): CoinRef[] | null;
|
|
11
16
|
hasInflight(wallet: string, coinType: string): boolean;
|
|
12
17
|
commit(txTag: string, changes: CoinObjectChange[]): void;
|
|
13
18
|
abort(txTag: string, consumed: boolean): void;
|
|
14
19
|
private removeEverywhere;
|
|
20
|
+
coinTypes(wallet: string): string[];
|
|
15
21
|
snapshot(wallet: string, coinType: string): {
|
|
16
22
|
count: number;
|
|
17
23
|
total: bigint;
|
|
@@ -6,6 +6,15 @@ class InProcessCoinCache {
|
|
|
6
6
|
constructor() {
|
|
7
7
|
this.available = new Map();
|
|
8
8
|
this.inflight = new Map();
|
|
9
|
+
this.epochs = new Map();
|
|
10
|
+
}
|
|
11
|
+
epochKey(wallet, coinType) { return `${wallet}|${coinType}`; }
|
|
12
|
+
bumpEpoch(wallet, coinType) {
|
|
13
|
+
const k = this.epochKey(wallet, coinType);
|
|
14
|
+
this.epochs.set(k, (this.epochs.get(k) || 0) + 1);
|
|
15
|
+
}
|
|
16
|
+
mutationEpoch(wallet, coinType) {
|
|
17
|
+
return this.epochs.get(this.epochKey(wallet, coinType)) || 0;
|
|
9
18
|
}
|
|
10
19
|
walletMap(wallet) {
|
|
11
20
|
let m = this.available.get(wallet);
|
|
@@ -31,9 +40,14 @@ class InProcessCoinCache {
|
|
|
31
40
|
const l = coins.slice();
|
|
32
41
|
this.sortDesc(l);
|
|
33
42
|
this.walletMap(wallet).set(coinType, l);
|
|
43
|
+
this.bumpEpoch(wallet, coinType);
|
|
34
44
|
(0, dist_1.log_info)(`[coin-cache] seed ${wallet} ${coinType}: ${l.length} coins`);
|
|
35
45
|
}
|
|
36
|
-
reconcile(wallet, coinType, freshCoins) {
|
|
46
|
+
reconcile(wallet, coinType, freshCoins, epochAtStart, quietIfUnchanged = false) {
|
|
47
|
+
if (epochAtStart != null && this.mutationEpoch(wallet, coinType) !== epochAtStart) {
|
|
48
|
+
(0, dist_1.log_info)(`[coin-cache] reconcile ${wallet} ${coinType}: 快照在途期间缓存已变更,丢弃(防旧覆盖新)`);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
37
51
|
const inflightIds = new Set();
|
|
38
52
|
for (const r of this.inflight.values()) {
|
|
39
53
|
if (r.wallet === wallet && r.coinType === coinType)
|
|
@@ -41,8 +55,15 @@ class InProcessCoinCache {
|
|
|
41
55
|
}
|
|
42
56
|
const kept = freshCoins.filter(c => !inflightIds.has(c.objectId));
|
|
43
57
|
this.sortDesc(kept);
|
|
58
|
+
const sig = (l) => l.map(c => `${c.objectId}@${c.version}:${c.balance}`).join(',');
|
|
59
|
+
const unchanged = sig(kept) === sig(this.list(wallet, coinType));
|
|
44
60
|
this.walletMap(wallet).set(coinType, kept);
|
|
45
|
-
|
|
61
|
+
const msg = `[coin-cache] reconcile ${wallet} ${coinType}: ${kept.length} avail (${inflightIds.size} inflight kept)`;
|
|
62
|
+
if (unchanged && quietIfUnchanged)
|
|
63
|
+
(0, dist_1.log_debug)(msg);
|
|
64
|
+
else
|
|
65
|
+
(0, dist_1.log_info)(msg);
|
|
66
|
+
return true;
|
|
46
67
|
}
|
|
47
68
|
acquire(wallet, coinType, amount, txTag, maxCoins = 8) {
|
|
48
69
|
if (this.inflight.has(txTag))
|
|
@@ -65,8 +86,22 @@ class InProcessCoinCache {
|
|
|
65
86
|
const pickedIds = new Set(picked.map(c => c.objectId));
|
|
66
87
|
this.walletMap(wallet).set(coinType, l.filter(c => !pickedIds.has(c.objectId)));
|
|
67
88
|
this.inflight.set(txTag, { wallet, coinType, coins: picked, gas: false });
|
|
89
|
+
this.bumpEpoch(wallet, coinType);
|
|
68
90
|
return { txTag, coinType, coins: picked };
|
|
69
91
|
}
|
|
92
|
+
reserve(wallet, coinType, objectIds, txTag) {
|
|
93
|
+
if (this.inflight.has(txTag))
|
|
94
|
+
throw new Error(`[coin-cache] txTag 重复: ${txTag}`);
|
|
95
|
+
const l = this.list(wallet, coinType);
|
|
96
|
+
const ids = new Set(objectIds);
|
|
97
|
+
const picked = l.filter(c => ids.has(c.objectId));
|
|
98
|
+
if (picked.length !== ids.size)
|
|
99
|
+
return null;
|
|
100
|
+
this.walletMap(wallet).set(coinType, l.filter(c => !ids.has(c.objectId)));
|
|
101
|
+
this.inflight.set(txTag, { wallet, coinType, coins: picked, gas: false });
|
|
102
|
+
this.bumpEpoch(wallet, coinType);
|
|
103
|
+
return picked;
|
|
104
|
+
}
|
|
70
105
|
hasInflight(wallet, coinType) {
|
|
71
106
|
for (const r of this.inflight.values()) {
|
|
72
107
|
if (r.wallet === wallet && r.coinType === coinType)
|
|
@@ -81,6 +116,9 @@ class InProcessCoinCache {
|
|
|
81
116
|
return;
|
|
82
117
|
}
|
|
83
118
|
this.inflight.delete(txTag);
|
|
119
|
+
this.bumpEpoch(res.wallet, res.coinType);
|
|
120
|
+
for (const ch of changes)
|
|
121
|
+
this.bumpEpoch(res.wallet, ch.coinType);
|
|
84
122
|
for (const ch of changes) {
|
|
85
123
|
const wallet = res.wallet;
|
|
86
124
|
if (ch.kind === 'deleted') {
|
|
@@ -107,6 +145,7 @@ class InProcessCoinCache {
|
|
|
107
145
|
return;
|
|
108
146
|
}
|
|
109
147
|
this.inflight.delete(txTag);
|
|
148
|
+
this.bumpEpoch(res.wallet, res.coinType);
|
|
110
149
|
if (consumed) {
|
|
111
150
|
(0, dist_1.log_warn)(`[coin-cache] abort consumed txTag=${txTag}(${res.coins.length} coin 交 reconcile 兜底)`);
|
|
112
151
|
return;
|
|
@@ -123,6 +162,9 @@ class InProcessCoinCache {
|
|
|
123
162
|
if (idx >= 0)
|
|
124
163
|
l.splice(idx, 1);
|
|
125
164
|
}
|
|
165
|
+
coinTypes(wallet) {
|
|
166
|
+
return [...this.walletMap(wallet).keys()];
|
|
167
|
+
}
|
|
126
168
|
snapshot(wallet, coinType) {
|
|
127
169
|
const l = this.list(wallet, coinType);
|
|
128
170
|
const total = l.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
@@ -21,6 +21,9 @@ const GRPC_CLIENT_OPTIONS = {
|
|
|
21
21
|
'grpc.max_send_message_length': 8 * 1024 * 1024,
|
|
22
22
|
'grpc.initial_reconnect_backoff_ms': 1000,
|
|
23
23
|
'grpc.max_reconnect_backoff_ms': 30000,
|
|
24
|
+
'grpc-node.flow_control_window': 8 * 1024 * 1024,
|
|
25
|
+
'grpc-node.max_session_memory': 64,
|
|
26
|
+
'grpc.default_compression_algorithm': 2,
|
|
24
27
|
};
|
|
25
28
|
function buildGrpcCore(opts = {}) {
|
|
26
29
|
const endpoint = opts.endpoint ?? (0, dist_1.getCoreEnv)().grpc_endpoint;
|
|
@@ -39,10 +39,18 @@ class SuiTransactionParser {
|
|
|
39
39
|
let tokenBChangeAmount = new decimal_js_1.default(0);
|
|
40
40
|
const balanceChanges = tx_receipt.transaction?.balance_changes || [];
|
|
41
41
|
(0, dist_1.log_info)('balance_changes', { txid, balance_changes: balanceChanges });
|
|
42
|
+
const g = tx_receipt.transaction?.effects?.gas_used;
|
|
43
|
+
const gasNetMist = g
|
|
44
|
+
? new decimal_js_1.default(g.computation_cost || 0).plus(g.storage_cost || 0).minus(g.storage_rebate || 0)
|
|
45
|
+
: new decimal_js_1.default(0);
|
|
46
|
+
const SUI_LONG = (0, index_1.normalizeSuiTokenAddress)('0x2::sui::SUI');
|
|
42
47
|
for (const change of balanceChanges) {
|
|
43
|
-
|
|
48
|
+
let amount = new decimal_js_1.default(change.amount);
|
|
44
49
|
const coinType = change.coin_type;
|
|
45
50
|
const normalizedCoinType = (0, index_1.normalizeSuiTokenAddress)(coinType);
|
|
51
|
+
if (normalizedCoinType === SUI_LONG) {
|
|
52
|
+
amount = amount.plus(gasNetMist);
|
|
53
|
+
}
|
|
46
54
|
const poolTokenAAddress = (0, index_1.normalizeSuiTokenAddress)(pool_info.tokenA.address);
|
|
47
55
|
const poolTokenBAddress = (0, index_1.normalizeSuiTokenAddress)(pool_info.tokenB.address);
|
|
48
56
|
if (normalizedCoinType === poolTokenAAddress) {
|
|
@@ -177,7 +185,7 @@ class SuiTransactionParser {
|
|
|
177
185
|
return Date.now();
|
|
178
186
|
}
|
|
179
187
|
extractCheckpoint(receipt) {
|
|
180
|
-
return parseInt(receipt.checkpoint || '0');
|
|
188
|
+
return parseInt(receipt.transaction?.checkpoint || receipt.checkpoint || '0');
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
191
|
exports.SuiTransactionParser = SuiTransactionParser;
|