@clonegod/ttd-sui-common 2.0.6 → 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.
@@ -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
+ }
@@ -5,4 +5,5 @@ export * from './quote_trace';
5
5
  export * from './quote_amount';
6
6
  export * from './verify';
7
7
  export * from './tick';
8
+ export * from './depth';
8
9
  export * from './abstract_dex_quote';
@@ -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);
@@ -34,6 +34,7 @@ export interface CentralExecutorOptions {
34
34
  transaction: any;
35
35
  };
36
36
  }) => void;
37
+ tradeCoinTypes?: () => Promise<string[]>;
37
38
  }
38
39
  type TxResponse = SuiClientTypes.Transaction<{
39
40
  effects: true;
@@ -49,12 +50,20 @@ export declare class CentralExecutor {
49
50
  private tradeWallets;
50
51
  private sharedRefCache;
51
52
  private seq;
53
+ private readonly tradeCoinTypesProvider?;
52
54
  constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
53
55
  init(): Promise<void>;
54
- reconcileCoins(wallet: string, coinType: string): Promise<void>;
56
+ reconcileCoins(wallet: string, coinType: string, quietIfUnchanged?: boolean): Promise<void>;
55
57
  rebalanceWalletFunds(): Promise<void>;
56
58
  private rebalanceOne;
59
+ private decimalsCache;
60
+ private objectReader?;
61
+ private getCoinDecimalsCached;
62
+ private maintainCoinObjects;
57
63
  private execMaintenance;
64
+ private poolGenericsCache;
65
+ private getPoolGenerics;
66
+ private canonicalizeReq;
58
67
  private getSharedRefCached;
59
68
  private chainIdentifier;
60
69
  private epochCache;
@@ -70,6 +79,8 @@ export declare class CentralExecutor {
70
79
  private toCheckerReceipt;
71
80
  private reconcileAfterFailure;
72
81
  simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
82
+ private minSplitFor;
83
+ private postTradeRebalance;
73
84
  private buildSwapTx;
74
85
  private onSuccess;
75
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
- const refs = [];
40
- let cursor = undefined;
41
- do {
42
- const page = await this.core.listCoins({ owner: wallet, coinType, cursor });
43
- for (const c of page.objects) {
44
- refs.push({ objectId: c.objectId, version: c.version, digest: c.digest, balance: c.balance });
45
- }
46
- cursor = page.hasNextPage ? page.cursor : null;
47
- } while (cursor);
48
- this.cache.reconcile(wallet, coinType, refs);
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 || 8);
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
- const bal = tx.moveCall({ target: '0x2::coin::into_balance', typeArguments: ['0x2::sui::SUI'], arguments: [dep] });
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
- if (coins.length > 0 && coins.length < coinTarget && BigInt(coins[0].balance) >= minSplit * 2n) {
110
- const big = coins[0];
111
- const half = BigInt(big.balance) / 2n;
112
- const tx = new transactions_1.Transaction();
113
- tx.setSender(addr);
114
- const c = tx.object(transactions_1.Inputs.ObjectRef(big));
115
- const [piece] = tx.splitCoins(c, [tx.pure.u64(half)]);
116
- tx.transferObjects([piece], tx.pure.address(addr));
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
- if (coins.length > coinMax) {
123
- const primary = coins[0];
124
- const toMerge = coins.slice(coinTarget);
125
- const tx = new transactions_1.Transaction();
126
- tx.setSender(addr);
127
- const primaryArg = tx.object(transactions_1.Inputs.ObjectRef(primary));
128
- tx.mergeCoins(primaryArg, toMerge.map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
129
- tx.setGasPayment([]);
130
- tx.setExpiration(await this.getValidDuringExpiration());
131
- await this.execMaintenance(tx, addr, kp, budget, `merge ${toMerge.length} 个碎片(${coins.length}→${coinTarget + 1} 个)`);
132
- return;
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 execMaintenance(tx, addr, kp, budget, label, buildClient) {
136
- tx.setGasBudget(budget);
137
- tx.setGasPrice(await this.getGasPrice());
138
- const bytes = buildClient ? await tx.build({ client: buildClient }) : await tx.build();
139
- const { signature } = await kp.signTransaction(bytes);
140
- const resp = await this.core.executeTransaction({ transaction: bytes, signatures: [signature], include: { effects: true, objectTypes: true, balanceChanges: true } });
141
- const tr = resp.Transaction;
142
- const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
143
- (0, dist_1.log_info)(`[executor] rebalance ${label} ${success ? 'OK' : 'FAILED'} wallet=${addr.slice(0, 10)}… digest=${tr.digest}${error ? ' err=' + error : ''}`);
144
- await this.reconcileCoins(addr, constants_1.SUI_TOKEN_ADDRESS.LONG);
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
- this.chainIdentifier = (await this.core.getChainIdentifier()).chainIdentifier;
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) {
@@ -209,8 +337,17 @@ class CentralExecutor {
209
337
  }
210
338
  async submitSwap(req) {
211
339
  const t0 = Date.now();
340
+ req = await this.canonicalizeReq(req);
212
341
  const inType = this.inputCoinType(req);
213
- const wallet = this.chooseTradeWallet(req, inType, req.amountIn);
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
+ }
214
351
  const inputTag = this.nextTag('in');
215
352
  const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
216
353
  const tAcquire = Date.now();
@@ -290,6 +427,7 @@ class CentralExecutor {
290
427
  void this.reconcileCoins(wallet, t);
291
428
  }
292
429
  async simulateSwap(req) {
430
+ req = await this.canonicalizeReq(req);
293
431
  const inType = this.inputCoinType(req);
294
432
  const wallet = req.walletAddress ?? this.chooseTradeWallet(req, inType, req.amountIn);
295
433
  const { coins } = this.cache.snapshot(wallet, inType);
@@ -312,6 +450,32 @@ class CentralExecutor {
312
450
  }
313
451
  return res.Transaction;
314
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
+ }
315
479
  async buildSwapTx(req, wallet, inputCoins) {
316
480
  const tx = new transactions_1.Transaction();
317
481
  tx.setSender(wallet);
@@ -344,8 +508,7 @@ class CentralExecutor {
344
508
  const tradeChanges = (0, effects_1.extractCoinChangesFromCore)(tr.effects, objectTypes, { owners: new Set([wallet]), coinTypeById });
345
509
  this.cache.commit(inputTag, tradeChanges);
346
510
  if (this.reconcileAfterTx) {
347
- for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
348
- void this.reconcileCoins(wallet, t);
511
+ void this.postTradeRebalance(wallet, inType, outType);
349
512
  }
350
513
  }
351
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[]): void;
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
- (0, dist_1.log_info)(`[coin-cache] reconcile ${wallet} ${coinType}: ${kept.length} avail (${inflightIds.size} inflight kept)`);
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);
@@ -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
- const amount = new decimal_js_1.default(change.amount);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sui-common",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Sui common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",