@clonegod/ttd-sui-common 2.0.9 → 2.0.10
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,6 +61,7 @@ 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>;
|
|
@@ -68,6 +69,10 @@ export declare class CentralExecutor {
|
|
|
68
69
|
private decimalsCache;
|
|
69
70
|
private objectReader?;
|
|
70
71
|
private getCoinDecimalsCached;
|
|
72
|
+
private coinSymbol;
|
|
73
|
+
private pairLabel;
|
|
74
|
+
private fmtAmount;
|
|
75
|
+
private walletBalancesLabel;
|
|
71
76
|
private maintainCoinObjects;
|
|
72
77
|
private execMaintenance;
|
|
73
78
|
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 {
|
|
@@ -208,6 +231,26 @@ class CentralExecutor {
|
|
|
208
231
|
}
|
|
209
232
|
return d;
|
|
210
233
|
}
|
|
234
|
+
coinSymbol(coinType) { return coinType.split('::').pop() || coinType; }
|
|
235
|
+
pairLabel(req) {
|
|
236
|
+
return `${this.coinSymbol(this.inputCoinType(req))}→${this.coinSymbol(this.outputCoinType(req))}`;
|
|
237
|
+
}
|
|
238
|
+
fmtAmount(coinType, raw) {
|
|
239
|
+
const sym = this.coinSymbol(coinType);
|
|
240
|
+
const d = this.decimalsCache.get(coinType);
|
|
241
|
+
if (d == null)
|
|
242
|
+
return `${raw.toString()}(raw) ${sym}`;
|
|
243
|
+
const neg = raw < 0n, v = neg ? -raw : raw, base = 10n ** BigInt(d);
|
|
244
|
+
const intStr = (v / base).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
245
|
+
const frac = (v % base).toString().padStart(d, '0').replace(/0+$/, '');
|
|
246
|
+
return `${neg ? '-' : ''}${intStr}${frac ? '.' + frac : ''} ${sym}`;
|
|
247
|
+
}
|
|
248
|
+
walletBalancesLabel(coinType) {
|
|
249
|
+
return [...this.tradeWallets.keys()].map(w => {
|
|
250
|
+
const { total, count } = this.cache.snapshot(w, coinType);
|
|
251
|
+
return `${w.slice(0, 8)}…=${this.fmtAmount(coinType, total)}(${count}枚)`;
|
|
252
|
+
}).join(', ');
|
|
253
|
+
}
|
|
211
254
|
async maintainCoinObjects(addr, kp, coinType, opts) {
|
|
212
255
|
const short = coinType.split('::').pop();
|
|
213
256
|
const zeroMergeMin = Number(process.env.SUI_ZERO_COIN_MERGE_THRESHOLD || 3);
|
|
@@ -379,19 +422,27 @@ class CentralExecutor {
|
|
|
379
422
|
const t0 = Date.now();
|
|
380
423
|
req = await this.canonicalizeReq(req);
|
|
381
424
|
const inType = this.inputCoinType(req);
|
|
425
|
+
await this.getCoinDecimalsCached(inType).catch(() => { });
|
|
382
426
|
let wallet;
|
|
383
427
|
try {
|
|
384
428
|
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
385
429
|
}
|
|
386
430
|
catch (e) {
|
|
387
|
-
(0, dist_1.log_warn)(`[executor] ${inType}
|
|
431
|
+
(0, dist_1.log_warn)(`[executor] ${this.pairLabel(req)} 选钱包失败:需 ${this.fmtAmount(inType, req.amountIn)},当前各钱包缓存 [${this.walletBalancesLabel(inType)}] → 按需 reconcile 重试`);
|
|
388
432
|
await Promise.all([...this.tradeWallets.keys()].map(w => this.reconcileCoins(w, inType)));
|
|
389
|
-
|
|
433
|
+
try {
|
|
434
|
+
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
435
|
+
}
|
|
436
|
+
catch (e2) {
|
|
437
|
+
(0, dist_1.log_error)(`[executor] ${this.pairLabel(req)} 余额不足:需 ${this.fmtAmount(inType, req.amountIn)},reconcile 后各钱包缓存仍 [${this.walletBalancesLabel(inType)}]`, e2);
|
|
438
|
+
throw e2;
|
|
439
|
+
}
|
|
390
440
|
}
|
|
391
441
|
const inputTag = this.nextTag('in');
|
|
392
442
|
const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
|
|
393
443
|
const tAcquire = Date.now();
|
|
394
|
-
|
|
444
|
+
const acqTotal = inputRes.coins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
445
|
+
(0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 8)}… ${this.pairLabel(req)} 取 ${inputRes.coins.length} 枚=${this.fmtAmount(inType, acqTotal)}(需 ${this.fmtAmount(inType, req.amountIn)})选钱包+占币 ${tAcquire - t0}ms`);
|
|
395
446
|
try {
|
|
396
447
|
const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
|
|
397
448
|
const tBuild = Date.now();
|
|
@@ -399,7 +450,7 @@ class CentralExecutor {
|
|
|
399
450
|
const digest = await tx.getDigest();
|
|
400
451
|
const tSign = Date.now();
|
|
401
452
|
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
|
|
453
|
+
(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
454
|
return { digest, submitted: true };
|
|
404
455
|
}
|
|
405
456
|
catch (e) {
|
|
@@ -2,10 +2,16 @@ 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;
|
|
@@ -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,30 @@ 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 l = this.list(wallet, coinType);
|
|
25
|
+
const total = l.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
26
|
+
const symbol = coinType.split('::').pop();
|
|
27
|
+
const detail = {
|
|
28
|
+
reason, wallet, symbol, coinType,
|
|
29
|
+
count: l.length,
|
|
30
|
+
total: this.fmt(coinType, total),
|
|
31
|
+
total_raw: total.toString(),
|
|
32
|
+
objects: l.map(c => ({
|
|
33
|
+
objectId: c.objectId,
|
|
34
|
+
version: c.version,
|
|
35
|
+
balance: this.fmt(coinType, BigInt(c.balance)),
|
|
36
|
+
balance_raw: c.balance,
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
const header = `[coin-cache] ${reason} ${symbol} ${l.length}枚 total=${this.fmt(coinType, total)}`;
|
|
40
|
+
if (quiet)
|
|
41
|
+
(0, dist_1.log_debug)(header, detail);
|
|
42
|
+
else
|
|
43
|
+
(0, dist_1.log_info)(header, detail);
|
|
44
|
+
}
|
|
19
45
|
walletMap(wallet) {
|
|
20
46
|
let m = this.available.get(wallet);
|
|
21
47
|
if (!m) {
|
|
@@ -54,18 +80,32 @@ class InProcessCoinCache {
|
|
|
54
80
|
r.coins.forEach(c => inflightIds.add(c.objectId));
|
|
55
81
|
}
|
|
56
82
|
const prev = new Map(this.list(wallet, coinType).map(c => [c.objectId, c]));
|
|
83
|
+
const freshIds = new Set(freshCoins.map(c => c.objectId));
|
|
57
84
|
const kept = freshCoins
|
|
58
85
|
.filter(c => !inflightIds.has(c.objectId))
|
|
59
86
|
.map(c => { const old = prev.get(c.objectId); return old && BigInt(old.version) > BigInt(c.version) ? old : c; });
|
|
87
|
+
const keptIds = new Set(kept.map(c => c.objectId));
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const graceMs = Number(process.env.SUI_COIN_RECONCILE_GRACE_MS || 10000);
|
|
90
|
+
for (const [id, c] of prev) {
|
|
91
|
+
if (keptIds.has(id) || inflightIds.has(id) || freshIds.has(id))
|
|
92
|
+
continue;
|
|
93
|
+
const ts = this.committedAt.get(this.commitKey(wallet, coinType, id));
|
|
94
|
+
if (ts != null && now - ts < graceMs) {
|
|
95
|
+
kept.push(c);
|
|
96
|
+
keptIds.add(id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const id of freshIds)
|
|
100
|
+
this.committedAt.delete(this.commitKey(wallet, coinType, id));
|
|
101
|
+
for (const id of prev.keys())
|
|
102
|
+
if (!keptIds.has(id))
|
|
103
|
+
this.committedAt.delete(this.commitKey(wallet, coinType, id));
|
|
60
104
|
this.sortDesc(kept);
|
|
61
105
|
const sig = (l) => l.map(c => `${c.objectId}@${c.version}:${c.balance}`).join(',');
|
|
62
106
|
const unchanged = sig(kept) === sig(this.list(wallet, coinType));
|
|
63
107
|
this.walletMap(wallet).set(coinType, kept);
|
|
64
|
-
|
|
65
|
-
if (unchanged && quietIfUnchanged)
|
|
66
|
-
(0, dist_1.log_debug)(msg);
|
|
67
|
-
else
|
|
68
|
-
(0, dist_1.log_info)(msg);
|
|
108
|
+
this.logCoinBreakdown(wallet, coinType, `reconcile(${inflightIds.size}在途)`, unchanged && quietIfUnchanged);
|
|
69
109
|
return true;
|
|
70
110
|
}
|
|
71
111
|
acquire(wallet, coinType, amount, txTag, maxCoins = 8) {
|
|
@@ -126,6 +166,7 @@ class InProcessCoinCache {
|
|
|
126
166
|
const wallet = res.wallet;
|
|
127
167
|
if (ch.kind === 'deleted') {
|
|
128
168
|
this.removeEverywhere(wallet, ch.coinType, ch.objectId);
|
|
169
|
+
this.committedAt.delete(this.commitKey(wallet, ch.coinType, ch.objectId));
|
|
129
170
|
continue;
|
|
130
171
|
}
|
|
131
172
|
if (ch.version == null || ch.digest == null) {
|
|
@@ -139,7 +180,10 @@ class InProcessCoinCache {
|
|
|
139
180
|
const l = this.list(wallet, ch.coinType);
|
|
140
181
|
l.push(ref);
|
|
141
182
|
this.sortDesc(l);
|
|
183
|
+
this.committedAt.set(this.commitKey(wallet, ch.coinType, ch.objectId), Date.now());
|
|
142
184
|
}
|
|
185
|
+
for (const ct of new Set(changes.map(c => c.coinType)))
|
|
186
|
+
this.logCoinBreakdown(res.wallet, ct, 'commit后');
|
|
143
187
|
}
|
|
144
188
|
abort(txTag, consumed) {
|
|
145
189
|
const res = this.inflight.get(txTag);
|
|
@@ -150,6 +194,8 @@ class InProcessCoinCache {
|
|
|
150
194
|
this.inflight.delete(txTag);
|
|
151
195
|
this.bumpEpoch(res.wallet, res.coinType);
|
|
152
196
|
if (consumed) {
|
|
197
|
+
for (const c of res.coins)
|
|
198
|
+
this.committedAt.delete(this.commitKey(res.wallet, res.coinType, c.objectId));
|
|
153
199
|
(0, dist_1.log_warn)(`[coin-cache] abort consumed txTag=${txTag}(${res.coins.length} coin 交 reconcile 兜底)`);
|
|
154
200
|
return;
|
|
155
201
|
}
|