@clonegod/ttd-sui-common 2.0.9 → 2.0.11

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.
@@ -61,13 +61,19 @@ export declare class CentralExecutor {
61
61
  private readonly maxRebuildRetries;
62
62
  constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
63
63
  init(): Promise<void>;
64
+ private dumpAllCoins;
64
65
  private warmup;
65
66
  reconcileCoins(wallet: string, coinType: string, quietIfUnchanged?: boolean): Promise<void>;
66
67
  rebalanceWalletFunds(): Promise<void>;
67
68
  private rebalanceOne;
69
+ private selectWalletWithBalance;
68
70
  private decimalsCache;
69
71
  private objectReader?;
70
72
  private getCoinDecimalsCached;
73
+ private coinSymbol;
74
+ private pairLabel;
75
+ private fmtAmount;
76
+ private walletBalancesLabel;
71
77
  private maintainCoinObjects;
72
78
  private execMaintenance;
73
79
  private poolGenericsCache;
@@ -48,6 +48,7 @@ class CentralExecutor {
48
48
  this.onBroadcastResult = opts.onBroadcastResult;
49
49
  this.tradeCoinTypesProvider = opts.tradeCoinTypes;
50
50
  this.tradePoolsProvider = opts.tradePools;
51
+ this.cache.setAmountFormatter((ct, raw) => this.fmtAmount(ct, raw));
51
52
  }
52
53
  async init() {
53
54
  const tradeIds = (process.env.SUI_WALLET_GROUP_IDS || '').split(',').map(s => s.trim()).filter(Boolean);
@@ -62,6 +63,28 @@ class CentralExecutor {
62
63
  }
63
64
  (0, dist_1.log_info)(`[executor] init: trade=${[...this.tradeWallets.keys()].join(',')} (单钱包模式: gas=各自地址余额), gasBudget=${this.gasBudget}`);
64
65
  await this.warmup();
66
+ await this.dumpAllCoins();
67
+ }
68
+ async dumpAllCoins() {
69
+ if (!this.tradeCoinTypesProvider)
70
+ return;
71
+ try {
72
+ const types = [...new Set((await this.tradeCoinTypesProvider()).map(t => (0, format_1.normalizeSuiTokenAddress)(t)))];
73
+ await Promise.all(types.map(t => this.getCoinDecimalsCached(t).catch(() => { })));
74
+ for (const w of this.tradeWallets.keys()) {
75
+ for (const t of types) {
76
+ if (t === constants_1.SUI_TOKEN_ADDRESS.LONG) {
77
+ this.cache.logCoinBreakdown(w, t, '启动一览');
78
+ continue;
79
+ }
80
+ await this.reconcileCoins(w, t);
81
+ }
82
+ }
83
+ (0, dist_1.log_info)(`[executor] 启动 coin 一览完成: ${types.length} 币种 × ${this.tradeWallets.size} 钱包`);
84
+ }
85
+ catch (e) {
86
+ (0, dist_1.log_warn)(`[executor] 启动 coin 一览失败(首笔将冷 reconcile): ${e.message}`);
87
+ }
65
88
  }
66
89
  async warmup() {
67
90
  try {
@@ -194,6 +217,17 @@ class CentralExecutor {
194
217
  return;
195
218
  }
196
219
  }
220
+ async selectWalletWithBalance(inType, amount) {
221
+ let best = null;
222
+ for (const w of this.tradeWallets.keys()) {
223
+ const objs = this.cache.snapshot(w, inType).total;
224
+ const b = await this.core.getBalance({ owner: w, coinType: inType });
225
+ const ab = BigInt(b.balance.addressBalance ?? '0');
226
+ if (objs + ab >= amount && (!best || objs + ab > best.sum))
227
+ best = { wallet: w, addressBalance: ab, sum: objs + ab };
228
+ }
229
+ return best ? { wallet: best.wallet, addressBalance: best.addressBalance } : null;
230
+ }
197
231
  async getCoinDecimalsCached(coinType) {
198
232
  let d = this.decimalsCache.get(coinType);
199
233
  if (d == null) {
@@ -208,6 +242,26 @@ class CentralExecutor {
208
242
  }
209
243
  return d;
210
244
  }
245
+ coinSymbol(coinType) { return coinType.split('::').pop() || coinType; }
246
+ pairLabel(req) {
247
+ return `${this.coinSymbol(this.inputCoinType(req))}→${this.coinSymbol(this.outputCoinType(req))}`;
248
+ }
249
+ fmtAmount(coinType, raw) {
250
+ const sym = this.coinSymbol(coinType);
251
+ const d = this.decimalsCache.get(coinType);
252
+ if (d == null)
253
+ return `${raw.toString()}(raw) ${sym}`;
254
+ const neg = raw < 0n, v = neg ? -raw : raw, base = 10n ** BigInt(d);
255
+ const intStr = (v / base).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
256
+ const frac = (v % base).toString().padStart(d, '0').replace(/0+$/, '');
257
+ return `${neg ? '-' : ''}${intStr}${frac ? '.' + frac : ''} ${sym}`;
258
+ }
259
+ walletBalancesLabel(coinType) {
260
+ return [...this.tradeWallets.keys()].map(w => {
261
+ const { total, count } = this.cache.snapshot(w, coinType);
262
+ return `${w.slice(0, 8)}…=${this.fmtAmount(coinType, total)}(${count}枚)`;
263
+ }).join(', ');
264
+ }
211
265
  async maintainCoinObjects(addr, kp, coinType, opts) {
212
266
  const short = coinType.split('::').pop();
213
267
  const zeroMergeMin = Number(process.env.SUI_ZERO_COIN_MERGE_THRESHOLD || 3);
@@ -379,27 +433,49 @@ class CentralExecutor {
379
433
  const t0 = Date.now();
380
434
  req = await this.canonicalizeReq(req);
381
435
  const inType = this.inputCoinType(req);
382
- let wallet;
436
+ await this.getCoinDecimalsCached(inType).catch(() => { });
437
+ let wallet = '';
438
+ let inputCoins = [];
439
+ let inputTag = '';
440
+ let shortfall = 0n;
383
441
  try {
384
442
  wallet = this.chooseTradeWallet(req, inType, req.amountIn);
443
+ inputTag = this.nextTag('in');
444
+ inputCoins = this.cache.acquire(wallet, inType, req.amountIn, inputTag).coins;
385
445
  }
386
446
  catch (e) {
387
- (0, dist_1.log_warn)(`[executor] ${inType} 选钱包失败(${e.message}),按需 reconcile 后重试`);
447
+ (0, dist_1.log_warn)(`[executor] ${this.pairLabel(req)} 对象不足:需 ${this.fmtAmount(inType, req.amountIn)},当前缓存 [${this.walletBalancesLabel(inType)}] reconcile 重试`);
388
448
  await Promise.all([...this.tradeWallets.keys()].map(w => this.reconcileCoins(w, inType)));
389
- wallet = this.chooseTradeWallet(req, inType, req.amountIn);
449
+ try {
450
+ wallet = this.chooseTradeWallet(req, inType, req.amountIn);
451
+ inputTag = this.nextTag('in');
452
+ inputCoins = this.cache.acquire(wallet, inType, req.amountIn, inputTag).coins;
453
+ }
454
+ catch (e2) {
455
+ const picked = await this.selectWalletWithBalance(inType, req.amountIn);
456
+ if (!picked) {
457
+ (0, dist_1.log_error)(`[executor] ${this.pairLabel(req)} 余额不足:需 ${this.fmtAmount(inType, req.amountIn)},对象+address-balance 仍不够 [${this.walletBalancesLabel(inType)}]`, e2);
458
+ throw e2;
459
+ }
460
+ wallet = picked.wallet;
461
+ inputTag = this.nextTag('in');
462
+ inputCoins = this.cache.acquireAvailable(wallet, inType, req.amountIn, inputTag);
463
+ const objSum = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
464
+ shortfall = req.amountIn - objSum;
465
+ }
390
466
  }
391
- const inputTag = this.nextTag('in');
392
- const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
393
467
  const tAcquire = Date.now();
394
- (0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 10)}… ${inputRes.coins.length} ${inType.split('::').pop()} (选钱包+占币 ${tAcquire - t0}ms)`);
468
+ const acqTotal = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
469
+ const shortStr = shortfall > 0n ? ` + address-balance 补 ${this.fmtAmount(inType, shortfall)}` : '';
470
+ (0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 8)}… ${this.pairLabel(req)} 取 ${inputCoins.length} 枚=${this.fmtAmount(inType, acqTotal)}${shortStr}(需 ${this.fmtAmount(inType, req.amountIn)})选钱包+占币 ${tAcquire - t0}ms`);
395
471
  try {
396
- const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
472
+ const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputCoins, shortfall);
397
473
  const tBuild = Date.now();
398
474
  const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
399
475
  const digest = await tx.getDigest();
400
476
  const tSign = Date.now();
401
- void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, req, inputRes.coins, inputTag);
402
- (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}`);
477
+ void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, req, inputCoins, inputTag, 0, shortfall > 0n);
478
+ (0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${this.pairLabel(req)} in=${this.fmtAmount(inType, req.amountIn)} minOut=${this.fmtAmount(this.outputCoinType(req), req.minOut)} cost={build:${tBuild - tAcquire}ms, sign+digest:${tSign - tBuild}ms, total:${tSign - t0}ms}`);
403
479
  return { digest, submitted: true };
404
480
  }
