@clonegod/ttd-sui-common 2.0.2 → 2.0.4

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.
@@ -51,6 +51,23 @@ class GrpcConnection {
51
51
  }
52
52
  return GrpcConnection.instance;
53
53
  }
54
+ static { this.CHANNEL_OPTIONS = {
55
+ 'grpc.keepalive_time_ms': 60000,
56
+ 'grpc.keepalive_timeout_ms': 10000,
57
+ 'grpc.keepalive_permit_without_calls': 0,
58
+ 'grpc.http2.max_pings_without_data': 0,
59
+ 'grpc.http2.min_time_between_pings_ms': 60000,
60
+ 'grpc.http2.min_ping_interval_without_data_ms': 60000,
61
+ 'grpc.max_receive_message_length': 4 * 1024 * 1024,
62
+ 'grpc.max_send_message_length': 4 * 1024 * 1024,
63
+ 'grpc.initial_reconnect_backoff_ms': 1000,
64
+ 'grpc.max_reconnect_backoff_ms': 30000,
65
+ 'grpc.enable_retries': 1,
66
+ 'grpc.retry_buffer_size': 256 * 1024,
67
+ 'grpc.http2.max_frame_size': 16384,
68
+ 'grpc.http2.max_header_list_size': 16384,
69
+ 'grpc.http2.initial_window_size': 65535,
70
+ }; }
54
71
  getChannel() {
55
72
  if (!this.channel) {
56
73
  this.channel = new grpc.Channel(this.endpoint, grpc.credentials.createSsl(), GrpcConnection.CHANNEL_OPTIONS);
@@ -91,20 +108,3 @@ class GrpcConnection {
91
108
  }
92
109
  }
93
110
  exports.GrpcConnection = GrpcConnection;
94
- GrpcConnection.CHANNEL_OPTIONS = {
95
- 'grpc.keepalive_time_ms': 60000,
96
- 'grpc.keepalive_timeout_ms': 10000,
97
- 'grpc.keepalive_permit_without_calls': 0,
98
- 'grpc.http2.max_pings_without_data': 0,
99
- 'grpc.http2.min_time_between_pings_ms': 60000,
100
- 'grpc.http2.min_ping_interval_without_data_ms': 60000,
101
- 'grpc.max_receive_message_length': 4 * 1024 * 1024,
102
- 'grpc.max_send_message_length': 4 * 1024 * 1024,
103
- 'grpc.initial_reconnect_backoff_ms': 1000,
104
- 'grpc.max_reconnect_backoff_ms': 30000,
105
- 'grpc.enable_retries': 1,
106
- 'grpc.retry_buffer_size': 256 * 1024,
107
- 'grpc.http2.max_frame_size': 16384,
108
- 'grpc.http2.max_header_list_size': 16384,
109
- 'grpc.http2.initial_window_size': 65535,
110
- };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TokenPriceCache = void 0;
4
4
  const dist_1 = require("@clonegod/ttd-core/dist");
5
+ const format_1 = require("../../utils/format");
5
6
  class TokenPriceCache {
6
7
  constructor() {
7
8
  this.tokenPriceCache = new Map();
@@ -12,6 +13,7 @@ class TokenPriceCache {
12
13
  let tokenParts = tokenAddress.split('::');
13
14
  tokenParts[0] = tokenParts[0].toLowerCase();
14
15
  tokenAddress = tokenParts.join('::');
16
+ tokenAddress = (0, format_1.normalizeSuiTokenAddress)(tokenAddress, false);
15
17
  const now = Date.now();
16
18
  const cachedData = this.tokenPriceCache.get(tokenAddress);
17
19
  if (cachedData && (now - cachedData.timestamp) < this.PRICE_CACHE_TIMEOUT_MILLS) {
@@ -7,6 +7,7 @@ exports.getQuoteAmountUsd = getQuoteAmountUsd;
7
7
  exports.usdToTokenUiAmount = usdToTokenUiAmount;
8
8
  const ttd_core_1 = require("@clonegod/ttd-core");
9
9
  const decimal_js_1 = __importDefault(require("decimal.js"));
10
+ const format_1 = require("../utils/format");
10
11
  function getQuoteAmountUsd(poolInfo) {
11
12
  const envValue = Number(process.env.QUOTE_AMOUNT_USD);
12
13
  if (envValue > 0) {
@@ -15,6 +16,7 @@ function getQuoteAmountUsd(poolInfo) {
15
16
  return poolInfo.quote_amount_usd;
16
17
  }
17
18
  async function usdToTokenUiAmount(amountInUsd, tokenAddress) {
19
+ tokenAddress = (0, format_1.normalizeSuiTokenAddress)(tokenAddress, false);
18
20
  const priceMap = await (0, ttd_core_1.get_sui_token_price_info)([tokenAddress]);
19
21
  const price = priceMap.get(tokenAddress)?.price;
20
22
  if (!price || price === '0') {
@@ -1,9 +1,8 @@
1
1
  import { QuoteResultType } from "@clonegod/ttd-core";
2
2
  import { AbastrcatTrade, AppConfig, CHAIN_ID, TradeContext } from "@clonegod/ttd-core/dist";
3
- import { SuiClient } from "@mysten/sui/client";
4
3
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
5
4
  import { Transaction } from '@mysten/sui/transactions';
6
- import { PoolObjectInfo, SuiGrpcClient, SuiTxSender } from '../index';
5
+ import { PoolObjectInfo, SuiGrpcClient } from '../index';
7
6
  import { SimpleRedisClient } from '../redis';
8
7
  import { ExecutorWsClient } from './executor';
9
8
  export declare abstract class AbstractSuiDexTrade extends AbastrcatTrade {
@@ -12,9 +11,7 @@ export declare abstract class AbstractSuiDexTrade extends AbastrcatTrade {
12
11
  protected group_wallets: Ed25519Keypair[];
13
12
  protected wallet: Ed25519Keypair;
14
13
  protected walletMode: 'single' | 'multi';
15
- sui_client: SuiClient;
16
14
  grpcClient: SuiGrpcClient;
17
- transactionSender: SuiTxSender;
18
15
  protected redisClient: SimpleRedisClient;
19
16
  protected executorClient: ExecutorWsClient;
20
17
  walletAddresses: string[];
@@ -22,7 +19,6 @@ export declare abstract class AbstractSuiDexTrade extends AbastrcatTrade {
22
19
  protected chainNameLower: string;
23
20
  constructor(appConfig: AppConfig, grpcClient: SuiGrpcClient);
24
21
  init(): Promise<void>;
25
- private initSuiClient;
26
22
  protected getWalletMode(): 'single' | 'multi';
27
23
  protected getSingleWallet(): Ed25519Keypair;
28
24
  protected getGroupWallets(): Ed25519Keypair[];
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AbstractSuiDexTrade = void 0;
7
7
  const dist_1 = require("@clonegod/ttd-core/dist");
8
- const client_1 = require("@mysten/sui/client");
9
8
  const ed25519_1 = require("@mysten/sui/keypairs/ed25519");
10
9
  const transactions_1 = require("@mysten/sui/transactions");
11
10
  const decimal_js_1 = __importDefault(require("decimal.js"));
@@ -25,7 +24,6 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
25
24
  this.redisClient = new redis_1.SimpleRedisClient(`${this.chainNameLower}:tx`);
26
25
  }
27
26
  async init() {
28
- this.initSuiClient();
29
27
  await this.initConfigs();
30
28
  const walletGroupIds = process.env.SUI_WALLET_GROUP_IDS || process.env.WALLET_GROUP_IDS || '';
31
29
  this.walletMode = walletGroupIds && walletGroupIds.trim().split(',').length > 0 ? 'multi' : 'single';
@@ -42,7 +40,6 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
42
40
  this.walletAddresses = [this.currentWalletAddress];
43
41
  (0, dist_1.log_info)(`init wallet mode: single, walletAddress= ${this.currentWalletAddress}`);
44
42
  }
45
- this.transactionSender = new index_1.SuiTxSender(this.appConfig, this.sui_client, this.grpcClient);
46
43
  this.executorClient = new executor_1.ExecutorWsClient();
47
44
  const txResultChannel = (0, tx_result_channel_1.get_sui_tx_result_channel)();
48
45
  this.appConfig.arb_cache.redis_cmd.subscribe(txResultChannel, (message) => {
@@ -56,11 +53,6 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
56
53
  }
57
54
  });
58
55
  }
59
- initSuiClient() {
60
- const sui_rpc_url = process.env.RPC_ENDPOINT || 'https://fullnode.mainnet.sui.io:443';
61
- this.sui_client = new client_1.SuiClient({ url: sui_rpc_url });
62
- (0, dist_1.log_info)(`init sui client, rpc_url: ${sui_rpc_url}`);
63
- }
64
56
  getWalletMode() {
65
57
  return this.walletMode;
66
58
  }
@@ -246,7 +238,7 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
246
238
  const { aToB, amount: uiAmount, price: cex_order_price } = order_msg;
247
239
  let { inputToken, outputToken } = (0, dist_1.get_input_out_token)(pool_info, aToB);
248
240
  let amountIn = new decimal_js_1.default(uiAmount).mul(10 ** inputToken.decimals);
249
- let slippage = slippage_bps / 10000;
241
+ let slippage = slippage_bps / 100_00;
250
242
  let price;
251
243
  let amountOut;
252
244
  let amountOutMin;
@@ -9,10 +9,6 @@ export interface CoinReservation {
9
9
  coinType: string;
10
10
  coins: CoinRef[];
11
11
  }
12
- export interface GasReservation {
13
- txTag: string;
14
- coin: CoinRef;
15
- }
16
12
  export interface CoinObjectChange {
17
13
  kind: 'mutated' | 'created' | 'deleted';
18
14
  objectId: string;
@@ -1,6 +1,5 @@
1
1
  import { DEX_ID } from '@clonegod/ttd-core/dist';
2
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
3
- import type { Experimental_SuiClientTypes } from '@mysten/sui/experimental';
2
+ import type { SuiClientTypes } from '@mysten/sui/client';
4
3
  import { InProcessCoinCache } from './coin_cache';
5
4
  import { ExecutorCore } from './core_channel';
6
5
  export interface SwapExecRequest {
@@ -26,7 +25,6 @@ export interface SwapSubmitResult {
26
25
  }
27
26
  export interface CentralExecutorOptions {
28
27
  gasBudget?: bigint;
29
- gasMinCoin?: bigint;
30
28
  reconcileAfterTx?: boolean;
31
29
  onBroadcastResult?: (r: {
32
30
  digest: string;
@@ -34,23 +32,30 @@ export interface CentralExecutorOptions {
34
32
  error?: string;
35
33
  }) => void;
36
34
  }
37
- type TxResponse = Experimental_SuiClientTypes.TransactionResponse;
35
+ type TxResponse = SuiClientTypes.Transaction<{
36
+ effects: true;
37
+ objectTypes: true;
38
+ balanceChanges: true;
39
+ }>;
38
40
  export declare class CentralExecutor {
39
41
  private readonly core;
40
42
  private readonly cache;
41
43
  private readonly gasBudget;
42
- private readonly gasMinCoin;
43
44
  private readonly reconcileAfterTx;
44
45
  private readonly onBroadcastResult?;
45
- private gasKeypair;
46
- private gasAddress;
47
46
  private tradeWallets;
48
47
  private sharedRefCache;
49
48
  private seq;
50
49
  constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
51
50
  init(): Promise<void>;
52
51
  reconcileCoins(wallet: string, coinType: string): Promise<void>;
52
+ rebalanceWalletFunds(): Promise<void>;
53
+ private rebalanceOne;
54
+ private execMaintenance;
53
55
  private getSharedRefCached;
56
+ private chainIdentifier;
57
+ private epochCache;
58
+ private getValidDuringExpiration;
54
59
  private getGasPrice;
55
60
  private nextTag;
56
61
  private inputCoinType;
@@ -64,8 +69,6 @@ export declare class CentralExecutor {
64
69
  private buildSwapTx;
65
70
  private onSuccess;
66
71
  get coinCache(): InProcessCoinCache;
67
- get gasWalletAddress(): string;
68
- get gasSigner(): Ed25519Keypair;
69
72
  get tradeWalletAddresses(): string[];
70
73
  get coreClient(): ExecutorCore;
71
74
  }
@@ -12,42 +12,36 @@ class CentralExecutor {
12
12
  constructor(core, opts = {}) {
13
13
  this.core = core;
14
14
  this.cache = new coin_cache_1.InProcessCoinCache();
15
- this.gasAddress = '';
16
15
  this.tradeWallets = new Map();
17
16
  this.sharedRefCache = new Map();
18
17
  this.seq = 0;
18
+ this.chainIdentifier = '';
19
+ this.epochCache = null;
19
20
  this.gasBudget = opts.gasBudget ?? BigInt(process.env.SUI_GAS_BUDGET || '50000000');
20
- this.gasMinCoin = opts.gasMinCoin ?? BigInt(process.env.SUI_GAS_MIN_COIN || this.gasBudget.toString());
21
21
  this.reconcileAfterTx = opts.reconcileAfterTx ?? true;
22
22
  this.onBroadcastResult = opts.onBroadcastResult;
23
23
  }
24
24
  async init() {
25
25
  const tradeIds = (process.env.SUI_WALLET_GROUP_IDS || '').split(',').map(s => s.trim()).filter(Boolean);
26
- const gasIds = (process.env.SUI_GAS_WALLET_GROUP_IDS || '').split(',').map(s => s.trim()).filter(Boolean);
27
26
  if (!tradeIds.length)
28
27
  throw new Error('SUI_WALLET_GROUP_IDS 未配置(集中执行器需交易钱包)');
29
- if (!gasIds.length)
30
- throw new Error('SUI_GAS_WALLET_GROUP_IDS 未配置(集中执行器需 gas 赞助钱包)');
31
28
  for (const info of (0, dist_1.load_wallet_multi)(tradeIds, false)) {
32
29
  const kp = ed25519_1.Ed25519Keypair.fromSecretKey(info.private_key);
33
30
  this.tradeWallets.set(kp.getPublicKey().toSuiAddress(), kp);
34
31
  }
35
- const gasInfos = (0, dist_1.load_wallet_multi)(gasIds, false);
36
- if (!gasInfos.length)
37
- throw new Error('gas 钱包加载为空');
38
- this.gasKeypair = ed25519_1.Ed25519Keypair.fromSecretKey(gasInfos[0].private_key);
39
- this.gasAddress = this.gasKeypair.getPublicKey().toSuiAddress();
40
- await this.reconcileCoins(this.gasAddress, constants_1.SUI_TOKEN_ADDRESS.LONG);
41
- (0, dist_1.log_info)(`[executor] init: trade=${[...this.tradeWallets.keys()].join(',')}, gas=${this.gasAddress}, gasBudget=${this.gasBudget}`);
32
+ for (const w of this.tradeWallets.keys()) {
33
+ await this.reconcileCoins(w, constants_1.SUI_TOKEN_ADDRESS.LONG);
34
+ }
35
+ (0, dist_1.log_info)(`[executor] init: trade=${[...this.tradeWallets.keys()].join(',')} (单钱包模式: gas=各自地址余额), gasBudget=${this.gasBudget}`);
42
36
  }
43
37
  async reconcileCoins(wallet, coinType) {
44
38
  try {
45
39
  const refs = [];
46
40
  let cursor = undefined;
47
41
  do {
48
- const page = await this.core.getCoins({ address: wallet, coinType, cursor });
42
+ const page = await this.core.listCoins({ owner: wallet, coinType, cursor });
49
43
  for (const c of page.objects) {
50
- refs.push({ objectId: c.id, version: c.version, digest: c.digest, balance: c.balance });
44
+ refs.push({ objectId: c.objectId, version: c.version, digest: c.digest, balance: c.balance });
51
45
  }
52
46
  cursor = page.hasNextPage ? page.cursor : null;
53
47
  } while (cursor);
@@ -57,6 +51,98 @@ class CentralExecutor {
57
51
  (0, dist_1.log_error)(`[executor] reconcileCoins 失败 ${wallet} ${coinType}`, e);
58
52
  }
59
53
  }
54
+ async rebalanceWalletFunds() {
55
+ for (const [addr, kp] of this.tradeWallets) {
56
+ try {
57
+ await this.rebalanceOne(addr, kp);
58
+ }
59
+ catch (e) {
60
+ (0, dist_1.log_error)(`[executor] rebalance 失败 ${addr.slice(0, 10)}…`, e);
61
+ }
62
+ }
63
+ }
64
+ async rebalanceOne(addr, kp) {
65
+ const SUI = constants_1.SUI_TOKEN_ADDRESS.LONG;
66
+ const balanceMin = BigInt(process.env.SUI_GAS_BALANCE_MIN || '500000000');
67
+ const balanceTarget = BigInt(process.env.SUI_GAS_BALANCE_TARGET || (balanceMin * 2n).toString());
68
+ 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');
71
+ const chunk = BigInt(process.env.SUI_REDEEM_MIN_CHUNK || '100000000');
72
+ const budget = BigInt(process.env.SUI_MAINTAIN_GAS_BUDGET || '20000000');
73
+ await this.reconcileCoins(addr, SUI);
74
+ if (this.cache.hasInflight(addr, SUI))
75
+ return;
76
+ const b = await this.core.getBalance({ owner: addr, coinType: '0x2::sui::SUI' });
77
+ const ab = BigInt(b.balance.addressBalance ?? '0');
78
+ const coins = this.cache.snapshot(addr, SUI).coins.slice()
79
+ .sort((x, y) => (BigInt(y.balance) > BigInt(x.balance) ? 1 : BigInt(y.balance) < BigInt(x.balance) ? -1 : 0));
80
+ if (ab < balanceMin) {
81
+ const need = balanceTarget - ab;
82
+ const src = coins.find(c => BigInt(c.balance) >= need + budget);
83
+ if (!src) {
84
+ (0, dist_1.log_warn)(`[executor] ${addr.slice(0, 10)}… 地址余额 ${ab} < min ${balanceMin} 且无足额 SUI coin 充值 —— 请注资(balance 转账即可)`);
85
+ return;
86
+ }
87
+ const tx = new transactions_1.Transaction();
88
+ tx.setSender(addr);
89
+ 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)] });
92
+ tx.setGasPayment([{ objectId: src.objectId, version: src.version, digest: src.digest }]);
93
+ await this.execMaintenance(tx, addr, kp, budget, `deposit ${need} → balance`);
94
+ return;
95
+ }
96
+ if (ab > balanceTarget + chunk) {
97
+ const excess = ab - balanceTarget;
98
+ const rawClient = this.core.rawClient;
99
+ if (!rawClient)
100
+ return;
101
+ const tx = new transactions_1.Transaction();
102
+ tx.setSender(addr);
103
+ tx.transferObjects([(0, transactions_1.coinWithBalance)({ type: '0x2::sui::SUI', balance: excess, useGasCoin: false })], tx.pure.address(addr));
104
+ tx.setGasPayment([]);
105
+ tx.setExpiration(await this.getValidDuringExpiration());
106
+ await this.execMaintenance(tx, addr, kp, budget, `redeem 超额 ${excess} → coin`, rawClient);
107
+ return;
108
+ }
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;
121
+ }
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;
133
+ }
134
+ }
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);
145
+ }
60
146
  async getSharedRefCached(objectId) {
61
147
  let isv = this.sharedRefCache.get(objectId);
62
148
  if (!isv) {
@@ -72,6 +158,26 @@ class CentralExecutor {
72
158
  }
73
159
  return isv;
74
160
  }
161
+ async getValidDuringExpiration() {
162
+ if (!this.chainIdentifier) {
163
+ this.chainIdentifier = (await this.core.getChainIdentifier()).chainIdentifier;
164
+ }
165
+ const now = Date.now();
166
+ if (!this.epochCache || now - this.epochCache.ts > 300_000) {
167
+ const s = await this.core.getCurrentSystemState();
168
+ const e = s?.systemState?.epoch ?? s?.epoch;
169
+ if (e == null)
170
+ throw new Error('[executor] getCurrentSystemState 未返回 epoch(ValidDuring 必需)');
171
+ this.epochCache = { epoch: BigInt(e), ts: now };
172
+ }
173
+ return { ValidDuring: {
174
+ minEpoch: String(this.epochCache.epoch),
175
+ maxEpoch: String(this.epochCache.epoch + 1n),
176
+ minTimestamp: null, maxTimestamp: null,
177
+ chain: this.chainIdentifier,
178
+ nonce: Math.floor(Math.random() * 0xFFFFFFFF),
179
+ } };
180
+ }
75
181
  async getGasPrice() {
76
182
  return BigInt((await this.core.getReferenceGasPrice()).referenceGasPrice);
77
183
  }
@@ -105,39 +211,27 @@ class CentralExecutor {
105
211
  const inType = this.inputCoinType(req);
106
212
  const wallet = this.chooseTradeWallet(req, inType, req.amountIn);
107
213
  const inputTag = this.nextTag('in');
108
- const gasTag = this.nextTag('gas');
109
214
  const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
110
- let gasRes;
111
215
  try {
112
- gasRes = this.cache.acquireGas(this.gasAddress, constants_1.SUI_TOKEN_ADDRESS.LONG, this.gasMinCoin, gasTag);
113
- }
114
- catch (e) {
115
- this.cache.abort(inputTag, false);
116
- throw e;
117
- }
118
- try {
119
- const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins, gasRes.coin);
216
+ const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
120
217
  const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
121
- const { signature: sponsorSig } = await this.gasKeypair.signTransaction(txBytes);
122
218
  const digest = await tx.getDigest();
123
- void this.broadcastAndCommit(txBytes, [senderSig, sponsorSig], digest, wallet, inType, this.outputCoinType(req), inputRes.coins, gasRes.coin.objectId, inputTag, gasTag);
219
+ void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, inType, this.outputCoinType(req), inputRes.coins, inputTag);
124
220
  (0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn}`);
125
221
  return { digest, submitted: true };
126
222
  }
127
223
  catch (e) {
128
224
  this.cache.abort(inputTag, false);
129
- this.cache.abort(gasTag, false);
130
225
  (0, dist_1.log_error)(`[executor] submitSwap 失败 dex=${req.dexId}`, e);
131
226
  return { digest: '', submitted: false, error: e.message };
132
227
  }
133
228
  }
134
- async broadcastAndCommit(txBytes, signatures, digest, wallet, inType, outType, inputCoins, gasCoinId, inputTag, gasTag) {
229
+ async broadcastAndCommit(txBytes, signatures, digest, wallet, inType, outType, inputCoins, inputTag) {
135
230
  try {
136
- const resp = await this.core.executeTransaction({ transaction: txBytes, signatures });
137
- const tr = resp.transaction;
231
+ const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
232
+ const tr = resp.Transaction;
138
233
  if (!tr?.effects) {
139
234
  this.cache.abort(inputTag, true);
140
- this.cache.abort(gasTag, true);
141
235
  this.reconcileAfterFailure(wallet, inType, outType);
142
236
  (0, dist_1.log_error)(`[executor] 响应缺 effects,无法更新 cache digest=${digest}`, new Error('missing effects'));
143
237
  this.onBroadcastResult?.({ digest, success: false, error: 'missing effects' });
@@ -146,19 +240,17 @@ class CentralExecutor {
146
240
  const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
147
241
  if (!success) {
148
242
  this.cache.abort(inputTag, true);
149
- this.cache.abort(gasTag, true);
150
243
  this.reconcileAfterFailure(wallet, inType, outType);
151
244
  (0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error}`);
152
245
  this.onBroadcastResult?.({ digest, success: false, error });
153
246
  return;
154
247
  }
155
- await this.onSuccess(tr, wallet, inType, outType, inputCoins, gasCoinId, inputTag, gasTag);
248
+ await this.onSuccess(tr, wallet, inType, outType, inputCoins, inputTag);
156
249
  (0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest}`);
157
250
  this.onBroadcastResult?.({ digest, success: true });
158
251
  }
159
252
  catch (e) {
160
253
  this.cache.abort(inputTag, true);
161
- this.cache.abort(gasTag, true);
162
254
  this.reconcileAfterFailure(wallet, inType, outType);
163
255
  (0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest}`, e);
164
256
  }
@@ -166,7 +258,6 @@ class CentralExecutor {
166
258
  reconcileAfterFailure(wallet, inType, outType) {
167
259
  for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
168
260
  void this.reconcileCoins(wallet, t);
169
- void this.reconcileCoins(this.gasAddress, constants_1.SUI_TOKEN_ADDRESS.LONG);
170
261
  }
171
262
  async simulateSwap(req) {
172
263
  const inType = this.inputCoinType(req);
@@ -184,14 +275,14 @@ class CentralExecutor {
184
275
  }
185
276
  if (sum < req.amountIn)
186
277
  throw new Error(`[executor] simulate 输入不足 ${sum} < ${req.amountIn}`);
187
- const gasSnap = this.cache.snapshot(this.gasAddress, constants_1.SUI_TOKEN_ADDRESS.LONG);
188
- const gasCoin = gasSnap.coins.find(c => BigInt(c.balance) >= this.gasMinCoin);
189
- if (!gasCoin)
190
- throw new Error(`[executor] simulate ${this.gasMinCoin} gas coin`);
191
- const { txBytes } = await this.buildSwapTx(req, wallet, picked, gasCoin);
192
- return (await this.core.dryRunTransaction({ transaction: txBytes })).transaction;
193
- }
194
- async buildSwapTx(req, wallet, inputCoins, gasCoin) {
278
+ const { txBytes } = await this.buildSwapTx(req, wallet, picked);
279
+ const res = await this.core.simulateTransaction({ transaction: txBytes, include: { effects: true, objectTypes: true, balanceChanges: true } });
280
+ if (!res?.Transaction) {
281
+ throw new Error(`[executor] simulate FailedTransaction: ${JSON.stringify(res?.FailedTransaction ?? res).slice(0, 400)}`);
282
+ }
283
+ return res.Transaction;
284
+ }
285
+ async buildSwapTx(req, wallet, inputCoins) {
195
286
  const tx = new transactions_1.Transaction();
196
287
  tx.setSender(wallet);
197
288
  await this.registerShared(tx, req.poolId, true);
@@ -208,32 +299,26 @@ class CentralExecutor {
208
299
  amountLimit: req.minOut, sqrtPriceLimit: req.sqrtPriceLimit,
209
300
  }, inCoin);
210
301
  tx.transferObjects([outputCoin, ...leftoverCoins], tx.pure.address(wallet));
211
- tx.setGasOwner(this.gasAddress);
212
- tx.setGasPayment([{ objectId: gasCoin.objectId, version: gasCoin.version, digest: gasCoin.digest }]);
302
+ tx.setGasPayment([]);
303
+ tx.setExpiration(await this.getValidDuringExpiration());
213
304
  tx.setGasBudget(this.gasBudget);
214
305
  tx.setGasPrice(await this.getGasPrice());
215
306
  const txBytes = await tx.build();
216
307
  return { txBytes, tx };
217
308
  }
218
- async onSuccess(tr, wallet, inType, outType, inputCoins, gasCoinId, inputTag, gasTag) {
309
+ async onSuccess(tr, wallet, inType, outType, inputCoins, inputTag) {
219
310
  const objectTypes = await tr.objectTypes;
220
311
  const coinTypeById = new Map();
221
312
  for (const c of inputCoins)
222
313
  coinTypeById.set(c.objectId, inType);
223
- coinTypeById.set(gasCoinId, constants_1.SUI_TOKEN_ADDRESS.LONG);
224
314
  const tradeChanges = (0, effects_1.extractCoinChangesFromCore)(tr.effects, objectTypes, { owners: new Set([wallet]), coinTypeById });
225
- const gasChanges = (0, effects_1.extractCoinChangesFromCore)(tr.effects, objectTypes, { owners: new Set([this.gasAddress]), coinTypeById });
226
315
  this.cache.commit(inputTag, tradeChanges);
227
- this.cache.commit(gasTag, gasChanges);
228
316
  if (this.reconcileAfterTx) {
229
317
  for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
230
318
  void this.reconcileCoins(wallet, t);
231
- void this.reconcileCoins(this.gasAddress, constants_1.SUI_TOKEN_ADDRESS.LONG);
232
319
  }
233
320
  }
234
321
  get coinCache() { return this.cache; }
235
- get gasWalletAddress() { return this.gasAddress; }
236
- get gasSigner() { return this.gasKeypair; }
237
322
  get tradeWalletAddresses() { return [...this.tradeWallets.keys()]; }
238
323
  get coreClient() { return this.core; }
239
324
  }
@@ -1,4 +1,4 @@
1
- import { CoinRef, CoinReservation, GasReservation, CoinObjectChange } from '../coin/types';
1
+ import { CoinRef, CoinReservation, CoinObjectChange } from '../coin/types';
2
2
  export declare class InProcessCoinCache {
3
3
  private available;
4
4
  private inflight;
@@ -8,7 +8,7 @@ export declare class InProcessCoinCache {
8
8
  seed(wallet: string, coinType: string, coins: CoinRef[]): void;
9
9
  reconcile(wallet: string, coinType: string, freshCoins: CoinRef[]): void;
10
10
  acquire(wallet: string, coinType: string, amount: bigint, txTag: string, maxCoins?: number): CoinReservation;
11
- acquireGas(wallet: string, coinType: string, minGas: bigint, txTag: string): GasReservation;
11
+ hasInflight(wallet: string, coinType: string): boolean;
12
12
  commit(txTag: string, changes: CoinObjectChange[]): void;
13
13
  abort(txTag: string, consumed: boolean): void;
14
14
  private removeEverywhere;
@@ -67,22 +67,12 @@ class InProcessCoinCache {
67
67
  this.inflight.set(txTag, { wallet, coinType, coins: picked, gas: false });
68
68
  return { txTag, coinType, coins: picked };
69
69
  }
70
- acquireGas(wallet, coinType, minGas, txTag) {
71
- if (this.inflight.has(txTag))
72
- throw new Error(`[coin-cache] gas txTag 重复: ${txTag}`);
73
- const l = this.list(wallet, coinType);
74
- let chosen;
75
- for (let i = l.length - 1; i >= 0; i--) {
76
- if (BigInt(l[i].balance) >= minGas) {
77
- chosen = l[i];
78
- break;
79
- }
70
+ hasInflight(wallet, coinType) {
71
+ for (const r of this.inflight.values()) {
72
+ if (r.wallet === wallet && r.coinType === coinType)
73
+ return true;
80
74
  }
81
- if (!chosen)
82
- throw new Error(`[coin-cache] gas 钱包 ${wallet} 无 ≥ ${minGas} 的 gas coin(需 split 补给)`);
83
- this.walletMap(wallet).set(coinType, l.filter(c => c.objectId !== chosen.objectId));
84
- this.inflight.set(txTag, { wallet, coinType, coins: [chosen], gas: true });
85
- return { txTag, coin: chosen };
75
+ return false;
86
76
  }
87
77
  commit(txTag, changes) {
88
78
  const res = this.inflight.get(txTag);
@@ -1,32 +1,12 @@
1
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
2
1
  import { CentralExecutor } from './central_executor';
3
- import { ExecutorCore } from './core_channel';
4
- export interface CoinMaintainerOptions {
5
- gasTarget?: number;
6
- gasMin?: number;
7
- gasMax?: number;
8
- gasCoinSize?: bigint;
9
- intervalMs?: number;
10
- maintainGasBudget?: bigint;
11
- }
12
2
  export declare class CoinMaintainer {
13
- private readonly core;
14
- private readonly gasKeypair;
15
3
  private readonly executor;
16
- private readonly gasTarget;
17
- private readonly gasMin;
18
- private readonly gasMax;
19
- private readonly gasCoinSize;
20
4
  private readonly intervalMs;
21
- private readonly maintainGasBudget;
22
5
  private timer;
23
6
  private running;
24
- constructor(core: ExecutorCore, gasKeypair: Ed25519Keypair, executor: CentralExecutor, opts?: CoinMaintainerOptions);
7
+ constructor(executor: CentralExecutor, opts?: {
8
+ intervalMs?: number;
9
+ });
25
10
  start(): void;
26
11
  stop(): void;
27
- maintainGasPool(): Promise<void>;
28
- private splitGasCoins;
29
- private mergeGasCoins;
30
- private sortedDesc;
31
- private buildAndSend;
32
12
  }
@@ -2,36 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CoinMaintainer = void 0;
4
4
  const dist_1 = require("@clonegod/ttd-core/dist");
5
- const transactions_1 = require("@mysten/sui/transactions");
6
- const effects_1 = require("./effects");
7
- const constants_1 = require("../../constants");
8
5
  class CoinMaintainer {
9
- constructor(core, gasKeypair, executor, opts = {}) {
10
- this.core = core;
11
- this.gasKeypair = gasKeypair;
6
+ constructor(executor, opts = {}) {
12
7
  this.executor = executor;
13
8
  this.timer = null;
14
9
  this.running = false;
15
- this.gasTarget = opts.gasTarget ?? Number(process.env.SUI_GAS_POOL_TARGET || 8);
16
- this.gasMin = opts.gasMin ?? Number(process.env.SUI_GAS_POOL_MIN || 5);
17
- this.gasMax = opts.gasMax ?? Number(process.env.SUI_GAS_POOL_MAX || 10);
18
- this.gasCoinSize = opts.gasCoinSize ?? BigInt(process.env.SUI_GAS_COIN_SIZE || '150000000');
19
10
  this.intervalMs = opts.intervalMs ?? Number(process.env.SUI_COIN_MAINTAIN_INTERVAL_MS || 30000);
20
- this.maintainGasBudget = opts.maintainGasBudget ?? BigInt(process.env.SUI_MAINTAIN_GAS_BUDGET || '20000000');
21
11
  }
22
12
  start() {
23
13
  if (this.timer)
24
14
  return;
25
- (0, dist_1.log_info)(`[maintainer] start: gas target=${this.gasTarget}[${this.gasMin}-${this.gasMax}], size=${this.gasCoinSize}, interval=${this.intervalMs}ms`);
15
+ (0, dist_1.log_info)(`[maintainer] start: 单钱包资金平衡(balance↔coin 双向 + split/merge),interval=${this.intervalMs}ms`);
26
16
  const tick = async () => {
27
17
  if (this.running)
28
18
  return;
29
19
  this.running = true;
30
20
  try {
31
- await this.maintainGasPool();
21
+ await this.executor.rebalanceWalletFunds();
32
22
  }
33
23
  catch (e) {
34
- (0, dist_1.log_error)('[maintainer] tick 失败', e);
24
+ (0, dist_1.log_error)('[maintainer] rebalance tick 失败', e);
35
25
  }
36
26
  finally {
37
27
  this.running = false;
@@ -44,80 +34,5 @@ class CoinMaintainer {
44
34
  clearInterval(this.timer);
45
35
  this.timer = null;
46
36
  } }
47
- async maintainGasPool() {
48
- const gasAddr = this.executor.gasWalletAddress;
49
- const snap = this.executor.coinCache.snapshot(gasAddr, constants_1.SUI_TOKEN_ADDRESS.LONG);
50
- const count = snap.count;
51
- if (count >= this.gasMin && count <= this.gasMax)
52
- return;
53
- if (count < this.gasMin)
54
- await this.splitGasCoins(gasAddr, snap.coins, this.gasTarget - count);
55
- else
56
- await this.mergeGasCoins(gasAddr, snap.coins, count - this.gasTarget);
57
- await this.executor.reconcileCoins(gasAddr, constants_1.SUI_TOKEN_ADDRESS.LONG);
58
- }
59
- async splitGasCoins(gasAddr, coins, n) {
60
- if (n <= 0)
61
- return;
62
- const sorted = this.sortedDesc(coins);
63
- const src = sorted[0];
64
- const gasPay = sorted[1];
65
- if (!src || !gasPay) {
66
- (0, dist_1.log_warn)('[maintainer] gas coin 不足 2 个,无法独立挑 gas payment 做 split');
67
- return;
68
- }
69
- const need = this.gasCoinSize * BigInt(n);
70
- if (BigInt(src.balance) < need) {
71
- (0, dist_1.log_warn)(`[maintainer] gas 源 coin ${src.balance} 不足 split ${n} × ${this.gasCoinSize}=${need}`);
72
- return;
73
- }
74
- const tx = new transactions_1.Transaction();
75
- tx.setSender(gasAddr);
76
- const srcCoin = tx.object(transactions_1.Inputs.ObjectRef(src));
77
- const amounts = Array.from({ length: n }, () => tx.pure.u64(this.gasCoinSize));
78
- const newCoins = tx.splitCoins(srcCoin, amounts);
79
- tx.transferObjects(Array.from({ length: n }, (_, i) => newCoins[i]), tx.pure.address(gasAddr));
80
- await this.buildAndSend(tx, gasAddr, gasPay, `split ${n} gas coins`);
81
- }
82
- async mergeGasCoins(gasAddr, coins, n) {
83
- if (n <= 0)
84
- return;
85
- const sorted = this.sortedDesc(coins);
86
- const primary = sorted[0];
87
- const toMerge = sorted.slice(-n).filter(c => c.objectId !== primary.objectId);
88
- if (!toMerge.length)
89
- return;
90
- const mergeIds = new Set(toMerge.map(c => c.objectId));
91
- const gasPay = sorted.find(c => c.objectId !== primary.objectId && !mergeIds.has(c.objectId));
92
- if (!gasPay) {
93
- (0, dist_1.log_warn)('[maintainer] 无空闲 gas coin 付 merge 交易 gas');
94
- return;
95
- }
96
- const tx = new transactions_1.Transaction();
97
- tx.setSender(gasAddr);
98
- const primaryArg = tx.object(transactions_1.Inputs.ObjectRef(primary));
99
- tx.mergeCoins(primaryArg, toMerge.map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
100
- await this.buildAndSend(tx, gasAddr, gasPay, `merge ${toMerge.length} gas coins`);
101
- }
102
- sortedDesc(coins) {
103
- return coins.slice().sort((a, b) => (BigInt(b.balance) > BigInt(a.balance) ? 1 : BigInt(b.balance) < BigInt(a.balance) ? -1 : 0));
104
- }
105
- async buildAndSend(tx, gasAddr, gasPay, label) {
106
- try {
107
- tx.setGasOwner(gasAddr);
108
- tx.setGasPayment([{ objectId: gasPay.objectId, version: gasPay.version, digest: gasPay.digest }]);
109
- tx.setGasBudget(this.maintainGasBudget);
110
- tx.setGasPrice(BigInt((await this.core.getReferenceGasPrice()).referenceGasPrice));
111
- const bytes = await tx.build();
112
- const { signature } = await this.gasKeypair.signTransaction(bytes);
113
- const resp = await this.core.executeTransaction({ transaction: bytes, signatures: [signature] });
114
- const tr = resp.transaction;
115
- const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
116
- (0, dist_1.log_info)(`[maintainer] ${label} ${success ? 'OK' : 'FAILED'} digest=${tr.digest}${error ? ' err=' + error : ''}`);
117
- }
118
- catch (e) {
119
- (0, dist_1.log_error)(`[maintainer] ${label} 失败`, e);
120
- }
121
- }
122
37
  }
123
38
  exports.CoinMaintainer = CoinMaintainer;
@@ -1,17 +1,29 @@
1
- import { Experimental_CoreClient, Experimental_SuiClientTypes } from '@mysten/sui/experimental';
1
+ import { CoreClient, SuiClientTypes } from '@mysten/sui/client';
2
2
  export interface GrpcCoreOptions {
3
3
  endpoint?: string;
4
4
  token?: string;
5
5
  network?: 'mainnet' | 'testnet' | 'devnet' | 'localnet';
6
6
  }
7
- export declare function buildGrpcCore(opts?: GrpcCoreOptions): Experimental_CoreClient;
7
+ export declare function buildGrpcCore(opts?: GrpcCoreOptions): CoreClient;
8
8
  export interface GraphqlCoreOptions {
9
9
  url?: string;
10
10
  }
11
- export declare function buildGraphqlCore(opts?: GraphqlCoreOptions): Experimental_CoreClient;
12
- export declare function buildDefaultCore(): Experimental_CoreClient;
13
- export type ExecutorCore = Pick<Experimental_CoreClient, 'getCoins' | 'getObjects' | 'executeTransaction' | 'dryRunTransaction'> & {
14
- getReferenceGasPrice(): Promise<Experimental_SuiClientTypes.GetReferenceGasPriceResponse>;
11
+ export declare function buildGraphqlCore(opts?: GraphqlCoreOptions): CoreClient;
12
+ export declare function buildDefaultCore(): CoreClient;
13
+ export type ExecInclude = {
14
+ effects: true;
15
+ objectTypes: true;
16
+ balanceChanges: true;
17
+ };
18
+ export type ExecutorCore = {
19
+ listCoins(o: SuiClientTypes.ListCoinsOptions): Promise<SuiClientTypes.ListCoinsResponse>;
20
+ getBalance(o: SuiClientTypes.GetBalanceOptions): Promise<SuiClientTypes.GetBalanceResponse>;
21
+ getChainIdentifier(): Promise<SuiClientTypes.GetChainIdentifierResponse>;
22
+ getCurrentSystemState(): Promise<SuiClientTypes.GetCurrentSystemStateResponse>;
23
+ getObjects(o: SuiClientTypes.GetObjectsOptions): Promise<SuiClientTypes.GetObjectsResponse>;
24
+ executeTransaction(o: SuiClientTypes.ExecuteTransactionOptions<ExecInclude>): Promise<SuiClientTypes.TransactionResult<ExecInclude>>;
25
+ simulateTransaction(o: SuiClientTypes.SimulateTransactionOptions<ExecInclude>): Promise<SuiClientTypes.SimulateTransactionResult<ExecInclude>>;
26
+ getReferenceGasPrice(): Promise<SuiClientTypes.GetReferenceGasPriceResponse>;
15
27
  };
16
28
  export interface ResilientCoreOptions {
17
29
  retries?: number;
@@ -27,12 +39,16 @@ export declare class ResilientCore implements ExecutorCore {
27
39
  private readonly execDeadlineMs;
28
40
  private readonly gasPriceTtlMs;
29
41
  private gasPriceCache?;
30
- constructor(primary: Experimental_CoreClient, fallback?: Experimental_CoreClient, opts?: ResilientCoreOptions);
42
+ constructor(primary: CoreClient, fallback?: CoreClient, opts?: ResilientCoreOptions);
31
43
  private read;
32
- getCoins(o: Experimental_SuiClientTypes.GetCoinsOptions): Promise<Experimental_SuiClientTypes.GetCoinsResponse>;
33
- getObjects(o: Experimental_SuiClientTypes.GetObjectsOptions): Promise<Experimental_SuiClientTypes.GetObjectsResponse>;
34
- dryRunTransaction(o: Experimental_SuiClientTypes.DryRunTransactionOptions): Promise<Experimental_SuiClientTypes.DryRunTransactionResponse>;
35
- getReferenceGasPrice(): Promise<Experimental_SuiClientTypes.GetReferenceGasPriceResponse>;
36
- executeTransaction(o: Experimental_SuiClientTypes.ExecuteTransactionOptions): Promise<Experimental_SuiClientTypes.ExecuteTransactionResponse>;
44
+ listCoins(o: SuiClientTypes.ListCoinsOptions): Promise<SuiClientTypes.ListCoinsResponse>;
45
+ getBalance(o: SuiClientTypes.GetBalanceOptions): Promise<SuiClientTypes.GetBalanceResponse>;
46
+ getChainIdentifier(): Promise<SuiClientTypes.GetChainIdentifierResponse>;
47
+ getCurrentSystemState(): Promise<SuiClientTypes.GetCurrentSystemStateResponse>;
48
+ get rawClient(): CoreClient;
49
+ getObjects(o: SuiClientTypes.GetObjectsOptions): Promise<SuiClientTypes.GetObjectsResponse<{}>>;
50
+ simulateTransaction(o: SuiClientTypes.SimulateTransactionOptions<ExecInclude>): Promise<SuiClientTypes.SimulateTransactionResult<ExecInclude>>;
51
+ getReferenceGasPrice(): Promise<SuiClientTypes.GetReferenceGasPriceResponse>;
52
+ executeTransaction(o: SuiClientTypes.ExecuteTransactionOptions<ExecInclude>): Promise<SuiClientTypes.TransactionResult<ExecInclude>>;
37
53
  }
38
54
  export declare function buildResilientCore(opts?: ResilientCoreOptions): ResilientCore;
@@ -23,9 +23,8 @@ const GRPC_CLIENT_OPTIONS = {
23
23
  'grpc.max_reconnect_backoff_ms': 30000,
24
24
  };
25
25
  function buildGrpcCore(opts = {}) {
26
- const coreEnv = (0, dist_1.getCoreEnv)();
27
- const endpoint = opts.endpoint ?? coreEnv.grpc_endpoint;
28
- const token = opts.token ?? coreEnv.grpc_token;
26
+ const endpoint = opts.endpoint ?? (0, dist_1.getCoreEnv)().grpc_endpoint;
27
+ const token = opts.token ?? (0, dist_1.getCoreEnv)().grpc_token;
29
28
  if (!endpoint)
30
29
  throw new Error('GRPC_ENDPOINT 未配置(集中执行器需 gRPC 端点)');
31
30
  const transport = new grpc_transport_1.GrpcTransport({
@@ -42,7 +41,7 @@ function buildGraphqlCore(opts = {}) {
42
41
  const url = opts.url ?? process.env.SUI_GRAPHQL_URL;
43
42
  if (!url)
44
43
  throw new Error('SUI_GRAPHQL_URL 未配置(GraphQL fallback 通道需端点)');
45
- const core = new graphql_1.SuiGraphQLClient({ url }).core;
44
+ const core = new graphql_1.SuiGraphQLClient({ url, network: 'mainnet' }).core;
46
45
  (0, dist_1.log_info)(`[core-channel] GraphQL core ready, url=${url}`);
47
46
  return core;
48
47
  }
@@ -89,14 +88,24 @@ class ResilientCore {
89
88
  return await withRetry(() => call(this.fallback), { retries: this.retries, deadlineMs: this.readDeadlineMs, label: label + ':fallback' });
90
89
  }
91
90
  }
92
- getCoins(o) {
93
- return this.read('getCoins', c => c.getCoins(o));
91
+ listCoins(o) {
92
+ return this.read('listCoins', c => c.listCoins(o));
94
93
  }
94
+ getBalance(o) {
95
+ return this.read('getBalance', c => c.getBalance(o));
96
+ }
97
+ getChainIdentifier() {
98
+ return this.read('getChainIdentifier', c => c.getChainIdentifier());
99
+ }
100
+ getCurrentSystemState() {
101
+ return this.read('getCurrentSystemState', c => c.getCurrentSystemState());
102
+ }
103
+ get rawClient() { return this.primary; }
95
104
  getObjects(o) {
96
105
  return this.read('getObjects', c => c.getObjects(o));
97
106
  }
98
- dryRunTransaction(o) {
99
- return this.read('dryRunTransaction', c => c.dryRunTransaction(o));
107
+ simulateTransaction(o) {
108
+ return this.read('simulateTransaction', c => c.simulateTransaction(o));
100
109
  }
101
110
  async getReferenceGasPrice() {
102
111
  const fresh = this.gasPriceCache && (Date.now() - this.gasPriceCache.ts) < this.gasPriceTtlMs;
@@ -1,6 +1,6 @@
1
- import type { Experimental_SuiClientTypes } from '@mysten/sui/experimental';
1
+ import type { SuiClientTypes } from '@mysten/sui/client';
2
2
  import { CoinObjectChange } from '../coin/types';
3
- type TransactionEffects = Experimental_SuiClientTypes.TransactionEffects;
3
+ type TransactionEffects = SuiClientTypes.TransactionEffects;
4
4
  export declare function parseCoinType(type: string | undefined): string | null;
5
5
  export declare function coreTxStatus(effects: TransactionEffects): {
6
6
  success: boolean;
@@ -21,7 +21,7 @@ function ownerAddress(owner) {
21
21
  }
22
22
  function coreTxStatus(effects) {
23
23
  const s = effects.status;
24
- return { success: s.success, error: s.success ? undefined : s.error };
24
+ return { success: s.success, error: s.success ? undefined : (s.error?.message ?? JSON.stringify(s.error)) };
25
25
  }
26
26
  function coreGasUsed(effects) {
27
27
  const g = effects.gasUsed;
@@ -33,7 +33,7 @@ function extractCoinChangesFromCore(effects, objectTypes, opts = {}) {
33
33
  const kind = changedKind(ch);
34
34
  if (!kind)
35
35
  continue;
36
- const coinType = parseCoinType(objectTypes[ch.id]) ?? opts.coinTypeById?.get(ch.id);
36
+ const coinType = parseCoinType(objectTypes[ch.objectId]) ?? opts.coinTypeById?.get(ch.objectId);
37
37
  if (!coinType)
38
38
  continue;
39
39
  if (opts.owners?.size) {
@@ -43,11 +43,11 @@ function extractCoinChangesFromCore(effects, objectTypes, opts = {}) {
43
43
  }
44
44
  out.push({
45
45
  kind,
46
- objectId: ch.id,
46
+ objectId: ch.objectId,
47
47
  coinType,
48
48
  version: ch.outputVersion ?? undefined,
49
49
  digest: ch.outputDigest ?? undefined,
50
- balance: opts.balanceById?.get(ch.id),
50
+ balance: opts.balanceById?.get(ch.objectId),
51
51
  });
52
52
  }
53
53
  return out;
@@ -1,4 +1,3 @@
1
- export * from './send_tx';
2
1
  export * from './check';
3
2
  export * from './parse';
4
3
  export * from './coin';
@@ -14,7 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./send_tx"), exports);
18
17
  __exportStar(require("./check"), exports);
19
18
  __exportStar(require("./parse"), exports);
20
19
  __exportStar(require("./coin"), exports);
@@ -4,15 +4,15 @@ exports.SQRT_PRICE_LIMIT = exports.MOMENTUM_SWAP_CONFIG = exports.BLUEFIN_SWAP_C
4
4
  exports.configSharedObjectId = configSharedObjectId;
5
5
  exports.SUI_CLOCK_ID = '0x6';
6
6
  exports.CETUS_SWAP_CONFIG = {
7
- integratePkg: '0x2d8c2e0fc6dd25b0214b3fa747e0fd27fd54608142cd2e4f64c1cd350cc4add4',
7
+ integratePkg: '0xae9c208cf58fd5ba36737c9ee5dcfa7f152d0fb5a5a99eebb7c881ebc2fe59e0',
8
8
  globalConfig: '0xdaa46292632c3c4d8f31f23ea0f9b36a28ff3677e9684980e4438403a67a3d8f',
9
9
  };
10
10
  exports.MAGMA_SWAP_CONFIG = {
11
- integratePkg: '0x4a9d6fb6f34ca8918756c2dfddf8f0a7fd5ef590bcffa6c03db24a6f10f42c5e',
11
+ integratePkg: '0x668909f9f30380dd1b63834534dc2bd19b274e6312a0dfa9be0ee5b0cef73446',
12
12
  globalConfig: '0x4c4e1402401f72c7d8533d0ed8d5f8949da363c7a3319ccef261ffe153d32f8a',
13
13
  };
14
14
  exports.BLUEFIN_SWAP_CONFIG = {
15
- pkg: '0x67b34b728c4e28e704dcfecf7c5cf55c7fc593b6c65c20d1836d97c209c1928a',
15
+ pkg: '0xd075338d105482f1527cbfd363d6413558f184dec36d9138a70261e87f486e9c',
16
16
  globalConfig: '0x03db251ba509a8d5d8777b6338836082335d93eecbdd09a11e190a1cff51c352',
17
17
  };
18
18
  exports.MOMENTUM_SWAP_CONFIG = {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sui-common",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Sui common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "@clonegod/ttd-core": "3.1.82",
19
19
  "@grpc/grpc-js": "^1.13.4",
20
20
  "@grpc/proto-loader": "^0.8.0",
21
- "@mysten/sui": "^1.37.5",
21
+ "@mysten/sui": "^2.17.0",
22
22
  "@protobuf-ts/grpc-transport": "~2.11.1",
23
23
  "axios": "^1.12.0",
24
24
  "dotenv": "^16.4.7",