@clonegod/ttd-sui-common 2.0.14 → 2.0.15
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.
|
@@ -25,7 +25,6 @@ export interface SwapSubmitResult {
|
|
|
25
25
|
}
|
|
26
26
|
export interface CentralExecutorOptions {
|
|
27
27
|
gasBudget?: bigint;
|
|
28
|
-
reconcileAfterTx?: boolean;
|
|
29
28
|
onBroadcastResult?: (r: {
|
|
30
29
|
digest: string;
|
|
31
30
|
success: boolean;
|
|
@@ -51,7 +50,6 @@ export declare class CentralExecutor {
|
|
|
51
50
|
private readonly core;
|
|
52
51
|
private readonly cache;
|
|
53
52
|
private readonly gasBudget;
|
|
54
|
-
private readonly reconcileAfterTx;
|
|
55
53
|
private readonly onBroadcastResult?;
|
|
56
54
|
private tradeWallets;
|
|
57
55
|
private sharedRefCache;
|
|
@@ -67,51 +65,40 @@ export declare class CentralExecutor {
|
|
|
67
65
|
rebalanceWalletFunds(): Promise<void>;
|
|
68
66
|
private rebalanceOne;
|
|
69
67
|
private rebalanceBalanceMode;
|
|
70
|
-
private rebalanceObjectMode;
|
|
71
68
|
private sweepObjectsToBalance;
|
|
72
|
-
private selectWalletWithBalance;
|
|
73
69
|
private decimalsCache;
|
|
74
70
|
private objectReader?;
|
|
75
71
|
private getCoinDecimalsCached;
|
|
76
72
|
private coinSymbol;
|
|
77
73
|
private pairLabel;
|
|
78
74
|
private fmtAmount;
|
|
79
|
-
private walletBalancesLabel;
|
|
80
|
-
private maintainCoinObjects;
|
|
81
|
-
private execMaintenance;
|
|
82
75
|
private poolGenericsCache;
|
|
83
76
|
private getPoolGenerics;
|
|
84
77
|
private canonicalizeReq;
|
|
85
78
|
private getSharedRefCached;
|
|
86
79
|
private chainIdentifier;
|
|
80
|
+
private chainIdInflight?;
|
|
87
81
|
private epochCache;
|
|
82
|
+
private epochInflight?;
|
|
83
|
+
private cachedChainId;
|
|
84
|
+
private cachedEpoch;
|
|
88
85
|
private getValidDuringExpiration;
|
|
89
86
|
private getGasPrice;
|
|
90
87
|
private fetchEpoch;
|
|
91
|
-
private fundModeFor;
|
|
92
88
|
private nextTag;
|
|
93
89
|
private inputCoinType;
|
|
94
90
|
private outputCoinType;
|
|
95
|
-
private chooseTradeWallet;
|
|
96
91
|
private registerShared;
|
|
97
92
|
submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
|
|
98
93
|
private submitSwapBalance;
|
|
99
94
|
private broadcastBalanceAndCommit;
|
|
100
95
|
private isInsufficientBalanceError;
|
|
101
96
|
private rebuildWithObjects;
|
|
102
|
-
private broadcastAndCommit;
|
|
103
|
-
private isRebuildableObjectError;
|
|
104
|
-
private rebuildSwap;
|
|
105
97
|
private toCheckerReceipt;
|
|
106
|
-
private reconcileAfterFailure;
|
|
107
98
|
simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
|
|
108
|
-
private minSplitFor;
|
|
109
|
-
private postTradeRebalance;
|
|
110
99
|
private swapTxShell;
|
|
111
100
|
private finishSwapTx;
|
|
112
|
-
private buildSwapTxObject;
|
|
113
101
|
private buildSwapTxBalance;
|
|
114
|
-
private onSuccess;
|
|
115
102
|
get coinCache(): InProcessCoinCache;
|
|
116
103
|
get tradeWalletAddresses(): string[];
|
|
117
104
|
get coreClient(): ExecutorCore;
|
|
@@ -45,7 +45,6 @@ class CentralExecutor {
|
|
|
45
45
|
this.chainIdentifier = '';
|
|
46
46
|
this.epochCache = null;
|
|
47
47
|
this.gasBudget = opts.gasBudget ?? (0, format_1.suiToMist)(process.env.SUI_GAS_BUDGET, '0.05');
|
|
48
|
-
this.reconcileAfterTx = opts.reconcileAfterTx ?? true;
|
|
49
48
|
this.onBroadcastResult = opts.onBroadcastResult;
|
|
50
49
|
this.tradeCoinTypesProvider = opts.tradeCoinTypes;
|
|
51
50
|
this.tradePoolsProvider = opts.tradePools;
|
|
@@ -143,6 +142,7 @@ class CentralExecutor {
|
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
async rebalanceWalletFunds() {
|
|
145
|
+
void this.getValidDuringExpiration().catch(() => { });
|
|
146
146
|
for (const [addr, kp] of this.tradeWallets) {
|
|
147
147
|
try {
|
|
148
148
|
await this.rebalanceOne(addr, kp);
|
|
@@ -153,13 +153,10 @@ class CentralExecutor {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
async rebalanceOne(addr, kp) {
|
|
156
|
-
|
|
157
|
-
return this.rebalanceBalanceMode(addr, kp);
|
|
158
|
-
return this.rebalanceObjectMode(addr, kp);
|
|
156
|
+
return this.rebalanceBalanceMode(addr, kp);
|
|
159
157
|
}
|
|
160
158
|
async rebalanceBalanceMode(addr, kp) {
|
|
161
159
|
const SUI = constants_1.SUI_TOKEN_ADDRESS.LONG;
|
|
162
|
-
const budget = this.gasBudget;
|
|
163
160
|
let tradeTypes = [];
|
|
164
161
|
if (this.tradeCoinTypesProvider) {
|
|
165
162
|
try {
|
|
@@ -169,132 +166,93 @@ class CentralExecutor {
|
|
|
169
166
|
(0, dist_1.log_warn)(`[executor] 读交易币种清单失败,本 tick 只归集 SUI: ${e.message}`);
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
|
-
|
|
169
|
+
const types = [SUI, ...new Set(tradeTypes.filter(t => t !== SUI))];
|
|
170
|
+
const withObjects = [];
|
|
171
|
+
for (const t of types) {
|
|
172
|
+
try {
|
|
173
|
+
const b = await this.core.getBalance({ owner: addr, coinType: t });
|
|
174
|
+
if (BigInt(b.balance.balance ?? '0') > BigInt(b.balance.addressBalance ?? '0'))
|
|
175
|
+
withObjects.push(t);
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
withObjects.push(t);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (withObjects.length === 0)
|
|
182
|
+
return;
|
|
183
|
+
for (const t of withObjects)
|
|
173
184
|
await this.reconcileCoins(addr, t, true);
|
|
185
|
+
await this.sweepObjectsToBalance(addr, kp, withObjects, this.gasBudget);
|
|
186
|
+
}
|
|
187
|
+
async sweepObjectsToBalance(addr, kp, coinTypes, budget) {
|
|
188
|
+
const SUI = constants_1.SUI_TOKEN_ADDRESS.LONG;
|
|
189
|
+
const plan = [];
|
|
190
|
+
for (const t of [...new Set(coinTypes)]) {
|
|
174
191
|
if (this.cache.hasInflight(addr, t))
|
|
175
192
|
continue;
|
|
176
|
-
|
|
193
|
+
const coins = this.cache.snapshot(addr, t).coins;
|
|
194
|
+
if (coins.length === 0)
|
|
177
195
|
continue;
|
|
178
|
-
|
|
179
|
-
|
|
196
|
+
const tag = this.nextTag('sweep');
|
|
197
|
+
if (!this.cache.reserve(addr, t, coins.map(c => c.objectId), tag))
|
|
198
|
+
continue;
|
|
199
|
+
plan.push({ coinType: t, coins, tag });
|
|
180
200
|
}
|
|
181
|
-
|
|
182
|
-
async rebalanceObjectMode(addr, kp) {
|
|
183
|
-
const SUI = constants_1.SUI_TOKEN_ADDRESS.LONG;
|
|
184
|
-
const balanceMin = (0, format_1.suiToMist)(process.env.SUI_GAS_BALANCE_MIN, '0.5');
|
|
185
|
-
const balanceTarget = (0, format_1.suiToMist)(process.env.SUI_GAS_BALANCE_TARGET, '1');
|
|
186
|
-
const chunk = (0, format_1.suiToMist)(process.env.SUI_REDEEM_MIN_CHUNK, '0.1');
|
|
187
|
-
const budget = this.gasBudget;
|
|
188
|
-
const coinTarget = Number(process.env.SUI_INPUT_COIN_TARGET || 3);
|
|
189
|
-
const coinMax = Number(process.env.SUI_INPUT_COIN_MAX || 5);
|
|
190
|
-
await this.reconcileCoins(addr, SUI, true);
|
|
191
|
-
if (this.cache.hasInflight(addr, SUI))
|
|
201
|
+
if (plan.length === 0)
|
|
192
202
|
return;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
try {
|
|
204
|
+
let suiAb = 0n;
|
|
205
|
+
try {
|
|
206
|
+
const sb = await this.core.getBalance({ owner: addr, coinType: '0x2::sui::SUI' });
|
|
207
|
+
suiAb = BigInt(sb.balance.addressBalance ?? '0');
|
|
208
|
+
}
|
|
209
|
+
catch { }
|
|
210
|
+
const balanceGas = suiAb >= budget;
|
|
211
|
+
const suiPlan = plan.find(p => p.coinType === SUI);
|
|
212
|
+
if (!balanceGas && !suiPlan) {
|
|
213
|
+
(0, dist_1.log_warn)(`[executor] sweep 跳过:SUI balance ${suiAb} < gas ${budget} 且无 SUI 对象付 gas(请注资)`);
|
|
202
214
|
return;
|
|
203
215
|
}
|
|
204
216
|
const tx = new transactions_1.Transaction();
|
|
205
217
|
tx.setSender(addr);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const tx = new transactions_1.Transaction();
|
|
218
|
-
tx.setSender(addr);
|
|
219
|
-
tx.transferObjects([(0, transactions_1.coinWithBalance)({ type: '0x2::sui::SUI', balance: excess, useGasCoin: false })], tx.pure.address(addr));
|
|
220
|
-
tx.setGasPayment([]);
|
|
221
|
-
tx.setExpiration(await this.getValidDuringExpiration());
|
|
222
|
-
await this.execMaintenance(tx, addr, kp, budget, `redeem 超额 ${excess} → coin`, rawClient);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
let tradeTypes = [];
|
|
226
|
-
if (this.tradeCoinTypesProvider) {
|
|
227
|
-
try {
|
|
228
|
-
tradeTypes = (await this.tradeCoinTypesProvider()).map(t => (0, format_1.normalizeSuiTokenAddress)(t));
|
|
218
|
+
if (balanceGas) {
|
|
219
|
+
tx.setGasPayment([]);
|
|
220
|
+
tx.setExpiration(await this.getValidDuringExpiration());
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
tx.setGasPayment(suiPlan.coins.map(c => ({ objectId: c.objectId, version: c.version, digest: c.digest })));
|
|
224
|
+
const suiTotal = suiPlan.coins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
225
|
+
if (suiTotal > budget) {
|
|
226
|
+
const [dep] = tx.splitCoins(tx.gas, [tx.pure.u64(suiTotal - budget)]);
|
|
227
|
+
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: [SUI], arguments: [dep, tx.pure.address(addr)] });
|
|
228
|
+
}
|
|
229
229
|
}
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
for (const p of plan) {
|
|
231
|
+
if (!balanceGas && p.coinType === SUI)
|
|
232
|
+
continue;
|
|
233
|
+
const primary = tx.object(transactions_1.Inputs.ObjectRef(p.coins[0]));
|
|
234
|
+
if (p.coins.length > 1)
|
|
235
|
+
tx.mergeCoins(primary, p.coins.slice(1).map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
236
|
+
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: [p.coinType], arguments: [primary, tx.pure.address(addr)] });
|
|
232
237
|
}
|
|
238
|
+
tx.setGasBudget(budget);
|
|
239
|
+
tx.setGasPrice(await this.getGasPrice());
|
|
240
|
+
const bytes = await tx.build();
|
|
241
|
+
const { signature } = await kp.signTransaction(bytes);
|
|
242
|
+
const resp = await this.core.executeTransaction({ transaction: bytes, signatures: [signature], include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
243
|
+
const st = resp.Transaction?.effects ? (0, effects_1.coreTxStatus)(resp.Transaction.effects) : { success: false, error: 'no effects' };
|
|
244
|
+
const syms = plan.map(p => `${p.coins.length}×${p.coinType.split('::').pop()}`).join(', ');
|
|
245
|
+
(0, dist_1.log_info)(`[executor] sweep 归集 → balance ${st.success ? 'OK' : 'FAIL'} [${syms}] digest=${resp.Transaction?.digest}${st.error ? ' err=' + st.error : ''}`);
|
|
233
246
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await this.reconcileCoins(addr, t, true);
|
|
237
|
-
if (this.cache.hasInflight(addr, t))
|
|
238
|
-
continue;
|
|
239
|
-
let minSplitT;
|
|
240
|
-
try {
|
|
241
|
-
minSplitT = await this.minSplitFor(t);
|
|
242
|
-
}
|
|
243
|
-
catch (e) {
|
|
244
|
-
(0, dist_1.log_warn)(`[executor] 读 ${t} decimals 失败,本 tick 跳过该币种维护: ${e.message}`);
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
if (await this.maintainCoinObjects(addr, kp, t, { coinTarget, coinMax, budget, minSplit: minSplitT }))
|
|
248
|
-
return;
|
|
247
|
+
catch (e) {
|
|
248
|
+
(0, dist_1.log_error)(`[executor] sweep 归集失败`, e);
|
|
249
249
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return false;
|
|
255
|
-
const SUI = constants_1.SUI_TOKEN_ADDRESS.LONG;
|
|
256
|
-
const tag = this.nextTag('sweep');
|
|
257
|
-
if (!this.cache.reserve(addr, coinType, coins.map(c => c.objectId), tag))
|
|
258
|
-
return false;
|
|
259
|
-
const tx = new transactions_1.Transaction();
|
|
260
|
-
tx.setSender(addr);
|
|
261
|
-
let suiAb = 0n;
|
|
262
|
-
try {
|
|
263
|
-
const sb = await this.core.getBalance({ owner: addr, coinType: '0x2::sui::SUI' });
|
|
264
|
-
suiAb = BigInt(sb.balance.addressBalance ?? '0');
|
|
265
|
-
}
|
|
266
|
-
catch { }
|
|
267
|
-
if (coinType === SUI && suiAb < budget) {
|
|
268
|
-
const total = coins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
269
|
-
if (total <= budget) {
|
|
270
|
-
this.cache.abort(tag, false);
|
|
271
|
-
return false;
|
|
250
|
+
finally {
|
|
251
|
+
for (const p of plan) {
|
|
252
|
+
this.cache.abort(p.tag, true);
|
|
253
|
+
await this.reconcileCoins(addr, p.coinType);
|
|
272
254
|
}
|
|
273
|
-
tx.setGasPayment(coins.map(c => ({ objectId: c.objectId, version: c.version, digest: c.digest })));
|
|
274
|
-
const [dep] = tx.splitCoins(tx.gas, [tx.pure.u64(total - budget)]);
|
|
275
|
-
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: [SUI], arguments: [dep, tx.pure.address(addr)] });
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
const primary = tx.object(transactions_1.Inputs.ObjectRef(coins[0]));
|
|
279
|
-
if (coins.length > 1)
|
|
280
|
-
tx.mergeCoins(primary, coins.slice(1).map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
281
|
-
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: [coinType], arguments: [primary, tx.pure.address(addr)] });
|
|
282
|
-
tx.setGasPayment([]);
|
|
283
|
-
tx.setExpiration(await this.getValidDuringExpiration());
|
|
284
|
-
}
|
|
285
|
-
await this.execMaintenance(tx, addr, kp, budget, `归集 ${coins.length} 个 ${coinType.split('::').pop()} 对象 → balance`, undefined, coinType, tag);
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
async selectWalletWithBalance(inType, amount) {
|
|
289
|
-
let best = null;
|
|
290
|
-
for (const w of this.tradeWallets.keys()) {
|
|
291
|
-
const objs = this.cache.snapshot(w, inType).total;
|
|
292
|
-
const b = await this.core.getBalance({ owner: w, coinType: inType });
|
|
293
|
-
const ab = BigInt(b.balance.addressBalance ?? '0');
|
|
294
|
-
if (objs + ab >= amount && (!best || objs + ab > best.sum))
|
|
295
|
-
best = { wallet: w, addressBalance: ab, sum: objs + ab };
|
|
296
255
|
}
|
|
297
|
-
return best ? { wallet: best.wallet, addressBalance: best.addressBalance } : null;
|
|
298
256
|
}
|
|
299
257
|
async getCoinDecimalsCached(coinType) {
|
|
300
258
|
let d = this.decimalsCache.get(coinType);
|
|
@@ -324,73 +282,6 @@ class CentralExecutor {
|
|
|
324
282
|
const frac = (v % base).toString().padStart(d, '0').replace(/0+$/, '');
|
|
325
283
|
return `${neg ? '-' : ''}${intStr}${frac ? '.' + frac : ''} ${sym}`;
|
|
326
284
|
}
|
|
327
|
-
walletBalancesLabel(coinType) {
|
|
328
|
-
return [...this.tradeWallets.keys()].map(w => {
|
|
329
|
-
const { total, count } = this.cache.snapshot(w, coinType);
|
|
330
|
-
return `${w.slice(0, 8)}…=${this.fmtAmount(coinType, total)}(${count}枚)`;
|
|
331
|
-
}).join(', ');
|
|
332
|
-
}
|
|
333
|
-
async maintainCoinObjects(addr, kp, coinType, opts) {
|
|
334
|
-
const short = coinType.split('::').pop();
|
|
335
|
-
const zeroMergeMin = Number(process.env.SUI_ZERO_COIN_MERGE_THRESHOLD || 3);
|
|
336
|
-
const coins = this.cache.snapshot(addr, coinType).coins;
|
|
337
|
-
const valued = coins.filter(c => BigInt(c.balance) > 0n);
|
|
338
|
-
const zeros = coins.filter(c => BigInt(c.balance) === 0n);
|
|
339
|
-
if (coins.length === 0)
|
|
340
|
-
return false;
|
|
341
|
-
const total = valued.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
342
|
-
const maxPieces = opts.minSplit > 0n ? total / opts.minSplit : 0n;
|
|
343
|
-
const pieces = Math.max(1, Number(maxPieces > BigInt(opts.coinTarget) ? BigInt(opts.coinTarget) : maxPieces));
|
|
344
|
-
const needSplit = valued.length > 0 && valued.length < opts.coinTarget && pieces > valued.length;
|
|
345
|
-
const needZeroPurge = zeros.length >= zeroMergeMin;
|
|
346
|
-
const needDefrag = coins.length > opts.coinMax;
|
|
347
|
-
if (!needSplit && !needZeroPurge && !needDefrag)
|
|
348
|
-
return false;
|
|
349
|
-
const tag = this.nextTag('maint');
|
|
350
|
-
if (!this.cache.reserve(addr, coinType, coins.map(c => c.objectId), tag))
|
|
351
|
-
return false;
|
|
352
|
-
const primaryRef = valued[0] ?? coins[0];
|
|
353
|
-
const tx = new transactions_1.Transaction();
|
|
354
|
-
tx.setSender(addr);
|
|
355
|
-
const primary = tx.object(transactions_1.Inputs.ObjectRef(primaryRef));
|
|
356
|
-
const others = coins.filter(c => c.objectId !== primaryRef.objectId);
|
|
357
|
-
if (others.length)
|
|
358
|
-
tx.mergeCoins(primary, others.map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
359
|
-
const per = pieces > 1 ? total / BigInt(pieces) : 0n;
|
|
360
|
-
if (pieces > 1) {
|
|
361
|
-
const splits = tx.splitCoins(primary, Array.from({ length: pieces - 1 }, () => tx.pure.u64(per)));
|
|
362
|
-
tx.transferObjects(Array.from({ length: pieces - 1 }, (_, i) => splits[i]), tx.pure.address(addr));
|
|
363
|
-
}
|
|
364
|
-
tx.setGasPayment([]);
|
|
365
|
-
tx.setExpiration(await this.getValidDuringExpiration());
|
|
366
|
-
await this.execMaintenance(tx, addr, kp, opts.budget, `整理 ${short}:${valued.length} 值 + ${zeros.length} 壳 → ${pieces} 份均分(每份≈${(total / BigInt(pieces)).toString()})`, undefined, coinType, tag, { primaryId: primaryRef.objectId, perPiece: per, primaryRemainder: total - per * BigInt(pieces - 1), reservedIds: coins.map(c => c.objectId) });
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
async execMaintenance(tx, addr, kp, budget, label, buildClient, coinType = constants_1.SUI_TOKEN_ADDRESS.LONG, reserveTag, commitPlan) {
|
|
370
|
-
let committed = false;
|
|
371
|
-
try {
|
|
372
|
-
tx.setGasBudget(budget);
|
|
373
|
-
tx.setGasPrice(await this.getGasPrice());
|
|
374
|
-
const bytes = buildClient ? await tx.build({ client: buildClient }) : await tx.build();
|
|
375
|
-
const { signature } = await kp.signTransaction(bytes);
|
|
376
|
-
const resp = await this.core.executeTransaction({ transaction: bytes, signatures: [signature], include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
377
|
-
const tr = resp.Transaction;
|
|
378
|
-
const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
|
|
379
|
-
(0, dist_1.log_info)(`[executor] rebalance ${label} ${success ? 'OK' : 'FAILED'} wallet=${addr.slice(0, 10)}… digest=${tr.digest}${error ? ' err=' + error : ''}`);
|
|
380
|
-
if (success && commitPlan && reserveTag && tr.effects) {
|
|
381
|
-
const changes = (0, effects_1.extractMaintCoinChanges)(tr.effects, { wallet: addr, coinType, ...commitPlan });
|
|
382
|
-
this.cache.commit(reserveTag, changes);
|
|
383
|
-
committed = true;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
finally {
|
|
387
|
-
if (!committed) {
|
|
388
|
-
if (reserveTag)
|
|
389
|
-
this.cache.abort(reserveTag, true);
|
|
390
|
-
await this.reconcileCoins(addr, coinType);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
285
|
async getPoolGenerics(poolId) {
|
|
395
286
|
const hit = this.poolGenericsCache.get(poolId);
|
|
396
287
|
if (hit !== undefined)
|
|
@@ -442,25 +333,39 @@ class CentralExecutor {
|
|
|
442
333
|
}
|
|
443
334
|
return isv;
|
|
444
335
|
}
|
|
445
|
-
async
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
(0, dist_1.log_warn)(`[executor] getChainIdentifier 失败(${e.message}),使用兜底 chain id ${
|
|
453
|
-
|
|
454
|
-
|
|
336
|
+
async cachedChainId() {
|
|
337
|
+
if (this.chainIdentifier)
|
|
338
|
+
return this.chainIdentifier;
|
|
339
|
+
if (!this.chainIdInflight) {
|
|
340
|
+
this.chainIdInflight = this.core.getChainIdentifier().then(r => r.chainIdentifier)
|
|
341
|
+
.catch(e => {
|
|
342
|
+
const fb = process.env.SUI_CHAIN_IDENTIFIER || '35834a8a';
|
|
343
|
+
(0, dist_1.log_warn)(`[executor] getChainIdentifier 失败(${e.message}),使用兜底 chain id ${fb}`);
|
|
344
|
+
return fb;
|
|
345
|
+
})
|
|
346
|
+
.then(id => { this.chainIdentifier = id; return id; })
|
|
347
|
+
.finally(() => { this.chainIdInflight = undefined; });
|
|
348
|
+
}
|
|
349
|
+
return this.chainIdInflight;
|
|
350
|
+
}
|
|
351
|
+
async cachedEpoch() {
|
|
455
352
|
const now = Date.now();
|
|
456
|
-
if (
|
|
457
|
-
this.epochCache
|
|
353
|
+
if (this.epochCache && now - this.epochCache.ts <= 3_600_000)
|
|
354
|
+
return this.epochCache.epoch;
|
|
355
|
+
if (!this.epochInflight) {
|
|
356
|
+
this.epochInflight = this.fetchEpoch()
|
|
357
|
+
.then(e => { this.epochCache = { epoch: e, ts: Date.now() }; return e; })
|
|
358
|
+
.finally(() => { this.epochInflight = undefined; });
|
|
458
359
|
}
|
|
360
|
+
return this.epochInflight;
|
|
361
|
+
}
|
|
362
|
+
async getValidDuringExpiration() {
|
|
363
|
+
const [chain, epoch] = await Promise.all([this.cachedChainId(), this.cachedEpoch()]);
|
|
459
364
|
return { ValidDuring: {
|
|
460
|
-
minEpoch: String(
|
|
461
|
-
maxEpoch: String(
|
|
365
|
+
minEpoch: String(epoch),
|
|
366
|
+
maxEpoch: String(epoch + 1n),
|
|
462
367
|
minTimestamp: null, maxTimestamp: null,
|
|
463
|
-
chain
|
|
368
|
+
chain,
|
|
464
369
|
nonce: Math.floor(Math.random() * 0xFFFFFFFF),
|
|
465
370
|
} };
|
|
466
371
|
}
|
|
@@ -483,31 +388,9 @@ class CentralExecutor {
|
|
|
483
388
|
throw new Error('[executor] getCurrentSystemState 未返回 epoch(ValidDuring 必需)');
|
|
484
389
|
return BigInt(e);
|
|
485
390
|
}
|
|
486
|
-
fundModeFor(_coinType) {
|
|
487
|
-
return (process.env.SUI_FUND_MODE || 'balance').toLowerCase() === 'object' ? 'object' : 'balance';
|
|
488
|
-
}
|
|
489
391
|
nextTag(prefix) { return `${prefix}:${Date.now()}:${++this.seq}`; }
|
|
490
392
|
inputCoinType(req) { return req.a2b ? req.coinTypeA : req.coinTypeB; }
|
|
491
393
|
outputCoinType(req) { return req.a2b ? req.coinTypeB : req.coinTypeA; }
|
|
492
|
-
chooseTradeWallet(req, coinType, amount) {
|
|
493
|
-
if (req.walletAddress) {
|
|
494
|
-
if (!this.tradeWallets.has(req.walletAddress))
|
|
495
|
-
throw new Error(`未知交易钱包: ${req.walletAddress}`);
|
|
496
|
-
return req.walletAddress;
|
|
497
|
-
}
|
|
498
|
-
let best = '';
|
|
499
|
-
let bestTotal = -1n;
|
|
500
|
-
for (const w of this.tradeWallets.keys()) {
|
|
501
|
-
const { total } = this.cache.snapshot(w, coinType);
|
|
502
|
-
if (total >= amount && total > bestTotal) {
|
|
503
|
-
best = w;
|
|
504
|
-
bestTotal = total;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (!best)
|
|
508
|
-
throw new Error(`无交易钱包持有 ≥ ${amount} 的 ${coinType}`);
|
|
509
|
-
return best;
|
|
510
|
-
}
|
|
511
394
|
async registerShared(tx, objectId, mutable) {
|
|
512
395
|
const initialSharedVersion = await this.getSharedRefCached(objectId);
|
|
513
396
|
tx.object(transactions_1.Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable }));
|
|
@@ -517,57 +400,7 @@ class CentralExecutor {
|
|
|
517
400
|
req = await this.canonicalizeReq(req);
|
|
518
401
|
const inType = this.inputCoinType(req);
|
|
519
402
|
await this.getCoinDecimalsCached(inType).catch(() => { });
|
|
520
|
-
|
|
521
|
-
return this.submitSwapBalance(req, t0);
|
|
522
|
-
let wallet = '';
|
|
523
|
-
let inputCoins = [];
|
|
524
|
-
let inputTag = '';
|
|
525
|
-
let shortfall = 0n;
|
|
526
|
-
try {
|
|
527
|
-
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
528
|
-
inputTag = this.nextTag('in');
|
|
529
|
-
inputCoins = this.cache.acquire(wallet, inType, req.amountIn, inputTag).coins;
|
|
530
|
-
}
|
|
531
|
-
catch (e) {
|
|
532
|
-
(0, dist_1.log_warn)(`[executor] ${this.pairLabel(req)} 对象不足:需 ${this.fmtAmount(inType, req.amountIn)},当前缓存 [${this.walletBalancesLabel(inType)}] → reconcile 重试`);
|
|
533
|
-
await Promise.all([...this.tradeWallets.keys()].map(w => this.reconcileCoins(w, inType)));
|
|
534
|
-
try {
|
|
535
|
-
wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
536
|
-
inputTag = this.nextTag('in');
|
|
537
|
-
inputCoins = this.cache.acquire(wallet, inType, req.amountIn, inputTag).coins;
|
|
538
|
-
}
|
|
539
|
-
catch (e2) {
|
|
540
|
-
const picked = await this.selectWalletWithBalance(inType, req.amountIn);
|
|
541
|
-
if (!picked) {
|
|
542
|
-
(0, dist_1.log_error)(`[executor] ${this.pairLabel(req)} 余额不足:需 ${this.fmtAmount(inType, req.amountIn)},对象+address-balance 仍不够 [${this.walletBalancesLabel(inType)}]`, e2);
|
|
543
|
-
throw e2;
|
|
544
|
-
}
|
|
545
|
-
wallet = picked.wallet;
|
|
546
|
-
inputTag = this.nextTag('in');
|
|
547
|
-
inputCoins = this.cache.acquireAvailable(wallet, inType, req.amountIn, inputTag);
|
|
548
|
-
const objSum = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
549
|
-
shortfall = req.amountIn - objSum;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const tAcquire = Date.now();
|
|
553
|
-
const acqTotal = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
554
|
-
const shortStr = shortfall > 0n ? ` + address-balance 补 ${this.fmtAmount(inType, shortfall)}` : '';
|
|
555
|
-
(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`);
|
|
556
|
-
try {
|
|
557
|
-
const { txBytes, tx } = await this.buildSwapTxObject(req, wallet, inputCoins, shortfall);
|
|
558
|
-
const tBuild = Date.now();
|
|
559
|
-
const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
560
|
-
const digest = await tx.getDigest();
|
|
561
|
-
const tSign = Date.now();
|
|
562
|
-
void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, req, inputCoins, inputTag, 0, shortfall > 0n);
|
|
563
|
-
(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}`);
|
|
564
|
-
return { digest, submitted: true };
|
|
565
|
-
}
|
|
566
|
-
catch (e) {
|
|
567
|
-
this.cache.abort(inputTag, false);
|
|
568
|
-
(0, dist_1.log_error)(`[executor] submitSwap 失败 dex=${req.dexId}`, e);
|
|
569
|
-
return { digest: '', submitted: false, error: e.message };
|
|
570
|
-
}
|
|
403
|
+
return this.submitSwapBalance(req, t0);
|
|
571
404
|
}
|
|
572
405
|
async submitSwapBalance(req, t0) {
|
|
573
406
|
const inType = this.inputCoinType(req);
|
|
@@ -613,12 +446,12 @@ class CentralExecutor {
|
|
|
613
446
|
this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
|
|
614
447
|
return;
|
|
615
448
|
}
|
|
616
|
-
if (inputTag)
|
|
617
|
-
this.cache.abort(inputTag, true);
|
|
618
449
|
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认(balance) digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
|
|
619
450
|
this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
|
|
620
|
-
if (
|
|
621
|
-
|
|
451
|
+
if (inputTag) {
|
|
452
|
+
this.cache.abort(inputTag, true);
|
|
453
|
+
void this.reconcileCoins(wallet, inType);
|
|
454
|
+
}
|
|
622
455
|
}
|
|
623
456
|
catch (e) {
|
|
624
457
|
if (!inputTag && this.isInsufficientBalanceError(e) && attempt < this.maxRebuildRetries) {
|
|
@@ -663,71 +496,6 @@ class CentralExecutor {
|
|
|
663
496
|
return null;
|
|
664
497
|
}
|
|
665
498
|
}
|
|
666
|
-
async broadcastAndCommit(txBytes, signatures, digest, wallet, req, inputCoins, inputTag, attempt = 0, usedAddressBalance = false) {
|
|
667
|
-
const inType = this.inputCoinType(req);
|
|
668
|
-
const outType = this.outputCoinType(req);
|
|
669
|
-
const tBroadcast = Date.now();
|
|
670
|
-
try {
|
|
671
|
-
const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
672
|
-
const tr = resp.Transaction;
|
|
673
|
-
if (!tr?.effects) {
|
|
674
|
-
this.cache.abort(inputTag, true);
|
|
675
|
-
this.reconcileAfterFailure(wallet, inType, outType);
|
|
676
|
-
(0, dist_1.log_error)(`[executor] 响应缺 effects,无法更新 cache digest=${digest}`, new Error('missing effects'));
|
|
677
|
-
this.onBroadcastResult?.({ digest, success: false, error: 'missing effects' });
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
|
|
681
|
-
if (!success) {
|
|
682
|
-
this.cache.abort(inputTag, true);
|
|
683
|
-
this.reconcileAfterFailure(wallet, inType, outType);
|
|
684
|
-
(0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error} broadcast=${Date.now() - tBroadcast}ms`);
|
|
685
|
-
this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
await this.onSuccess(tr, wallet, req, inputCoins, inputTag, usedAddressBalance);
|
|
689
|
-
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
|
|
690
|
-
this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
|
|
691
|
-
}
|
|
692
|
-
catch (e) {
|
|
693
|
-
this.cache.abort(inputTag, true);
|
|
694
|
-
if (!usedAddressBalance && this.isRebuildableObjectError(e) && attempt < this.maxRebuildRetries) {
|
|
695
|
-
(0, dist_1.log_warn)(`[executor] swap 输入对象版本陈旧,定向刷新 ${inType.split('::').pop()} + 重建重发(第 ${attempt + 1}/${this.maxRebuildRetries} 次)digest=${digest}: ${e.message}`);
|
|
696
|
-
await this.reconcileCoins(wallet, inType);
|
|
697
|
-
const retry = await this.rebuildSwap(req, wallet);
|
|
698
|
-
if (retry) {
|
|
699
|
-
void this.broadcastAndCommit(retry.txBytes, retry.signatures, retry.digest, wallet, req, retry.inputCoins, retry.inputTag, attempt + 1);
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
(0, dist_1.log_warn)(`[executor] swap 重建失败(输入不足等),转 reconcile 兜底 digest=${digest}`);
|
|
703
|
-
}
|
|
704
|
-
this.reconcileAfterFailure(wallet, inType, outType);
|
|
705
|
-
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms attempt=${attempt}`, e);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
isRebuildableObjectError(e) {
|
|
709
|
-
const err = e;
|
|
710
|
-
const msg = (err?.message || String(e)).toLowerCase();
|
|
711
|
-
return msg.includes('unavailable for consumption')
|
|
712
|
-
|| msg.includes('needs to be rebuilt')
|
|
713
|
-
|| (err?.code === 'INVALID_ARGUMENT' && msg.includes('object'));
|
|
714
|
-
}
|
|
715
|
-
async rebuildSwap(req, wallet) {
|
|
716
|
-
try {
|
|
717
|
-
const inType = this.inputCoinType(req);
|
|
718
|
-
const inputTag = this.nextTag('in');
|
|
719
|
-
const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
|
|
720
|
-
const { txBytes, tx } = await this.buildSwapTxObject(req, wallet, inputRes.coins);
|
|
721
|
-
const { signature } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
722
|
-
const digest = await tx.getDigest();
|
|
723
|
-
(0, dist_1.log_info)(`[executor] swap 重建 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn}`);
|
|
724
|
-
return { txBytes, signatures: [signature], digest, inputCoins: inputRes.coins, inputTag };
|
|
725
|
-
}
|
|
726
|
-
catch (e) {
|
|
727
|
-
(0, dist_1.log_warn)(`[executor] swap 重建失败 dex=${req.dexId}: ${e.message}`);
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
499
|
toCheckerReceipt(tr, digest) {
|
|
732
500
|
const eff = tr.effects;
|
|
733
501
|
const status = eff?.status?.success
|
|
@@ -752,73 +520,16 @@ class CentralExecutor {
|
|
|
752
520
|
},
|
|
753
521
|
};
|
|
754
522
|
}
|
|
755
|
-
reconcileAfterFailure(wallet, inType, outType) {
|
|
756
|
-
for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
|
|
757
|
-
void this.reconcileCoins(wallet, t);
|
|
758
|
-
}
|
|
759
523
|
async simulateSwap(req) {
|
|
760
524
|
req = await this.canonicalizeReq(req);
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
const wallet = req.walletAddress ?? (balanceMode ? this.tradeWalletAddresses[0] : this.chooseTradeWallet(req, inType, req.amountIn));
|
|
764
|
-
let txBytes;
|
|
765
|
-
if (balanceMode) {
|
|
766
|
-
({ txBytes } = await this.buildSwapTxBalance(req, wallet));
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
const { coins } = this.cache.snapshot(wallet, inType);
|
|
770
|
-
if (!coins.length)
|
|
771
|
-
throw new Error(`[executor] simulate 无 ${inType} 输入币(先 reconcileCoins)`);
|
|
772
|
-
const picked = [];
|
|
773
|
-
let sum = 0n;
|
|
774
|
-
for (const c of coins) {
|
|
775
|
-
picked.push(c);
|
|
776
|
-
sum += BigInt(c.balance);
|
|
777
|
-
if (sum >= req.amountIn)
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
|
-
if (sum < req.amountIn)
|
|
781
|
-
throw new Error(`[executor] simulate 输入不足 ${sum} < ${req.amountIn}`);
|
|
782
|
-
({ txBytes } = await this.buildSwapTxObject(req, wallet, picked));
|
|
783
|
-
}
|
|
525
|
+
const wallet = req.walletAddress ?? this.tradeWalletAddresses[0];
|
|
526
|
+
const { txBytes } = await this.buildSwapTxBalance(req, wallet);
|
|
784
527
|
const res = await this.core.simulateTransaction({ transaction: txBytes, include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
785
528
|
if (!res?.Transaction) {
|
|
786
529
|
throw new Error(`[executor] simulate FailedTransaction: ${JSON.stringify(res?.FailedTransaction ?? res).slice(0, 400)}`);
|
|
787
530
|
}
|
|
788
531
|
return res.Transaction;
|
|
789
532
|
}
|
|
790
|
-
async minSplitFor(coinType) {
|
|
791
|
-
if (coinType === constants_1.SUI_TOKEN_ADDRESS.LONG) {
|
|
792
|
-
return (0, format_1.suiToMist)(process.env.SUI_INPUT_COIN_MIN_SPLIT, '0.1');
|
|
793
|
-
}
|
|
794
|
-
const d = await this.getCoinDecimalsCached(coinType);
|
|
795
|
-
return 10n ** BigInt(d) / 10n;
|
|
796
|
-
}
|
|
797
|
-
async postTradeRebalance(wallet, inType, outType) {
|
|
798
|
-
const kp = this.tradeWallets.get(wallet);
|
|
799
|
-
if (!kp)
|
|
800
|
-
return;
|
|
801
|
-
const coinTarget = Number(process.env.SUI_INPUT_COIN_TARGET || 3);
|
|
802
|
-
const coinMax = Number(process.env.SUI_INPUT_COIN_MAX || 5);
|
|
803
|
-
const budget = this.gasBudget;
|
|
804
|
-
for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG])) {
|
|
805
|
-
try {
|
|
806
|
-
await this.reconcileCoins(wallet, t);
|
|
807
|
-
if (this.cache.hasInflight(wallet, t))
|
|
808
|
-
continue;
|
|
809
|
-
if (this.fundModeFor(t) === 'balance') {
|
|
810
|
-
if (this.cache.snapshot(wallet, t).coins.length > 0)
|
|
811
|
-
await this.sweepObjectsToBalance(wallet, kp, t, budget);
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
await this.maintainCoinObjects(wallet, kp, t, { coinTarget, coinMax, budget, minSplit: await this.minSplitFor(t) });
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
catch (e) {
|
|
818
|
-
(0, dist_1.log_warn)(`[executor] postTradeRebalance ${t} 失败(交定时 tick 兜底): ${e.message}`);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
533
|
async swapTxShell(req, wallet) {
|
|
823
534
|
const tx = new transactions_1.Transaction();
|
|
824
535
|
tx.setSender(wallet);
|
|
@@ -835,33 +546,6 @@ class CentralExecutor {
|
|
|
835
546
|
const txBytes = online ? await tx.build({ client: this.core.rawClient }) : await tx.build();
|
|
836
547
|
return { txBytes, tx };
|
|
837
548
|
}
|
|
838
|
-
async buildSwapTxObject(req, wallet, inputCoins, shortfall = 0n) {
|
|
839
|
-
const tx = await this.swapTxShell(req, wallet);
|
|
840
|
-
const inType = this.inputCoinType(req);
|
|
841
|
-
let inCoin;
|
|
842
|
-
let usedResolver = false;
|
|
843
|
-
if (inputCoins.length === 0) {
|
|
844
|
-
inCoin = (0, transactions_1.coinWithBalance)({ type: inType, balance: req.amountIn });
|
|
845
|
-
usedResolver = true;
|
|
846
|
-
}
|
|
847
|
-
else {
|
|
848
|
-
const primary = tx.object(transactions_1.Inputs.ObjectRef(inputCoins[0]));
|
|
849
|
-
if (inputCoins.length > 1)
|
|
850
|
-
tx.mergeCoins(primary, inputCoins.slice(1).map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
851
|
-
if (shortfall > 0n) {
|
|
852
|
-
tx.mergeCoins(primary, [(0, transactions_1.coinWithBalance)({ type: inType, balance: shortfall })]);
|
|
853
|
-
usedResolver = true;
|
|
854
|
-
}
|
|
855
|
-
;
|
|
856
|
-
[inCoin] = tx.splitCoins(primary, [tx.pure.u64(req.amountIn)]);
|
|
857
|
-
}
|
|
858
|
-
const { outputCoin, leftoverCoins } = (0, swap_1.buildSwapMoveCall)(req.dexId, tx, {
|
|
859
|
-
coinTypeA: req.coinTypeA, coinTypeB: req.coinTypeB, poolId: req.poolId,
|
|
860
|
-
a2b: req.a2b, byAmountIn: true, amount: req.amountIn, amountLimit: req.minOut, sqrtPriceLimit: req.sqrtPriceLimit,
|
|
861
|
-
}, inCoin);
|
|
862
|
-
tx.transferObjects([outputCoin, ...leftoverCoins], tx.pure.address(wallet));
|
|
863
|
-
return this.finishSwapTx(tx, usedResolver);
|
|
864
|
-
}
|
|
865
549
|
async buildSwapTxBalance(req, wallet, objectCoins = [], shortfall = 0n) {
|
|
866
550
|
const tx = await this.swapTxShell(req, wallet);
|
|
867
551
|
const inType = this.inputCoinType(req);
|
|
@@ -893,27 +577,6 @@ class CentralExecutor {
|
|
|
893
577
|
tx.moveCall({ target: '0x2::coin::send_funds', typeArguments: [inType], arguments: [lo, tx.pure.address(wallet)] });
|
|
894
578
|
return this.finishSwapTx(tx, false);
|
|
895
579
|
}
|
|
896
|
-
async onSuccess(tr, wallet, req, inputCoins, inputTag, usedAddressBalance = false) {
|
|
897
|
-
const inType = this.inputCoinType(req);
|
|
898
|
-
const outType = this.outputCoinType(req);
|
|
899
|
-
if (usedAddressBalance) {
|
|
900
|
-
this.cache.abort(inputTag, true);
|
|
901
|
-
await this.reconcileCoins(wallet, inType);
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
904
|
-
const inputTotal = inputCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
|
|
905
|
-
const inputChanges = (0, effects_1.extractSwapInputChanges)(tr.effects, {
|
|
906
|
-
wallet, inCoinType: inType,
|
|
907
|
-
primaryId: inputCoins[0].objectId,
|
|
908
|
-
reservedInputIds: inputCoins.map(c => c.objectId),
|
|
909
|
-
inputTotal, amountIn: req.amountIn,
|
|
910
|
-
});
|
|
911
|
-
this.cache.commit(inputTag, inputChanges);
|
|
912
|
-
}
|
|
913
|
-
if (this.reconcileAfterTx) {
|
|
914
|
-
void this.postTradeRebalance(wallet, inType, outType);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
580
|
get coinCache() { return this.cache; }
|
|
918
581
|
get tradeWalletAddresses() { return [...this.tradeWallets.keys()]; }
|
|
919
582
|
get coreClient() { return this.core; }
|
|
@@ -8,7 +8,7 @@ class CoinMaintainer {
|
|
|
8
8
|
this.executor = executor;
|
|
9
9
|
this.timer = null;
|
|
10
10
|
this.running = false;
|
|
11
|
-
this.intervalMs = opts.intervalMs ?? (0, format_1.parseDurationMs)(process.env.SUI_COIN_MAINTAIN_INTERVAL_MS,
|
|
11
|
+
this.intervalMs = opts.intervalMs ?? (0, format_1.parseDurationMs)(process.env.SUI_COIN_MAINTAIN_INTERVAL_MS, 300000);
|
|
12
12
|
}
|
|
13
13
|
start() {
|
|
14
14
|
if (this.timer)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CoreClient, SuiClientTypes } from '@mysten/sui/client';
|
|
2
|
-
export declare function fetchCurrentEpochViaGraphql(url: string): Promise<bigint>;
|
|
2
|
+
export declare function fetchCurrentEpochViaGraphql(url: string, retries?: number): Promise<bigint>;
|
|
3
3
|
export interface GrpcCoreOptions {
|
|
4
4
|
endpoint?: string;
|
|
5
5
|
token?: string;
|
|
@@ -26,13 +26,29 @@ const GRPC_CLIENT_OPTIONS = {
|
|
|
26
26
|
'grpc-node.max_session_memory': 64,
|
|
27
27
|
'grpc.default_compression_algorithm': 2,
|
|
28
28
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
const _gqlEpochClients = new Map();
|
|
30
|
+
async function fetchCurrentEpochViaGraphql(url, retries = 2) {
|
|
31
|
+
let client = _gqlEpochClients.get(url);
|
|
32
|
+
if (!client) {
|
|
33
|
+
client = new graphql_1.SuiGraphQLClient({ url, network: 'mainnet' });
|
|
34
|
+
_gqlEpochClients.set(url, client);
|
|
35
|
+
}
|
|
36
|
+
let lastErr;
|
|
37
|
+
for (let i = 0; i <= retries; i++) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await client.query({ query: 'query { epoch { epochId } }', variables: {} });
|
|
40
|
+
const e = res?.data?.epoch?.epochId;
|
|
41
|
+
if (e == null)
|
|
42
|
+
throw new Error(`GraphQL epoch 查询无结果: ${JSON.stringify(res?.errors ?? res).slice(0, 200)}`);
|
|
43
|
+
return BigInt(e);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
lastErr = e;
|
|
47
|
+
if (i < retries)
|
|
48
|
+
await (0, dist_1.sleep)(150 * (i + 1));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw lastErr;
|
|
36
52
|
}
|
|
37
53
|
function buildGrpcCore(opts = {}) {
|
|
38
54
|
const endpoint = opts.endpoint ?? (0, dist_1.getCoreEnv)().grpc_endpoint;
|