four-flap-meme-sdk 1.5.91 → 1.5.93

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.
@@ -14,7 +14,7 @@ import { encodeBuyCall, encodeBuyCallV3, encodeSellCall, encodeApproveCall, enco
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';
17
- import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactETHForTokensV3, } from './dex.js';
17
+ import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForETHV3, } from './dex.js';
18
18
  // ============================================================================
19
19
  // AA Nonce(EntryPoint nonce)本地分配器
20
20
  // ============================================================================
@@ -557,6 +557,10 @@ export class BundleExecutor {
557
557
  if (profitSettings.extractProfit && nativeProfitAmount > 0n) {
558
558
  console.log(`[利润提取] 总利润: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
559
559
  }
560
+ // ✅ 获取代币状态,判断是走内盘 Portal 还是外盘 DEX
561
+ const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
562
+ const isGraduated = tokenState.status === 4; // DEX = 4 表示已毕业
563
+ console.log(`[BundleBuy] Token status: ${tokenState.status} (${isGraduated ? 'DEX/已毕业' : '内盘'})`);
560
564
  // 1. 预取账户信息(并行),并批量估算 gas(减少对 bundler 的 N 次请求)
561
565
  const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
562
566
  const nonceMap = new AANonceMap();
@@ -575,30 +579,62 @@ export class BundleExecutor {
575
579
  // TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
576
580
  }
577
581
  });
578
- // ✅ 构建买入 callData:如果使用 ERC20 代币,需要先 approve
579
- const buyCallDatas = buyWeis.map((buyWei) => {
580
- // 使用 portal 的 swapExactInput,支持 inputToken 参数
581
- const portalIface = new Interface([
582
- 'function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)',
583
- ]);
584
- const swapData = portalIface.encodeFunctionData('swapExactInput', [
585
- {
586
- inputToken,
587
- outputToken: tokenAddress,
588
- inputAmount: buyWei,
589
- minOutputAmount: 0n,
590
- permitData: '0x',
591
- },
592
- ]);
593
- // ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
594
- if (useNativeToken) {
595
- // 原生代币:直接 swap
596
- return encodeExecute(this.portalAddress, buyWei, swapData);
582
+ // ✅ 构建买入 callData:根据代币状态选择 Portal(内盘)或 DEX(外盘)
583
+ const buyCallDatas = buyWeis.map((buyWei, i) => {
584
+ if (isGraduated) {
585
+ // 已毕业:使用 DEX Router 买入
586
+ const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
587
+ const dexType = (tokenState.lpFeeProfile !== undefined && tokenState.lpFeeProfile >= 0) ? 'V3' : 'V2';
588
+ const recipient = accountInfos[i].sender;
589
+ let swapData;
590
+ let routerAddress;
591
+ if (dexType === 'V3') {
592
+ routerAddress = POTATOSWAP_V3_ROUTER;
593
+ const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
594
+ swapData = encodeSwapExactETHForTokensV3({
595
+ tokenIn: WOKB,
596
+ tokenOut: tokenAddress,
597
+ fee: v3Fee,
598
+ recipient,
599
+ deadline,
600
+ amountIn: buyWei,
601
+ amountOutMinimum: 0n,
602
+ sqrtPriceLimitX96: 0n
603
+ });
604
+ }
605
+ else {
606
+ routerAddress = POTATOSWAP_V2_ROUTER;
607
+ swapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], recipient, deadline);
608
+ }
609
+ if (i < 3) {
610
+ console.log(`[BundleBuy] Wallet[${i}]: 使用 ${dexType} DEX Router 买入, fee=${dexType === 'V3' ? lpFeeProfileToV3Fee(tokenState.lpFeeProfile) : 'N/A'}`);
611
+ }
612
+ return encodeExecute(routerAddress, buyWei, swapData);
597
613
  }
598
614
  else {
599
- // ERC20 代币:approve + swap 批量调用
600
- const approveData = encodeApproveCall(this.portalAddress, buyWei);
601
- return encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
615
+ // 未毕业:使用 Portal swapExactInput
616
+ const portalIface = new Interface([
617
+ 'function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)',
618
+ ]);
619
+ const swapData = portalIface.encodeFunctionData('swapExactInput', [
620
+ {
621
+ inputToken,
622
+ outputToken: tokenAddress,
623
+ inputAmount: buyWei,
624
+ minOutputAmount: 0n,
625
+ permitData: '0x',
626
+ },
627
+ ]);
628
+ // ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
629
+ if (useNativeToken) {
630
+ // 原生代币:直接 swap
631
+ return encodeExecute(this.portalAddress, buyWei, swapData);
632
+ }
633
+ else {
634
+ // ERC20 代币:approve + swap 批量调用
635
+ const approveData = encodeApproveCall(this.portalAddress, buyWei);
636
+ return encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
637
+ }
602
638
  }
603
639
  });
604
640
  const initCodes = accountInfos.map((ai, i) => ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address));
@@ -730,6 +766,10 @@ export class BundleExecutor {
730
766
  console.log('token:', tokenAddress);
731
767
  console.log('owners:', wallets.length);
732
768
  console.log('sellPercent:', sellPercent);
769
+ // ✅ 获取代币状态,判断是走内盘 Portal 还是外盘 DEX
770
+ const tokenState = await this.portalQuery.getTokenV7(tokenAddress);
771
+ const isGraduated = tokenState.status === 4; // DEX = 4 表示已毕业
772
+ console.log(`[BundleSell] Token status: ${tokenState.status} (${isGraduated ? 'DEX/已毕业' : '内盘'})`);
733
773
  // ✅ 批量获取 accountInfo(含 sender/nonce/deployed),避免循环内重复 getAccountInfo
734
774
  const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
735
775
  const senders = accountInfos.map((ai) => ai.sender);
@@ -757,18 +797,71 @@ export class BundleExecutor {
757
797
  touched[i] = true;
758
798
  }
759
799
  if (sellItems.length > 0) {
760
- const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
761
- const i = it.i;
762
- const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode, false, // needApprove=false,假设已预先授权
763
- `owner${i + 1}`);
764
- return signed.userOp;
765
- });
766
- sellOps.push(...signedSells);
800
+ const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
801
+ if (isGraduated) {
802
+ // 已毕业:使用 DEX Router 卖出
803
+ const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
804
+ const dexType = (tokenState.lpFeeProfile !== undefined && tokenState.lpFeeProfile >= 0) ? 'V3' : 'V2';
805
+ const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
806
+ const i = it.i;
807
+ let swapData;
808
+ let routerAddress;
809
+ if (dexType === 'V3') {
810
+ routerAddress = POTATOSWAP_V3_ROUTER;
811
+ const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
812
+ swapData = encodeSwapExactTokensForETHV3({
813
+ tokenIn: tokenAddress,
814
+ tokenOut: WOKB,
815
+ fee: v3Fee,
816
+ recipient: it.sender,
817
+ deadline,
818
+ amountIn: it.sellAmount,
819
+ amountOutMinimum: 0n,
820
+ sqrtPriceLimitX96: 0n
821
+ });
822
+ }
823
+ else {
824
+ routerAddress = POTATOSWAP_V2_ROUTER;
825
+ swapData = encodeSwapExactTokensForETHSupportingFee(it.sellAmount, 0n, [tokenAddress, WOKB], it.sender, deadline);
826
+ }
827
+ if (i < 3) {
828
+ console.log(`[BundleSell] Wallet[${i}]: 使用 ${dexType} DEX Router 卖出, fee=${dexType === 'V3' ? lpFeeProfileToV3Fee(tokenState.lpFeeProfile) : 'N/A'}`);
829
+ }
830
+ const sellCallData = encodeExecute(routerAddress, 0n, swapData);
831
+ // 确保有足够余额
832
+ await this.aaManager.ensureSenderBalance(wallets[i], it.sender, parseOkb('0.0003'), `owner${i + 1}/sell-prefund`);
833
+ const { userOp } = await this.aaManager.buildUserOpWithFixedGas({
834
+ ownerWallet: wallets[i],
835
+ sender: it.sender,
836
+ callData: sellCallData,
837
+ nonce: it.nonce,
838
+ initCode: it.initCode,
839
+ deployed: it.initCode === '0x',
840
+ fixedGas: {
841
+ ...(effConfig.fixedGas ?? {}),
842
+ callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
843
+ },
844
+ });
845
+ const signed = await this.aaManager.signUserOp(userOp, wallets[i]);
846
+ return signed.userOp;
847
+ });
848
+ sellOps.push(...signedSells);
849
+ }
850
+ else {
851
+ // ✅ 未毕业:使用 Portal 卖出
852
+ const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
853
+ const i = it.i;
854
+ const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode, false, // needApprove=false,假设已预先授权
855
+ `owner${i + 1}`);
856
+ return signed.userOp;
857
+ });
858
+ sellOps.push(...signedSells);
859
+ }
767
860
  }
768
- const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
861
+ const effConfigForSell = { ...(this.config ?? {}), ...(config ?? {}) };
769
862
  const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary, {
770
- gasLimit: effConfig.gasLimit,
771
- gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
863
+ gasLimit: effConfigForSell.gasLimit,
864
+ gasPrice: effConfigForSell.minGasPriceGwei ? ethers.parseUnits(effConfigForSell.minGasPriceGwei.toString(), 'gwei') : undefined
772
865
  });
773
866
  if (!sellResult) {
774
867
  throw new Error('卖出交易失败');
@@ -819,8 +912,8 @@ export class BundleExecutor {
819
912
  }
820
913
  if (withdrawOps.length > 0) {
821
914
  withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary, {
822
- gasLimit: effConfig.gasLimit,
823
- gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
915
+ gasLimit: effConfigForSell.gasLimit,
916
+ gasPrice: effConfigForSell.minGasPriceGwei ? ethers.parseUnits(effConfigForSell.minGasPriceGwei.toString(), 'gwei') : undefined
824
917
  }) ?? undefined;
825
918
  }
826
919
  }
@@ -1736,7 +1829,7 @@ export class BundleExecutor {
1736
1829
  // ✅ 判断是否是最后一笔内盘买入(触发毕业的那笔)
1737
1830
  const isGraduationTrigger = (i === curveBuyers.length - 1);
1738
1831
  const callGasLimit = isGraduationTrigger
1739
- ? 6500000n // 毕业触发交易需要更高 gas
1832
+ ? 8000000n // 毕业触发交易需要更高 gas
1740
1833
  : (effConfig.fixedGas?.callGasLimit ?? 500000n); // 其他买入使用前端配置或默认值
1741
1834
  const buyOpRes = await aaManager.buildUserOpWithFixedGas({
1742
1835
  ownerWallet: wallet,
@@ -1773,10 +1866,13 @@ export class BundleExecutor {
1773
1866
  let routerAddress;
1774
1867
  if (dexType === 'V3') {
1775
1868
  routerAddress = POTATOSWAP_V3_ROUTER;
1869
+ // ✅ 使用 getTokenV7 获取的 lpFeeProfile 动态计算 V3 fee
1870
+ // lpFeeProfile: 0 → 2500 (0.25%), 1 → 100 (0.01%), 2 → 10000 (1%)
1871
+ const v3Fee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
1776
1872
  swapData = encodeSwapExactETHForTokensV3({
1777
1873
  tokenIn: WOKB,
1778
1874
  tokenOut: tokenAddress,
1779
- fee: lpFeeProfileToV3Fee(tokenState.lpFeeProfile),
1875
+ fee: v3Fee,
1780
1876
  recipient: info.sender,
1781
1877
  deadline,
1782
1878
  amountIn: amount,
@@ -69,7 +69,7 @@ export declare const ENTRYPOINT_ABI: readonly ["function getNonce(address sender
69
69
  /** SimpleAccount ABI */
70
70
  export declare const SIMPLE_ACCOUNT_ABI: readonly ["function execute(address dest, uint256 value, bytes func) external", "function executeBatch(address[] calldata dest, bytes[] calldata func) external", "function executeBatch(address[] calldata dest, uint256[] calldata values, bytes[] calldata func) external"];
71
71
  /** Flap Portal ABI */
72
- export declare const PORTAL_ABI: readonly ["function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)", "function swapExactInputV3((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData,bytes extensionData)) external payable returns (uint256)", "function buy(address token, address to, uint256 minAmount) external payable returns (uint256)", "function sell(address token, uint256 amount, uint256 minEth) external returns (uint256)", "function quoteExactInput((address inputToken,address outputToken,uint256 inputAmount)) external returns (uint256)", "function previewBuy(address token, uint256 ethAmount) external view returns (uint256)", "function previewSell(address token, uint256 tokenAmount) external view returns (uint256)", "function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))", "function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint8,uint8))", "function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData) params) external payable returns (address)", "function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData) params) external payable returns (address)", "function newTokenV4((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData,uint8 dexId,uint8 lpFeeProfile) params) external payable returns (address)"];
72
+ export declare const PORTAL_ABI: readonly ["function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData) params) payable returns (uint256)", "function swapExactInputV3((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData,bytes extensionData)) external payable returns (uint256)", "function buy(address token, address to, uint256 minAmount) external payable returns (uint256)", "function sell(address token, uint256 amount, uint256 minEth) external returns (uint256)", "function quoteExactInput((address inputToken,address outputToken,uint256 inputAmount)) external returns (uint256)", "function previewBuy(address token, uint256 ethAmount) external view returns (uint256)", "function previewSell(address token, uint256 tokenAmount) external view returns (uint256)", "function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))", "function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint16,address,uint256,uint8))", "function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData) params) external payable returns (address)", "function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData) params) external payable returns (address)", "function newTokenV4((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData,uint8 dexId,uint8 lpFeeProfile) params) external payable returns (address)"];
73
73
  /** ERC20 ABI */
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 */
@@ -115,7 +115,8 @@ export const PORTAL_ABI = [
115
115
  'function previewBuy(address token, uint256 ethAmount) external view returns (uint256)',
116
116
  'function previewSell(address token, uint256 tokenAmount) external view returns (uint256)',
117
117
  'function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))',
118
- 'function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint8,uint8))',
118
+ // ✅ 修复:getTokenV7 返回 16 个字段,与 BSC portal.ts 一致
119
+ 'function getTokenV7(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32,uint16,address,uint256,uint8))',
119
120
  'function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData) params) external payable returns (address)',
120
121
  'function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData) params) external payable returns (address)',
121
122
  'function newTokenV4((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData,uint8 dexId,uint8 lpFeeProfile) params) external payable returns (address)',
@@ -180,6 +180,8 @@ export declare class PortalQuery {
180
180
  }>;
181
181
  /**
182
182
  * 获取代币状态 V7 (包含 lpFeeProfile)
183
+ *
184
+ * ✅ 修复:与 BSC portal.ts 对齐,使用 16 字段结构
183
185
  */
184
186
  getTokenV7(tokenAddress: string): Promise<{
185
187
  status: number;
@@ -194,7 +196,9 @@ export declare class PortalQuery {
194
196
  quoteTokenAddress: string;
195
197
  nativeToQuoteSwapEnabled: boolean;
196
198
  extensionID: string;
197
- dexId: number;
199
+ taxRate: number;
200
+ pool: string;
201
+ progress: bigint;
198
202
  lpFeeProfile: number;
199
203
  }>;
200
204
  /**
@@ -224,6 +224,8 @@ export class PortalQuery {
224
224
  }
225
225
  /**
226
226
  * 获取代币状态 V7 (包含 lpFeeProfile)
227
+ *
228
+ * ✅ 修复:与 BSC portal.ts 对齐,使用 16 字段结构
227
229
  */
228
230
  async getTokenV7(tokenAddress) {
229
231
  const raw = await this.portal.getTokenV7(tokenAddress);
@@ -240,8 +242,10 @@ export class PortalQuery {
240
242
  quoteTokenAddress: raw[9],
241
243
  nativeToQuoteSwapEnabled: raw[10],
242
244
  extensionID: raw[11],
243
- dexId: Number(raw[12]),
244
- lpFeeProfile: Number(raw[13]),
245
+ taxRate: Number(raw[12]),
246
+ pool: raw[13],
247
+ progress: raw[14],
248
+ lpFeeProfile: Number(raw[15]),
245
249
  };
246
250
  }
247
251
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.5.91",
3
+ "version": "1.5.93",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",