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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA Buy-First(外盘 / PotatoSwap V2)
|
|
3
|
+
*
|
|
4
|
+
* 对齐 BSC buy-first 思路:
|
|
5
|
+
* - 买方先买入(OKB -> token)
|
|
6
|
+
* - 卖方再卖出等值 token(token -> OKB,卖方需要预持仓)
|
|
7
|
+
* - ✅ 利润:在卖出后归集阶段刮取
|
|
8
|
+
*/
|
|
9
|
+
import type { XLayerConfig } from './types.js';
|
|
10
|
+
import type { BuyFirstParams, BuyFirstResult } from './types.js';
|
|
11
|
+
export declare class AADexBuyFirstExecutor {
|
|
12
|
+
private aaManager;
|
|
13
|
+
private portalQuery;
|
|
14
|
+
private dexQuery;
|
|
15
|
+
private config;
|
|
16
|
+
constructor(config?: XLayerConfig);
|
|
17
|
+
private getEffectiveRouter;
|
|
18
|
+
private runHandleOps;
|
|
19
|
+
private pickWallets;
|
|
20
|
+
private buildWithdrawUserOp;
|
|
21
|
+
private safeQuoteBuy;
|
|
22
|
+
execute(params: BuyFirstParams): Promise<BuyFirstResult>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createAADexBuyFirstExecutor(config?: XLayerConfig): AADexBuyFirstExecutor;
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA Buy-First(外盘 / PotatoSwap V2)
|
|
3
|
+
*
|
|
4
|
+
* 对齐 BSC buy-first 思路:
|
|
5
|
+
* - 买方先买入(OKB -> token)
|
|
6
|
+
* - 卖方再卖出等值 token(token -> OKB,卖方需要预持仓)
|
|
7
|
+
* - ✅ 利润:在卖出后归集阶段刮取
|
|
8
|
+
*/
|
|
9
|
+
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
10
|
+
import { AANonceMap } from './types.js';
|
|
11
|
+
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, } from './constants.js';
|
|
12
|
+
import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
13
|
+
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee } from './dex.js';
|
|
14
|
+
import { PortalQuery, encodeApproveCall, parseOkb } from './portal-ops.js';
|
|
15
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
16
|
+
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
17
|
+
// 固定 gas(用于减少 RPC 与提高可控性)
|
|
18
|
+
const DEFAULT_CALL_GAS_LIMIT_BUY = 450000n;
|
|
19
|
+
const DEFAULT_CALL_GAS_LIMIT_SELL = 450000n;
|
|
20
|
+
const DEFAULT_CALL_GAS_LIMIT_APPROVE = 200000n;
|
|
21
|
+
const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
|
|
22
|
+
function getDexDeadline(minutes = 20) {
|
|
23
|
+
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
24
|
+
}
|
|
25
|
+
function resolveProfitSettings(config) {
|
|
26
|
+
const extractProfit = config?.extractProfit !== false; // 默认 true
|
|
27
|
+
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
|
|
28
|
+
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
29
|
+
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
30
|
+
return { extractProfit, profitBps, profitRecipient };
|
|
31
|
+
}
|
|
32
|
+
function calculateProfitWei(amountWei, profitBps) {
|
|
33
|
+
if (amountWei <= 0n)
|
|
34
|
+
return 0n;
|
|
35
|
+
if (!Number.isFinite(profitBps) || profitBps <= 0)
|
|
36
|
+
return 0n;
|
|
37
|
+
return (amountWei * BigInt(profitBps)) / 10000n;
|
|
38
|
+
}
|
|
39
|
+
function shuffle(arr) {
|
|
40
|
+
const a = arr.slice();
|
|
41
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
42
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
43
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
44
|
+
}
|
|
45
|
+
return a;
|
|
46
|
+
}
|
|
47
|
+
function splitAmount(totalAmount, count) {
|
|
48
|
+
if (count <= 0)
|
|
49
|
+
throw new Error('拆分份数必须大于 0');
|
|
50
|
+
if (count === 1)
|
|
51
|
+
return [totalAmount];
|
|
52
|
+
if (totalAmount <= 0n)
|
|
53
|
+
return new Array(count).fill(0n);
|
|
54
|
+
const weights = [];
|
|
55
|
+
for (let i = 0; i < count; i++) {
|
|
56
|
+
const w = BigInt(500000 + Math.floor(Math.random() * 1000000)); // [0.5,1.5)
|
|
57
|
+
weights.push(w);
|
|
58
|
+
}
|
|
59
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0n);
|
|
60
|
+
const amounts = [];
|
|
61
|
+
let allocated = 0n;
|
|
62
|
+
for (let i = 0; i < count - 1; i++) {
|
|
63
|
+
const amt = (totalAmount * weights[i]) / totalWeight;
|
|
64
|
+
amounts.push(amt);
|
|
65
|
+
allocated += amt;
|
|
66
|
+
}
|
|
67
|
+
amounts.push(totalAmount - allocated);
|
|
68
|
+
return shuffle(amounts);
|
|
69
|
+
}
|
|
70
|
+
export class AADexBuyFirstExecutor {
|
|
71
|
+
constructor(config = {}) {
|
|
72
|
+
this.config = config;
|
|
73
|
+
this.aaManager = new AAAccountManager(config);
|
|
74
|
+
this.portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
75
|
+
this.dexQuery = new DexQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
76
|
+
}
|
|
77
|
+
getEffectiveRouter(params) {
|
|
78
|
+
if (params.routerAddress)
|
|
79
|
+
return params.routerAddress;
|
|
80
|
+
if (params.dexKey === 'DYORSWAP') {
|
|
81
|
+
return '0xfb001fbbace32f09cb6d3c449b935183de53ee96';
|
|
82
|
+
}
|
|
83
|
+
return POTATOSWAP_V2_ROUTER;
|
|
84
|
+
}
|
|
85
|
+
async runHandleOps(label, ops, bundlerSigner, beneficiary) {
|
|
86
|
+
const provider = this.aaManager.getProvider();
|
|
87
|
+
const entryPointAddress = this.aaManager.getEntryPointAddress();
|
|
88
|
+
const feeData = await provider.getFeeData();
|
|
89
|
+
console.log(`\n[${label}] 发送 handleOps,ops=${ops.length} ...`);
|
|
90
|
+
const entryPointWithSigner = new Contract(entryPointAddress, ENTRYPOINT_ABI, bundlerSigner);
|
|
91
|
+
const tx = await entryPointWithSigner.handleOps(ops, beneficiary, { gasPrice: feeData.gasPrice ?? 100000000n });
|
|
92
|
+
console.log(`[${label}] txHash:`, tx.hash);
|
|
93
|
+
const receipt = await tx.wait();
|
|
94
|
+
console.log(`[${label}] mined block=${receipt.blockNumber} status=${receipt.status}`);
|
|
95
|
+
const epIface = new Interface(ENTRYPOINT_ABI);
|
|
96
|
+
const userOpEvents = [];
|
|
97
|
+
for (const log of receipt.logs) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = epIface.parseLog(log);
|
|
100
|
+
if (parsed?.name === 'UserOperationEvent') {
|
|
101
|
+
const e = parsed.args;
|
|
102
|
+
userOpEvents.push({
|
|
103
|
+
userOpHash: e.userOpHash,
|
|
104
|
+
sender: e.sender,
|
|
105
|
+
paymaster: e.paymaster,
|
|
106
|
+
success: e.success,
|
|
107
|
+
actualGasCost: e.actualGasCost,
|
|
108
|
+
actualGasUsed: e.actualGasUsed,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
}
|
|
114
|
+
return { txHash: tx.hash, blockNumber: receipt.blockNumber, status: receipt.status, userOpEvents };
|
|
115
|
+
}
|
|
116
|
+
pickWallets(privateKeys, n) {
|
|
117
|
+
const provider = this.aaManager.getProvider();
|
|
118
|
+
const wallets = privateKeys.map((pk) => new Wallet(pk, provider));
|
|
119
|
+
if (!n || n <= 0 || n >= wallets.length)
|
|
120
|
+
return wallets;
|
|
121
|
+
return wallets.slice(0, n);
|
|
122
|
+
}
|
|
123
|
+
async buildWithdrawUserOp(params) {
|
|
124
|
+
const senderBalance = params.senderBalance;
|
|
125
|
+
if (senderBalance <= params.reserveWei)
|
|
126
|
+
return null;
|
|
127
|
+
const effConfig = { ...(this.config ?? {}), ...(params.configOverride ?? {}) };
|
|
128
|
+
const gasPolicyRaw = effConfig.gasPolicy ?? 'bundlerEstimate';
|
|
129
|
+
const gasPolicy = gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
|
|
130
|
+
const tempCallData = encodeExecute(params.ownerWallet.address, 0n, '0x');
|
|
131
|
+
const { prefundWei } = gasPolicy === 'fixed'
|
|
132
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
133
|
+
ownerWallet: params.ownerWallet,
|
|
134
|
+
sender: params.sender,
|
|
135
|
+
callData: tempCallData,
|
|
136
|
+
nonce: params.nonce,
|
|
137
|
+
initCode: params.initCode,
|
|
138
|
+
deployed: params.initCode === '0x',
|
|
139
|
+
fixedGas: {
|
|
140
|
+
...(effConfig.fixedGas ?? {}),
|
|
141
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
145
|
+
ownerWallet: params.ownerWallet,
|
|
146
|
+
sender: params.sender,
|
|
147
|
+
callData: tempCallData,
|
|
148
|
+
nonce: params.nonce,
|
|
149
|
+
initCode: params.initCode,
|
|
150
|
+
});
|
|
151
|
+
const withdrawAmount = senderBalance > prefundWei + params.reserveWei
|
|
152
|
+
? senderBalance - prefundWei - params.reserveWei
|
|
153
|
+
: 0n;
|
|
154
|
+
if (withdrawAmount <= 0n)
|
|
155
|
+
return null;
|
|
156
|
+
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
157
|
+
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
158
|
+
const toOwnerWei = withdrawAmount - profitWei;
|
|
159
|
+
const callData = extractProfit && profitWei > 0n
|
|
160
|
+
? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
|
|
161
|
+
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
162
|
+
const { userOp } = gasPolicy === 'fixed'
|
|
163
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
164
|
+
ownerWallet: params.ownerWallet,
|
|
165
|
+
sender: params.sender,
|
|
166
|
+
callData,
|
|
167
|
+
nonce: params.nonce,
|
|
168
|
+
initCode: params.initCode,
|
|
169
|
+
deployed: params.initCode === '0x',
|
|
170
|
+
fixedGas: {
|
|
171
|
+
...(effConfig.fixedGas ?? {}),
|
|
172
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
176
|
+
ownerWallet: params.ownerWallet,
|
|
177
|
+
sender: params.sender,
|
|
178
|
+
callData,
|
|
179
|
+
nonce: params.nonce,
|
|
180
|
+
initCode: params.initCode,
|
|
181
|
+
});
|
|
182
|
+
return { userOp, prefundWei, profitWei };
|
|
183
|
+
}
|
|
184
|
+
async safeQuoteBuy(tokenAddress, okbAmount) {
|
|
185
|
+
if (okbAmount <= 0n)
|
|
186
|
+
return 0n;
|
|
187
|
+
try {
|
|
188
|
+
return await this.dexQuery.quoteOkbToToken(okbAmount, tokenAddress);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return 0n;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async execute(params) {
|
|
195
|
+
const { tradeType = 'V2', dexKey, routerAddress, deadlineMinutes = 20, tokenAddress, buyerPrivateKeys, sellerPrivateKeys, buyerFunds, buyCount, sellCount, slippageBps = 0, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, config, } = params;
|
|
196
|
+
if (tradeType !== 'V2') {
|
|
197
|
+
throw new Error('AADexBuyFirstExecutor 仅支持 tradeType=V2(外盘)');
|
|
198
|
+
}
|
|
199
|
+
const effectiveRouter = this.getEffectiveRouter({ dexKey, routerAddress });
|
|
200
|
+
const deadline = getDexDeadline(deadlineMinutes);
|
|
201
|
+
const buyers = this.pickWallets(buyerPrivateKeys, buyCount);
|
|
202
|
+
const sellers = this.pickWallets(sellerPrivateKeys, sellCount);
|
|
203
|
+
if (buyers.length === 0)
|
|
204
|
+
throw new Error('buyCount=0 或 buyerPrivateKeys 为空');
|
|
205
|
+
if (sellers.length === 0)
|
|
206
|
+
throw new Error('sellCount=0 或 sellerPrivateKeys 为空');
|
|
207
|
+
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
208
|
+
const profitSettings = resolveProfitSettings(effConfig);
|
|
209
|
+
const totalBuyWei = parseOkb(String(buyerFunds));
|
|
210
|
+
if (totalBuyWei <= 0n)
|
|
211
|
+
throw new Error('buyerFunds 需要 > 0');
|
|
212
|
+
const buyAmountsWei = splitAmount(totalBuyWei, buyers.length);
|
|
213
|
+
const quotedPerBuy = await mapWithConcurrency(buyAmountsWei, 6, async (amt) => this.safeQuoteBuy(tokenAddress, amt));
|
|
214
|
+
const quotedTotalTokenOut = quotedPerBuy.reduce((a, b) => a + (b ?? 0n), 0n);
|
|
215
|
+
const estimatedTokenOutWei = (quotedTotalTokenOut * BigInt(10000 - Math.max(0, Math.min(10000, slippageBps)))) / 10000n;
|
|
216
|
+
if (estimatedTokenOutWei <= 0n) {
|
|
217
|
+
throw new Error('买入报价为 0,无法规划 sellAmount(可能无流动性)');
|
|
218
|
+
}
|
|
219
|
+
const sellAmountsWei = splitAmount(estimatedTokenOutWei, sellers.length);
|
|
220
|
+
// account infos
|
|
221
|
+
const buyerAis = await this.aaManager.getMultipleAccountInfo(buyers.map((w) => w.address));
|
|
222
|
+
const sellerAis = await this.aaManager.getMultipleAccountInfo(sellers.map((w) => w.address));
|
|
223
|
+
const nonceMap = new AANonceMap();
|
|
224
|
+
for (const ai of buyerAis)
|
|
225
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
226
|
+
for (const ai of sellerAis)
|
|
227
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
228
|
+
// initCode:确保同一 sender 只在第一笔 op 携带
|
|
229
|
+
const initCodeBySenderLower = new Map();
|
|
230
|
+
const initIfNeeded = (sender, deployed, ownerAddress) => {
|
|
231
|
+
const k = sender.toLowerCase();
|
|
232
|
+
if (initCodeBySenderLower.has(k))
|
|
233
|
+
return;
|
|
234
|
+
initCodeBySenderLower.set(k, deployed ? '0x' : this.aaManager.generateInitCode(ownerAddress));
|
|
235
|
+
};
|
|
236
|
+
const consumeInitCode = (sender) => {
|
|
237
|
+
const k = sender.toLowerCase();
|
|
238
|
+
const cur = initCodeBySenderLower.get(k);
|
|
239
|
+
if (cur === undefined)
|
|
240
|
+
throw new Error(`initCode not initialized for sender: ${sender}`);
|
|
241
|
+
if (cur !== '0x')
|
|
242
|
+
initCodeBySenderLower.set(k, '0x');
|
|
243
|
+
return cur;
|
|
244
|
+
};
|
|
245
|
+
for (let i = 0; i < buyers.length; i++)
|
|
246
|
+
initIfNeeded(buyerAis[i].sender, buyerAis[i].deployed, buyers[i].address);
|
|
247
|
+
for (let i = 0; i < sellers.length; i++)
|
|
248
|
+
initIfNeeded(sellerAis[i].sender, sellerAis[i].deployed, sellers[i].address);
|
|
249
|
+
// 卖方预持仓检查
|
|
250
|
+
const sellerSenders = sellerAis.map((ai) => ai.sender);
|
|
251
|
+
const sellerTokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, sellerSenders);
|
|
252
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
253
|
+
const sender = sellerSenders[i];
|
|
254
|
+
const needSell = sellAmountsWei[i] ?? 0n;
|
|
255
|
+
const bal = sellerTokenBalances.get(sender) ?? 0n;
|
|
256
|
+
if (needSell > bal) {
|
|
257
|
+
throw new Error(`卖方预持仓不足: sender=${sender} need=${needSell.toString()} bal=${bal.toString()}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// allowance(spender=router)
|
|
261
|
+
const sellerAllowances = await this.portalQuery.getMultipleAllowances(tokenAddress, sellerSenders, effectiveRouter);
|
|
262
|
+
const ops = [];
|
|
263
|
+
// buy ops
|
|
264
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
265
|
+
const w = buyers[i];
|
|
266
|
+
const ai = buyerAis[i];
|
|
267
|
+
const sender = ai.sender;
|
|
268
|
+
const buyWei = buyAmountsWei[i] ?? 0n;
|
|
269
|
+
if (buyWei <= 0n)
|
|
270
|
+
continue;
|
|
271
|
+
const swapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], sender, deadline);
|
|
272
|
+
const callData = encodeExecute(effectiveRouter, buyWei, swapData);
|
|
273
|
+
const { userOp, prefundWei } = await this.aaManager.buildUserOpWithFixedGas({
|
|
274
|
+
ownerWallet: w,
|
|
275
|
+
sender,
|
|
276
|
+
nonce: nonceMap.next(sender),
|
|
277
|
+
initCode: consumeInitCode(sender),
|
|
278
|
+
callData,
|
|
279
|
+
deployed: ai.deployed,
|
|
280
|
+
fixedGas: {
|
|
281
|
+
...(effConfig.fixedGas ?? {}),
|
|
282
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_BUY,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
await this.aaManager.ensureSenderBalance(w, sender, buyWei + prefundWei + parseOkb('0.0001'), `buyFirstDex/buyer${i + 1}/fund`);
|
|
286
|
+
const signed = await this.aaManager.signUserOp(userOp, w);
|
|
287
|
+
ops.push(signed.userOp);
|
|
288
|
+
}
|
|
289
|
+
// seller approve + sell ops
|
|
290
|
+
for (let i = 0; i < sellers.length; i++) {
|
|
291
|
+
const w = sellers[i];
|
|
292
|
+
const ai = sellerAis[i];
|
|
293
|
+
const sender = ai.sender;
|
|
294
|
+
const sellWei = sellAmountsWei[i] ?? 0n;
|
|
295
|
+
if (sellWei <= 0n)
|
|
296
|
+
continue;
|
|
297
|
+
const allowance = sellerAllowances.get(sender) ?? 0n;
|
|
298
|
+
if (allowance < sellWei) {
|
|
299
|
+
const approveCallData = encodeExecute(tokenAddress, 0n, encodeApproveCall(effectiveRouter, ethers.MaxUint256));
|
|
300
|
+
const { userOp: approveOp, prefundWei } = await this.aaManager.buildUserOpWithFixedGas({
|
|
301
|
+
ownerWallet: w,
|
|
302
|
+
sender,
|
|
303
|
+
nonce: nonceMap.next(sender),
|
|
304
|
+
initCode: consumeInitCode(sender),
|
|
305
|
+
callData: approveCallData,
|
|
306
|
+
deployed: ai.deployed,
|
|
307
|
+
fixedGas: {
|
|
308
|
+
...(effConfig.fixedGas ?? {}),
|
|
309
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_APPROVE,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
await this.aaManager.ensureSenderBalance(w, sender, prefundWei + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/approve-fund`);
|
|
313
|
+
const signedApprove = await this.aaManager.signUserOp(approveOp, w);
|
|
314
|
+
ops.push(signedApprove.userOp);
|
|
315
|
+
}
|
|
316
|
+
const sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellWei, 0n, [tokenAddress, WOKB], sender, deadline);
|
|
317
|
+
const sellCallData = encodeExecute(effectiveRouter, 0n, sellSwapData);
|
|
318
|
+
const { userOp: sellOp, prefundWei: sellPrefund } = await this.aaManager.buildUserOpWithFixedGas({
|
|
319
|
+
ownerWallet: w,
|
|
320
|
+
sender,
|
|
321
|
+
nonce: nonceMap.next(sender),
|
|
322
|
+
initCode: consumeInitCode(sender),
|
|
323
|
+
callData: sellCallData,
|
|
324
|
+
deployed: ai.deployed,
|
|
325
|
+
fixedGas: {
|
|
326
|
+
...(effConfig.fixedGas ?? {}),
|
|
327
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
await this.aaManager.ensureSenderBalance(w, sender, sellPrefund + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/sell-fund`);
|
|
331
|
+
const signedSell = await this.aaManager.signUserOp(sellOp, w);
|
|
332
|
+
ops.push(signedSell.userOp);
|
|
333
|
+
}
|
|
334
|
+
if (ops.length === 0)
|
|
335
|
+
throw new Error('本轮没有生成任何 UserOp');
|
|
336
|
+
const payerWallet = buyers[0];
|
|
337
|
+
const beneficiary = payerWallet.address;
|
|
338
|
+
const buySellResult = await this.runHandleOps('aa-buy-first/dex', ops, payerWallet, beneficiary);
|
|
339
|
+
let withdrawResult;
|
|
340
|
+
let finalSellerBalances;
|
|
341
|
+
let totalProfitWei = 0n;
|
|
342
|
+
if (withdrawToOwner) {
|
|
343
|
+
const reserveWei = parseOkb(withdrawReserve);
|
|
344
|
+
const sellerOkbBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
|
|
345
|
+
const withdrawOps = [];
|
|
346
|
+
const builtOps = await mapWithConcurrency(sellers, 3, async (w, i) => {
|
|
347
|
+
const sender = sellerAis[i].sender;
|
|
348
|
+
const senderBalance = sellerOkbBalances.get(sender) ?? 0n;
|
|
349
|
+
const usedNonce = nonceMap.peek(sender);
|
|
350
|
+
const built = await this.buildWithdrawUserOp({
|
|
351
|
+
ownerWallet: w,
|
|
352
|
+
sender,
|
|
353
|
+
nonce: usedNonce,
|
|
354
|
+
initCode: '0x',
|
|
355
|
+
senderBalance,
|
|
356
|
+
reserveWei,
|
|
357
|
+
configOverride: config,
|
|
358
|
+
});
|
|
359
|
+
if (!built)
|
|
360
|
+
return null;
|
|
361
|
+
nonceMap.commit(sender, usedNonce);
|
|
362
|
+
totalProfitWei += built.profitWei;
|
|
363
|
+
const signed = await this.aaManager.signUserOp(built.userOp, w);
|
|
364
|
+
return signed.userOp;
|
|
365
|
+
});
|
|
366
|
+
for (const op of builtOps)
|
|
367
|
+
if (op)
|
|
368
|
+
withdrawOps.push(op);
|
|
369
|
+
if (withdrawOps.length > 0) {
|
|
370
|
+
withdrawResult = await this.runHandleOps('aa-buy-first/dex-withdraw', withdrawOps, payerWallet, beneficiary);
|
|
371
|
+
}
|
|
372
|
+
finalSellerBalances = await this.portalQuery.getMultipleOkbBalances(sellerSenders);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
buySellResult,
|
|
376
|
+
withdrawResult,
|
|
377
|
+
finalSellerBalances,
|
|
378
|
+
profit: withdrawToOwner
|
|
379
|
+
? {
|
|
380
|
+
extractProfit: profitSettings.extractProfit,
|
|
381
|
+
profitBps: profitSettings.profitBps,
|
|
382
|
+
profitRecipient: profitSettings.profitRecipient,
|
|
383
|
+
totalProfitWei: totalProfitWei.toString(),
|
|
384
|
+
}
|
|
385
|
+
: undefined,
|
|
386
|
+
metadata: {
|
|
387
|
+
tradeType: 'V2',
|
|
388
|
+
tokenAddress,
|
|
389
|
+
buyerOwners: buyers.map((w) => w.address),
|
|
390
|
+
buyerSenders: buyerAis.map((ai) => ai.sender),
|
|
391
|
+
sellerOwners: sellers.map((w) => w.address),
|
|
392
|
+
sellerSenders,
|
|
393
|
+
buyAmountsWei: buyAmountsWei.map((x) => x.toString()),
|
|
394
|
+
sellAmountsWei: sellAmountsWei.map((x) => x.toString()),
|
|
395
|
+
totalBuyWei: totalBuyWei.toString(),
|
|
396
|
+
estimatedTokenOutWei: estimatedTokenOutWei.toString(),
|
|
397
|
+
slippageBps,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
export function createAADexBuyFirstExecutor(config) {
|
|
403
|
+
return new AADexBuyFirstExecutor(config);
|
|
404
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer 外盘刷量/做市 SDK(AA / ERC-4337)
|
|
3
|
+
*
|
|
4
|
+
* - 与 `src/xlayer/volume.ts`(内盘)分离
|
|
5
|
+
* - 底层使用 `DexBundleExecutor.bundleBuySell`(买入 -> 卖出 -> 归集 -> 刮利润)
|
|
6
|
+
*/
|
|
7
|
+
import type { XLayerConfig, VolumeResult } from './types.js';
|
|
8
|
+
export interface DexVolumeParams {
|
|
9
|
+
tokenAddress: string;
|
|
10
|
+
privateKeys: string[];
|
|
11
|
+
buyAmountPerRound: string;
|
|
12
|
+
rounds: number;
|
|
13
|
+
intervalMs?: number;
|
|
14
|
+
sellImmediately?: boolean;
|
|
15
|
+
sellPercent?: number;
|
|
16
|
+
withdrawToOwner?: boolean;
|
|
17
|
+
withdrawReserve?: string;
|
|
18
|
+
dexKey?: string;
|
|
19
|
+
routerAddress?: string;
|
|
20
|
+
deadlineMinutes?: number;
|
|
21
|
+
config?: Partial<XLayerConfig>;
|
|
22
|
+
}
|
|
23
|
+
export declare class DexVolumeExecutor {
|
|
24
|
+
private dexBundleExecutor;
|
|
25
|
+
private config;
|
|
26
|
+
constructor(config?: XLayerConfig);
|
|
27
|
+
execute(params: DexVolumeParams): Promise<VolumeResult>;
|
|
28
|
+
}
|
|
29
|
+
export declare function createDexVolumeExecutor(config?: XLayerConfig): DexVolumeExecutor;
|
|
30
|
+
export declare function makeDexVolume(params: DexVolumeParams): Promise<VolumeResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer 外盘刷量/做市 SDK(AA / ERC-4337)
|
|
3
|
+
*
|
|
4
|
+
* - 与 `src/xlayer/volume.ts`(内盘)分离
|
|
5
|
+
* - 底层使用 `DexBundleExecutor.bundleBuySell`(买入 -> 卖出 -> 归集 -> 刮利润)
|
|
6
|
+
*/
|
|
7
|
+
import { parseOkb, formatOkb } from './portal-ops.js';
|
|
8
|
+
import { DexBundleExecutor } from './dex-bundle.js';
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
export class DexVolumeExecutor {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.dexBundleExecutor = new DexBundleExecutor(config);
|
|
16
|
+
}
|
|
17
|
+
async execute(params) {
|
|
18
|
+
const { tokenAddress, privateKeys, buyAmountPerRound, rounds, intervalMs = 3000, sellImmediately = true, sellPercent, withdrawToOwner = true, withdrawReserve, dexKey, routerAddress, deadlineMinutes, config, } = params;
|
|
19
|
+
console.log('=== XLayer DEX Volume Maker ===');
|
|
20
|
+
console.log('token:', tokenAddress);
|
|
21
|
+
console.log('owners:', privateKeys.length);
|
|
22
|
+
console.log('rounds:', rounds);
|
|
23
|
+
console.log('buyAmountPerRound:', buyAmountPerRound, 'OKB');
|
|
24
|
+
console.log('intervalMs:', intervalMs);
|
|
25
|
+
console.log('sellImmediately:', sellImmediately);
|
|
26
|
+
const roundResults = [];
|
|
27
|
+
let successRounds = 0;
|
|
28
|
+
let failedRounds = 0;
|
|
29
|
+
let totalVolume = 0n;
|
|
30
|
+
const buyWei = parseOkb(buyAmountPerRound);
|
|
31
|
+
for (let round = 0; round < rounds; round++) {
|
|
32
|
+
console.log(`\n========== DEX Round ${round + 1}/${rounds} ==========`);
|
|
33
|
+
try {
|
|
34
|
+
const buyAmounts = privateKeys.map(() => buyAmountPerRound);
|
|
35
|
+
const effSellPercent = typeof sellPercent === 'number' ? sellPercent : (sellImmediately ? 100 : 0);
|
|
36
|
+
const result = await this.dexBundleExecutor.bundleBuySell({
|
|
37
|
+
tokenAddress,
|
|
38
|
+
privateKeys,
|
|
39
|
+
buyAmounts,
|
|
40
|
+
sellPercent: effSellPercent,
|
|
41
|
+
withdrawToOwner,
|
|
42
|
+
withdrawReserve,
|
|
43
|
+
dexKey,
|
|
44
|
+
routerAddress,
|
|
45
|
+
deadlineMinutes,
|
|
46
|
+
config: { ...(this.config ?? {}), ...(config ?? {}) },
|
|
47
|
+
});
|
|
48
|
+
roundResults.push(result);
|
|
49
|
+
successRounds++;
|
|
50
|
+
totalVolume += buyWei * BigInt(privateKeys.length);
|
|
51
|
+
console.log(`DEX Round ${round + 1} 完成`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(`DEX Round ${round + 1} 失败:`, error);
|
|
55
|
+
failedRounds++;
|
|
56
|
+
}
|
|
57
|
+
if (round < rounds - 1 && intervalMs > 0) {
|
|
58
|
+
console.log(`等待 ${intervalMs}ms...`);
|
|
59
|
+
await sleep(intervalMs);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
console.log('\n=== DEX Volume Summary ===');
|
|
63
|
+
console.log('成功轮数:', successRounds);
|
|
64
|
+
console.log('失败轮数:', failedRounds);
|
|
65
|
+
console.log('总交易量:', formatOkb(totalVolume), 'OKB');
|
|
66
|
+
return {
|
|
67
|
+
successRounds,
|
|
68
|
+
failedRounds,
|
|
69
|
+
roundResults,
|
|
70
|
+
totalVolume,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function createDexVolumeExecutor(config) {
|
|
75
|
+
return new DexVolumeExecutor(config);
|
|
76
|
+
}
|
|
77
|
+
export async function makeDexVolume(params) {
|
|
78
|
+
const executor = createDexVolumeExecutor(params.config);
|
|
79
|
+
return executor.execute(params);
|
|
80
|
+
}
|
package/dist/xlayer/index.d.ts
CHANGED
|
@@ -63,8 +63,12 @@ export { BundlerClient, createBundlerClient, type BundlerConfig, type BundlerRec
|
|
|
63
63
|
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
|
|
64
64
|
export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, createPortalQuery, applySlippage, formatOkb, parseOkb, formatTokenAmount, parseTokenAmount, type PortalQueryConfig, } from './portal-ops.js';
|
|
65
65
|
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, } from './bundle.js';
|
|
66
|
+
export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, type DexBundleConfig, type DexBundleBuySellParams, } from './dex-bundle.js';
|
|
66
67
|
export { AAPortalSwapExecutor, } from './portal-bundle-swap.js';
|
|
67
68
|
export { AADexSwapExecutor, } from './dex-bundle-swap.js';
|
|
69
|
+
export { AAPortalBuyFirstExecutor, createAAPortalBuyFirstExecutor, } from './portal-buy-first.js';
|
|
70
|
+
export { AADexBuyFirstExecutor, createAADexBuyFirstExecutor, } from './dex-buy-first.js';
|
|
71
|
+
export { BuyFirstVolumeExecutor, createBuyFirstVolumeExecutor, makeBuyFirstVolume, } from './buy-first-volume.js';
|
|
68
72
|
/**
|
|
69
73
|
* XLayer AA 换手签名 (卖 -> 买)
|
|
70
74
|
*/
|
|
@@ -78,6 +82,7 @@ export declare function bundleBatchSwapSign(params: BundleBatchSwapSignParams &
|
|
|
78
82
|
skipApprovalCheck?: boolean;
|
|
79
83
|
}): Promise<BundleBatchSwapSignResult>;
|
|
80
84
|
export { VolumeExecutor, createVolumeExecutor, makeVolume, singleRoundVolume, } from './volume.js';
|
|
85
|
+
export { DexVolumeExecutor, createDexVolumeExecutor, makeDexVolume, type DexVolumeParams, } from './dex-volume.js';
|
|
81
86
|
export { DexQuery, DexExecutor, createDexQuery, createDexExecutor, quoteOkbToToken, quoteTokenToOkb, encodeSwapExactETHForTokens, encodeSwapExactTokensForETH, encodeSwapExactTokensForTokens, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForTokensSupportingFee, type DexConfig, } from './dex.js';
|
|
82
87
|
export declare const xlayer: {
|
|
83
88
|
bundleBuy: (params: import("./types.js").BundleBuyParams) => Promise<import("./types.js").BundleBuyResult>;
|
package/dist/xlayer/index.js
CHANGED
|
@@ -83,10 +83,20 @@ export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, P
|
|
|
83
83
|
// ============================================================================
|
|
84
84
|
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, } from './bundle.js';
|
|
85
85
|
// ============================================================================
|
|
86
|
+
// 外盘捆绑交易(DEX Bundle)
|
|
87
|
+
// ============================================================================
|
|
88
|
+
export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, } from './dex-bundle.js';
|
|
89
|
+
// ============================================================================
|
|
86
90
|
// 捆绑换手 (AASwap)
|
|
87
91
|
// ============================================================================
|
|
88
92
|
export { AAPortalSwapExecutor, } from './portal-bundle-swap.js';
|
|
89
93
|
export { AADexSwapExecutor, } from './dex-bundle-swap.js';
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// AA Buy-First(刷量专用)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
export { AAPortalBuyFirstExecutor, createAAPortalBuyFirstExecutor, } from './portal-buy-first.js';
|
|
98
|
+
export { AADexBuyFirstExecutor, createAADexBuyFirstExecutor, } from './dex-buy-first.js';
|
|
99
|
+
export { BuyFirstVolumeExecutor, createBuyFirstVolumeExecutor, makeBuyFirstVolume, } from './buy-first-volume.js';
|
|
90
100
|
/**
|
|
91
101
|
* XLayer AA 换手签名 (卖 -> 买)
|
|
92
102
|
*/
|
|
@@ -116,6 +126,10 @@ export async function bundleBatchSwapSign(params) {
|
|
|
116
126
|
// ============================================================================
|
|
117
127
|
export { VolumeExecutor, createVolumeExecutor, makeVolume, singleRoundVolume, } from './volume.js';
|
|
118
128
|
// ============================================================================
|
|
129
|
+
// 外盘刷量/做市(DEX Volume)
|
|
130
|
+
// ============================================================================
|
|
131
|
+
export { DexVolumeExecutor, createDexVolumeExecutor, makeDexVolume, } from './dex-volume.js';
|
|
132
|
+
// ============================================================================
|
|
119
133
|
// 外盘交易 (DEX)
|
|
120
134
|
// ============================================================================
|
|
121
135
|
export { DexQuery, DexExecutor, createDexQuery, createDexExecutor, quoteOkbToToken, quoteTokenToOkb,
|