four-flap-meme-sdk 1.5.96 → 1.5.99
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 +2 -1
- package/dist/contracts/tm-bundle-merkle/index.js +2 -1
- package/dist/contracts/tm-bundle-merkle/submit.d.ts +11 -0
- package/dist/contracts/tm-bundle-merkle/submit.js +88 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/xlayer/bundle.js +68 -9
- package/dist/xlayer/constants.d.ts +2 -2
- package/dist/xlayer/constants.js +8 -1
- package/dist/xlayer/dex-bundle-swap.js +104 -5
- package/dist/xlayer/dex-bundle.js +2 -0
- package/dist/xlayer/dex.d.ts +34 -13
- package/dist/xlayer/dex.js +51 -4
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ export { fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle,
|
|
|
9
9
|
export { disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle } from './utils.js';
|
|
10
10
|
export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch } from './pancake-proxy.js';
|
|
11
11
|
export { approveFourTokenManagerBatch, type ApproveFourTokenManagerBatchParams, type ApproveFourTokenManagerBatchResult } from './approve-tokenmanager.js';
|
|
12
|
-
export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, type MerkleSubmitConfig, type SubmitBundleResult, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, submitDirectToRpc, submitDirectToRpcSequential, // ✅
|
|
12
|
+
export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, type MerkleSubmitConfig, type SubmitBundleResult, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, submitDirectToRpc, submitDirectToRpcSequential, // ✅ 顺序广播并等待确认(用于多跳)
|
|
13
|
+
submitDirectToRpcSequentialNoWait, // ✅ 顺序广播但不等待确认(用于 AA handleOps + tail)
|
|
13
14
|
submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './submit.js';
|
|
14
15
|
export { fourBundleBuyFirstMerkle, type FourBundleBuyFirstSignParams, type FourBuyFirstSignConfig, type FourBuyFirstResult } from './swap-buy-first.js';
|
|
@@ -22,7 +22,8 @@ submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel,
|
|
|
22
22
|
// ✅ BlockRazor 提交方法
|
|
23
23
|
submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel,
|
|
24
24
|
// ✅ Monad 等链的逐笔广播方法
|
|
25
|
-
submitDirectToRpc, submitDirectToRpcSequential, // ✅
|
|
25
|
+
submitDirectToRpc, submitDirectToRpcSequential, // ✅ 顺序广播并等待确认(用于多跳)
|
|
26
|
+
submitDirectToRpcSequentialNoWait, // ✅ 顺序广播但不等待确认(用于 AA handleOps + tail)
|
|
26
27
|
submitDirectToRpcParallel } from './submit.js';
|
|
27
28
|
// 先买后卖换手方法
|
|
28
29
|
export { fourBundleBuyFirstMerkle } from './swap-buy-first.js';
|
|
@@ -260,6 +260,17 @@ export declare function submitDirectToRpc(signedTransactions: string[], config:
|
|
|
260
260
|
* @returns 广播结果
|
|
261
261
|
*/
|
|
262
262
|
export declare function submitDirectToRpcSequential(signedTransactions: string[], config: DirectSubmitConfig): Promise<DirectSubmitResult>;
|
|
263
|
+
/**
|
|
264
|
+
* ✅ 顺序广播但不等待确认(用于 AA handleOps + tail 等 nonce 连续的场景)
|
|
265
|
+
*
|
|
266
|
+
* 按顺序逐笔发送交易,每笔只等待广播成功(RPC 返回 txHash),不等待链上确认。
|
|
267
|
+
* 这样可以保证交易按顺序进入 mempool,同时避免等待确认超时。
|
|
268
|
+
*
|
|
269
|
+
* @param signedTransactions 签名后的交易数组
|
|
270
|
+
* @param config 直接广播配置
|
|
271
|
+
* @returns 广播结果
|
|
272
|
+
*/
|
|
273
|
+
export declare function submitDirectToRpcSequentialNoWait(signedTransactions: string[], config: DirectSubmitConfig): Promise<DirectSubmitResult>;
|
|
263
274
|
/**
|
|
264
275
|
* 并行逐笔广播到 RPC(速度更快,但可能有 nonce 冲突)
|
|
265
276
|
*
|
|
@@ -510,6 +510,94 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
|
|
|
510
510
|
errorSummary: errors.length > 0 ? errors.join('; ') : undefined
|
|
511
511
|
};
|
|
512
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* ✅ 顺序广播但不等待确认(用于 AA handleOps + tail 等 nonce 连续的场景)
|
|
515
|
+
*
|
|
516
|
+
* 按顺序逐笔发送交易,每笔只等待广播成功(RPC 返回 txHash),不等待链上确认。
|
|
517
|
+
* 这样可以保证交易按顺序进入 mempool,同时避免等待确认超时。
|
|
518
|
+
*
|
|
519
|
+
* @param signedTransactions 签名后的交易数组
|
|
520
|
+
* @param config 直接广播配置
|
|
521
|
+
* @returns 广播结果
|
|
522
|
+
*/
|
|
523
|
+
export async function submitDirectToRpcSequentialNoWait(signedTransactions, config) {
|
|
524
|
+
const totalTransactions = signedTransactions?.length ?? 0;
|
|
525
|
+
if (!signedTransactions || signedTransactions.length === 0) {
|
|
526
|
+
return {
|
|
527
|
+
code: false,
|
|
528
|
+
totalTransactions: 0,
|
|
529
|
+
successCount: 0,
|
|
530
|
+
failedCount: 0,
|
|
531
|
+
txHashes: [],
|
|
532
|
+
results: [],
|
|
533
|
+
errorSummary: 'signedTransactions cannot be empty'
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (!config.rpcUrl) {
|
|
537
|
+
return {
|
|
538
|
+
code: false,
|
|
539
|
+
totalTransactions,
|
|
540
|
+
successCount: 0,
|
|
541
|
+
failedCount: totalTransactions,
|
|
542
|
+
txHashes: [],
|
|
543
|
+
results: [],
|
|
544
|
+
errorSummary: 'rpcUrl is required in config'
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const chainId = config.chainId ?? 143;
|
|
548
|
+
const chainName = config.chainName ?? 'MONAD';
|
|
549
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
550
|
+
chainId,
|
|
551
|
+
name: chainName
|
|
552
|
+
});
|
|
553
|
+
const results = [];
|
|
554
|
+
const txHashes = [];
|
|
555
|
+
const errors = [];
|
|
556
|
+
// ✅ 顺序广播,每笔只等待广播成功(不等待链上确认)
|
|
557
|
+
for (let i = 0; i < signedTransactions.length; i++) {
|
|
558
|
+
const signedTx = signedTransactions[i];
|
|
559
|
+
try {
|
|
560
|
+
// 广播交易,等待 RPC 返回 txHash
|
|
561
|
+
const txResponse = await provider.broadcastTransaction(signedTx);
|
|
562
|
+
results.push({
|
|
563
|
+
index: i,
|
|
564
|
+
success: true,
|
|
565
|
+
txHash: txResponse.hash
|
|
566
|
+
});
|
|
567
|
+
txHashes.push(txResponse.hash);
|
|
568
|
+
console.log(`✅ [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播成功: ${txResponse.hash}`);
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
const errorMessage = error?.message || String(error);
|
|
572
|
+
console.error(`❌ [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播失败:`, errorMessage);
|
|
573
|
+
// ✅ 兼容:nonce too low / already known 等情况下,交易可能已经被接收
|
|
574
|
+
const recovered = await recoverTxHashIfAlreadySeen(provider, signedTx, errorMessage);
|
|
575
|
+
if (recovered) {
|
|
576
|
+
results.push({ index: i, success: true, txHash: recovered, error: errorMessage });
|
|
577
|
+
txHashes.push(recovered);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
results.push({
|
|
581
|
+
index: i,
|
|
582
|
+
success: false,
|
|
583
|
+
error: errorMessage
|
|
584
|
+
});
|
|
585
|
+
errors.push(`交易 ${i + 1}: ${errorMessage}`);
|
|
586
|
+
// ✅ 如果某笔失败,继续尝试后续交易(可能是 nonce 问题可以自动恢复)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const successCount = txHashes.length;
|
|
590
|
+
const failedCount = totalTransactions - successCount;
|
|
591
|
+
return {
|
|
592
|
+
code: successCount > 0,
|
|
593
|
+
totalTransactions,
|
|
594
|
+
successCount,
|
|
595
|
+
failedCount,
|
|
596
|
+
txHashes,
|
|
597
|
+
results,
|
|
598
|
+
errorSummary: errors.length > 0 ? errors.join('; ') : undefined
|
|
599
|
+
};
|
|
600
|
+
}
|
|
513
601
|
/**
|
|
514
602
|
* 并行逐笔广播到 RPC(速度更快,但可能有 nonce 冲突)
|
|
515
603
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -49,7 +49,8 @@ export { flapBundleCurveToDex, type CurveToDexChain, type DexPoolType, type Curv
|
|
|
49
49
|
export { flapBundleCreateToDex, type CreateToDexChain, type CreateToDexSignConfig, type CreateTokenInfo, type FlapCreateToDexParams, type FlapCreateToDexResult } from './flap/portal-bundle-merkle/create-to-dex.js';
|
|
50
50
|
export { PROFIT_CONFIG } from './utils/constants.js';
|
|
51
51
|
export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote, getWrappedNativeAddress, getStableCoins, V3_FEE_TIERS, QUOTE_CONFIG, type QuoteParams, type QuoteResult, type SupportedChain, } from './utils/quote-helpers.js';
|
|
52
|
-
export { submitDirectToRpc, submitDirectToRpcSequential, // ✅
|
|
52
|
+
export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 顺序广播并等待确认(用于多跳)
|
|
53
|
+
submitDirectToRpcSequentialNoWait, // ✅ 顺序广播但不等待确认(用于 AA handleOps + tail)
|
|
53
54
|
submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './contracts/tm-bundle-merkle/submit.js';
|
|
54
55
|
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from './dex/index.js';
|
|
55
56
|
export * as XLayer from './xlayer/index.js';
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,8 @@ export { PROFIT_CONFIG } from './utils/constants.js';
|
|
|
82
82
|
// ✅ V2/V3 报价工具(统一管理)
|
|
83
83
|
export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote, getWrappedNativeAddress, getStableCoins, V3_FEE_TIERS, QUOTE_CONFIG, } from './utils/quote-helpers.js';
|
|
84
84
|
// ✅ Monad 等不支持 Bundle 的链:逐笔 RPC 广播方法(服务器端使用)
|
|
85
|
-
export { submitDirectToRpc, submitDirectToRpcSequential, // ✅
|
|
85
|
+
export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 顺序广播并等待确认(用于多跳)
|
|
86
|
+
submitDirectToRpcSequentialNoWait, // ✅ 顺序广播但不等待确认(用于 AA handleOps + tail)
|
|
86
87
|
submitDirectToRpcParallel } from './contracts/tm-bundle-merkle/submit.js';
|
|
87
88
|
// ============================================================
|
|
88
89
|
// ✅ DEX 直接交易(不走代理合约)
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { Wallet, Interface, Contract, ethers } from 'ethers';
|
|
11
11
|
import { FLAP_PORTAL, ENTRYPOINT_ABI, PORTAL_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, WOKB, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, } from './constants.js';
|
|
12
12
|
import { AAAccountManager, encodeExecute, encodeExecuteBatch, createWallet } from './aa-account.js';
|
|
13
|
-
import {
|
|
13
|
+
import { encodeBuyCallV3, encodeSellCall, encodeApproveCall, encodeTransferCall, encodeCreateCallV2, encodeCreateCallV3, encodeCreateCallV4, PortalQuery, parseOkb, formatOkb, lpFeeProfileToV3Fee, } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
15
15
|
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
|
|
16
16
|
import { DexQuery } from './dex.js';
|
|
@@ -808,6 +808,7 @@ export class BundleExecutor {
|
|
|
808
808
|
if (dexType === 'V3') {
|
|
809
809
|
routerAddress = POTATOSWAP_V3_ROUTER;
|
|
810
810
|
const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
|
|
811
|
+
// ✅ V3 卖出需要 unwrap WOKB 到 OKB
|
|
811
812
|
swapData = encodeSwapExactTokensForETHV3({
|
|
812
813
|
tokenIn: tokenAddress,
|
|
813
814
|
tokenOut: WOKB,
|
|
@@ -816,7 +817,8 @@ export class BundleExecutor {
|
|
|
816
817
|
deadline,
|
|
817
818
|
amountIn: it.sellAmount,
|
|
818
819
|
amountOutMinimum: 0n,
|
|
819
|
-
sqrtPriceLimitX96: 0n
|
|
820
|
+
sqrtPriceLimitX96: 0n,
|
|
821
|
+
unwrapRecipient: it.sender, // ✅ 添加 unwrap 接收者
|
|
820
822
|
});
|
|
821
823
|
}
|
|
822
824
|
else {
|
|
@@ -1569,9 +1571,11 @@ export class BundleExecutor {
|
|
|
1569
1571
|
});
|
|
1570
1572
|
totalCurveBuyWei = curveBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1571
1573
|
// ✅ 并行构建和签名所有内盘买入 UserOps
|
|
1574
|
+
// ✅ 使用 encodeBuyCallV3(支持 extensionData),与 V4 代币创建保持一致
|
|
1575
|
+
const extensionData = params.extensionData ?? '0x';
|
|
1572
1576
|
const signedCurveBuyOps = await mapWithConcurrency(curveBuyData, 5, async (data) => {
|
|
1573
1577
|
const { wallet, info, buyWei, gasLimit } = data;
|
|
1574
|
-
const buyData =
|
|
1578
|
+
const buyData = encodeBuyCallV3(tokenAddress, buyWei, 0n, extensionData);
|
|
1575
1579
|
const buyCallData = encodeExecute(FLAP_PORTAL, buyWei, buyData);
|
|
1576
1580
|
const buyOpRes = await aaManager.buildUserOpWithFixedGas({
|
|
1577
1581
|
ownerWallet: wallet,
|
|
@@ -1586,17 +1590,48 @@ export class BundleExecutor {
|
|
|
1586
1590
|
return signedBuyOp.userOp;
|
|
1587
1591
|
});
|
|
1588
1592
|
ops1.push(...signedCurveBuyOps);
|
|
1593
|
+
// === 向内盘买家 AA 账户转入 OKB(prefund + 买入金额)===
|
|
1594
|
+
// ✅ 修复 AA21 didn't pay prefund 错误
|
|
1595
|
+
const feeData = await provider.getFeeData();
|
|
1596
|
+
const gasPrice = feeData.gasPrice ?? 100000000n;
|
|
1597
|
+
const chainId = (await provider.getNetwork()).chainId;
|
|
1598
|
+
let currentNonce = params.payerStartNonce ?? (await provider.getTransactionCount(payerWallet.address, 'pending'));
|
|
1599
|
+
const DEFAULT_PREFUND_ESTIMATE = parseOkb('0.002'); // 预估每个 UserOp 的 prefund
|
|
1600
|
+
const BUFFER_WEI = parseOkb('0.0005'); // 缓冲金额
|
|
1601
|
+
for (let i = 0; i < curveBuyerWallets.length; i++) {
|
|
1602
|
+
const buyerSender = curveBuyerInfos[i].sender;
|
|
1603
|
+
const buyWei = curveBuyData[i].buyWei;
|
|
1604
|
+
// 计算需要转入的金额:买入金额 + prefund + 缓冲
|
|
1605
|
+
const requiredAmount = buyWei + DEFAULT_PREFUND_ESTIMATE + BUFFER_WEI;
|
|
1606
|
+
// 检查买家 AA 账户当前余额
|
|
1607
|
+
const currentBalance = await provider.getBalance(buyerSender);
|
|
1608
|
+
if (currentBalance < requiredAmount) {
|
|
1609
|
+
const transferAmount = requiredAmount - currentBalance;
|
|
1610
|
+
console.log(`[内盘买家${i + 1}] 向 AA 账户 ${buyerSender} 转入 ${formatOkb(transferAmount)} OKB`);
|
|
1611
|
+
const fundTxRequest = {
|
|
1612
|
+
to: buyerSender,
|
|
1613
|
+
value: transferAmount,
|
|
1614
|
+
nonce: currentNonce,
|
|
1615
|
+
gasLimit: 21000n,
|
|
1616
|
+
gasPrice,
|
|
1617
|
+
chainId
|
|
1618
|
+
};
|
|
1619
|
+
const signedFundTx = await payerWallet.signTransaction(fundTxRequest);
|
|
1620
|
+
signedTransactions.push(signedFundTx);
|
|
1621
|
+
currentNonce++;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1589
1624
|
// 签名第一个 handleOps
|
|
1590
|
-
const startNonce = params.payerStartNonce ?? (await provider.getTransactionCount(payerWallet.address, 'pending'));
|
|
1591
1625
|
const signedMainTx = await this.signHandleOpsTx({
|
|
1592
1626
|
ops: ops1,
|
|
1593
1627
|
payerWallet: payerWallet,
|
|
1594
1628
|
beneficiary: params.beneficiary ?? payerWallet.address,
|
|
1595
|
-
nonce:
|
|
1629
|
+
nonce: currentNonce,
|
|
1596
1630
|
gasLimit: effConfig.gasLimit,
|
|
1597
1631
|
gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
|
|
1598
1632
|
});
|
|
1599
1633
|
signedTransactions.push(signedMainTx);
|
|
1634
|
+
currentNonce++;
|
|
1600
1635
|
// --- 4. 构建外盘买入 handleOps (如果启用) ---
|
|
1601
1636
|
if (enableDexBuy && dexBuyerPrivateKeys.length > 0) {
|
|
1602
1637
|
const dexBuyerWallets = dexBuyerPrivateKeys.map(pk => createWallet(pk, config));
|
|
@@ -1614,10 +1649,34 @@ export class BundleExecutor {
|
|
|
1614
1649
|
return { wallet, info: dexBuyerInfos[i], buyWei };
|
|
1615
1650
|
});
|
|
1616
1651
|
totalDexBuyWei = dexBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1652
|
+
// === 向外盘买家 AA 账户转入 OKB(prefund + 买入金额)===
|
|
1653
|
+
for (let i = 0; i < dexBuyerWallets.length; i++) {
|
|
1654
|
+
const buyerSender = dexBuyerInfos[i].sender;
|
|
1655
|
+
const buyWei = dexBuyData[i].buyWei;
|
|
1656
|
+
// 计算需要转入的金额:买入金额 + prefund + 缓冲
|
|
1657
|
+
const requiredAmount = buyWei + DEFAULT_PREFUND_ESTIMATE + BUFFER_WEI;
|
|
1658
|
+
// 检查买家 AA 账户当前余额
|
|
1659
|
+
const dexBuyerBalance = await provider.getBalance(buyerSender);
|
|
1660
|
+
if (dexBuyerBalance < requiredAmount) {
|
|
1661
|
+
const transferAmount = requiredAmount - dexBuyerBalance;
|
|
1662
|
+
console.log(`[外盘买家${i + 1}] 向 AA 账户 ${buyerSender} 转入 ${formatOkb(transferAmount)} OKB`);
|
|
1663
|
+
const fundTxRequest = {
|
|
1664
|
+
to: buyerSender,
|
|
1665
|
+
value: transferAmount,
|
|
1666
|
+
nonce: currentNonce,
|
|
1667
|
+
gasLimit: 21000n,
|
|
1668
|
+
gasPrice,
|
|
1669
|
+
chainId
|
|
1670
|
+
};
|
|
1671
|
+
const signedFundTx = await payerWallet.signTransaction(fundTxRequest);
|
|
1672
|
+
signedTransactions.push(signedFundTx);
|
|
1673
|
+
currentNonce++;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1617
1676
|
// ✅ 并行构建和签名所有外盘买入 UserOps
|
|
1618
1677
|
const signedDexBuyOps = await mapWithConcurrency(dexBuyData, 5, async (data) => {
|
|
1619
1678
|
const { wallet, info, buyWei } = data;
|
|
1620
|
-
// ✅ 外盘买入:使用
|
|
1679
|
+
// ✅ 外盘买入:使用 DEX Router 进行交换
|
|
1621
1680
|
let swapData;
|
|
1622
1681
|
let routerAddress;
|
|
1623
1682
|
if (dexType === 'V3') {
|
|
@@ -1655,11 +1714,12 @@ export class BundleExecutor {
|
|
|
1655
1714
|
ops: ops2,
|
|
1656
1715
|
payerWallet: payerWallet,
|
|
1657
1716
|
beneficiary: params.beneficiary ?? payerWallet.address,
|
|
1658
|
-
nonce:
|
|
1717
|
+
nonce: currentNonce,
|
|
1659
1718
|
gasLimit: effConfig.gasLimit,
|
|
1660
1719
|
gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
|
|
1661
1720
|
});
|
|
1662
1721
|
signedTransactions.push(signedDexTx);
|
|
1722
|
+
currentNonce++;
|
|
1663
1723
|
}
|
|
1664
1724
|
// --- 5. 利润提取 (Direct EOA transfer) ---
|
|
1665
1725
|
let totalProfitWei = 0n;
|
|
@@ -1669,12 +1729,11 @@ export class BundleExecutor {
|
|
|
1669
1729
|
totalProfitWei += calculateProfitWei(totalDexBuyWei, profitSettings.profitBps);
|
|
1670
1730
|
}
|
|
1671
1731
|
if (totalProfitWei > 0n) {
|
|
1672
|
-
const profitTxNonce = startNonce + (enableDexBuy ? 2 : 1);
|
|
1673
1732
|
const signedProfitTx = await this.signProfitTransaction({
|
|
1674
1733
|
payerWallet,
|
|
1675
1734
|
profitWei: totalProfitWei,
|
|
1676
1735
|
recipient: profitSettings.profitRecipient,
|
|
1677
|
-
nonce:
|
|
1736
|
+
nonce: currentNonce
|
|
1678
1737
|
});
|
|
1679
1738
|
signedTransactions.push(signedProfitTx);
|
|
1680
1739
|
}
|
|
@@ -74,6 +74,6 @@ export declare const PORTAL_ABI: readonly ["function swapExactInput((address inp
|
|
|
74
74
|
export declare const ERC20_ABI: readonly ["function balanceOf(address account) view returns (uint256)", "function allowance(address owner, address spender) view returns (uint256)", "function approve(address spender, uint256 amount) returns (bool)", "function transfer(address to, uint256 amount) returns (bool)", "function decimals() view returns (uint8)", "function symbol() view returns (string)", "function name() view returns (string)"];
|
|
75
75
|
/** PotatoSwap V2 Router ABI */
|
|
76
76
|
export declare const POTATOSWAP_V2_ROUTER_ABI: readonly ["function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)", "function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)", "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)", "function swapExactETHForTokensSupportingFeeOnTransferTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable", "function swapExactTokensForETHSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external", "function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external", "function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)", "function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts)", "function WETH() external pure returns (address)"];
|
|
77
|
-
/** PotatoSwap V3 Router ABI (
|
|
78
|
-
export declare const POTATOSWAP_V3_ROUTER_ABI: readonly ["function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)", "function exactOutputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountIn)"];
|
|
77
|
+
/** PotatoSwap V3 Router ABI (SwapRouter - Legacy 版本,deadline 在 struct 内部) */
|
|
78
|
+
export declare const POTATOSWAP_V3_ROUTER_ABI: readonly ["function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)", "function exactOutputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountIn)", "function multicall(bytes[] calldata data) external payable returns (bytes[] memory results)", "function unwrapWETH9(uint256 amountMinimum, address recipient) external payable", "function refundETH() external payable"];
|
|
79
79
|
export type HexString = `0x${string}`;
|
package/dist/xlayer/constants.js
CHANGED
|
@@ -146,8 +146,15 @@ export const POTATOSWAP_V2_ROUTER_ABI = [
|
|
|
146
146
|
'function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts)',
|
|
147
147
|
'function WETH() external pure returns (address)',
|
|
148
148
|
];
|
|
149
|
-
/** PotatoSwap V3 Router ABI (
|
|
149
|
+
/** PotatoSwap V3 Router ABI (SwapRouter - Legacy 版本,deadline 在 struct 内部) */
|
|
150
150
|
export const POTATOSWAP_V3_ROUTER_ABI = [
|
|
151
|
+
// exactInputSingle - 单跳交换,deadline 在 params 内部(Legacy 版本)
|
|
151
152
|
'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountOut)',
|
|
152
153
|
'function exactOutputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountIn)',
|
|
154
|
+
// multicall - 打包多个调用(Legacy 版本不含 deadline 参数)
|
|
155
|
+
'function multicall(bytes[] calldata data) external payable returns (bytes[] memory results)',
|
|
156
|
+
// unwrapWETH9 - 将 WOKB 转换为 OKB(用于卖出后获取原生币)
|
|
157
|
+
'function unwrapWETH9(uint256 amountMinimum, address recipient) external payable',
|
|
158
|
+
// refundETH - 退还多余的 ETH(用于买入时的找零)
|
|
159
|
+
'function refundETH() external payable',
|
|
153
160
|
];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Wallet, ethers } from 'ethers';
|
|
5
5
|
import { AANonceMap, } from './types.js';
|
|
6
|
-
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, WOKB, MULTICALL3, } from './constants.js';
|
|
6
|
+
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, WOKB, MULTICALL3, } from './constants.js';
|
|
7
7
|
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
8
8
|
import { encodeApproveCall, lpFeeProfileToV3Fee, } from './portal-ops.js';
|
|
9
9
|
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
|
|
@@ -12,6 +12,64 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
|
12
12
|
const multicallIface = new ethers.Interface([
|
|
13
13
|
'function aggregate3Value((address target,bool allowFailure,uint256 value,bytes callData)[] calls) payable returns ((bool success,bytes returnData)[] returnData)',
|
|
14
14
|
]);
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// V3 Slot0 报价(用于 XLayer 没有 V3 Quoter 的情况)
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const V3_FACTORY_ABI = [
|
|
19
|
+
'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)',
|
|
20
|
+
];
|
|
21
|
+
const V3_POOL_ABI = [
|
|
22
|
+
'function slot0() view returns (uint160 sqrtPriceX96,int24 tick,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)',
|
|
23
|
+
'function token0() view returns (address)',
|
|
24
|
+
'function token1() view returns (address)',
|
|
25
|
+
];
|
|
26
|
+
const V3_FEE_DENOMINATOR = 1000000n;
|
|
27
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
28
|
+
/**
|
|
29
|
+
* 使用 V3 Pool 的 slot0 获取 Token → WOKB 的报价
|
|
30
|
+
* 这是一个现货价计算,比 V3 Quoter 更简单但精度稍低
|
|
31
|
+
*/
|
|
32
|
+
async function quoteV3ViaSlot0(params) {
|
|
33
|
+
try {
|
|
34
|
+
const { provider, tokenIn, amountIn, fee } = params;
|
|
35
|
+
if (!tokenIn || amountIn <= 0n)
|
|
36
|
+
return 0n;
|
|
37
|
+
const tokenInLower = tokenIn.toLowerCase();
|
|
38
|
+
const wokbLower = WOKB.toLowerCase();
|
|
39
|
+
if (tokenInLower === wokbLower)
|
|
40
|
+
return amountIn;
|
|
41
|
+
const factory = new ethers.Contract(POTATOSWAP_V3_FACTORY, V3_FACTORY_ABI, provider);
|
|
42
|
+
const poolAddr = await factory.getPool(tokenIn, WOKB, fee);
|
|
43
|
+
if (!poolAddr || poolAddr.toLowerCase() === ZERO_ADDRESS.toLowerCase())
|
|
44
|
+
return 0n;
|
|
45
|
+
const pool = new ethers.Contract(poolAddr, V3_POOL_ABI, provider);
|
|
46
|
+
const [t0, t1, slot0] = await Promise.all([pool.token0(), pool.token1(), pool.slot0()]);
|
|
47
|
+
if (!t0 || !t1 || !slot0)
|
|
48
|
+
return 0n;
|
|
49
|
+
const sqrtPriceX96 = BigInt(slot0[0]);
|
|
50
|
+
if (sqrtPriceX96 <= 0n)
|
|
51
|
+
return 0n;
|
|
52
|
+
// 扣除手续费
|
|
53
|
+
const amountInLessFee = (amountIn * (V3_FEE_DENOMINATOR - BigInt(fee))) / V3_FEE_DENOMINATOR;
|
|
54
|
+
if (amountInLessFee <= 0n)
|
|
55
|
+
return 0n;
|
|
56
|
+
const Q192 = 2n ** 192n;
|
|
57
|
+
const num = sqrtPriceX96 * sqrtPriceX96;
|
|
58
|
+
const t0Lower = String(t0).toLowerCase();
|
|
59
|
+
const t1Lower = String(t1).toLowerCase();
|
|
60
|
+
// sqrtPriceX96 表示 token1/token0 的现货价
|
|
61
|
+
if (tokenInLower === t0Lower && wokbLower === t1Lower) {
|
|
62
|
+
return (amountInLessFee * num) / Q192;
|
|
63
|
+
}
|
|
64
|
+
if (tokenInLower === t1Lower && wokbLower === t0Lower) {
|
|
65
|
+
return (amountInLessFee * Q192) / num;
|
|
66
|
+
}
|
|
67
|
+
return 0n;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return 0n;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
15
73
|
function chunkArray(arr, size) {
|
|
16
74
|
const out = [];
|
|
17
75
|
const n = Math.max(1, Math.floor(size));
|
|
@@ -140,15 +198,37 @@ export class AADexSwapExecutor {
|
|
|
140
198
|
needApprove = allowance < sellAmountWei;
|
|
141
199
|
}
|
|
142
200
|
// 估算卖出输出(用于利润提取与 buy 预算)
|
|
201
|
+
// ✅ V3 模式:V2 报价可能失败,需要使用 slot0 报价
|
|
143
202
|
const quotedSellOutWei = await (async () => {
|
|
203
|
+
// 如果是 V3 模式,优先使用 slot0 报价
|
|
204
|
+
if (isV3Trade) {
|
|
205
|
+
const v3Fee = lpFeeProfileToV3Fee(lpFeeProfile);
|
|
206
|
+
const slot0Quote = await quoteV3ViaSlot0({
|
|
207
|
+
provider,
|
|
208
|
+
tokenIn: tokenAddress,
|
|
209
|
+
amountIn: sellAmountWei,
|
|
210
|
+
fee: v3Fee,
|
|
211
|
+
});
|
|
212
|
+
if (slot0Quote > 0n) {
|
|
213
|
+
console.log(`[AA V3 捆绑换手] slot0 报价成功: ${ethers.formatEther(slot0Quote)} OKB`);
|
|
214
|
+
return slot0Quote;
|
|
215
|
+
}
|
|
216
|
+
// slot0 报价失败,尝试 V2 fallback
|
|
217
|
+
}
|
|
144
218
|
try {
|
|
219
|
+
// V2 报价(大多数代币同时有 V2/V3 池子)
|
|
145
220
|
return await this.dexQuery.quoteTokenToOkb(sellAmountWei, tokenAddress);
|
|
146
221
|
}
|
|
147
222
|
catch {
|
|
223
|
+
// V2 报价失败时返回 0
|
|
148
224
|
return 0n;
|
|
149
225
|
}
|
|
150
226
|
})();
|
|
151
227
|
// buyAmount:若未传则按 quotedSellOut 计算;利润从 quotedSellOut 中刮取,但要保证买入资金充足
|
|
228
|
+
// ✅ 修复:如果报价失败且未传入 buyAmountOkb,抛出明确错误
|
|
229
|
+
if (!buyAmountOkb && quotedSellOutWei === 0n) {
|
|
230
|
+
throw new Error('AA 捆绑换手:无法获取报价(V3 池子不存在或流动性不足),请明确传入 buyAmountOkb 参数');
|
|
231
|
+
}
|
|
152
232
|
const requestedBuyWei = buyAmountOkb
|
|
153
233
|
? ethers.parseEther(String(buyAmountOkb))
|
|
154
234
|
: (quotedSellOutWei * BigInt(10000 - slippageBps)) / 10000n;
|
|
@@ -177,16 +257,18 @@ export class AADexSwapExecutor {
|
|
|
177
257
|
const deadline = this.getDexDeadline();
|
|
178
258
|
let sellSwapData;
|
|
179
259
|
if (isV3Trade) {
|
|
180
|
-
// V3: 使用 exactInputSingle
|
|
260
|
+
// V3: 使用 multicall(exactInputSingle + unwrapWETH9)
|
|
261
|
+
// 注意:V3 卖出得到的是 WOKB,需要 unwrap 成 OKB
|
|
181
262
|
sellSwapData = encodeSwapExactTokensForETHV3({
|
|
182
263
|
tokenIn: tokenAddress,
|
|
183
264
|
tokenOut: WOKB,
|
|
184
265
|
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
185
|
-
recipient: sellerSender,
|
|
266
|
+
recipient: sellerSender, // 会被函数内部替换为 ADDRESS_THIS
|
|
186
267
|
deadline,
|
|
187
268
|
amountIn: sellAmountWei,
|
|
188
269
|
amountOutMinimum: 0n,
|
|
189
270
|
sqrtPriceLimitX96: 0n,
|
|
271
|
+
unwrapRecipient: sellerSender, // ✅ unwrap 后发送到 sellerSender
|
|
190
272
|
});
|
|
191
273
|
}
|
|
192
274
|
else {
|
|
@@ -368,16 +450,18 @@ export class AADexSwapExecutor {
|
|
|
368
450
|
const deadline = this.getDexDeadline();
|
|
369
451
|
let sellSwapData;
|
|
370
452
|
if (isV3Trade) {
|
|
371
|
-
// V3: 使用 exactInputSingle
|
|
453
|
+
// V3: 使用 multicall(exactInputSingle + unwrapWETH9)
|
|
454
|
+
// 注意:V3 卖出得到的是 WOKB,需要 unwrap 成 OKB
|
|
372
455
|
sellSwapData = encodeSwapExactTokensForETHV3({
|
|
373
456
|
tokenIn: tokenAddress,
|
|
374
457
|
tokenOut: WOKB,
|
|
375
458
|
fee: lpFeeProfileToV3Fee(lpFeeProfile),
|
|
376
|
-
recipient: sellerAi.sender,
|
|
459
|
+
recipient: sellerAi.sender, // 会被函数内部替换为 ADDRESS_THIS
|
|
377
460
|
deadline,
|
|
378
461
|
amountIn: sellAmountWei,
|
|
379
462
|
amountOutMinimum: 0n,
|
|
380
463
|
sqrtPriceLimitX96: 0n,
|
|
464
|
+
unwrapRecipient: sellerAi.sender, // ✅ unwrap 后发送到 sellerAi.sender
|
|
381
465
|
});
|
|
382
466
|
}
|
|
383
467
|
else {
|
|
@@ -398,7 +482,22 @@ export class AADexSwapExecutor {
|
|
|
398
482
|
const buyAmountsWei = buyAmountsOkb.map(a => ethers.parseEther(a));
|
|
399
483
|
const totalBuyWei = buyAmountsWei.reduce((a, b) => a + (b ?? 0n), 0n);
|
|
400
484
|
// Profit op:估算卖出输出,按比例刮取(但必须保证分发/买入资金充足)
|
|
485
|
+
// ✅ V3 模式:优先使用 slot0 报价
|
|
401
486
|
const quotedSellOutWei = await (async () => {
|
|
487
|
+
// 如果是 V3 模式,优先使用 slot0 报价
|
|
488
|
+
if (isV3Trade) {
|
|
489
|
+
const v3Fee = lpFeeProfileToV3Fee(lpFeeProfile);
|
|
490
|
+
const slot0Quote = await quoteV3ViaSlot0({
|
|
491
|
+
provider,
|
|
492
|
+
tokenIn: tokenAddress,
|
|
493
|
+
amountIn: sellAmountWei,
|
|
494
|
+
fee: v3Fee,
|
|
495
|
+
});
|
|
496
|
+
if (slot0Quote > 0n) {
|
|
497
|
+
console.log(`[AA V3 批量换手] slot0 报价成功: ${ethers.formatEther(slot0Quote)} OKB`);
|
|
498
|
+
return slot0Quote;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
402
501
|
try {
|
|
403
502
|
return await this.dexQuery.quoteTokenToOkb(sellAmountWei, tokenAddress);
|
|
404
503
|
}
|
|
@@ -306,6 +306,7 @@ export class DexBundleExecutor {
|
|
|
306
306
|
const sellNonce = nonceMap.next(sender);
|
|
307
307
|
let sellSwapData;
|
|
308
308
|
if (isV3) {
|
|
309
|
+
// ✅ V3 卖出需要 unwrap WOKB 到 OKB
|
|
309
310
|
sellSwapData = encodeSwapExactTokensForETHV3({
|
|
310
311
|
tokenIn: tokenAddress,
|
|
311
312
|
tokenOut: WOKB,
|
|
@@ -315,6 +316,7 @@ export class DexBundleExecutor {
|
|
|
315
316
|
amountIn: sellAmount,
|
|
316
317
|
amountOutMinimum: 0n,
|
|
317
318
|
sqrtPriceLimitX96: 0n,
|
|
319
|
+
unwrapRecipient: sender, // ✅ 添加 unwrap 接收者
|
|
318
320
|
});
|
|
319
321
|
}
|
|
320
322
|
else {
|
package/dist/xlayer/dex.d.ts
CHANGED
|
@@ -7,21 +7,10 @@
|
|
|
7
7
|
* - 通过 AA 账户执行
|
|
8
8
|
*/
|
|
9
9
|
import type { XLayerConfig, DexSwapResult } from './types.js';
|
|
10
|
-
export declare function encodeSwapExactETHForTokensV3(params: {
|
|
11
|
-
tokenIn: string;
|
|
12
|
-
tokenOut: string;
|
|
13
|
-
fee: number;
|
|
14
|
-
recipient: string;
|
|
15
|
-
deadline: number;
|
|
16
|
-
amountIn: bigint;
|
|
17
|
-
amountOutMinimum: bigint;
|
|
18
|
-
sqrtPriceLimitX96: bigint;
|
|
19
|
-
}): string;
|
|
20
10
|
/**
|
|
21
|
-
*
|
|
22
|
-
* 对齐 Uniswap V3 的 exactInputSingle 逻辑
|
|
11
|
+
* V3 exactInputSingle 参数结构(Legacy 版本,包含 deadline)
|
|
23
12
|
*/
|
|
24
|
-
|
|
13
|
+
interface V3ExactInputSingleParams {
|
|
25
14
|
tokenIn: string;
|
|
26
15
|
tokenOut: string;
|
|
27
16
|
fee: number;
|
|
@@ -30,6 +19,37 @@ export declare function encodeSwapExactTokensForETHV3(params: {
|
|
|
30
19
|
amountIn: bigint;
|
|
31
20
|
amountOutMinimum: bigint;
|
|
32
21
|
sqrtPriceLimitX96: bigint;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 编码 unwrapWETH9 调用(将 WOKB 转换为 OKB)
|
|
25
|
+
*/
|
|
26
|
+
export declare function encodeUnwrapWETH9(amountMinimum: bigint, recipient: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* 编码 refundETH 调用(退还多余的 ETH)
|
|
29
|
+
*/
|
|
30
|
+
export declare function encodeRefundETH(): string;
|
|
31
|
+
/**
|
|
32
|
+
* 编码 V3 multicall 调用(Legacy 版本,不含 deadline)
|
|
33
|
+
*/
|
|
34
|
+
export declare function encodeV3Multicall(data: string[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* ✅ 编码 V3 买入调用(OKB → Token)
|
|
37
|
+
*
|
|
38
|
+
* 对于 AA 模式:直接返回 exactInputSingle 编码(不需要 multicall 包装)
|
|
39
|
+
* - AA 的 execute 会正确传递 msg.value
|
|
40
|
+
* - V3 Router 会自动将原生币包装为 WOKB
|
|
41
|
+
*
|
|
42
|
+
* 对于 EOA 模式:调用者需要自己用 multicall 包装
|
|
43
|
+
*/
|
|
44
|
+
export declare function encodeSwapExactETHForTokensV3(params: V3ExactInputSingleParams): string;
|
|
45
|
+
/**
|
|
46
|
+
* ✅ 编码 V3 卖出调用(Token → OKB)
|
|
47
|
+
* 使用 multicall 包装 exactInputSingle + unwrapWETH9
|
|
48
|
+
*
|
|
49
|
+
* 注意:V3 卖出返回的是 WOKB(ERC20),需要调用 unwrapWETH9 转换为 OKB
|
|
50
|
+
*/
|
|
51
|
+
export declare function encodeSwapExactTokensForETHV3(params: V3ExactInputSingleParams & {
|
|
52
|
+
unwrapRecipient?: string;
|
|
33
53
|
}): string;
|
|
34
54
|
/**
|
|
35
55
|
* 编码 swapExactETHForTokens 调用
|
|
@@ -190,3 +210,4 @@ export declare function quoteOkbToToken(okbAmount: bigint, tokenAddress: string,
|
|
|
190
210
|
* 快速获取 Token -> OKB 报价
|
|
191
211
|
*/
|
|
192
212
|
export declare function quoteTokenToOkb(tokenAmount: bigint, tokenAddress: string, config?: DexConfig): Promise<bigint>;
|
|
213
|
+
export {};
|
package/dist/xlayer/dex.js
CHANGED
|
@@ -15,15 +15,62 @@ import { encodeApproveCall, parseOkb, formatOkb } from './portal-ops.js';
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
const routerIface = new Interface(POTATOSWAP_V2_ROUTER_ABI);
|
|
17
17
|
const v3RouterIface = new Interface(POTATOSWAP_V3_ROUTER_ABI);
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 编码 exactInputSingle 调用(内部使用)
|
|
20
|
+
*/
|
|
21
|
+
function encodeExactInputSingle(params) {
|
|
19
22
|
return v3RouterIface.encodeFunctionData('exactInputSingle', [params]);
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
22
|
-
* 编码
|
|
23
|
-
|
|
25
|
+
* 编码 unwrapWETH9 调用(将 WOKB 转换为 OKB)
|
|
26
|
+
*/
|
|
27
|
+
export function encodeUnwrapWETH9(amountMinimum, recipient) {
|
|
28
|
+
return v3RouterIface.encodeFunctionData('unwrapWETH9', [amountMinimum, recipient]);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 编码 refundETH 调用(退还多余的 ETH)
|
|
32
|
+
*/
|
|
33
|
+
export function encodeRefundETH() {
|
|
34
|
+
return v3RouterIface.encodeFunctionData('refundETH', []);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 编码 V3 multicall 调用(Legacy 版本,不含 deadline)
|
|
38
|
+
*/
|
|
39
|
+
export function encodeV3Multicall(data) {
|
|
40
|
+
return v3RouterIface.encodeFunctionData('multicall', [data]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* ✅ 编码 V3 买入调用(OKB → Token)
|
|
44
|
+
*
|
|
45
|
+
* 对于 AA 模式:直接返回 exactInputSingle 编码(不需要 multicall 包装)
|
|
46
|
+
* - AA 的 execute 会正确传递 msg.value
|
|
47
|
+
* - V3 Router 会自动将原生币包装为 WOKB
|
|
48
|
+
*
|
|
49
|
+
* 对于 EOA 模式:调用者需要自己用 multicall 包装
|
|
50
|
+
*/
|
|
51
|
+
export function encodeSwapExactETHForTokensV3(params) {
|
|
52
|
+
// 直接返回 exactInputSingle 编码
|
|
53
|
+
// V3 Router 会自动处理原生币 → WOKB 的转换
|
|
54
|
+
return encodeExactInputSingle(params);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* ✅ 编码 V3 卖出调用(Token → OKB)
|
|
58
|
+
* 使用 multicall 包装 exactInputSingle + unwrapWETH9
|
|
59
|
+
*
|
|
60
|
+
* 注意:V3 卖出返回的是 WOKB(ERC20),需要调用 unwrapWETH9 转换为 OKB
|
|
24
61
|
*/
|
|
25
62
|
export function encodeSwapExactTokensForETHV3(params) {
|
|
26
|
-
|
|
63
|
+
// V3 卖出需要:1) 先 swap 到 Router 地址;2) 再 unwrap 到最终接收者
|
|
64
|
+
const ADDRESS_THIS = '0x0000000000000000000000000000000000000002'; // V3 Router 的 ADDRESS_THIS
|
|
65
|
+
const swapParams = {
|
|
66
|
+
...params,
|
|
67
|
+
recipient: ADDRESS_THIS, // 先发送到 Router 内部
|
|
68
|
+
};
|
|
69
|
+
const swapData = encodeExactInputSingle(swapParams);
|
|
70
|
+
// unwrap 到最终接收者
|
|
71
|
+
const finalRecipient = params.unwrapRecipient || params.recipient;
|
|
72
|
+
const unwrapData = encodeUnwrapWETH9(0n, finalRecipient); // amountMinimum = 0,不做最小值检查
|
|
73
|
+
return encodeV3Multicall([swapData, unwrapData]);
|
|
27
74
|
}
|
|
28
75
|
/**
|
|
29
76
|
* 编码 swapExactETHForTokens 调用
|