four-flap-meme-sdk 1.5.20 → 1.5.22
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/contracts/tm-bundle-merkle/index.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/index.js +1 -1
- package/dist/contracts/tm-bundle-merkle/types.d.ts +24 -0
- package/dist/contracts/tm-bundle-merkle/utils.d.ts +8 -1
- package/dist/contracts/tm-bundle-merkle/utils.js +287 -0
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/concurrency.d.ts +5 -0
- package/dist/utils/concurrency.js +19 -0
- package/dist/xlayer/aa-account.d.ts +10 -0
- package/dist/xlayer/aa-account.js +257 -20
- package/dist/xlayer/bundle.d.ts +5 -0
- package/dist/xlayer/bundle.js +202 -72
- package/package.json +1 -1
package/dist/xlayer/bundle.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Wallet, Interface, Contract } from 'ethers';
|
|
|
11
11
|
import { FLAP_PORTAL, ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, } from './constants.js';
|
|
12
12
|
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
13
13
|
import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
|
|
14
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
14
15
|
// ============================================================================
|
|
15
16
|
// 捆绑交易执行器
|
|
16
17
|
// ============================================================================
|
|
@@ -129,45 +130,46 @@ export class BundleExecutor {
|
|
|
129
130
|
/**
|
|
130
131
|
* 构建授权 UserOp
|
|
131
132
|
*/
|
|
132
|
-
async buildApproveUserOp(ownerWallet, tokenAddress, spender, nonce, ownerName) {
|
|
133
|
-
const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
|
|
133
|
+
async buildApproveUserOp(ownerWallet, tokenAddress, spender, sender, nonce, initCode, ownerName) {
|
|
134
134
|
const approveData = encodeApproveCall(spender);
|
|
135
135
|
const callData = encodeExecute(tokenAddress, 0n, approveData);
|
|
136
|
-
await this.aaManager.ensureSenderBalance(ownerWallet,
|
|
136
|
+
await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/approve-prefund`);
|
|
137
137
|
const { userOp, prefundWei } = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
138
138
|
ownerWallet,
|
|
139
|
-
sender
|
|
139
|
+
sender,
|
|
140
140
|
callData,
|
|
141
141
|
nonce,
|
|
142
|
+
initCode,
|
|
142
143
|
});
|
|
143
|
-
await this.aaManager.ensureSenderBalance(ownerWallet,
|
|
144
|
+
await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/approve-fund`);
|
|
144
145
|
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
145
146
|
return { ...signed, prefundWei, ownerName };
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
149
|
* 构建卖出 UserOp
|
|
149
150
|
*/
|
|
150
|
-
async buildSellUserOp(ownerWallet, tokenAddress, sellAmount, nonce, needApprove, ownerName) {
|
|
151
|
-
const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
|
|
151
|
+
async buildSellUserOp(ownerWallet, tokenAddress, sellAmount, sender, nonce, initCode, needApprove, ownerName) {
|
|
152
152
|
const sellData = encodeSellCall(tokenAddress, sellAmount, 0n);
|
|
153
153
|
const callData = encodeExecute(this.portalAddress, 0n, sellData);
|
|
154
|
-
await this.aaManager.ensureSenderBalance(ownerWallet,
|
|
154
|
+
await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0003'), `${ownerName ?? 'owner'}/sell-prefund`);
|
|
155
155
|
// 如果需要 approve(还未执行),estimateGas 会 revert,使用固定值
|
|
156
156
|
const { userOp, prefundWei } = needApprove
|
|
157
157
|
? await this.aaManager.buildUserOpWithLocalEstimate({
|
|
158
158
|
ownerWallet,
|
|
159
|
-
sender
|
|
159
|
+
sender,
|
|
160
160
|
callData,
|
|
161
161
|
nonce,
|
|
162
|
+
initCode,
|
|
162
163
|
callGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
163
164
|
})
|
|
164
165
|
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
165
166
|
ownerWallet,
|
|
166
|
-
sender
|
|
167
|
+
sender,
|
|
167
168
|
callData,
|
|
168
169
|
nonce,
|
|
170
|
+
initCode,
|
|
169
171
|
});
|
|
170
|
-
await this.aaManager.ensureSenderBalance(ownerWallet,
|
|
172
|
+
await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/sell-fund`);
|
|
171
173
|
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
172
174
|
return { ...signed, prefundWei, ownerName };
|
|
173
175
|
}
|
|
@@ -177,39 +179,56 @@ export class BundleExecutor {
|
|
|
177
179
|
async buildWithdrawUserOp(ownerWallet, reserveWei, ownerName) {
|
|
178
180
|
const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
|
|
179
181
|
const senderBalance = await this.portalQuery.getOkbBalance(accountInfo.sender);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
const initCode = accountInfo.deployed ? '0x' : this.aaManager.generateInitCode(ownerWallet.address);
|
|
183
|
+
return await this.buildWithdrawUserOpWithState({
|
|
184
|
+
ownerWallet,
|
|
185
|
+
sender: accountInfo.sender,
|
|
186
|
+
nonce: accountInfo.nonce,
|
|
187
|
+
initCode,
|
|
188
|
+
senderBalance,
|
|
189
|
+
reserveWei,
|
|
190
|
+
ownerName,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 构建归集 UserOp(已知 sender/nonce/balance 的快速版本)
|
|
195
|
+
* - 用于批量流程:避免重复 getAccountInfo / getOkbBalance
|
|
196
|
+
*/
|
|
197
|
+
async buildWithdrawUserOpWithState(params) {
|
|
198
|
+
const senderBalance = params.senderBalance;
|
|
199
|
+
if (senderBalance <= params.reserveWei) {
|
|
200
|
+
console.log(`\n[${params.ownerName ?? 'owner'}] sender OKB 太少,跳过归集:${formatOkb(senderBalance)} OKB`);
|
|
182
201
|
return null;
|
|
183
202
|
}
|
|
184
|
-
// 先估算 prefund
|
|
185
|
-
const tempCallData = encodeExecute(ownerWallet.address, 0n, '0x');
|
|
186
|
-
await this.aaManager.ensureSenderBalance(ownerWallet,
|
|
203
|
+
// 先估算 prefund(使用空调用)
|
|
204
|
+
const tempCallData = encodeExecute(params.ownerWallet.address, 0n, '0x');
|
|
205
|
+
await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, parseOkb('0.0002'), `${params.ownerName ?? 'owner'}/withdraw-prefund`);
|
|
187
206
|
const { prefundWei } = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
188
|
-
ownerWallet,
|
|
189
|
-
sender:
|
|
207
|
+
ownerWallet: params.ownerWallet,
|
|
208
|
+
sender: params.sender,
|
|
190
209
|
callData: tempCallData,
|
|
191
|
-
nonce:
|
|
210
|
+
nonce: params.nonce,
|
|
211
|
+
initCode: params.initCode,
|
|
192
212
|
});
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
? currentBalance - prefundWei - reserveWei
|
|
213
|
+
// 计算可归集金额(用已知余额近似;fund 发生时余额会变大,属于可接受的保守近似)
|
|
214
|
+
const withdrawAmount = senderBalance > prefundWei + params.reserveWei
|
|
215
|
+
? senderBalance - prefundWei - params.reserveWei
|
|
197
216
|
: 0n;
|
|
198
217
|
if (withdrawAmount <= 0n) {
|
|
199
|
-
console.log(`\n[${ownerName ?? 'owner'}] 归集后可转出=0(余额不足以覆盖 prefund+reserve)`);
|
|
218
|
+
console.log(`\n[${params.ownerName ?? 'owner'}] 归集后可转出=0(余额不足以覆盖 prefund+reserve)`);
|
|
200
219
|
return null;
|
|
201
220
|
}
|
|
202
|
-
|
|
203
|
-
const callData = encodeExecute(ownerWallet.address, withdrawAmount, '0x');
|
|
221
|
+
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
204
222
|
const { userOp } = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
205
|
-
ownerWallet,
|
|
206
|
-
sender:
|
|
223
|
+
ownerWallet: params.ownerWallet,
|
|
224
|
+
sender: params.sender,
|
|
207
225
|
callData,
|
|
208
|
-
nonce:
|
|
226
|
+
nonce: params.nonce,
|
|
227
|
+
initCode: params.initCode,
|
|
209
228
|
});
|
|
210
|
-
console.log(`\n[${ownerName ?? 'owner'}] withdraw: ${formatOkb(withdrawAmount)} OKB`);
|
|
211
|
-
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
212
|
-
return { ...signed, prefundWei, ownerName };
|
|
229
|
+
console.log(`\n[${params.ownerName ?? 'owner'}] withdraw: ${formatOkb(withdrawAmount)} OKB`);
|
|
230
|
+
const signed = await this.aaManager.signUserOp(userOp, params.ownerWallet);
|
|
231
|
+
return { ...signed, prefundWei, ownerName: params.ownerName };
|
|
213
232
|
}
|
|
214
233
|
/**
|
|
215
234
|
* 构建代币转账 UserOp(将代币从 sender 转回 owner)
|
|
@@ -338,12 +357,14 @@ export class BundleExecutor {
|
|
|
338
357
|
console.log('token:', tokenAddress);
|
|
339
358
|
console.log('owners:', wallets.length);
|
|
340
359
|
console.log('sellPercent:', sellPercent);
|
|
341
|
-
//
|
|
342
|
-
const
|
|
360
|
+
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed),避免循环内重复 getAccountInfo
|
|
361
|
+
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
362
|
+
const senders = accountInfos.map((ai) => ai.sender);
|
|
343
363
|
const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
|
|
344
364
|
const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
|
|
345
365
|
// 1. 检查授权,必要时先 approve
|
|
346
|
-
const
|
|
366
|
+
const approveItems = [];
|
|
367
|
+
const didApprove = new Array(wallets.length).fill(false);
|
|
347
368
|
for (let i = 0; i < wallets.length; i++) {
|
|
348
369
|
const sender = senders[i];
|
|
349
370
|
const balance = tokenBalances.get(sender) ?? 0n;
|
|
@@ -352,9 +373,20 @@ export class BundleExecutor {
|
|
|
352
373
|
continue;
|
|
353
374
|
if (allowance >= balance)
|
|
354
375
|
continue;
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
376
|
+
const ai = accountInfos[i];
|
|
377
|
+
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
378
|
+
approveItems.push({ i, sender, nonce: ai.nonce, initCode });
|
|
379
|
+
didApprove[i] = true;
|
|
380
|
+
}
|
|
381
|
+
const approveOps = [];
|
|
382
|
+
if (approveItems.length > 0) {
|
|
383
|
+
const signedApproves = await mapWithConcurrency(approveItems, 4, async (it) => {
|
|
384
|
+
const i = it.i;
|
|
385
|
+
const signed = await this.buildApproveUserOp(wallets[i], tokenAddress, this.portalAddress, it.sender, it.nonce, it.initCode, `owner${i + 1}`);
|
|
386
|
+
return { i, userOp: signed.userOp };
|
|
387
|
+
});
|
|
388
|
+
for (const r of signedApproves)
|
|
389
|
+
approveOps.push(r.userOp);
|
|
358
390
|
}
|
|
359
391
|
let approveResult;
|
|
360
392
|
if (approveOps.length > 0) {
|
|
@@ -362,6 +394,7 @@ export class BundleExecutor {
|
|
|
362
394
|
}
|
|
363
395
|
// 2. 卖出
|
|
364
396
|
const sellOps = [];
|
|
397
|
+
const sellItems = [];
|
|
365
398
|
for (let i = 0; i < wallets.length; i++) {
|
|
366
399
|
const sender = senders[i];
|
|
367
400
|
const balance = tokenBalances.get(sender) ?? 0n;
|
|
@@ -370,11 +403,20 @@ export class BundleExecutor {
|
|
|
370
403
|
const sellAmount = (balance * BigInt(sellPercent)) / 100n;
|
|
371
404
|
if (sellAmount === 0n)
|
|
372
405
|
continue;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
406
|
+
const ai = accountInfos[i];
|
|
407
|
+
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
408
|
+
// approve 已单独打包并等待确认,因此这里不需要用“needApprove=真”去走保守 callGasLimit
|
|
409
|
+
const needApprove = false;
|
|
410
|
+
const nonce = ai.nonce + (didApprove[i] ? 1n : 0n);
|
|
411
|
+
sellItems.push({ i, sender, nonce, initCode: didApprove[i] ? '0x' : initCode, needApprove, sellAmount });
|
|
412
|
+
}
|
|
413
|
+
if (sellItems.length > 0) {
|
|
414
|
+
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
415
|
+
const i = it.i;
|
|
416
|
+
const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode, it.needApprove, `owner${i + 1}`);
|
|
417
|
+
return signed.userOp;
|
|
418
|
+
});
|
|
419
|
+
sellOps.push(...signedSells);
|
|
378
420
|
}
|
|
379
421
|
const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary);
|
|
380
422
|
if (!sellResult) {
|
|
@@ -384,11 +426,42 @@ export class BundleExecutor {
|
|
|
384
426
|
let withdrawResult;
|
|
385
427
|
if (withdrawToOwner) {
|
|
386
428
|
const withdrawOps = [];
|
|
429
|
+
// 批量获取 sender OKB 余额
|
|
430
|
+
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
431
|
+
// 计算 sell 后的下一 nonce
|
|
432
|
+
const nextNonces = new Array(wallets.length).fill(0n);
|
|
433
|
+
const sold = new Array(wallets.length).fill(false);
|
|
434
|
+
for (const it of sellItems) {
|
|
435
|
+
sold[it.i] = true;
|
|
436
|
+
}
|
|
387
437
|
for (let i = 0; i < wallets.length; i++) {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
438
|
+
const ai = accountInfos[i];
|
|
439
|
+
const sellNonceUsed = ai.nonce + (didApprove[i] ? 1n : 0n);
|
|
440
|
+
nextNonces[i] = sold[i] ? (sellNonceUsed + 1n) : (ai.nonce + (didApprove[i] ? 1n : 0n));
|
|
441
|
+
}
|
|
442
|
+
const withdrawItems = wallets.map((w, i) => ({
|
|
443
|
+
i,
|
|
444
|
+
ownerWallet: w,
|
|
445
|
+
sender: senders[i],
|
|
446
|
+
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
447
|
+
nonce: nextNonces[i],
|
|
448
|
+
initCode: (accountInfos[i].deployed || didApprove[i] || sold[i]) ? '0x' : this.aaManager.generateInitCode(wallets[i].address),
|
|
449
|
+
}));
|
|
450
|
+
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
451
|
+
const signed = await this.buildWithdrawUserOpWithState({
|
|
452
|
+
ownerWallet: it.ownerWallet,
|
|
453
|
+
sender: it.sender,
|
|
454
|
+
nonce: it.nonce,
|
|
455
|
+
initCode: it.initCode,
|
|
456
|
+
senderBalance: it.senderBalance,
|
|
457
|
+
reserveWei,
|
|
458
|
+
ownerName: `owner${it.i + 1}`,
|
|
459
|
+
});
|
|
460
|
+
return signed?.userOp ?? null;
|
|
461
|
+
});
|
|
462
|
+
for (const op of signedWithdraws) {
|
|
463
|
+
if (op)
|
|
464
|
+
withdrawOps.push(op);
|
|
392
465
|
}
|
|
393
466
|
if (withdrawOps.length > 0) {
|
|
394
467
|
withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
|
|
@@ -415,15 +488,36 @@ export class BundleExecutor {
|
|
|
415
488
|
console.log('token:', tokenAddress);
|
|
416
489
|
console.log('owners:', wallets.length);
|
|
417
490
|
console.log('sellPercent:', sellPercent);
|
|
418
|
-
//
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
491
|
+
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed)
|
|
492
|
+
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
493
|
+
const senders = accountInfos.map((ai) => ai.sender);
|
|
494
|
+
// 1. 买入(批量估算 + 并发补余额 + 并发签名)
|
|
495
|
+
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
496
|
+
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
497
|
+
const buyWei = buyWeis[i] ?? 0n;
|
|
498
|
+
if (buyWei <= 0n)
|
|
499
|
+
return;
|
|
500
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWei + parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
501
|
+
});
|
|
502
|
+
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
503
|
+
const swapData = encodeBuyCall(tokenAddress, buyWei, 0n);
|
|
504
|
+
return encodeExecute(this.portalAddress, buyWei, swapData);
|
|
505
|
+
});
|
|
506
|
+
const initCodes = accountInfos.map((ai, i) => (ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address)));
|
|
507
|
+
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
508
|
+
ops: accountInfos.map((ai, i) => ({
|
|
509
|
+
sender: ai.sender,
|
|
510
|
+
nonce: ai.nonce,
|
|
511
|
+
callData: buyCallDatas[i],
|
|
512
|
+
initCode: initCodes[i],
|
|
513
|
+
})),
|
|
514
|
+
});
|
|
515
|
+
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
516
|
+
const buyWei = buyWeis[i] ?? 0n;
|
|
517
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWei + (prefundWeis[i] ?? 0n) + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
518
|
+
});
|
|
519
|
+
const signedBuy = await mapWithConcurrency(buyUserOps, 10, async (op, i) => this.aaManager.signUserOp(op, wallets[i]));
|
|
520
|
+
const buyOps = signedBuy.map((s) => s.userOp);
|
|
427
521
|
const buyResult = await this.runHandleOps('buyBundle', buyOps, bundlerSigner, beneficiary);
|
|
428
522
|
if (!buyResult) {
|
|
429
523
|
throw new Error('买入交易失败');
|
|
@@ -433,27 +527,33 @@ export class BundleExecutor {
|
|
|
433
527
|
const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
|
|
434
528
|
// 2. 授权 + 卖出(可以合并到同一笔 handleOps)
|
|
435
529
|
const sellOps = [];
|
|
436
|
-
|
|
530
|
+
const sellPerWallet = await mapWithConcurrency(wallets, 4, async (w, i) => {
|
|
437
531
|
const sender = senders[i];
|
|
438
532
|
const balance = tokenBalances.get(sender) ?? 0n;
|
|
439
533
|
if (balance === 0n) {
|
|
440
534
|
console.log(`[owner${i + 1}] 没买到代币,跳过卖出`);
|
|
441
|
-
|
|
535
|
+
return [];
|
|
442
536
|
}
|
|
443
537
|
const allowance = allowances.get(sender) ?? 0n;
|
|
444
|
-
let baseNonce = (await this.aaManager.getAccountInfo(wallets[i].address)).nonce;
|
|
445
|
-
// 如果需要授权,先添加 approve op
|
|
446
538
|
const needApprove = allowance < balance;
|
|
539
|
+
// buy 已在上一笔 handleOps 执行,因此 nonce = 原 nonce + 1
|
|
540
|
+
let nonce = accountInfos[i].nonce + 1n;
|
|
541
|
+
const initCode = '0x';
|
|
542
|
+
const out = [];
|
|
447
543
|
if (needApprove) {
|
|
448
|
-
const approveOp = await this.buildApproveUserOp(
|
|
449
|
-
|
|
450
|
-
|
|
544
|
+
const approveOp = await this.buildApproveUserOp(w, tokenAddress, this.portalAddress, sender, nonce, initCode, `owner${i + 1}`);
|
|
545
|
+
out.push(approveOp.userOp);
|
|
546
|
+
nonce = nonce + 1n;
|
|
451
547
|
}
|
|
452
|
-
// 添加 sell op
|
|
453
548
|
const sellAmount = (balance * BigInt(sellPercent)) / 100n;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
549
|
+
if (sellAmount === 0n)
|
|
550
|
+
return out;
|
|
551
|
+
const sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, nonce, initCode, needApprove, `owner${i + 1}`);
|
|
552
|
+
out.push(sellOp.userOp);
|
|
553
|
+
return out;
|
|
554
|
+
});
|
|
555
|
+
for (const ops of sellPerWallet)
|
|
556
|
+
sellOps.push(...ops);
|
|
457
557
|
const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary);
|
|
458
558
|
if (!sellResult) {
|
|
459
559
|
throw new Error('卖出交易失败');
|
|
@@ -462,11 +562,41 @@ export class BundleExecutor {
|
|
|
462
562
|
let withdrawResult;
|
|
463
563
|
if (withdrawToOwner) {
|
|
464
564
|
const withdrawOps = [];
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
565
|
+
// 批量获取 OKB 余额(sell 后状态)
|
|
566
|
+
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
567
|
+
// sell handleOps 里每个 wallet:一定有 sell op(balance>0)且可能还有 approve op
|
|
568
|
+
const nextNonces = wallets.map((_, i) => {
|
|
569
|
+
const sender = senders[i];
|
|
570
|
+
const bal = tokenBalances.get(sender) ?? 0n;
|
|
571
|
+
if (bal === 0n)
|
|
572
|
+
return accountInfos[i].nonce + 1n; // buy 后但未 sell
|
|
573
|
+
const allowance = allowances.get(sender) ?? 0n;
|
|
574
|
+
const needApprove = allowance < bal;
|
|
575
|
+
return accountInfos[i].nonce + 1n + (needApprove ? 2n : 1n);
|
|
576
|
+
});
|
|
577
|
+
const withdrawItems = wallets.map((w, i) => ({
|
|
578
|
+
i,
|
|
579
|
+
ownerWallet: w,
|
|
580
|
+
sender: senders[i],
|
|
581
|
+
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
582
|
+
nonce: nextNonces[i],
|
|
583
|
+
initCode: '0x',
|
|
584
|
+
}));
|
|
585
|
+
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
586
|
+
const signed = await this.buildWithdrawUserOpWithState({
|
|
587
|
+
ownerWallet: it.ownerWallet,
|
|
588
|
+
sender: it.sender,
|
|
589
|
+
nonce: it.nonce,
|
|
590
|
+
initCode: it.initCode,
|
|
591
|
+
senderBalance: it.senderBalance,
|
|
592
|
+
reserveWei,
|
|
593
|
+
ownerName: `owner${it.i + 1}`,
|
|
594
|
+
});
|
|
595
|
+
return signed?.userOp ?? null;
|
|
596
|
+
});
|
|
597
|
+
for (const op of signedWithdraws) {
|
|
598
|
+
if (op)
|
|
599
|
+
withdrawOps.push(op);
|
|
470
600
|
}
|
|
471
601
|
if (withdrawOps.length > 0) {
|
|
472
602
|
withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
|