four-flap-meme-sdk 1.5.46 → 1.5.49
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/xlayer/aa-account.d.ts +1 -0
- package/dist/xlayer/aa-account.js +34 -9
- package/dist/xlayer/bundle.d.ts +5 -0
- package/dist/xlayer/bundle.js +45 -0
- package/dist/xlayer/bundler.d.ts +1 -0
- package/dist/xlayer/bundler.js +3 -0
- package/dist/xlayer/buy-first-volume.js +1 -5
- package/dist/xlayer/constants.d.ts +2 -2
- package/dist/xlayer/constants.js +2 -2
- package/dist/xlayer/dex-bundle-swap.js +7 -33
- package/dist/xlayer/dex-bundle.js +19 -4
- package/dist/xlayer/dex-buy-first.js +19 -4
- package/dist/xlayer/dex.d.ts +32 -0
- package/dist/xlayer/dex.js +187 -1
- package/dist/xlayer/portal-bundle-swap.js +12 -41
- package/dist/xlayer/portal-buy-first.js +19 -4
- package/dist/xlayer/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -55,6 +55,20 @@ export class AAAccountManager {
|
|
|
55
55
|
timeoutMs: config.timeoutMs,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
async syncEntryPointIfNeeded() {
|
|
59
|
+
try {
|
|
60
|
+
const eps = await this.bundler.getSupportedEntryPoints();
|
|
61
|
+
if (Array.isArray(eps) && eps.length > 0) {
|
|
62
|
+
const target = eps.includes(this.entryPointAddress) ? this.entryPointAddress : eps[0];
|
|
63
|
+
if (target !== this.entryPointAddress) {
|
|
64
|
+
this.entryPointAddress = target;
|
|
65
|
+
this.entryPoint = new Contract(this.entryPointAddress, ENTRYPOINT_ABI, this.provider);
|
|
66
|
+
this.bundler.setEntryPoint(this.entryPointAddress);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
}
|
|
58
72
|
/**
|
|
59
73
|
* 获取 Provider
|
|
60
74
|
*/
|
|
@@ -289,6 +303,7 @@ export class AAAccountManager {
|
|
|
289
303
|
* 构建未签名的 UserOperation(使用 Bundler 估算 Gas)
|
|
290
304
|
*/
|
|
291
305
|
async buildUserOpWithBundlerEstimate(params) {
|
|
306
|
+
await this.syncEntryPointIfNeeded();
|
|
292
307
|
const feeData = await this.getFeeData();
|
|
293
308
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
294
309
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
@@ -308,12 +323,15 @@ export class AAAccountManager {
|
|
|
308
323
|
};
|
|
309
324
|
// 调用 Bundler 估算 Gas
|
|
310
325
|
const estimate = await this.bundler.estimateUserOperationGas(userOp);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
326
|
+
{
|
|
327
|
+
const scaledCall = ethers.toBeHex(BigInt(estimate.callGasLimit) * BigInt(Math.ceil(this.gasLimitMultiplier)));
|
|
328
|
+
userOp = {
|
|
329
|
+
...userOp,
|
|
330
|
+
callGasLimit: scaledCall,
|
|
331
|
+
verificationGasLimit: estimate.verificationGasLimit,
|
|
332
|
+
preVerificationGas: estimate.preVerificationGas,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
317
335
|
// 使用 Bundler 建议的费率
|
|
318
336
|
if (estimate.maxFeePerGas && estimate.maxPriorityFeePerGas) {
|
|
319
337
|
userOp = {
|
|
@@ -335,6 +353,7 @@ export class AAAccountManager {
|
|
|
335
353
|
* 目标:把 N 次 eth_estimateUserOperationGas 合并为 1 次(JSON-RPC batch),显著降低延迟。
|
|
336
354
|
*/
|
|
337
355
|
async buildUserOpsWithBundlerEstimateBatch(params) {
|
|
356
|
+
await this.syncEntryPointIfNeeded();
|
|
338
357
|
if (params.ops.length === 0) {
|
|
339
358
|
return { userOps: [], prefundWeis: [] };
|
|
340
359
|
}
|
|
@@ -362,7 +381,7 @@ export class AAAccountManager {
|
|
|
362
381
|
const estimate = estimates[i];
|
|
363
382
|
let userOp = {
|
|
364
383
|
...skeletons[i],
|
|
365
|
-
callGasLimit: estimate.callGasLimit,
|
|
384
|
+
callGasLimit: ethers.toBeHex(BigInt(estimate.callGasLimit) * BigInt(Math.ceil(this.gasLimitMultiplier))),
|
|
366
385
|
verificationGasLimit: estimate.verificationGasLimit,
|
|
367
386
|
preVerificationGas: estimate.preVerificationGas,
|
|
368
387
|
};
|
|
@@ -389,6 +408,7 @@ export class AAAccountManager {
|
|
|
389
408
|
* 适用于无法通过 Bundler 估算的场景(如尚未授权的 sell 操作)
|
|
390
409
|
*/
|
|
391
410
|
async buildUserOpWithLocalEstimate(params) {
|
|
411
|
+
await this.syncEntryPointIfNeeded();
|
|
392
412
|
const feeData = await this.getFeeData();
|
|
393
413
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
394
414
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
@@ -437,6 +457,7 @@ export class AAAccountManager {
|
|
|
437
457
|
* 适用于大规模(1000 地址)场景:尽量减少 RPC 调用量。
|
|
438
458
|
*/
|
|
439
459
|
async buildUserOpWithFixedGas(params) {
|
|
460
|
+
await this.syncEntryPointIfNeeded();
|
|
440
461
|
const feeData = await this.getFeeData();
|
|
441
462
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
442
463
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
@@ -480,6 +501,7 @@ export class AAAccountManager {
|
|
|
480
501
|
* 构建并签名 UserOperation(完整流程)
|
|
481
502
|
*/
|
|
482
503
|
async buildAndSignUserOp(params) {
|
|
504
|
+
await this.syncEntryPointIfNeeded();
|
|
483
505
|
const ownerAddress = params.ownerWallet.address;
|
|
484
506
|
const accountInfo = await this.getAccountInfo(ownerAddress);
|
|
485
507
|
const initCode = accountInfo.deployed ? '0x' : this.generateInitCode(ownerAddress);
|
|
@@ -544,6 +566,7 @@ export class AAAccountManager {
|
|
|
544
566
|
* 构建并签名 UserOperation(基于已有状态,支持 signOnly)
|
|
545
567
|
*/
|
|
546
568
|
async buildUserOpWithState(params) {
|
|
569
|
+
await this.syncEntryPointIfNeeded();
|
|
547
570
|
const { ownerWallet, sender, nonce, initCode, callData, signOnly = false } = params;
|
|
548
571
|
let userOp;
|
|
549
572
|
let prefundWei;
|
|
@@ -588,9 +611,11 @@ export class AAAccountManager {
|
|
|
588
611
|
* @returns 是否进行了转账
|
|
589
612
|
*/
|
|
590
613
|
async ensureSenderBalance(ownerWallet, sender, requiredWei, tag) {
|
|
591
|
-
// 如果使用 Paymaster,不需要给 sender 转账
|
|
592
614
|
if (this.paymaster) {
|
|
593
|
-
|
|
615
|
+
const code = await this.provider.getCode(this.paymaster);
|
|
616
|
+
if (code && code !== '0x') {
|
|
617
|
+
return { funded: false };
|
|
618
|
+
}
|
|
594
619
|
}
|
|
595
620
|
const balance = await this.provider.getBalance(sender);
|
|
596
621
|
if (balance >= requiredWei) {
|
package/dist/xlayer/bundle.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export declare class BundleExecutor {
|
|
|
41
41
|
* - 用于“像 BSC 一样:前端/后端提交 raw tx bundle”的场景
|
|
42
42
|
*/
|
|
43
43
|
private signHandleOpsTx;
|
|
44
|
+
/**
|
|
45
|
+
* 签名利润交易(Tail Transaction)
|
|
46
|
+
* - 用于 AA 交易的尾部,将利润转账给指定地址
|
|
47
|
+
*/
|
|
48
|
+
private signProfitTransaction;
|
|
44
49
|
private getErc20Decimals;
|
|
45
50
|
/**
|
|
46
51
|
* 构建买入 UserOp
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -217,6 +217,29 @@ export class BundleExecutor {
|
|
|
217
217
|
type: 0,
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 签名利润交易(Tail Transaction)
|
|
222
|
+
* - 用于 AA 交易的尾部,将利润转账给指定地址
|
|
223
|
+
*/
|
|
224
|
+
async signProfitTransaction(params) {
|
|
225
|
+
const provider = this.aaManager.getProvider();
|
|
226
|
+
const feeData = await provider.getFeeData();
|
|
227
|
+
const nonce = params.nonce;
|
|
228
|
+
if (nonce === undefined)
|
|
229
|
+
throw new Error('Signed Tail Transaction requires explicit nonce');
|
|
230
|
+
const gasPrice = feeData.gasPrice ?? 100000000n;
|
|
231
|
+
// 构造简单的转账交易
|
|
232
|
+
return await params.payerWallet.signTransaction({
|
|
233
|
+
to: params.recipient,
|
|
234
|
+
value: params.profitWei,
|
|
235
|
+
data: '0x',
|
|
236
|
+
nonce,
|
|
237
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
238
|
+
gasPrice,
|
|
239
|
+
chainId: this.config.chainId ?? 196,
|
|
240
|
+
type: 0,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
220
243
|
async getErc20Decimals(tokenAddress) {
|
|
221
244
|
const provider = this.aaManager.getProvider();
|
|
222
245
|
const token = new Contract(tokenAddress, ['function decimals() view returns (uint8)'], provider);
|
|
@@ -1093,6 +1116,26 @@ export class BundleExecutor {
|
|
|
1093
1116
|
}
|
|
1094
1117
|
if (withdrawOps.length > 0) {
|
|
1095
1118
|
withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
|
|
1119
|
+
if (profitSettingsBuySell.extractProfit) {
|
|
1120
|
+
const totalProfitForTail = nativeBuyProfitAmount + totalSellWithdrawProfitWei;
|
|
1121
|
+
if (totalProfitForTail > 0n) {
|
|
1122
|
+
const provider = this.aaManager.getProvider();
|
|
1123
|
+
const feeData = await provider.getFeeData();
|
|
1124
|
+
const nonce = await provider.getTransactionCount(bundlerSigner.address, 'pending');
|
|
1125
|
+
const tailTx = await bundlerSigner.signTransaction({
|
|
1126
|
+
to: profitSettingsBuySell.profitRecipient,
|
|
1127
|
+
value: totalProfitForTail,
|
|
1128
|
+
data: '0x',
|
|
1129
|
+
nonce,
|
|
1130
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
1131
|
+
gasPrice: feeData.gasPrice ?? 100000000n,
|
|
1132
|
+
chainId: this.config.chainId ?? 196,
|
|
1133
|
+
type: 0,
|
|
1134
|
+
});
|
|
1135
|
+
const tx = await provider.broadcastTransaction(tailTx);
|
|
1136
|
+
await tx.wait();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1096
1139
|
}
|
|
1097
1140
|
}
|
|
1098
1141
|
// 最终余额
|
|
@@ -1248,6 +1291,8 @@ export class BundleExecutor {
|
|
|
1248
1291
|
const signedBuyOp = await this.aaManager.signUserOp(buyOp.userOp, buyer);
|
|
1249
1292
|
ops.push(signedBuyOp.userOp);
|
|
1250
1293
|
}
|
|
1294
|
+
// 利润由尾笔交易处理(不再在买入阶段追加利润转账 UserOp)
|
|
1295
|
+
// === 计算利润金额 ===
|
|
1251
1296
|
let nativeProfitAmount = 0n;
|
|
1252
1297
|
if (profitSettings.extractProfit && totalBuyProfitWei > 0n) {
|
|
1253
1298
|
nativeProfitAmount = totalBuyProfitWei;
|
package/dist/xlayer/bundler.d.ts
CHANGED
package/dist/xlayer/bundler.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - 内盘执行器:`portal-buy-first.ts`
|
|
9
9
|
* - 外盘执行器:`dex-buy-first.ts`
|
|
10
10
|
*/
|
|
11
|
-
import { parseOkb
|
|
11
|
+
import { parseOkb } from './portal-ops.js';
|
|
12
12
|
import { AAPortalBuyFirstExecutor } from './portal-buy-first.js';
|
|
13
13
|
import { AADexBuyFirstExecutor } from './dex-buy-first.js';
|
|
14
14
|
function sleep(ms) {
|
|
@@ -52,10 +52,6 @@ export class BuyFirstVolumeExecutor {
|
|
|
52
52
|
await sleep(intervalMs);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
console.log('\n=== AA BuyFirst Volume Summary ===');
|
|
56
|
-
console.log('成功轮数:', successRounds);
|
|
57
|
-
console.log('失败轮数:', failedRounds);
|
|
58
|
-
console.log('总交易量:', formatOkb(totalVolume), 'OKB');
|
|
59
55
|
return { successRounds, failedRounds, roundResults, totalVolume };
|
|
60
56
|
}
|
|
61
57
|
}
|
|
@@ -56,8 +56,8 @@ export declare const VERIFICATION_GAS_LIMIT_DEPLOY = 800000n;
|
|
|
56
56
|
export declare const VERIFICATION_GAS_LIMIT_NORMAL = 250000n;
|
|
57
57
|
/** Pre-verification Gas */
|
|
58
58
|
export declare const PRE_VERIFICATION_GAS = 60000n;
|
|
59
|
-
/** 默认 Call Gas Limit
|
|
60
|
-
export declare const DEFAULT_CALL_GAS_LIMIT_SELL =
|
|
59
|
+
/** 默认 Call Gas Limit(对齐 BSC,买入/卖出共用保守值) */
|
|
60
|
+
export declare const DEFAULT_CALL_GAS_LIMIT_SELL = 800000n;
|
|
61
61
|
/** XLayer Flap 买入手续费率 (1.5%) */
|
|
62
62
|
export declare const FLAP_BUY_FEE_RATE = 0.015;
|
|
63
63
|
/** XLayer Flap 卖出手续费率 (1.5%) */
|
package/dist/xlayer/constants.js
CHANGED
|
@@ -77,8 +77,8 @@ export const VERIFICATION_GAS_LIMIT_DEPLOY = 800000n;
|
|
|
77
77
|
export const VERIFICATION_GAS_LIMIT_NORMAL = 250000n;
|
|
78
78
|
/** Pre-verification Gas */
|
|
79
79
|
export const PRE_VERIFICATION_GAS = 60000n;
|
|
80
|
-
/** 默认 Call Gas Limit
|
|
81
|
-
export const DEFAULT_CALL_GAS_LIMIT_SELL =
|
|
80
|
+
/** 默认 Call Gas Limit(对齐 BSC,买入/卖出共用保守值) */
|
|
81
|
+
export const DEFAULT_CALL_GAS_LIMIT_SELL = 800000n;
|
|
82
82
|
// ============================================================================
|
|
83
83
|
// 费率配置
|
|
84
84
|
// ============================================================================
|
|
@@ -169,19 +169,7 @@ export class AADexSwapExecutor {
|
|
|
169
169
|
signOnly: true,
|
|
170
170
|
});
|
|
171
171
|
outOps.push(signedSell.userOp);
|
|
172
|
-
// Profit
|
|
173
|
-
if (extractProfit && profitWei > 0n) {
|
|
174
|
-
const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
|
|
175
|
-
const signedProfit = await this.aaManager.buildUserOpWithState({
|
|
176
|
-
ownerWallet: sellerOwner,
|
|
177
|
-
sender: sellerSender,
|
|
178
|
-
nonce: nonceMap.next(sellerSender),
|
|
179
|
-
initCode: consumeInitCode(sellerSender),
|
|
180
|
-
callData: profitCallData,
|
|
181
|
-
signOnly: true,
|
|
182
|
-
});
|
|
183
|
-
outOps.push(signedProfit.userOp);
|
|
184
|
-
}
|
|
172
|
+
// Profit 由尾部交易处理(不再在 handleOps 内部追加 profit UserOp)
|
|
185
173
|
// ✅ 卖出所得 OKB 转给买方 AA(Sender)
|
|
186
174
|
if (sellerSender.toLowerCase() !== buyerSender.toLowerCase() && finalBuyAmountWei > 0n) {
|
|
187
175
|
const transferCallData = encodeExecute(buyerSender, finalBuyAmountWei, '0x');
|
|
@@ -342,18 +330,7 @@ export class AADexSwapExecutor {
|
|
|
342
330
|
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
343
331
|
const profitCap = quotedSellOutWei > totalBuyWei ? (quotedSellOutWei - totalBuyWei) : 0n;
|
|
344
332
|
const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
|
|
345
|
-
|
|
346
|
-
const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
|
|
347
|
-
const signedProfit = await this.aaManager.buildUserOpWithState({
|
|
348
|
-
ownerWallet: sellerOwner,
|
|
349
|
-
sender: sellerAi.sender,
|
|
350
|
-
nonce: nonceMap.next(sellerAi.sender),
|
|
351
|
-
initCode: consumeInitCode(sellerAi.sender),
|
|
352
|
-
callData: profitCallData,
|
|
353
|
-
signOnly: true,
|
|
354
|
-
});
|
|
355
|
-
outOps.push(signedProfit.userOp);
|
|
356
|
-
}
|
|
333
|
+
// Profit 由尾部交易处理(不再在 handleOps 内部追加 profit UserOp)
|
|
357
334
|
// ✅ 卖出所得 OKB 分发给多个买方 AA(Sender)(支持多跳)
|
|
358
335
|
const buyerSenders = buyerAis.map(ai => ai.sender);
|
|
359
336
|
const hopCountRaw = Math.max(0, Math.floor(Number(disperseHopCountIn ?? 0)));
|
|
@@ -462,16 +439,13 @@ export class AADexSwapExecutor {
|
|
|
462
439
|
nonce: payerStartNonce,
|
|
463
440
|
});
|
|
464
441
|
const signedTransactions = [signedHandleOps];
|
|
465
|
-
if (
|
|
442
|
+
if (extractProfit && profitWei > 0n) {
|
|
466
443
|
const tx = ethers.Transaction.from(signedHandleOps);
|
|
467
|
-
const tailTx = await
|
|
468
|
-
|
|
469
|
-
|
|
444
|
+
const tailTx = await this.bundleExecutor['signProfitTransaction']({
|
|
445
|
+
payerWallet,
|
|
446
|
+
profitWei,
|
|
447
|
+
recipient: profitRecipient,
|
|
470
448
|
nonce: Number(tx.nonce) + 1,
|
|
471
|
-
gasLimit: 21000n,
|
|
472
|
-
gasPrice: tx.gasPrice,
|
|
473
|
-
chainId: tx.chainId,
|
|
474
|
-
type: 0,
|
|
475
449
|
});
|
|
476
450
|
signedTransactions.push(tailTx);
|
|
477
451
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Wallet, Contract, Interface, ethers } from 'ethers';
|
|
10
10
|
import { ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, } from './constants.js';
|
|
11
|
-
import { AAAccountManager, encodeExecute
|
|
11
|
+
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
12
12
|
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee } from './dex.js';
|
|
13
13
|
import { PortalQuery, encodeApproveCall, parseOkb, formatOkb } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -151,9 +151,7 @@ export class DexBundleExecutor {
|
|
|
151
151
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
152
152
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
153
153
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
154
|
-
const callData =
|
|
155
|
-
? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
|
|
156
|
-
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
154
|
+
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
157
155
|
const { userOp } = gasPolicy === 'fixed'
|
|
158
156
|
? await this.aaManager.buildUserOpWithFixedGas({
|
|
159
157
|
ownerWallet: params.ownerWallet,
|
|
@@ -347,6 +345,23 @@ export class DexBundleExecutor {
|
|
|
347
345
|
if (withdrawOps.length > 0) {
|
|
348
346
|
withdrawResult = await this.runHandleOps('dex-withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
|
|
349
347
|
}
|
|
348
|
+
if (profitSettings.extractProfit && totalProfitWei > 0n) {
|
|
349
|
+
const provider = this.aaManager.getProvider();
|
|
350
|
+
const feeData = await provider.getFeeData();
|
|
351
|
+
const nonce = await provider.getTransactionCount(bundlerSigner.address, 'pending');
|
|
352
|
+
const tailTx = await bundlerSigner.signTransaction({
|
|
353
|
+
to: profitSettings.profitRecipient,
|
|
354
|
+
value: totalProfitWei,
|
|
355
|
+
data: '0x',
|
|
356
|
+
nonce,
|
|
357
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
358
|
+
gasPrice: feeData.gasPrice ?? 100000000n,
|
|
359
|
+
chainId: this.config.chainId ?? 196,
|
|
360
|
+
type: 0,
|
|
361
|
+
});
|
|
362
|
+
const tx = await provider.broadcastTransaction(tailTx);
|
|
363
|
+
await tx.wait();
|
|
364
|
+
}
|
|
350
365
|
}
|
|
351
366
|
const finalBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
352
367
|
return {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
10
10
|
import { AANonceMap } from './types.js';
|
|
11
11
|
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, } from './constants.js';
|
|
12
|
-
import { AAAccountManager, encodeExecute
|
|
12
|
+
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
13
13
|
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee } from './dex.js';
|
|
14
14
|
import { PortalQuery, encodeApproveCall, parseOkb } from './portal-ops.js';
|
|
15
15
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -156,9 +156,7 @@ export class AADexBuyFirstExecutor {
|
|
|
156
156
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
157
157
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
158
158
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
159
|
-
const callData =
|
|
160
|
-
? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
|
|
161
|
-
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
159
|
+
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
162
160
|
const { userOp } = gasPolicy === 'fixed'
|
|
163
161
|
? await this.aaManager.buildUserOpWithFixedGas({
|
|
164
162
|
ownerWallet: params.ownerWallet,
|
|
@@ -370,6 +368,23 @@ export class AADexBuyFirstExecutor {
|
|
|
370
368
|
withdrawResult = await this.runHandleOps('aa-buy-first/dex-withdraw', withdrawOps, payerWallet, beneficiary);
|
|
371
369
|
}
|
|
372
370
|
finalSellerBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
|
|
371
|
+
if (profitSettings.extractProfit && totalProfitWei > 0n) {
|
|
372
|
+
const provider = this.aaManager.getProvider();
|
|
373
|
+
const feeData = await provider.getFeeData();
|
|
374
|
+
const nonce = await provider.getTransactionCount(payerWallet.address, 'pending');
|
|
375
|
+
const tailTx = await payerWallet.signTransaction({
|
|
376
|
+
to: profitSettings.profitRecipient,
|
|
377
|
+
value: totalProfitWei,
|
|
378
|
+
data: '0x',
|
|
379
|
+
nonce,
|
|
380
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
381
|
+
gasPrice: feeData.gasPrice ?? 100000000n,
|
|
382
|
+
chainId: this.config.chainId ?? 196,
|
|
383
|
+
type: 0,
|
|
384
|
+
});
|
|
385
|
+
const tx = await provider.broadcastTransaction(tailTx);
|
|
386
|
+
await tx.wait();
|
|
387
|
+
}
|
|
373
388
|
}
|
|
374
389
|
return {
|
|
375
390
|
buySellResult,
|
package/dist/xlayer/dex.d.ts
CHANGED
|
@@ -86,6 +86,7 @@ export declare class DexExecutor {
|
|
|
86
86
|
private wokb;
|
|
87
87
|
private deadlineMinutes;
|
|
88
88
|
constructor(config?: DexConfig);
|
|
89
|
+
private assertRouter;
|
|
89
90
|
/**
|
|
90
91
|
* 获取 deadline 时间戳
|
|
91
92
|
*/
|
|
@@ -109,6 +110,37 @@ export declare class DexExecutor {
|
|
|
109
110
|
* 通过 AA 账户执行 Token -> OKB 交易
|
|
110
111
|
*/
|
|
111
112
|
swapTokenForOkbViaAA(privateKey: string, tokenAddress: string, tokenAmount: bigint, minOkbAmount?: bigint): Promise<DexSwapResult>;
|
|
113
|
+
/**
|
|
114
|
+
* 通过 AA 账户执行 QuoteToken -> Token 交易
|
|
115
|
+
*/
|
|
116
|
+
swapQuoteTokenForTokenViaAA(privateKey: string, quoteTokenAddress: string, tokenAddress: string, quoteAmount: bigint, minTokenAmount?: bigint): Promise<DexSwapResult>;
|
|
117
|
+
/**
|
|
118
|
+
* 签名 OKB -> Token 交易 (AA + Profit Tail)
|
|
119
|
+
*/
|
|
120
|
+
signDexBuy(params: {
|
|
121
|
+
privateKey: string;
|
|
122
|
+
tokenAddress: string;
|
|
123
|
+
okbAmount: bigint;
|
|
124
|
+
minTokenAmount?: bigint;
|
|
125
|
+
profitConfig?: {
|
|
126
|
+
extractProfit: boolean;
|
|
127
|
+
profitBps: number;
|
|
128
|
+
profitRecipient: string;
|
|
129
|
+
};
|
|
130
|
+
}): Promise<{
|
|
131
|
+
signedTransactions: string[];
|
|
132
|
+
}>;
|
|
133
|
+
/**
|
|
134
|
+
* 签名 Token -> OKB 交易 (AA + Profit Tail)
|
|
135
|
+
*/
|
|
136
|
+
signDexSell(params: {
|
|
137
|
+
privateKey: string;
|
|
138
|
+
tokenAddress: string;
|
|
139
|
+
tokenAmount: bigint;
|
|
140
|
+
minOkbAmount?: bigint;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
signedTransactions: string[];
|
|
143
|
+
}>;
|
|
112
144
|
/**
|
|
113
145
|
* 获取 DEX 查询器
|
|
114
146
|
*/
|
package/dist/xlayer/dex.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { ethers, Contract, Interface, JsonRpcProvider } from 'ethers';
|
|
10
10
|
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V2_ROUTER_ABI, WOKB, DEFAULT_RPC_URL, XLAYER_CHAIN_ID, ERC20_ABI, } from './constants.js';
|
|
11
11
|
import { AAAccountManager, encodeExecute, createWallet } from './aa-account.js';
|
|
12
|
-
import { encodeApproveCall, parseOkb } from './portal-ops.js';
|
|
12
|
+
import { encodeApproveCall, parseOkb, formatOkb } from './portal-ops.js';
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// DEX 交易编码器
|
|
15
15
|
// ============================================================================
|
|
@@ -156,6 +156,13 @@ export class DexExecutor {
|
|
|
156
156
|
this.wokb = config.wokbAddress ?? WOKB;
|
|
157
157
|
this.deadlineMinutes = config.deadlineMinutes ?? 20;
|
|
158
158
|
}
|
|
159
|
+
async assertRouter() {
|
|
160
|
+
const provider = this.aaManager.getProvider();
|
|
161
|
+
const code = await provider.getCode(this.routerAddress);
|
|
162
|
+
if (!code || code === '0x') {
|
|
163
|
+
throw new Error(`Router address has no code: ${this.routerAddress}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
159
166
|
/**
|
|
160
167
|
* 获取 deadline 时间戳
|
|
161
168
|
*/
|
|
@@ -167,6 +174,7 @@ export class DexExecutor {
|
|
|
167
174
|
* 使用 SupportingFeeOnTransferTokens 版本,支持有手续费/分红的代币
|
|
168
175
|
*/
|
|
169
176
|
async swapOkbForToken(privateKey, tokenAddress, okbAmount, minTokenAmount = 0n) {
|
|
177
|
+
await this.assertRouter();
|
|
170
178
|
const wallet = createWallet(privateKey, this.config);
|
|
171
179
|
const router = new Contract(this.routerAddress, POTATOSWAP_V2_ROUTER_ABI, wallet);
|
|
172
180
|
const path = [this.wokb, tokenAddress];
|
|
@@ -185,6 +193,7 @@ export class DexExecutor {
|
|
|
185
193
|
* 使用 SupportingFeeOnTransferTokens 版本,支持有手续费/分红的代币
|
|
186
194
|
*/
|
|
187
195
|
async swapTokenForOkb(privateKey, tokenAddress, tokenAmount, minOkbAmount = 0n) {
|
|
196
|
+
await this.assertRouter();
|
|
188
197
|
const wallet = createWallet(privateKey, this.config);
|
|
189
198
|
const token = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
190
199
|
const router = new Contract(this.routerAddress, POTATOSWAP_V2_ROUTER_ABI, wallet);
|
|
@@ -211,6 +220,7 @@ export class DexExecutor {
|
|
|
211
220
|
* 使用 SupportingFeeOnTransferTokens 版本,支持有手续费/分红的代币
|
|
212
221
|
*/
|
|
213
222
|
async swapOkbForTokenViaAA(privateKey, tokenAddress, okbAmount, minTokenAmount = 0n) {
|
|
223
|
+
await this.assertRouter();
|
|
214
224
|
const wallet = createWallet(privateKey, this.config);
|
|
215
225
|
const accountInfo = await this.aaManager.getAccountInfo(wallet.address);
|
|
216
226
|
const path = [this.wokb, tokenAddress];
|
|
@@ -247,6 +257,7 @@ export class DexExecutor {
|
|
|
247
257
|
* 通过 AA 账户执行 Token -> OKB 交易
|
|
248
258
|
*/
|
|
249
259
|
async swapTokenForOkbViaAA(privateKey, tokenAddress, tokenAmount, minOkbAmount = 0n) {
|
|
260
|
+
await this.assertRouter();
|
|
250
261
|
const wallet = createWallet(privateKey, this.config);
|
|
251
262
|
const accountInfo = await this.aaManager.getAccountInfo(wallet.address);
|
|
252
263
|
const provider = this.aaManager.getProvider();
|
|
@@ -297,6 +308,181 @@ export class DexExecutor {
|
|
|
297
308
|
gasUsed: receipt.gasUsed,
|
|
298
309
|
};
|
|
299
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* 通过 AA 账户执行 QuoteToken -> Token 交易
|
|
313
|
+
*/
|
|
314
|
+
async swapQuoteTokenForTokenViaAA(privateKey, quoteTokenAddress, tokenAddress, quoteAmount, minTokenAmount = 0n) {
|
|
315
|
+
await this.assertRouter();
|
|
316
|
+
const wallet = createWallet(privateKey, this.config);
|
|
317
|
+
const accountInfo = await this.aaManager.getAccountInfo(wallet.address);
|
|
318
|
+
const provider = this.aaManager.getProvider();
|
|
319
|
+
const entryPoint = this.aaManager.getEntryPoint();
|
|
320
|
+
const feeData = await provider.getFeeData();
|
|
321
|
+
const token = new Contract(quoteTokenAddress, ERC20_ABI, provider);
|
|
322
|
+
const allowance = await token.allowance(accountInfo.sender, this.routerAddress);
|
|
323
|
+
const ops = [];
|
|
324
|
+
let nonce = accountInfo.nonce;
|
|
325
|
+
if (allowance < quoteAmount) {
|
|
326
|
+
const approveData = encodeApproveCall(this.routerAddress);
|
|
327
|
+
const approveCallData = encodeExecute(quoteTokenAddress, 0n, approveData);
|
|
328
|
+
const approveOp = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
329
|
+
ownerWallet: wallet,
|
|
330
|
+
sender: accountInfo.sender,
|
|
331
|
+
callData: approveCallData,
|
|
332
|
+
nonce,
|
|
333
|
+
});
|
|
334
|
+
const signedApprove = await this.aaManager.signUserOp(approveOp.userOp, wallet);
|
|
335
|
+
ops.push(signedApprove.userOp);
|
|
336
|
+
nonce = nonce + 1n;
|
|
337
|
+
}
|
|
338
|
+
const path = [quoteTokenAddress, tokenAddress];
|
|
339
|
+
const deadline = this.getDeadline();
|
|
340
|
+
const swapData = encodeSwapExactTokensForTokensSupportingFee(quoteAmount, minTokenAmount, path, accountInfo.sender, deadline);
|
|
341
|
+
const swapCallData = encodeExecute(this.routerAddress, 0n, swapData);
|
|
342
|
+
await this.aaManager.ensureSenderBalance(wallet, accountInfo.sender, parseOkb('0.001'), 'dex-swap-gas');
|
|
343
|
+
const swapOp = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
344
|
+
ownerWallet: wallet,
|
|
345
|
+
sender: accountInfo.sender,
|
|
346
|
+
callData: swapCallData,
|
|
347
|
+
nonce,
|
|
348
|
+
});
|
|
349
|
+
const signedSwap = await this.aaManager.signUserOp(swapOp.userOp, wallet);
|
|
350
|
+
ops.push(signedSwap.userOp);
|
|
351
|
+
const entryPointWithSigner = new Contract(await entryPoint.getAddress(), ['function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external'], wallet);
|
|
352
|
+
const tx = await entryPointWithSigner.handleOps(ops, wallet.address, { gasPrice: feeData.gasPrice ?? 100000000n });
|
|
353
|
+
const receipt = await tx.wait();
|
|
354
|
+
return {
|
|
355
|
+
txHash: tx.hash,
|
|
356
|
+
inputAmount: quoteAmount,
|
|
357
|
+
outputAmount: 0n,
|
|
358
|
+
gasUsed: receipt.gasUsed,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 签名 OKB -> Token 交易 (AA + Profit Tail)
|
|
363
|
+
*/
|
|
364
|
+
async signDexBuy(params) {
|
|
365
|
+
await this.assertRouter();
|
|
366
|
+
const wallet = createWallet(params.privateKey, this.config);
|
|
367
|
+
const accountInfo = await this.aaManager.getAccountInfo(wallet.address);
|
|
368
|
+
const path = [this.wokb, params.tokenAddress];
|
|
369
|
+
const deadline = this.getDeadline();
|
|
370
|
+
// 1. 计算利润 (扣除模式:买入金额 = 总金额 - 利润)
|
|
371
|
+
let buyAmount = params.okbAmount;
|
|
372
|
+
let profitAmount = 0n;
|
|
373
|
+
if (params.profitConfig?.extractProfit) {
|
|
374
|
+
profitAmount = (params.okbAmount * BigInt(params.profitConfig.profitBps)) / 10000n;
|
|
375
|
+
buyAmount = params.okbAmount - profitAmount;
|
|
376
|
+
}
|
|
377
|
+
// 2. 构建 UserOp
|
|
378
|
+
const swapData = encodeSwapExactETHForTokensSupportingFee(params.minTokenAmount ?? 0n, path, accountInfo.sender, deadline);
|
|
379
|
+
const callData = encodeExecute(this.routerAddress, buyAmount, swapData);
|
|
380
|
+
// 预充值检查 (模拟时需要)
|
|
381
|
+
await this.aaManager.ensureSenderBalance(wallet, accountInfo.sender, buyAmount + parseOkb('0.001'), 'dex-buy-sign-prefund');
|
|
382
|
+
const signedOp = await this.aaManager.buildAndSignUserOp({
|
|
383
|
+
ownerWallet: wallet,
|
|
384
|
+
callData,
|
|
385
|
+
value: buyAmount
|
|
386
|
+
});
|
|
387
|
+
// 3. 签名 handleOps
|
|
388
|
+
const provider = this.aaManager.getProvider();
|
|
389
|
+
const feeData = await provider.getFeeData();
|
|
390
|
+
const gasPrice = feeData.gasPrice ?? 100000000n;
|
|
391
|
+
// 估算 handleOps gas
|
|
392
|
+
const entryPointAddress = this.aaManager.getEntryPointAddress();
|
|
393
|
+
const epIface = new Interface(['function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external']);
|
|
394
|
+
const handleOpsData = epIface.encodeFunctionData('handleOps', [[signedOp.userOp], wallet.address]);
|
|
395
|
+
// 获取 nonce
|
|
396
|
+
const payerNonce = await provider.getTransactionCount(wallet.address, 'pending');
|
|
397
|
+
const handleOpsTx = await wallet.signTransaction({
|
|
398
|
+
to: entryPointAddress,
|
|
399
|
+
data: handleOpsData,
|
|
400
|
+
value: 0n,
|
|
401
|
+
nonce: payerNonce,
|
|
402
|
+
gasLimit: 3500000n, // 保守估计
|
|
403
|
+
gasPrice,
|
|
404
|
+
chainId: this.config.chainId ?? 196,
|
|
405
|
+
type: 0
|
|
406
|
+
});
|
|
407
|
+
const signedTransactions = [handleOpsTx];
|
|
408
|
+
// 4. 签名利润交易 (Tail Tx)
|
|
409
|
+
if (profitAmount > 0n && params.profitConfig?.profitRecipient) {
|
|
410
|
+
console.log(`[DEX-Buy] 利润: ${formatOkb(profitAmount)} OKB -> ${params.profitConfig.profitRecipient}`);
|
|
411
|
+
const tailTx = await wallet.signTransaction({
|
|
412
|
+
to: params.profitConfig.profitRecipient,
|
|
413
|
+
value: profitAmount,
|
|
414
|
+
data: '0x',
|
|
415
|
+
nonce: payerNonce + 1,
|
|
416
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
417
|
+
gasPrice,
|
|
418
|
+
chainId: this.config.chainId ?? 196,
|
|
419
|
+
type: 0
|
|
420
|
+
});
|
|
421
|
+
signedTransactions.push(tailTx);
|
|
422
|
+
}
|
|
423
|
+
return { signedTransactions };
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* 签名 Token -> OKB 交易 (AA + Profit Tail)
|
|
427
|
+
*/
|
|
428
|
+
async signDexSell(params) {
|
|
429
|
+
await this.assertRouter();
|
|
430
|
+
const wallet = createWallet(params.privateKey, this.config);
|
|
431
|
+
const accountInfo = await this.aaManager.getAccountInfo(wallet.address);
|
|
432
|
+
const provider = this.aaManager.getProvider();
|
|
433
|
+
const feeData = await provider.getFeeData();
|
|
434
|
+
const gasPrice = feeData.gasPrice ?? 100000000n;
|
|
435
|
+
const path = [params.tokenAddress, this.wokb];
|
|
436
|
+
const deadline = this.getDeadline();
|
|
437
|
+
const ops = [];
|
|
438
|
+
let nonce = accountInfo.nonce;
|
|
439
|
+
// 1. 检查授权
|
|
440
|
+
const token = new Contract(params.tokenAddress, ERC20_ABI, provider);
|
|
441
|
+
const allowance = await token.allowance(accountInfo.sender, this.routerAddress);
|
|
442
|
+
if (allowance < params.tokenAmount) {
|
|
443
|
+
const approveData = encodeApproveCall(this.routerAddress);
|
|
444
|
+
const approveCallData = encodeExecute(params.tokenAddress, 0n, approveData);
|
|
445
|
+
const approveOp = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
446
|
+
ownerWallet: wallet,
|
|
447
|
+
sender: accountInfo.sender,
|
|
448
|
+
callData: approveCallData,
|
|
449
|
+
nonce,
|
|
450
|
+
});
|
|
451
|
+
const signedApprove = await this.aaManager.signUserOp(approveOp.userOp, wallet);
|
|
452
|
+
ops.push(signedApprove.userOp);
|
|
453
|
+
nonce = nonce + 1n;
|
|
454
|
+
}
|
|
455
|
+
// 2. 构建 Swap UserOp
|
|
456
|
+
const swapData = encodeSwapExactTokensForETHSupportingFee(params.tokenAmount, params.minOkbAmount ?? 0n, path, accountInfo.sender, deadline);
|
|
457
|
+
const swapCallData = encodeExecute(this.routerAddress, 0n, swapData);
|
|
458
|
+
// Gas 检查
|
|
459
|
+
await this.aaManager.ensureSenderBalance(wallet, accountInfo.sender, parseOkb('0.001'), // 少量 gas
|
|
460
|
+
'dex-sell-sign-gas');
|
|
461
|
+
const swapOp = await this.aaManager.buildUserOpWithLocalEstimate({
|
|
462
|
+
ownerWallet: wallet,
|
|
463
|
+
sender: accountInfo.sender,
|
|
464
|
+
callData: swapCallData,
|
|
465
|
+
nonce,
|
|
466
|
+
});
|
|
467
|
+
const signedSwap = await this.aaManager.signUserOp(swapOp.userOp, wallet);
|
|
468
|
+
ops.push(signedSwap.userOp);
|
|
469
|
+
// 3. 签名 handleOps
|
|
470
|
+
const entryPointAddress = this.aaManager.getEntryPointAddress();
|
|
471
|
+
const epIface = new Interface(['function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external']);
|
|
472
|
+
const handleOpsData = epIface.encodeFunctionData('handleOps', [ops, wallet.address]);
|
|
473
|
+
const payerNonce = await provider.getTransactionCount(wallet.address, 'pending');
|
|
474
|
+
const handleOpsTx = await wallet.signTransaction({
|
|
475
|
+
to: entryPointAddress,
|
|
476
|
+
data: handleOpsData,
|
|
477
|
+
value: 0n,
|
|
478
|
+
nonce: payerNonce,
|
|
479
|
+
gasLimit: 4500000n, // 保守估计
|
|
480
|
+
gasPrice,
|
|
481
|
+
chainId: this.config.chainId ?? 196,
|
|
482
|
+
type: 0
|
|
483
|
+
});
|
|
484
|
+
return { signedTransactions: [handleOpsTx] };
|
|
485
|
+
}
|
|
300
486
|
/**
|
|
301
487
|
* 获取 DEX 查询器
|
|
302
488
|
*/
|
|
@@ -160,19 +160,7 @@ export class AAPortalSwapExecutor {
|
|
|
160
160
|
signOnly: true,
|
|
161
161
|
});
|
|
162
162
|
outOps.push(signedSell.userOp);
|
|
163
|
-
// Profit
|
|
164
|
-
if (extractProfit && profitWei > 0n) {
|
|
165
|
-
const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
|
|
166
|
-
const signedProfit = await this.aaManager.buildUserOpWithState({
|
|
167
|
-
ownerWallet: sellerOwner,
|
|
168
|
-
sender: sellerSender,
|
|
169
|
-
nonce: nonceMap.next(sellerSender),
|
|
170
|
-
initCode: consumeInitCode(sellerSender),
|
|
171
|
-
callData: profitCallData,
|
|
172
|
-
signOnly: true,
|
|
173
|
-
});
|
|
174
|
-
outOps.push(signedProfit.userOp);
|
|
175
|
-
}
|
|
163
|
+
// Profit 由尾部交易处理(不再在 handleOps 内部追加 profit UserOp)
|
|
176
164
|
// ✅ 卖出所得 OKB 转给买方 AA(Sender),否则买方 UserOp 无法携带 value 执行 buy
|
|
177
165
|
if (sellerSender.toLowerCase() !== buyerSender.toLowerCase() && finalBuyAmountWei > 0n) {
|
|
178
166
|
const transferCallData = encodeExecute(buyerSender, finalBuyAmountWei, '0x');
|
|
@@ -206,16 +194,13 @@ export class AAPortalSwapExecutor {
|
|
|
206
194
|
nonce: payerStartNonce,
|
|
207
195
|
});
|
|
208
196
|
const signedTransactions = [signedHandleOps];
|
|
209
|
-
if (
|
|
197
|
+
if (extractProfit && profitWei > 0n) {
|
|
210
198
|
const tx = ethers.Transaction.from(signedHandleOps);
|
|
211
|
-
const tailTx = await
|
|
212
|
-
|
|
213
|
-
|
|
199
|
+
const tailTx = await this.bundleExecutor['signProfitTransaction']({
|
|
200
|
+
payerWallet,
|
|
201
|
+
profitWei,
|
|
202
|
+
recipient: profitRecipient,
|
|
214
203
|
nonce: Number(tx.nonce) + 1,
|
|
215
|
-
gasLimit: 21000n,
|
|
216
|
-
gasPrice: tx.gasPrice,
|
|
217
|
-
chainId: tx.chainId,
|
|
218
|
-
type: 0,
|
|
219
204
|
});
|
|
220
205
|
signedTransactions.push(tailTx);
|
|
221
206
|
}
|
|
@@ -332,18 +317,7 @@ export class AAPortalSwapExecutor {
|
|
|
332
317
|
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
333
318
|
const profitCap = quotedSellOutWei - totalBuyWei;
|
|
334
319
|
const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
|
|
335
|
-
|
|
336
|
-
const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
|
|
337
|
-
const signedProfit = await this.aaManager.buildUserOpWithState({
|
|
338
|
-
ownerWallet: sellerOwner,
|
|
339
|
-
sender: sellerAi.sender,
|
|
340
|
-
nonce: nonceMap.next(sellerAi.sender),
|
|
341
|
-
initCode: consumeInitCode(sellerAi.sender),
|
|
342
|
-
callData: profitCallData,
|
|
343
|
-
signOnly: true,
|
|
344
|
-
});
|
|
345
|
-
outOps.push(signedProfit.userOp);
|
|
346
|
-
}
|
|
320
|
+
// Profit 由尾部交易处理(不再在 handleOps 内部追加 profit UserOp)
|
|
347
321
|
// ✅ 卖出所得 OKB 分发给多个买方 AA(Sender)(支持多跳)
|
|
348
322
|
const buyerSenders = buyerAis.map(ai => ai.sender);
|
|
349
323
|
const hopCountRaw = Math.max(0, Math.floor(Number(disperseHopCountIn ?? 0)));
|
|
@@ -457,16 +431,13 @@ export class AAPortalSwapExecutor {
|
|
|
457
431
|
nonce: payerStartNonce,
|
|
458
432
|
});
|
|
459
433
|
const signedTransactions = [signedHandleOps];
|
|
460
|
-
if (
|
|
434
|
+
if (extractProfit && profitWei > 0n) {
|
|
461
435
|
const tx = ethers.Transaction.from(signedHandleOps);
|
|
462
|
-
const tailTx = await
|
|
463
|
-
|
|
464
|
-
|
|
436
|
+
const tailTx = await this.bundleExecutor['signProfitTransaction']({
|
|
437
|
+
payerWallet,
|
|
438
|
+
profitWei,
|
|
439
|
+
recipient: profitRecipient,
|
|
465
440
|
nonce: Number(tx.nonce) + 1,
|
|
466
|
-
gasLimit: 21000n,
|
|
467
|
-
gasPrice: tx.gasPrice,
|
|
468
|
-
chainId: tx.chainId,
|
|
469
|
-
type: 0,
|
|
470
441
|
});
|
|
471
442
|
signedTransactions.push(tailTx);
|
|
472
443
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
14
14
|
import { AANonceMap } from './types.js';
|
|
15
15
|
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, FLAP_PORTAL, } from './constants.js';
|
|
16
|
-
import { AAAccountManager, encodeExecute
|
|
16
|
+
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
17
17
|
import { PortalQuery, encodeApproveCall, encodeBuyCall, encodeSellCall, parseOkb } from './portal-ops.js';
|
|
18
18
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
19
19
|
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
@@ -168,9 +168,7 @@ export class AAPortalBuyFirstExecutor {
|
|
|
168
168
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
169
169
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
170
170
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
171
|
-
const callData =
|
|
172
|
-
? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
|
|
173
|
-
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
171
|
+
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
174
172
|
const { userOp } = gasPolicy === 'fixed'
|
|
175
173
|
? await this.aaManager.buildUserOpWithFixedGas({
|
|
176
174
|
ownerWallet: params.ownerWallet,
|
|
@@ -381,6 +379,23 @@ export class AAPortalBuyFirstExecutor {
|
|
|
381
379
|
withdrawResult = await this.runHandleOps('aa-buy-first/portal-withdraw', withdrawOps, payerWallet, beneficiary);
|
|
382
380
|
}
|
|
383
381
|
finalSellerBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
|
|
382
|
+
if (profitSettings.extractProfit && totalProfitWei > 0n) {
|
|
383
|
+
const provider = this.aaManager.getProvider();
|
|
384
|
+
const feeData = await provider.getFeeData();
|
|
385
|
+
const nonce = await provider.getTransactionCount(payerWallet.address, 'pending');
|
|
386
|
+
const tailTx = await payerWallet.signTransaction({
|
|
387
|
+
to: profitSettings.profitRecipient,
|
|
388
|
+
value: totalProfitWei,
|
|
389
|
+
data: '0x',
|
|
390
|
+
nonce,
|
|
391
|
+
gasLimit: this.config.profitTailGasLimit ?? 21000n,
|
|
392
|
+
gasPrice: feeData.gasPrice ?? 100000000n,
|
|
393
|
+
chainId: this.config.chainId ?? 196,
|
|
394
|
+
type: 0,
|
|
395
|
+
});
|
|
396
|
+
const tx = await provider.broadcastTransaction(tailTx);
|
|
397
|
+
await tx.wait();
|
|
398
|
+
}
|
|
384
399
|
}
|
|
385
400
|
return {
|
|
386
401
|
buySellResult,
|
package/dist/xlayer/types.d.ts
CHANGED