four-flap-meme-sdk 1.5.32 → 1.5.34
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/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/xlayer/bundle.d.ts +2 -0
- package/dist/xlayer/bundle.js +272 -22
- package/dist/xlayer/buy-first-volume.d.ts +18 -0
- package/dist/xlayer/buy-first-volume.js +68 -0
- package/dist/xlayer/dex-bundle-swap.js +150 -19
- package/dist/xlayer/dex-bundle.d.ts +42 -0
- package/dist/xlayer/dex-bundle.js +378 -0
- package/dist/xlayer/dex-buy-first.d.ts +24 -0
- package/dist/xlayer/dex-buy-first.js +404 -0
- package/dist/xlayer/dex-volume.d.ts +30 -0
- package/dist/xlayer/dex-volume.js +80 -0
- package/dist/xlayer/index.d.ts +5 -0
- package/dist/xlayer/index.js +14 -0
- package/dist/xlayer/portal-bundle-swap.js +153 -12
- package/dist/xlayer/portal-buy-first.d.ts +30 -0
- package/dist/xlayer/portal-buy-first.js +415 -0
- package/dist/xlayer/types.d.ts +109 -2
- package/package.json +1 -1
|
@@ -3,14 +3,36 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Wallet, ethers } from 'ethers';
|
|
5
5
|
import { AANonceMap, } from './types.js';
|
|
6
|
-
import { FLAP_PORTAL, ZERO_ADDRESS, } from './constants.js';
|
|
6
|
+
import { FLAP_PORTAL, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
|
|
7
7
|
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
8
8
|
import { encodeBuyCall, encodeSellCall, encodeApproveCall, PortalQuery, parseOkb, } from './portal-ops.js';
|
|
9
9
|
import { BundleExecutor } from './bundle.js';
|
|
10
10
|
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
11
|
+
const multicallIface = new ethers.Interface([
|
|
12
|
+
'function aggregate3Value((address target,bool allowFailure,uint256 value,bytes callData)[] calls) payable returns ((bool success,bytes returnData)[] returnData)',
|
|
13
|
+
]);
|
|
14
|
+
function chunkArray(arr, size) {
|
|
15
|
+
const out = [];
|
|
16
|
+
const n = Math.max(1, Math.floor(size));
|
|
17
|
+
for (let i = 0; i < arr.length; i += n)
|
|
18
|
+
out.push(arr.slice(i, i + n));
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function encodeNativeDisperseViaMulticall3(params) {
|
|
22
|
+
const calls = params.to.map((target, idx) => ({
|
|
23
|
+
target,
|
|
24
|
+
allowFailure: false,
|
|
25
|
+
value: params.values[idx] ?? 0n,
|
|
26
|
+
callData: '0x',
|
|
27
|
+
}));
|
|
28
|
+
const totalValue = params.values.reduce((a, b) => a + (b ?? 0n), 0n);
|
|
29
|
+
const data = multicallIface.encodeFunctionData('aggregate3Value', [calls]);
|
|
30
|
+
return { totalValue, data };
|
|
31
|
+
}
|
|
11
32
|
function resolveProfitSettings(config) {
|
|
12
33
|
const extractProfit = config?.extractProfit !== false; // 默认 true(对齐 BSC)
|
|
13
|
-
|
|
34
|
+
// ✅ 对齐 BSC “捆绑换手模式”默认费率
|
|
35
|
+
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
|
|
14
36
|
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
15
37
|
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
16
38
|
return { extractProfit, profitBps, profitRecipient };
|
|
@@ -39,7 +61,7 @@ export class AAPortalSwapExecutor {
|
|
|
39
61
|
* AA 内盘单钱包换手签名
|
|
40
62
|
*/
|
|
41
63
|
async bundleSwapSign(params) {
|
|
42
|
-
const { tokenAddress, sellerPrivateKey, buyerPrivateKey, sellAmount, sellPercent = 100, buyAmountOkb, slippageBps = 100, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
64
|
+
const { tokenAddress, sellerPrivateKey, buyerPrivateKey, sellAmount, sellPercent = 100, buyAmountOkb, slippageBps = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
43
65
|
const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
|
|
44
66
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
|
|
45
67
|
const provider = this.aaManager.getProvider();
|
|
@@ -100,11 +122,18 @@ export class AAPortalSwapExecutor {
|
|
|
100
122
|
}
|
|
101
123
|
})();
|
|
102
124
|
const quotedSellOutWei = quoted;
|
|
103
|
-
|
|
125
|
+
// 3.1 利润提取(从卖出输出 OKB 里按比例刮取;但必须保证买入资金充足)
|
|
126
|
+
const requestedBuyWei = buyAmountOkb
|
|
104
127
|
? parseOkb(String(buyAmountOkb))
|
|
105
|
-
: (
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
: (quotedSellOutWei * BigInt(10000 - slippageBps)) / 10000n;
|
|
129
|
+
if (requestedBuyWei > quotedSellOutWei) {
|
|
130
|
+
throw new Error('AA 捆绑换手:buyAmountOkb 超过预估卖出输出(quotedSellOut)');
|
|
131
|
+
}
|
|
132
|
+
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
133
|
+
const profitCap = quotedSellOutWei - requestedBuyWei;
|
|
134
|
+
const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
|
|
135
|
+
const finalBuyAmountWei = requestedBuyWei;
|
|
136
|
+
const hopCount = Math.max(0, Math.floor(Number(disperseHopCountIn ?? 0)));
|
|
108
137
|
// 4. 构建 Ops
|
|
109
138
|
const outOps = [];
|
|
110
139
|
if (needApprove) {
|
|
@@ -144,6 +173,19 @@ export class AAPortalSwapExecutor {
|
|
|
144
173
|
});
|
|
145
174
|
outOps.push(signedProfit.userOp);
|
|
146
175
|
}
|
|
176
|
+
// ✅ 卖出所得 OKB 转给买方 AA(Sender),否则买方 UserOp 无法携带 value 执行 buy
|
|
177
|
+
if (sellerSender.toLowerCase() !== buyerSender.toLowerCase() && finalBuyAmountWei > 0n) {
|
|
178
|
+
const transferCallData = encodeExecute(buyerSender, finalBuyAmountWei, '0x');
|
|
179
|
+
const signedTransfer = await this.aaManager.buildUserOpWithState({
|
|
180
|
+
ownerWallet: sellerOwner,
|
|
181
|
+
sender: sellerSender,
|
|
182
|
+
nonce: nonceMap.next(sellerSender),
|
|
183
|
+
initCode: consumeInitCode(sellerSender),
|
|
184
|
+
callData: transferCallData,
|
|
185
|
+
signOnly: true,
|
|
186
|
+
});
|
|
187
|
+
outOps.push(signedTransfer.userOp);
|
|
188
|
+
}
|
|
147
189
|
// Buy op
|
|
148
190
|
const buySwapData = encodeBuyCall(tokenAddress, finalBuyAmountWei, 0n);
|
|
149
191
|
const buyCallData = encodeExecute(FLAP_PORTAL, finalBuyAmountWei, buySwapData);
|
|
@@ -195,6 +237,7 @@ export class AAPortalSwapExecutor {
|
|
|
195
237
|
profitRecipient,
|
|
196
238
|
profitWei: profitWei.toString(),
|
|
197
239
|
quotedSellOutWei: quotedSellOutWei.toString(),
|
|
240
|
+
disperseHopCount: String(hopCount),
|
|
198
241
|
},
|
|
199
242
|
};
|
|
200
243
|
}
|
|
@@ -202,7 +245,7 @@ export class AAPortalSwapExecutor {
|
|
|
202
245
|
* AA 内盘批量换手签名 (一卖多买)
|
|
203
246
|
*/
|
|
204
247
|
async bundleBatchSwapSign(params) {
|
|
205
|
-
const { tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
248
|
+
const { tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, } = params;
|
|
206
249
|
const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
|
|
207
250
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
|
|
208
251
|
const provider = this.aaManager.getProvider();
|
|
@@ -272,7 +315,10 @@ export class AAPortalSwapExecutor {
|
|
|
272
315
|
signOnly: true,
|
|
273
316
|
});
|
|
274
317
|
outOps.push(signedSell.userOp);
|
|
275
|
-
//
|
|
318
|
+
// 先计算 buyAmountsWei(用于后续分发与校验)
|
|
319
|
+
const buyAmountsWei = buyAmountsOkb.map(a => parseOkb(a));
|
|
320
|
+
const totalBuyWei = buyAmountsWei.reduce((a, b) => a + (b ?? 0n), 0n);
|
|
321
|
+
// Profit op:用 previewSell/quoteExactInput 估算卖出输出,再按比例刮取(但必须保证分发/买入资金充足)
|
|
276
322
|
let quotedSellOutWei = 0n;
|
|
277
323
|
try {
|
|
278
324
|
quotedSellOutWei = await this.portalQuery.previewSell(tokenAddress, sellAmountWei);
|
|
@@ -280,7 +326,12 @@ export class AAPortalSwapExecutor {
|
|
|
280
326
|
catch {
|
|
281
327
|
quotedSellOutWei = await this.portalQuery.quoteExactInput(tokenAddress, ZERO_ADDRESS, sellAmountWei);
|
|
282
328
|
}
|
|
283
|
-
|
|
329
|
+
if (totalBuyWei > quotedSellOutWei) {
|
|
330
|
+
throw new Error('AA 批量换手:buyAmountsOkb 总和超过预估卖出输出(quotedSellOut)');
|
|
331
|
+
}
|
|
332
|
+
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
333
|
+
const profitCap = quotedSellOutWei - totalBuyWei;
|
|
334
|
+
const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
|
|
284
335
|
if (extractProfit && profitWei > 0n) {
|
|
285
336
|
const profitCallData = encodeExecute(profitRecipient, profitWei, '0x');
|
|
286
337
|
const signedProfit = await this.aaManager.buildUserOpWithState({
|
|
@@ -293,8 +344,97 @@ export class AAPortalSwapExecutor {
|
|
|
293
344
|
});
|
|
294
345
|
outOps.push(signedProfit.userOp);
|
|
295
346
|
}
|
|
296
|
-
//
|
|
297
|
-
const
|
|
347
|
+
// ✅ 卖出所得 OKB 分发给多个买方 AA(Sender)(支持多跳)
|
|
348
|
+
const buyerSenders = buyerAis.map(ai => ai.sender);
|
|
349
|
+
const hopCountRaw = Math.max(0, Math.floor(Number(disperseHopCountIn ?? 0)));
|
|
350
|
+
const hopCount = Math.min(hopCountRaw, buyerSenders.length); // 允许等于 buyerCount(最后一跳不再分发)
|
|
351
|
+
const maxPerOp = Math.max(1, Math.floor(Number(effectiveConfig.maxTransfersPerUserOpNative ?? 30)));
|
|
352
|
+
if (buyerSenders.length > 0 && totalBuyWei > 0n) {
|
|
353
|
+
if (hopCount <= 0) {
|
|
354
|
+
// 0 跳:卖方 AA(Sender) 直接用 multicall3 分发给所有买方 AA(Sender)
|
|
355
|
+
const items = buyerSenders.map((to, i) => ({ to, value: buyAmountsWei[i] ?? 0n })).filter(x => x.value > 0n);
|
|
356
|
+
const chunks = chunkArray(items, maxPerOp);
|
|
357
|
+
for (const ch of chunks) {
|
|
358
|
+
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
359
|
+
to: ch.map(x => x.to),
|
|
360
|
+
values: ch.map(x => x.value),
|
|
361
|
+
});
|
|
362
|
+
const callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
363
|
+
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
364
|
+
ownerWallet: sellerOwner,
|
|
365
|
+
sender: sellerAi.sender,
|
|
366
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
367
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
368
|
+
callData,
|
|
369
|
+
signOnly: true,
|
|
370
|
+
});
|
|
371
|
+
outOps.push(signedDisperse.userOp);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// 多跳:使用前 hopCount 个买方作为中转链(与 BSC 多跳语义独立,仅作用于 xlayer AA)
|
|
376
|
+
const hopSenders = buyerSenders.slice(0, hopCount);
|
|
377
|
+
const hopOwners = buyerOwners.slice(0, hopCount);
|
|
378
|
+
// 1) seller -> hop0(总额)
|
|
379
|
+
const hop0 = hopSenders[0];
|
|
380
|
+
const callData0 = encodeExecute(hop0, totalBuyWei, '0x');
|
|
381
|
+
const signedToHop0 = await this.aaManager.buildUserOpWithState({
|
|
382
|
+
ownerWallet: sellerOwner,
|
|
383
|
+
sender: sellerAi.sender,
|
|
384
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
385
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
386
|
+
callData: callData0,
|
|
387
|
+
signOnly: true,
|
|
388
|
+
});
|
|
389
|
+
outOps.push(signedToHop0.userOp);
|
|
390
|
+
// 2) hop j -> hop j+1(扣掉自己那份)
|
|
391
|
+
let prefixKept = 0n;
|
|
392
|
+
for (let j = 0; j < hopSenders.length - 1; j++) {
|
|
393
|
+
const sender = hopSenders[j];
|
|
394
|
+
const next = hopSenders[j + 1];
|
|
395
|
+
const keep = buyAmountsWei[j] ?? 0n;
|
|
396
|
+
prefixKept += keep;
|
|
397
|
+
const remaining = totalBuyWei - prefixKept;
|
|
398
|
+
if (remaining <= 0n)
|
|
399
|
+
break;
|
|
400
|
+
const callData = encodeExecute(next, remaining, '0x');
|
|
401
|
+
const signedHop = await this.aaManager.buildUserOpWithState({
|
|
402
|
+
ownerWallet: hopOwners[j],
|
|
403
|
+
sender,
|
|
404
|
+
nonce: nonceMap.next(sender),
|
|
405
|
+
initCode: consumeInitCode(sender),
|
|
406
|
+
callData,
|
|
407
|
+
signOnly: true,
|
|
408
|
+
});
|
|
409
|
+
outOps.push(signedHop.userOp);
|
|
410
|
+
}
|
|
411
|
+
// 3) lastHop 分发给剩余买方(不含 hop 自己)
|
|
412
|
+
const lastHopIdx = hopSenders.length - 1;
|
|
413
|
+
const lastHopSender = hopSenders[lastHopIdx];
|
|
414
|
+
const lastHopOwner = hopOwners[lastHopIdx];
|
|
415
|
+
const rest = buyerSenders.slice(hopSenders.length);
|
|
416
|
+
const restAmounts = buyAmountsWei.slice(hopSenders.length);
|
|
417
|
+
const restItems = rest.map((to, i) => ({ to, value: restAmounts[i] ?? 0n })).filter(x => x.value > 0n);
|
|
418
|
+
const restChunks = chunkArray(restItems, maxPerOp);
|
|
419
|
+
for (const ch of restChunks) {
|
|
420
|
+
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
421
|
+
to: ch.map(x => x.to),
|
|
422
|
+
values: ch.map(x => x.value),
|
|
423
|
+
});
|
|
424
|
+
const callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
425
|
+
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
426
|
+
ownerWallet: lastHopOwner,
|
|
427
|
+
sender: lastHopSender,
|
|
428
|
+
nonce: nonceMap.next(lastHopSender),
|
|
429
|
+
initCode: consumeInitCode(lastHopSender),
|
|
430
|
+
callData,
|
|
431
|
+
signOnly: true,
|
|
432
|
+
});
|
|
433
|
+
outOps.push(signedDisperse.userOp);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Batch Buy ops(分发后再执行,确保每个买方 sender 先有 OKB)
|
|
298
438
|
for (let i = 0; i < buyerOwners.length; i++) {
|
|
299
439
|
const ai = buyerAis[i];
|
|
300
440
|
const buyWei = buyAmountsWei[i];
|
|
@@ -348,6 +488,7 @@ export class AAPortalSwapExecutor {
|
|
|
348
488
|
profitRecipient,
|
|
349
489
|
profitWei: profitWei.toString(),
|
|
350
490
|
quotedSellOutWei: quotedSellOutWei.toString(),
|
|
491
|
+
disperseHopCount: String(hopCount),
|
|
351
492
|
},
|
|
352
493
|
};
|
|
353
494
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA Buy-First(内盘 / Flap Portal)
|
|
3
|
+
*
|
|
4
|
+
* 对齐 BSC buy-first 思路:
|
|
5
|
+
* - 买方先买入(多钱包随机拆分资金)
|
|
6
|
+
* - 卖方再卖出“等值 token”(卖方需要预持仓)
|
|
7
|
+
* - ✅ 利润:在“卖出后归集 OKB”阶段刮取(executeBatch 拆分给 profitRecipient + owner)
|
|
8
|
+
*
|
|
9
|
+
* 说明:
|
|
10
|
+
* - buy + sell 会放在同一笔 handleOps 内,保持“buy-first 原子”语义
|
|
11
|
+
* - withdraw 需要知道 sell 后的余额,因此单独再发一笔 handleOps
|
|
12
|
+
*/
|
|
13
|
+
import type { XLayerConfig } from './types.js';
|
|
14
|
+
import type { BuyFirstParams, BuyFirstResult } from './types.js';
|
|
15
|
+
export declare class AAPortalBuyFirstExecutor {
|
|
16
|
+
private aaManager;
|
|
17
|
+
private portalQuery;
|
|
18
|
+
private config;
|
|
19
|
+
constructor(config?: XLayerConfig);
|
|
20
|
+
private runHandleOps;
|
|
21
|
+
private pickWallets;
|
|
22
|
+
private getAccountInfos;
|
|
23
|
+
private safePreviewBuy;
|
|
24
|
+
private buildWithdrawUserOp;
|
|
25
|
+
/**
|
|
26
|
+
* 执行一轮 buy-first(内盘)
|
|
27
|
+
*/
|
|
28
|
+
execute(params: BuyFirstParams): Promise<BuyFirstResult>;
|
|
29
|
+
}
|
|
30
|
+
export declare function createAAPortalBuyFirstExecutor(config?: XLayerConfig): AAPortalBuyFirstExecutor;
|