@clonegod/ttd-sui-common 2.0.7 → 2.0.9
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.
- package/dist/appconfig/sui_dex_env_args.js +0 -1
- package/dist/appconfig/sui_env_args.js +1 -0
- package/dist/trade/executor/central_executor.d.ts +11 -0
- package/dist/trade/executor/central_executor.js +96 -17
- package/dist/trade/executor/coin_cache.js +4 -1
- package/dist/trade/executor/effects.d.ts +18 -0
- package/dist/trade/executor/effects.js +49 -0
- package/package.json +1 -1
|
@@ -18,7 +18,6 @@ class SuiDexEnvArgs extends sui_env_args_1.SuiEnvArgs {
|
|
|
18
18
|
this.quote_amount_usd = cfg.quote_amount_usd;
|
|
19
19
|
this.wallet_dir = cfg.wallet_dir;
|
|
20
20
|
this.encryption_key = cfg.encryption_key ?? '';
|
|
21
|
-
this.trade_analyze_host = cfg.trade_analyze_host ?? '';
|
|
22
21
|
}
|
|
23
22
|
print(moduleName) {
|
|
24
23
|
(0, dist_1.printEnvConfig)(moduleName || this.app_name || 'sui-dex', this);
|
|
@@ -17,6 +17,7 @@ class SuiEnvArgs extends dist_1.EnvArgs {
|
|
|
17
17
|
this.ip_exclude_prefix = cfg.ip_exclude_prefix ?? '';
|
|
18
18
|
this.config_center_host = cfg.config_center_host;
|
|
19
19
|
this.token_price_refresh_interval_seconds = cfg.token_price_refresh_interval_seconds;
|
|
20
|
+
this.trade_analyze_host = cfg.trade_analyze_host ?? '';
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
exports.SuiEnvArgs = SuiEnvArgs;
|
|
@@ -35,6 +35,12 @@ export interface CentralExecutorOptions {
|
|
|
35
35
|
};
|
|
36
36
|
}) => void;
|
|
37
37
|
tradeCoinTypes?: () => Promise<string[]>;
|
|
38
|
+
tradePools?: () => Promise<Array<{
|
|
39
|
+
dexId: DEX_ID;
|
|
40
|
+
poolId: string;
|
|
41
|
+
coinTypeA: string;
|
|
42
|
+
coinTypeB: string;
|
|
43
|
+
}>>;
|
|
38
44
|
}
|
|
39
45
|
type TxResponse = SuiClientTypes.Transaction<{
|
|
40
46
|
effects: true;
|
|
@@ -51,8 +57,11 @@ export declare class CentralExecutor {
|
|
|
51
57
|
private sharedRefCache;
|
|
52
58
|
private seq;
|
|
53
59
|
private readonly tradeCoinTypesProvider?;
|
|
60
|
+
private readonly tradePoolsProvider?;
|
|
61
|
+
private readonly maxRebuildRetries;
|
|
54
62
|
constructor(core: ExecutorCore, opts?: CentralExecutorOptions);
|
|
55
63
|
init(): Promise<void>;
|
|
64
|
+
private warmup;
|
|
56
65
|
reconcileCoins(wallet: string, coinType: string, quietIfUnchanged?: boolean): Promise<void>;
|
|
57
66
|
rebalanceWalletFunds(): Promise<void>;
|
|
58
67
|
private rebalanceOne;
|
|
@@ -76,6 +85,8 @@ export declare class CentralExecutor {
|
|
|
76
85
|
private registerShared;
|
|
77
86
|
submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
|
|
78
87
|
private broadcastAndCommit;
|
|
88
|
+
private isRebuildableObjectError;
|
|
89
|
+
private rebuildSwap;
|
|
79
90
|
private toCheckerReceipt;
|
|
80
91
|
private reconcileAfterFailure;
|
|
81
92
|
simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
|
|
@@ -38,6 +38,7 @@ class CentralExecutor {
|
|
|
38
38
|
this.tradeWallets = new Map();
|
|
39
39
|
this.sharedRefCache = new Map();
|
|
40
40
|
this.seq = 0;
|
|
41
|
+
this.maxRebuildRetries = Number(process.env.SUI_SWAP_REBUILD_RETRIES || 2);
|
|
41
42
|
this.decimalsCache = new Map();
|
|
42
43
|
this.poolGenericsCache = new Map();
|
|
43
44
|
this.chainIdentifier = '';
|
|
@@ -46,6 +47,7 @@ class CentralExecutor {
|
|
|
46
47
|
this.reconcileAfterTx = opts.reconcileAfterTx ?? true;
|
|
47
48
|
this.onBroadcastResult = opts.onBroadcastResult;
|
|
48
49
|
this.tradeCoinTypesProvider = opts.tradeCoinTypes;
|
|
50
|
+
this.tradePoolsProvider = opts.tradePools;
|
|
49
51
|
}
|
|
50
52
|
async init() {
|
|
51
53
|
const tradeIds = (process.env.SUI_WALLET_GROUP_IDS || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -59,6 +61,36 @@ class CentralExecutor {
|
|
|
59
61
|
await this.reconcileCoins(w, constants_1.SUI_TOKEN_ADDRESS.LONG);
|
|
60
62
|
}
|
|
61
63
|
(0, dist_1.log_info)(`[executor] init: trade=${[...this.tradeWallets.keys()].join(',')} (单钱包模式: gas=各自地址余额), gasBudget=${this.gasBudget}`);
|
|
64
|
+
await this.warmup();
|
|
65
|
+
}
|
|
66
|
+
async warmup() {
|
|
67
|
+
try {
|
|
68
|
+
await Promise.all([this.getGasPrice(), this.getValidDuringExpiration()]);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
(0, dist_1.log_warn)(`[executor] 预热全局缓存(gas/epoch)失败(首笔将冷启动): ${e.message}`);
|
|
72
|
+
}
|
|
73
|
+
if (!this.tradePoolsProvider)
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
const pools = await this.tradePoolsProvider();
|
|
77
|
+
let ok = 0;
|
|
78
|
+
await Promise.all(pools.map(async (p) => {
|
|
79
|
+
try {
|
|
80
|
+
await this.canonicalizeReq({ dexId: p.dexId, poolId: p.poolId, coinTypeA: p.coinTypeA, coinTypeB: p.coinTypeB, a2b: true, amountIn: 0n, minOut: 0n, sqrtPriceLimit: 0n });
|
|
81
|
+
await this.getSharedRefCached(p.poolId);
|
|
82
|
+
await this.getSharedRefCached((0, swap_1.configSharedObjectId)(p.dexId));
|
|
83
|
+
ok++;
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
(0, dist_1.log_warn)(`[executor] 预热池 ${p.poolId.slice(0, 10)}…(${p.dexId}) 失败(首笔冷启动,自愈): ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
}));
|
|
89
|
+
(0, dist_1.log_info)(`[executor] 预热完成: ${ok}/${pools.length} 池 shared ref + 链上真序 + gas/epoch 全局缓存`);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
(0, dist_1.log_warn)(`[executor] 读交易池清单失败,跳过池级预热: ${e.message}`);
|
|
93
|
+
}
|
|
62
94
|
}
|
|
63
95
|
async reconcileCoins(wallet, coinType, quietIfUnchanged = false) {
|
|
64
96
|
try {
|
|
@@ -202,17 +234,18 @@ class CentralExecutor {
|
|
|
202
234
|
const others = coins.filter(c => c.objectId !== primaryRef.objectId);
|
|
203
235
|
if (others.length)
|
|
204
236
|
tx.mergeCoins(primary, others.map(c => tx.object(transactions_1.Inputs.ObjectRef(c))));
|
|
237
|
+
const per = pieces > 1 ? total / BigInt(pieces) : 0n;
|
|
205
238
|
if (pieces > 1) {
|
|
206
|
-
const per = total / BigInt(pieces);
|
|
207
239
|
const splits = tx.splitCoins(primary, Array.from({ length: pieces - 1 }, () => tx.pure.u64(per)));
|
|
208
240
|
tx.transferObjects(Array.from({ length: pieces - 1 }, (_, i) => splits[i]), tx.pure.address(addr));
|
|
209
241
|
}
|
|
210
242
|
tx.setGasPayment([]);
|
|
211
243
|
tx.setExpiration(await this.getValidDuringExpiration());
|
|
212
|
-
await this.execMaintenance(tx, addr, kp, opts.budget, `整理 ${short}:${valued.length} 值 + ${zeros.length} 壳 → ${pieces} 份均分(每份≈${(total / BigInt(pieces)).toString()})`, undefined, coinType, tag);
|
|
244
|
+
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) });
|
|
213
245
|
return true;
|
|
214
246
|
}
|
|
215
|
-
async execMaintenance(tx, addr, kp, budget, label, buildClient, coinType = constants_1.SUI_TOKEN_ADDRESS.LONG, reserveTag) {
|
|
247
|
+
async execMaintenance(tx, addr, kp, budget, label, buildClient, coinType = constants_1.SUI_TOKEN_ADDRESS.LONG, reserveTag, commitPlan) {
|
|
248
|
+
let committed = false;
|
|
216
249
|
try {
|
|
217
250
|
tx.setGasBudget(budget);
|
|
218
251
|
tx.setGasPrice(await this.getGasPrice());
|
|
@@ -222,11 +255,18 @@ class CentralExecutor {
|
|
|
222
255
|
const tr = resp.Transaction;
|
|
223
256
|
const { success, error } = (0, effects_1.coreTxStatus)(tr.effects);
|
|
224
257
|
(0, dist_1.log_info)(`[executor] rebalance ${label} ${success ? 'OK' : 'FAILED'} wallet=${addr.slice(0, 10)}… digest=${tr.digest}${error ? ' err=' + error : ''}`);
|
|
258
|
+
if (success && commitPlan && reserveTag && tr.effects) {
|
|
259
|
+
const changes = (0, effects_1.extractMaintCoinChanges)(tr.effects, { wallet: addr, coinType, ...commitPlan });
|
|
260
|
+
this.cache.commit(reserveTag, changes);
|
|
261
|
+
committed = true;
|
|
262
|
+
}
|
|
225
263
|
}
|
|
226
264
|
finally {
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
if (!committed) {
|
|
266
|
+
if (reserveTag)
|
|
267
|
+
this.cache.abort(reserveTag, true);
|
|
268
|
+
await this.reconcileCoins(addr, coinType);
|
|
269
|
+
}
|
|
230
270
|
}
|
|
231
271
|
}
|
|
232
272
|
async getPoolGenerics(poolId) {
|
|
@@ -358,7 +398,7 @@ class CentralExecutor {
|
|
|
358
398
|
const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
359
399
|
const digest = await tx.getDigest();
|
|
360
400
|
const tSign = Date.now();
|
|
361
|
-
void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet,
|
|
401
|
+
void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, req, inputRes.coins, inputTag);
|
|
362
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}`);
|
|
363
403
|
return { digest, submitted: true };
|
|
364
404
|
}
|
|
@@ -368,7 +408,9 @@ class CentralExecutor {
|
|
|
368
408
|
return { digest: '', submitted: false, error: e.message };
|
|
369
409
|
}
|
|
370
410
|
}
|
|
371
|
-
async broadcastAndCommit(txBytes, signatures, digest, wallet,
|
|
411
|
+
async broadcastAndCommit(txBytes, signatures, digest, wallet, req, inputCoins, inputTag, attempt = 0) {
|
|
412
|
+
const inType = this.inputCoinType(req);
|
|
413
|
+
const outType = this.outputCoinType(req);
|
|
372
414
|
const tBroadcast = Date.now();
|
|
373
415
|
try {
|
|
374
416
|
const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
@@ -388,14 +430,47 @@ class CentralExecutor {
|
|
|
388
430
|
this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
|
|
389
431
|
return;
|
|
390
432
|
}
|
|
391
|
-
await this.onSuccess(tr, wallet,
|
|
433
|
+
await this.onSuccess(tr, wallet, req, inputCoins, inputTag);
|
|
392
434
|
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
|
|
393
435
|
this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
|
|
394
436
|
}
|
|
395
437
|
catch (e) {
|
|
396
438
|
this.cache.abort(inputTag, true);
|
|
439
|
+
if (this.isRebuildableObjectError(e) && attempt < this.maxRebuildRetries) {
|
|
440
|
+
(0, dist_1.log_warn)(`[executor] swap 输入对象版本陈旧,定向刷新 ${inType.split('::').pop()} + 重建重发(第 ${attempt + 1}/${this.maxRebuildRetries} 次)digest=${digest}: ${e.message}`);
|
|
441
|
+
await this.reconcileCoins(wallet, inType);
|
|
442
|
+
const retry = await this.rebuildSwap(req, wallet);
|
|
443
|
+
if (retry) {
|
|
444
|
+
void this.broadcastAndCommit(retry.txBytes, retry.signatures, retry.digest, wallet, req, retry.inputCoins, retry.inputTag, attempt + 1);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
(0, dist_1.log_warn)(`[executor] swap 重建失败(输入不足等),转 reconcile 兜底 digest=${digest}`);
|
|
448
|
+
}
|
|
397
449
|
this.reconcileAfterFailure(wallet, inType, outType);
|
|
398
|
-
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`, e);
|
|
450
|
+
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms attempt=${attempt}`, e);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
isRebuildableObjectError(e) {
|
|
454
|
+
const err = e;
|
|
455
|
+
const msg = (err?.message || String(e)).toLowerCase();
|
|
456
|
+
return msg.includes('unavailable for consumption')
|
|
457
|
+
|| msg.includes('needs to be rebuilt')
|
|
458
|
+
|| (err?.code === 'INVALID_ARGUMENT' && msg.includes('object'));
|
|
459
|
+
}
|
|
460
|
+
async rebuildSwap(req, wallet) {
|
|
461
|
+
try {
|
|
462
|
+
const inType = this.inputCoinType(req);
|
|
463
|
+
const inputTag = this.nextTag('in');
|
|
464
|
+
const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
|
|
465
|
+
const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
|
|
466
|
+
const { signature } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
467
|
+
const digest = await tx.getDigest();
|
|
468
|
+
(0, dist_1.log_info)(`[executor] swap 重建 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn}`);
|
|
469
|
+
return { txBytes, signatures: [signature], digest, inputCoins: inputRes.coins, inputTag };
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
(0, dist_1.log_warn)(`[executor] swap 重建失败 dex=${req.dexId}: ${e.message}`);
|
|
473
|
+
return null;
|
|
399
474
|
}
|
|
400
475
|
}
|
|
401
476
|
toCheckerReceipt(tr, digest) {
|
|
@@ -500,13 +575,17 @@ class CentralExecutor {
|
|
|
500
575
|
const txBytes = await tx.build();
|
|
501
576
|
return { txBytes, tx };
|
|
502
577
|
}
|
|
503
|
-
async onSuccess(tr, wallet,
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
578
|
+
async onSuccess(tr, wallet, req, inputCoins, inputTag) {
|
|
579
|
+
const inType = this.inputCoinType(req);
|
|
580
|
+
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);
|
|
510
589
|
if (this.reconcileAfterTx) {
|
|
511
590
|
void this.postTradeRebalance(wallet, inType, outType);
|
|
512
591
|
}
|
|
@@ -53,7 +53,10 @@ class InProcessCoinCache {
|
|
|
53
53
|
if (r.wallet === wallet && r.coinType === coinType)
|
|
54
54
|
r.coins.forEach(c => inflightIds.add(c.objectId));
|
|
55
55
|
}
|
|
56
|
-
const
|
|
56
|
+
const prev = new Map(this.list(wallet, coinType).map(c => [c.objectId, c]));
|
|
57
|
+
const kept = freshCoins
|
|
58
|
+
.filter(c => !inflightIds.has(c.objectId))
|
|
59
|
+
.map(c => { const old = prev.get(c.objectId); return old && BigInt(old.version) > BigInt(c.version) ? old : c; });
|
|
57
60
|
this.sortDesc(kept);
|
|
58
61
|
const sig = (l) => l.map(c => `${c.objectId}@${c.version}:${c.balance}`).join(',');
|
|
59
62
|
const unchanged = sig(kept) === sig(this.list(wallet, coinType));
|
|
@@ -13,4 +13,22 @@ export interface ExtractCoinChangesOptions {
|
|
|
13
13
|
coinTypeById?: Map<string, string>;
|
|
14
14
|
}
|
|
15
15
|
export declare function extractCoinChangesFromCore(effects: TransactionEffects, objectTypes: Record<string, string>, opts?: ExtractCoinChangesOptions): CoinObjectChange[];
|
|
16
|
+
export interface MaintCoinChangesOptions {
|
|
17
|
+
wallet: string;
|
|
18
|
+
coinType: string;
|
|
19
|
+
primaryId: string;
|
|
20
|
+
perPiece: bigint;
|
|
21
|
+
primaryRemainder: bigint;
|
|
22
|
+
reservedIds: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function extractMaintCoinChanges(effects: TransactionEffects, o: MaintCoinChangesOptions): CoinObjectChange[];
|
|
25
|
+
export interface SwapInputChangesOptions {
|
|
26
|
+
wallet: string;
|
|
27
|
+
inCoinType: string;
|
|
28
|
+
primaryId: string;
|
|
29
|
+
reservedInputIds: string[];
|
|
30
|
+
inputTotal: bigint;
|
|
31
|
+
amountIn: bigint;
|
|
32
|
+
}
|
|
33
|
+
export declare function extractSwapInputChanges(effects: TransactionEffects, o: SwapInputChangesOptions): CoinObjectChange[];
|
|
16
34
|
export {};
|
|
@@ -4,6 +4,8 @@ exports.parseCoinType = parseCoinType;
|
|
|
4
4
|
exports.coreTxStatus = coreTxStatus;
|
|
5
5
|
exports.coreGasUsed = coreGasUsed;
|
|
6
6
|
exports.extractCoinChangesFromCore = extractCoinChangesFromCore;
|
|
7
|
+
exports.extractMaintCoinChanges = extractMaintCoinChanges;
|
|
8
|
+
exports.extractSwapInputChanges = extractSwapInputChanges;
|
|
7
9
|
function parseCoinType(type) {
|
|
8
10
|
if (!type)
|
|
9
11
|
return null;
|
|
@@ -52,6 +54,53 @@ function extractCoinChangesFromCore(effects, objectTypes, opts = {}) {
|
|
|
52
54
|
}
|
|
53
55
|
return out;
|
|
54
56
|
}
|
|
57
|
+
function extractMaintCoinChanges(effects, o) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const reserved = new Set(o.reservedIds);
|
|
60
|
+
for (const ch of effects.changedObjects) {
|
|
61
|
+
const kind = changedKind(ch);
|
|
62
|
+
if (!kind)
|
|
63
|
+
continue;
|
|
64
|
+
if (kind === 'deleted') {
|
|
65
|
+
if (reserved.has(ch.objectId))
|
|
66
|
+
out.push({ kind: 'deleted', objectId: ch.objectId, coinType: o.coinType });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ownerAddress(ch.outputOwner) !== o.wallet)
|
|
70
|
+
continue;
|
|
71
|
+
if (ch.objectId === o.primaryId) {
|
|
72
|
+
out.push({ kind: 'mutated', objectId: ch.objectId, coinType: o.coinType,
|
|
73
|
+
version: ch.outputVersion ?? undefined, digest: ch.outputDigest ?? undefined, balance: o.primaryRemainder.toString() });
|
|
74
|
+
}
|
|
75
|
+
else if (kind === 'created') {
|
|
76
|
+
out.push({ kind: 'created', objectId: ch.objectId, coinType: o.coinType,
|
|
77
|
+
version: ch.outputVersion ?? undefined, digest: ch.outputDigest ?? undefined, balance: o.perPiece.toString() });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function extractSwapInputChanges(effects, o) {
|
|
83
|
+
const out = [];
|
|
84
|
+
const reserved = new Set(o.reservedInputIds);
|
|
85
|
+
const leftover = o.inputTotal - o.amountIn;
|
|
86
|
+
for (const ch of effects.changedObjects) {
|
|
87
|
+
const kind = changedKind(ch);
|
|
88
|
+
if (!kind)
|
|
89
|
+
continue;
|
|
90
|
+
if (kind === 'deleted') {
|
|
91
|
+
if (reserved.has(ch.objectId))
|
|
92
|
+
out.push({ kind: 'deleted', objectId: ch.objectId, coinType: o.inCoinType });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch.objectId !== o.primaryId)
|
|
96
|
+
continue;
|
|
97
|
+
if (ownerAddress(ch.outputOwner) !== o.wallet)
|
|
98
|
+
continue;
|
|
99
|
+
out.push({ kind: 'mutated', objectId: ch.objectId, coinType: o.inCoinType,
|
|
100
|
+
version: ch.outputVersion ?? undefined, digest: ch.outputDigest ?? undefined, balance: leftover.toString() });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
55
104
|
function changedKind(ch) {
|
|
56
105
|
if (ch.idOperation === 'Created')
|
|
57
106
|
return 'created';
|