405
481
  catch (e) {
@@ -408,7 +484,7 @@ class CentralExecutor {
408
484
  return { digest: '', submitted: false, error: e.message };
409
485
  }
410
486
  }
411
- async broadcastAndCommit(txBytes, signatures, digest, wallet, req, inputCoins, inputTag, attempt = 0) {
487
+ async broadcastAndCommit(txBytes, signatures, digest, wallet, req, inputCoins, inputTag, attempt = 0, usedAddressBalance = false) {
412
488
  const inType = this.inputCoinType(req);
413
489
  const outType = this.outputCoinType(req);
414
490
  const tBroadcast = Date.now();
@@ -430,13 +506,13 @@ class CentralExecutor {
430
506
  this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
431
507
  return;
432
508
  }
433
- await this.onSuccess(tr, wallet, req, inputCoins, inputTag);
509
+ await this.onSuccess(tr, wallet, req, inputCoins, inputTag, usedAddressBalance);
434
510
  (0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
435
511
  this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
436
512
  }
437
513
  catch (e) {
438
514
  this.cache.abort(inputTag, true);
439
- if (this.isRebuildableObjectError(e) && attempt < this.maxRebuildRetries) {
515
+ if (!usedAddressBalance && this.isRebuildableObjectError(e) && attempt < this.maxRebuildRetries) {
440
516
  (0, dist_1.log_warn)(`[executor] swap 输入对象版本陈旧,定向刷新 ${inType.split('::').pop()} + 重建重发(第 ${attempt + 1}/${this.maxRebuildRetries} 次)digest=${digest}: ${e.message}`);
441
517
  await this.reconcileCoins(wallet, inType);
442
518
  const retry = await this.rebuildSwap(req, wallet);
@@ -551,17 +627,26 @@ class CentralExecutor {
551
627
  }
552
628
  }
553
629
  }
554
- async buildSwapTx(req, wallet, inputCoins) {
630
+ async buildSwapTx(req, wallet, inputCoins, shortfall = 0n) {
555
631
  const tx = new transactions_1.Transaction();
556
632
  tx.setSender(wallet);
557
633
  await this.registerShared(tx, req.poolId, true);
558
634
  await this.registerShared(tx, (0, swap_1.configSharedObjectId)(req.dexId), true);
559
635
  tx.object(transactions_1.Inputs.SharedObjectRef({ objectId: swap_1.SUI_CLOCK_ID, initialSharedVersion: '1', mutable: false }));
560
- const primary = tx.object(transactions_1.Inputs.ObjectRef(inputCoins[0]));
561
- if (inputCoins.length > 1) {
562
- tx.mergeCoins(primary, inputCoins.slice(1).map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
636
+ const inType = this.inputCoinType(req);
637
+ const usedBalance = shortfall > 0n || inputCoins.length === 0;
638
+ let inCoin;
639
+ if (inputCoins.length === 0) {
640
+ inCoin = (0, transactions_1.coinWithBalance)({ type: inType, balance: req.amountIn });
641
+ }
642
+ else {
643
+ const primary = tx.object(transactions_1.Inputs.ObjectRef(inputCoins[0]));
644
+ if (inputCoins.length > 1)
645
+ tx.mergeCoins(primary, inputCoins.slice(1).map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
646
+ if (shortfall > 0n)
647
+ tx.mergeCoins(primary, [(0, transactions_1.coinWithBalance)({ type: inType, balance: shortfall })]);
648
+ [inCoin] = tx.splitCoins(primary, [tx.pure.u64(req.amountIn)]);
563
649
  }
564
- const [inCoin] = tx.splitCoins(primary, [tx.pure.u64(req.amountIn)]);
565
650
  const { outputCoin, leftoverCoins } = (0, swap_1.buildSwapMoveCall)(req.dexId, tx, {
566
651
  coinTypeA: req.coinTypeA, coinTypeB: req.coinTypeB, poolId: req.poolId,
567
652
  a2b: req.a2b, byAmountIn: true, amount: req.amountIn,
@@ -572,20 +657,26 @@ class CentralExecutor {
572
657
  tx.setExpiration(await this.getValidDuringExpiration());
573
658
  tx.setGasBudget(this.gasBudget);
574
659
  tx.setGasPrice(await this.getGasPrice());
575
- const txBytes = await tx.build();
660
+ const txBytes = usedBalance ? await tx.build({ client: this.core.rawClient }) : await tx.build();
576
661
  return { txBytes, tx };
577
662
  }
578
- async onSuccess(tr, wallet, req, inputCoins, inputTag) {
663
+ async onSuccess(tr, wallet, req, inputCoins, inputTag, usedAddressBalance = false) {
579
664
  const inType = this.inputCoinType(req);
580
665
  const outType = this.outputCoinType(req);
581
- const inputTotal = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
582
- const inputChanges = (0, effects_1.extractSwapInputChanges)(tr.effects, {
583
- wallet, inCoinType: inType,
584
- primaryId: inputCoins[0].objectId,
585
- reservedInputIds: inputCoins.map(c => c.objectId),
586
- inputTotal, amountIn: req.amountIn,
587
- });
588
- this.cache.commit(inputTag, inputChanges);
666
+ if (usedAddressBalance) {
667
+ this.cache.abort(inputTag, true);
668
+ await this.reconcileCoins(wallet, inType);
669
+ }
670
+ else {
671
+ const inputTotal = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
672
+ const inputChanges = (0, effects_1.extractSwapInputChanges)(tr.effects, {
673
+ wallet, inCoinType: inType,
674
+ primaryId: inputCoins[0].objectId,
675
+ reservedInputIds: inputCoins.map(c => c.objectId),
676
+ inputTotal, amountIn: req.amountIn,
677
+ });
678
+ this.cache.commit(inputTag, inputChanges);
679
+ }
589
680
  if (this.reconcileAfterTx) {
590
681
  void this.postTradeRebalance(wallet, inType, outType);
591
682
  }
@@ -2,16 +2,23 @@ import { CoinRef, CoinReservation, CoinObjectChange } from '../coin/types';
2
2
  export declare class InProcessCoinCache {
3
3
  private available;
4
4
  private inflight;
5
+ private committedAt;
6
+ private commitKey;
5
7
  private epochs;
6
8
  private epochKey;
7
9
  private bumpEpoch;
8
10
  mutationEpoch(wallet: string, coinType: string): number;
11
+ private amountFmt?;
12
+ setAmountFormatter(fn: (coinType: string, raw: bigint) => string): void;
13
+ private fmt;
14
+ logCoinBreakdown(wallet: string, coinType: string, reason: string, quiet?: boolean): void;
9
15
  private walletMap;
10
16
  private list;
11
17
  private sortDesc;
12
18
  seed(wallet: string, coinType: string, coins: CoinRef[]): void;
13
19
  reconcile(wallet: string, coinType: string, freshCoins: CoinRef[], epochAtStart?: number, quietIfUnchanged?: boolean): boolean;
14
20
  acquire(wallet: string, coinType: string, amount: bigint, txTag: string, maxCoins?: number): CoinReservation;
21
+ acquireAvailable(wallet: string, coinType: string, amount: bigint, txTag: string): CoinRef[];
15
22
  reserve(wallet: string, coinType: string, objectIds: string[], txTag: string): CoinRef[] | null;
16
23
  hasInflight(wallet: string, coinType: string): boolean;
17
24
  commit(txTag: string, changes: CoinObjectChange[]): void;
@@ -6,8 +6,10 @@ class InProcessCoinCache {
6
6
  constructor() {
7
7
  this.available = new Map();
8
8
  this.inflight = new Map();
9
+ this.committedAt = new Map();
9
10
  this.epochs = new Map();
10
11
  }
12
+ commitKey(wallet, coinType, objectId) { return `${wallet}|${coinType}|${objectId}`; }
11
13
  epochKey(wallet, coinType) { return `${wallet}|${coinType}`; }
12
14
  bumpEpoch(wallet, coinType) {
13
15
  const k = this.epochKey(wallet, coinType);
@@ -16,6 +18,36 @@ class InProcessCoinCache {
16
18
  mutationEpoch(wallet, coinType) {
17
19
  return this.epochs.get(this.epochKey(wallet, coinType)) || 0;
18
20
  }
21
+ setAmountFormatter(fn) { this.amountFmt = fn; }
22
+ fmt(coinType, raw) { return this.amountFmt ? this.amountFmt(coinType, raw) : `${raw.toString()}(raw)`; }
23
+ logCoinBreakdown(wallet, coinType, reason, quiet = false) {
24
+ const avail = this.list(wallet, coinType);
25
+ const inUse = [];
26
+ for (const r of this.inflight.values()) {
27
+ if (r.wallet === wallet && r.coinType === coinType)
28
+ inUse.push(...r.coins);
29
+ }
30
+ const symbol = coinType.split('::').pop();
31
+ const sum = (cs) => cs.reduce((s, c) => s + BigInt(c.balance), 0n);
32
+ const availT = sum(avail), useT = sum(inUse), grand = availT + useT;
33
+ const mapObj = (cs) => cs.map(c => ({
34
+ objectId: c.objectId,
35
+ version: c.version,
36
+ balance: this.fmt(coinType, BigInt(c.balance)),
37
+ }));
38
+ const detail = {
39
+ reason, wallet, symbol, coinType,
40
+ total: this.fmt(coinType, grand),
41
+ available_count: avail.length, available_total: this.fmt(coinType, availT), available: mapObj(avail),
42
+ in_use_count: inUse.length, in_use_total: this.fmt(coinType, useT), in_use: mapObj(inUse),
43
+ };
44
+ const useStr = inUse.length ? `, ${inUse.length} 个正在交易中` : '';
45
+ const header = `[coin-cache] ${reason} ${symbol}: 共 ${this.fmt(coinType, grand)}(可用 ${avail.length} 个${useStr})`;
46
+ if (quiet)
47
+ (0, dist_1.log_debug)(header, detail);
48
+ else
49
+ (0, dist_1.log_info)(header, detail);
50
+ }
19
51
  walletMap(wallet) {
20
52
  let m = this.available.get(wallet);
21
53
  if (!m) {
@@ -54,18 +86,32 @@ class InProcessCoinCache {
54
86
  r.coins.forEach(c => inflightIds.add(c.objectId));
55
87
  }
56
88
  const prev = new Map(this.list(wallet, coinType).map(c => [c.objectId, c]));
89
+ const freshIds = new Set(freshCoins.map(c => c.objectId));
57
90
  const kept = freshCoins
58
91
  .filter(c => !inflightIds.has(c.objectId))
59
92
  .map(c => { const old = prev.get(c.objectId); return old && BigInt(old.version) > BigInt(c.version) ? old : c; });
93
+ const keptIds = new Set(kept.map(c => c.objectId));
94
+ const now = Date.now();
95
+ const graceMs = Number(process.env.SUI_COIN_RECONCILE_GRACE_MS || 10000);
96
+ for (const [id, c] of prev) {
97
+ if (keptIds.has(id) || inflightIds.has(id) || freshIds.has(id))
98
+ continue;
99
+ const ts = this.committedAt.get(this.commitKey(wallet, coinType, id));
100
+ if (ts != null && now - ts < graceMs) {
101
+ kept.push(c);
102
+ keptIds.add(id);
103
+ }
104
+ }
105
+ for (const id of freshIds)
106
+ this.committedAt.delete(this.commitKey(wallet, coinType, id));
107
+ for (const id of prev.keys())
108
+ if (!keptIds.has(id))
109
+ this.committedAt.delete(this.commitKey(wallet, coinType, id));
60
110
  this.sortDesc(kept);
61
111
  const sig = (l) => l.map(c => `${c.objectId}@${c.version}:${c.balance}`).join(',');
62
112
  const unchanged = sig(kept) === sig(this.list(wallet, coinType));
63
113
  this.walletMap(wallet).set(coinType, kept);
64
- const msg = `[coin-cache] reconcile ${wallet} ${coinType}: ${kept.length} avail (${inflightIds.size} inflight kept)`;
65
- if (unchanged && quietIfUnchanged)
66
- (0, dist_1.log_debug)(msg);
67
- else
68
- (0, dist_1.log_info)(msg);
114
+ this.logCoinBreakdown(wallet, coinType, '链上对账', unchanged && quietIfUnchanged);
69
115
  return true;
70
116
  }
71
117
  acquire(wallet, coinType, amount, txTag, maxCoins = 8) {
@@ -92,6 +138,24 @@ class InProcessCoinCache {
92
138
  this.bumpEpoch(wallet, coinType);
93
139
  return { txTag, coinType, coins: picked };
94
140
  }
141
+ acquireAvailable(wallet, coinType, amount, txTag) {
142
+ if (this.inflight.has(txTag))
143
+ throw new Error(`[coin-cache] txTag 重复: ${txTag}`);
144
+ const l = this.list(wallet, coinType);
145
+ const picked = [];
146
+ let sum = 0n;
147
+ for (const c of l) {
148
+ if (sum >= amount)
149
+ break;
150
+ picked.push(c);
151
+ sum += BigInt(c.balance);
152
+ }
153
+ const ids = new Set(picked.map(c => c.objectId));
154
+ this.walletMap(wallet).set(coinType, l.filter(c => !ids.has(c.objectId)));
155
+ this.inflight.set(txTag, { wallet, coinType, coins: picked, gas: false });
156
+ this.bumpEpoch(wallet, coinType);
157
+ return picked;
158
+ }
95
159
  reserve(wallet, coinType, objectIds, txTag) {
96
160
  if (this.inflight.has(txTag))
97
161
  throw new Error(`[coin-cache] txTag 重复: ${txTag}`);
@@ -126,6 +190,7 @@ class InProcessCoinCache {
126
190
  const wallet = res.wallet;
127
191
  if (ch.kind === 'deleted') {
128
192
  this.removeEverywhere(wallet, ch.coinType, ch.objectId);
193
+ this.committedAt.delete(this.commitKey(wallet, ch.coinType, ch.objectId));
129
194
  continue;
130
195
  }
131
196
  if (ch.version == null || ch.digest == null) {
@@ -139,7 +204,10 @@ class InProcessCoinCache {
139
204
  const l = this.list(wallet, ch.coinType);
140
205
  l.push(ref);
141
206
  this.sortDesc(l);
207
+ this.committedAt.set(this.commitKey(wallet, ch.coinType, ch.objectId), Date.now());
142
208
  }
209
+ for (const ct of new Set(changes.map(c => c.coinType)))
210
+ this.logCoinBreakdown(res.wallet, ct, '交易后更新');
143
211
  }
144
212
  abort(txTag, consumed) {
145
213
  const res = this.inflight.get(txTag);
@@ -150,6 +218,8 @@ class InProcessCoinCache {
150
218
  this.inflight.delete(txTag);
151
219
  this.bumpEpoch(res.wallet, res.coinType);
152
220
  if (consumed) {
221
+ for (const c of res.coins)
222
+ this.committedAt.delete(this.commitKey(res.wallet, res.coinType, c.objectId));
153
223
  (0, dist_1.log_warn)(`[coin-cache] abort consumed txTag=${txTag}(${res.coins.length} coin 交 reconcile 兜底)`);
154
224
  return;
155
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-sui-common",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
4
4
  "description": "Sui common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",