@clonegod/ttd-sui-common 2.0.5 → 2.0.6

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.
@@ -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;
@@ -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,6 +30,9 @@ export interface CentralExecutorOptions {
30
30
  digest: string;
31
31
  success: boolean;
32
32
  error?: string;
33
+ receipt?: {
34
+ transaction: any;
35
+ };
33
36
  }) => void;
34
37
  }
35
38
  type TxResponse = SuiClientTypes.Transaction<{
@@ -64,6 +67,7 @@ export declare class CentralExecutor {
64
67
  private registerShared;
65
68
  submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
66
69
  private broadcastAndCommit;
70
+ private toCheckerReceipt;
67
71
  private reconcileAfterFailure;
68
72
  simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
69
73
  private buildSwapTx;
@@ -208,16 +208,21 @@ class CentralExecutor {
208
208
  tx.object(transactions_1.Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable }));
209
209
  }
210
210
  async submitSwap(req) {
211
+ const t0 = Date.now();
211
212
  const inType = this.inputCoinType(req);
212
213
  const wallet = this.chooseTradeWallet(req, inType, req.amountIn);
213
214
  const inputTag = this.nextTag('in');
214
215
  const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
216
+ const tAcquire = Date.now();
217
+ (0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 10)}… ${inputRes.coins.length} 个 ${inType.split('::').pop()} (选钱包+占币 ${tAcquire - t0}ms)`);
215
218
  try {
216
219
  const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
220
+ const tBuild = Date.now();
217
221
  const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
218
222
  const digest = await tx.getDigest();
223
+ const tSign = Date.now();
219
224
  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}`);
225
+ (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
226
  return { digest, submitted: true };
222
227
  }
223
228
  catch (e) {
@@ -227,6 +232,7 @@ class CentralExecutor {
227
232
  }
228
233
  }
229
234
  async broadcastAndCommit(txBytes, signatures, digest, wallet, inType, outType, inputCoins, inputTag) {
235
+ const tBroadcast = Date.now();
230
236
  try {
231
237
  const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
232
238
  const tr = resp.Transaction;
@@ -241,20 +247,44 @@ class CentralExecutor {
241
247
  if (!success) {
242
248
  this.cache.abort(inputTag, true);
243
249
  this.reconcileAfterFailure(wallet, inType, outType);
244
- (0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error}`);
245
- this.onBroadcastResult?.({ digest, success: false, error });
250
+ (0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error} broadcast=${Date.now() - tBroadcast}ms`);
251
+ this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
246
252
  return;
247
253
  }
248
254
  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 });
255
+ (0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
256
+ this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
251
257
  }
252
258
  catch (e) {
253
259
  this.cache.abort(inputTag, true);
254
260
  this.reconcileAfterFailure(wallet, inType, outType);
255
- (0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest}`, e);
261
+ (0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`, e);
256
262
  }
257
263
  }
264
+ toCheckerReceipt(tr, digest) {
265
+ const eff = tr.effects;
266
+ const status = eff?.status?.success
267
+ ? { success: true }
268
+ : { success: false, error: { kind: eff?.status?.error?.message ?? JSON.stringify(eff?.status?.error ?? 'unknown') } };
269
+ const g = eff?.gasUsed ?? {};
270
+ return {
271
+ transaction: {
272
+ digest,
273
+ effects: {
274
+ status,
275
+ gas_used: {
276
+ computation_cost: g.computationCost,
277
+ storage_cost: g.storageCost,
278
+ storage_rebate: g.storageRebate,
279
+ non_refundable_storage_fee: g.nonRefundableStorageFee,
280
+ },
281
+ },
282
+ balance_changes: (tr.balanceChanges ?? []).map((b) => ({
283
+ address: b.address, coin_type: b.coinType, amount: b.amount,
284
+ })),
285
+ },
286
+ };
287
+ }
258
288
  reconcileAfterFailure(wallet, inType, outType) {
259
289
  for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
260
290
  void this.reconcileCoins(wallet, t);
@@ -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;
@@ -177,7 +177,7 @@ class SuiTransactionParser {
177
177
  return Date.now();
178
178
  }
179
179
  extractCheckpoint(receipt) {
180
- return parseInt(receipt.checkpoint || '0');
180
+ return parseInt(receipt.transaction?.checkpoint || receipt.checkpoint || '0');
181
181
  }
182
182
  }
183
183
  exports.SuiTransactionParser = SuiTransactionParser;
@@ -4,4 +4,7 @@ export interface SuiTxResultMsg {
4
4
  success?: boolean;
5
5
  error?: string;
6
6
  source?: 'executor' | 'stream-trade';
7
+ receipt?: {
8
+ transaction: any;
9
+ };
7
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sui-common",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "Sui common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",