four-flap-meme-sdk 1.6.25 → 1.6.28
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/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/xlayer/aa-transfer-profit.js +43 -2
- package/dist/xlayer/bundle.js +9 -5
- package/dist/xlayer/dex-bundle-swap.js +23 -2
- package/dist/xlayer/portal-bundle-swap.js +23 -2
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECDH + AES-GCM 加密工具(浏览器兼容)
|
|
3
|
+
* 用于将签名交易用服务器公钥加密
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 用服务器公钥加密签名交易(ECDH + AES-GCM)
|
|
7
|
+
*
|
|
8
|
+
* @param signedTransactions 签名后的交易数组
|
|
9
|
+
* @param publicKeyBase64 服务器提供的公钥(Base64 格式)
|
|
10
|
+
* @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
|
|
11
|
+
*/
|
|
12
|
+
export declare function encryptWithPublicKey(signedTransactions: string[], publicKeyBase64: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* 验证公钥格式(Base64)
|
|
15
|
+
*/
|
|
16
|
+
export declare function validatePublicKey(publicKeyBase64: string): boolean;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECDH + AES-GCM 加密工具(浏览器兼容)
|
|
3
|
+
* 用于将签名交易用服务器公钥加密
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 获取全局 crypto 对象(最简单直接的方式)
|
|
7
|
+
*/
|
|
8
|
+
function getCryptoAPI() {
|
|
9
|
+
// 尝试所有可能的全局对象,优先浏览器环境
|
|
10
|
+
const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
|
|
11
|
+
(typeof self !== 'undefined' && self.crypto) ||
|
|
12
|
+
(typeof global !== 'undefined' && global.crypto) ||
|
|
13
|
+
(typeof globalThis !== 'undefined' && globalThis.crypto);
|
|
14
|
+
if (!cryptoObj) {
|
|
15
|
+
const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
|
|
16
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
17
|
+
throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
|
|
18
|
+
'请确保在 HTTPS 或 localhost 下运行');
|
|
19
|
+
}
|
|
20
|
+
return cryptoObj;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 获取 SubtleCrypto(用于加密操作)
|
|
24
|
+
*/
|
|
25
|
+
function getSubtleCrypto() {
|
|
26
|
+
const crypto = getCryptoAPI();
|
|
27
|
+
if (!crypto.subtle) {
|
|
28
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
29
|
+
const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
|
|
30
|
+
throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
|
|
31
|
+
'请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
|
|
32
|
+
'3) 不在无痕/隐私浏览模式下');
|
|
33
|
+
}
|
|
34
|
+
return crypto.subtle;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Base64 转 ArrayBuffer(优先使用浏览器 API)
|
|
38
|
+
*/
|
|
39
|
+
function base64ToArrayBuffer(base64) {
|
|
40
|
+
// 浏览器环境(优先)
|
|
41
|
+
if (typeof atob !== 'undefined') {
|
|
42
|
+
const binaryString = atob(base64);
|
|
43
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
44
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
45
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
46
|
+
}
|
|
47
|
+
return bytes.buffer;
|
|
48
|
+
}
|
|
49
|
+
// Node.js 环境(fallback)
|
|
50
|
+
if (typeof Buffer !== 'undefined') {
|
|
51
|
+
return Buffer.from(base64, 'base64').buffer;
|
|
52
|
+
}
|
|
53
|
+
throw new Error('❌ Base64 解码不可用');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ArrayBuffer 转 Base64(优先使用浏览器 API)
|
|
57
|
+
*/
|
|
58
|
+
function arrayBufferToBase64(buffer) {
|
|
59
|
+
// 浏览器环境(优先)
|
|
60
|
+
if (typeof btoa !== 'undefined') {
|
|
61
|
+
const bytes = new Uint8Array(buffer);
|
|
62
|
+
let binary = '';
|
|
63
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
+
binary += String.fromCharCode(bytes[i]);
|
|
65
|
+
}
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
// Node.js 环境(fallback)
|
|
69
|
+
if (typeof Buffer !== 'undefined') {
|
|
70
|
+
return Buffer.from(buffer).toString('base64');
|
|
71
|
+
}
|
|
72
|
+
throw new Error('❌ Base64 编码不可用');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 生成随机 Hex 字符串
|
|
76
|
+
*/
|
|
77
|
+
function randomHex(length) {
|
|
78
|
+
const crypto = getCryptoAPI();
|
|
79
|
+
const array = new Uint8Array(length);
|
|
80
|
+
crypto.getRandomValues(array);
|
|
81
|
+
return Array.from(array)
|
|
82
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
83
|
+
.join('');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 用服务器公钥加密签名交易(ECDH + AES-GCM)
|
|
87
|
+
*
|
|
88
|
+
* @param signedTransactions 签名后的交易数组
|
|
89
|
+
* @param publicKeyBase64 服务器提供的公钥(Base64 格式)
|
|
90
|
+
* @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
|
|
91
|
+
*/
|
|
92
|
+
export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
|
|
93
|
+
try {
|
|
94
|
+
// 0. 获取 SubtleCrypto 和 Crypto API
|
|
95
|
+
const subtle = getSubtleCrypto();
|
|
96
|
+
const crypto = getCryptoAPI();
|
|
97
|
+
// 1. 准备数据
|
|
98
|
+
const payload = {
|
|
99
|
+
signedTransactions,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
nonce: randomHex(8)
|
|
102
|
+
};
|
|
103
|
+
const plaintext = JSON.stringify(payload);
|
|
104
|
+
// 2. 生成临时 ECDH 密钥对
|
|
105
|
+
const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
|
|
106
|
+
// 3. 导入服务器公钥
|
|
107
|
+
const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
|
|
108
|
+
const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
|
|
109
|
+
// 4. 派生共享密钥(AES-256)
|
|
110
|
+
const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
|
|
111
|
+
// 5. AES-GCM 加密
|
|
112
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
113
|
+
const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
|
|
114
|
+
// 6. 导出临时公钥
|
|
115
|
+
const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
|
|
116
|
+
// 7. 返回加密包(JSON 格式)
|
|
117
|
+
return JSON.stringify({
|
|
118
|
+
e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
|
|
119
|
+
i: arrayBufferToBase64(iv.buffer), // IV
|
|
120
|
+
d: arrayBufferToBase64(encrypted) // 密文
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new Error(`加密失败: ${error?.message || String(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 验证公钥格式(Base64)
|
|
129
|
+
*/
|
|
130
|
+
export function validatePublicKey(publicKeyBase64) {
|
|
131
|
+
try {
|
|
132
|
+
if (!publicKeyBase64)
|
|
133
|
+
return false;
|
|
134
|
+
// Base64 字符集验证
|
|
135
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
|
|
136
|
+
return false;
|
|
137
|
+
// ECDH P-256 公钥固定长度 65 字节(未压缩)
|
|
138
|
+
// Base64 编码后约 88 字符
|
|
139
|
+
if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
|
|
140
|
+
return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -147,6 +147,26 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
|
|
|
147
147
|
const fnFixed = aaManager.buildUserOpWithFixedGas;
|
|
148
148
|
if (typeof fnFixed !== 'function')
|
|
149
149
|
throw new Error('AA disperse:SDK 不支持 buildUserOpWithFixedGas');
|
|
150
|
+
// ✅ 关键修复:如果有多个 UserOps,需要确保 sender 有足够余额支付所有 prefund
|
|
151
|
+
// 多个 UserOps 在同一个 handleOps 中提交时,prefund 在 validation 阶段同时扣除
|
|
152
|
+
if (chunks.length > 1 && params.kind === 'native') {
|
|
153
|
+
const feeData = await provider.getFeeData();
|
|
154
|
+
const gasPrice = feeData.gasPrice ?? 5000000000n;
|
|
155
|
+
const estimatePrefundPerOp = (listLength, deployed) => {
|
|
156
|
+
const base = 180000n;
|
|
157
|
+
const per = 45000n;
|
|
158
|
+
const callGas = base + per * BigInt(listLength);
|
|
159
|
+
const verifyGas = deployed ? 250000n : 800000n;
|
|
160
|
+
const preVerifyGas = 60000n;
|
|
161
|
+
return (callGas + verifyGas + preVerifyGas) * gasPrice * 120n / 100n;
|
|
162
|
+
};
|
|
163
|
+
let totalPrefundNeeded = 0n;
|
|
164
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
165
|
+
const deployed = i === 0 ? deployed0 : true;
|
|
166
|
+
totalPrefundNeeded += estimatePrefundPerOp(chunks[i].length, deployed);
|
|
167
|
+
}
|
|
168
|
+
console.log(`[AA 分发] 多批次分发:${chunks.length} 批,总 prefund 预估: ${ethers.formatEther(totalPrefundNeeded)} OKB`);
|
|
169
|
+
}
|
|
150
170
|
const ops = [];
|
|
151
171
|
for (let i = 0; i < chunks.length; i++) {
|
|
152
172
|
const list = chunks[i];
|
|
@@ -268,6 +288,19 @@ export async function buildTransfersWithProfit(params) {
|
|
|
268
288
|
initCodeUsed.add(k);
|
|
269
289
|
return aaManager.generateInitCode(ownerAddress);
|
|
270
290
|
};
|
|
291
|
+
// ✅ 关键修复:计算每个 sender 执行 UserOp 需要的 prefund(gas 费用)
|
|
292
|
+
// 在 Native 模式下,需要确保留足够的金额支付 prefund
|
|
293
|
+
const feeData = await provider.getFeeData();
|
|
294
|
+
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
|
|
295
|
+
const estimateTransferPrefund = (deployed) => {
|
|
296
|
+
// 归集操作的 gas:callGasLimit + verificationGasLimit + preVerificationGas
|
|
297
|
+
const callGas = 120000n; // Native 转账
|
|
298
|
+
const verifyGas = deployed ? 250000n : 800000n;
|
|
299
|
+
const preVerifyGas = 60000n;
|
|
300
|
+
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
301
|
+
// 加 30% buffer 以确保足够(归集操作需要更保守)
|
|
302
|
+
return (totalGas * gasPrice * 130n) / 100n;
|
|
303
|
+
};
|
|
271
304
|
// ✅ 第一步:计算每个钱包的归集金额和总利润
|
|
272
305
|
// 利润从每个钱包的归集金额中扣除,但只由第一个有效钱包统一转账给利润地址
|
|
273
306
|
const transferInfos = [];
|
|
@@ -292,10 +325,18 @@ export async function buildTransfersWithProfit(params) {
|
|
|
292
325
|
if (amountWei <= 0n)
|
|
293
326
|
continue;
|
|
294
327
|
const profitWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
|
|
295
|
-
const
|
|
328
|
+
const deployed = accountInfos[i]?.deployed ?? false;
|
|
329
|
+
const prefundWei = params.kind === 'native' ? estimateTransferPrefund(deployed) : 0n;
|
|
330
|
+
// ✅ 关键修复:toWei 需要扣除 max(profitWei, prefundWei),确保留足够的 gas
|
|
331
|
+
// 在 Native 模式下,如果 profitWei < prefundWei,需要留更多余额
|
|
332
|
+
const reserveWei = params.kind === 'native'
|
|
333
|
+
? (profitWei > prefundWei ? profitWei : prefundWei)
|
|
334
|
+
: profitWei;
|
|
335
|
+
const toWei = amountWei > reserveWei ? (amountWei - reserveWei) : 0n;
|
|
296
336
|
totalProfitWei += profitWei;
|
|
297
|
-
transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, toWei });
|
|
337
|
+
transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, toWei, prefundWei });
|
|
298
338
|
}
|
|
339
|
+
console.log(`[AA 归集] 钱包数量: ${transferInfos.length}, 总利润: ${ethers.formatEther(totalProfitWei)} OKB`);
|
|
299
340
|
const unsigned = [];
|
|
300
341
|
const opOwnerIndex = [];
|
|
301
342
|
// ✅ 第二步:构建 UserOps
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -1555,7 +1555,7 @@ export class BundleExecutor {
|
|
|
1555
1555
|
nonce: nonceMap.next(payerAccount.sender),
|
|
1556
1556
|
initCode: payerAccount.deployed ? '0x' : (await aaManager.generateInitCode(payerWallet.address)),
|
|
1557
1557
|
deployed: payerAccount.deployed,
|
|
1558
|
-
fixedGas: { callGasLimit:
|
|
1558
|
+
fixedGas: { callGasLimit: 960000n } // 发币较重,给 80W
|
|
1559
1559
|
});
|
|
1560
1560
|
const signedCreateOp = await aaManager.signUserOp(createOpRes.userOp, payerWallet);
|
|
1561
1561
|
ops1.push(signedCreateOp.userOp);
|
|
@@ -1575,7 +1575,9 @@ export class BundleExecutor {
|
|
|
1575
1575
|
// ✅ 并行构建和签名所有内盘买入 UserOps(降低并发数避免 RPC 限制)
|
|
1576
1576
|
// ✅ 使用 encodeBuyCallV3(支持 extensionData),与 V4 代币创建保持一致
|
|
1577
1577
|
const extensionData = params.extensionData ?? '0x';
|
|
1578
|
-
|
|
1578
|
+
// ✅ 预分配 nonce(并发前同步分配,避免 race condition)
|
|
1579
|
+
const curveBuyNonces = curveBuyData.map((data) => nonceMap.next(data.info.sender));
|
|
1580
|
+
const signedCurveBuyOps = await mapWithConcurrency(curveBuyData, 2, async (data, i) => {
|
|
1579
1581
|
const { wallet, info, buyWei, gasLimit } = data;
|
|
1580
1582
|
const buyData = encodeBuyCallV3(tokenAddress, buyWei, 0n, extensionData);
|
|
1581
1583
|
const buyCallData = encodeExecute(FLAP_PORTAL, buyWei, buyData);
|
|
@@ -1583,7 +1585,7 @@ export class BundleExecutor {
|
|
|
1583
1585
|
ownerWallet: wallet,
|
|
1584
1586
|
sender: info.sender,
|
|
1585
1587
|
callData: buyCallData,
|
|
1586
|
-
nonce:
|
|
1588
|
+
nonce: curveBuyNonces[i], // ✅ 使用预分配的 nonce
|
|
1587
1589
|
initCode: info.deployed ? '0x' : (await aaManager.generateInitCode(wallet.address)),
|
|
1588
1590
|
deployed: info.deployed,
|
|
1589
1591
|
fixedGas: { callGasLimit: gasLimit }
|
|
@@ -1610,8 +1612,10 @@ export class BundleExecutor {
|
|
|
1610
1612
|
return { wallet, info: dexBuyerInfos[i], buyWei };
|
|
1611
1613
|
});
|
|
1612
1614
|
totalDexBuyWei = dexBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1615
|
+
// ✅ 预分配 nonce(并发前同步分配,避免 race condition)
|
|
1616
|
+
const dexBuyNonces = dexBuyData.map((data) => nonceMap.next(data.info.sender));
|
|
1613
1617
|
// ✅ 并行构建和签名所有外盘买入 UserOps(降低并发数避免 RPC 限制)
|
|
1614
|
-
const signedDexBuyOps = await mapWithConcurrency(dexBuyData, 2, async (data) => {
|
|
1618
|
+
const signedDexBuyOps = await mapWithConcurrency(dexBuyData, 2, async (data, i) => {
|
|
1615
1619
|
const { wallet, info, buyWei } = data;
|
|
1616
1620
|
// ✅ 外盘买入:使用 DEX Router 进行交换
|
|
1617
1621
|
let swapData;
|
|
@@ -1638,7 +1642,7 @@ export class BundleExecutor {
|
|
|
1638
1642
|
ownerWallet: wallet,
|
|
1639
1643
|
sender: info.sender,
|
|
1640
1644
|
callData: dexBuyCallData,
|
|
1641
|
-
nonce:
|
|
1645
|
+
nonce: dexBuyNonces[i], // ✅ 使用预分配的 nonce
|
|
1642
1646
|
initCode: info.deployed ? '0x' : (await aaManager.generateInitCode(wallet.address)),
|
|
1643
1647
|
deployed: info.deployed,
|
|
1644
1648
|
fixedGas: { callGasLimit: 500000n } // 外盘买入:50W
|
|
@@ -581,8 +581,29 @@ export class AADexSwapExecutor {
|
|
|
581
581
|
if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
|
|
582
582
|
if (hopCount <= 0) {
|
|
583
583
|
console.log('[AA DEX 批量换手] 进入直接分发模式 (hopCount <= 0)');
|
|
584
|
-
// ✅
|
|
585
|
-
|
|
584
|
+
// ✅ 关键修复:计算每个买方执行买入 UserOp 需要的 prefund(gas 费用)
|
|
585
|
+
// EntryPoint 在 validation 阶段会扣除 prefund,此时分发还没执行
|
|
586
|
+
// 因此需要为每个买方额外预留 prefund
|
|
587
|
+
const feeData = await this.aaManager.getFeeData();
|
|
588
|
+
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
|
|
589
|
+
const estimateBuyerPrefund = (deployed) => {
|
|
590
|
+
// 买入操作的 gas:V3 swap 约 650,000,部署需要额外 verificationGas
|
|
591
|
+
const callGas = 650000n;
|
|
592
|
+
const verifyGas = deployed ? 250000n : 800000n;
|
|
593
|
+
const preVerifyGas = 60000n;
|
|
594
|
+
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
595
|
+
// 加 20% buffer 以确保足够
|
|
596
|
+
return (totalGas * gasPrice * 120n) / 100n;
|
|
597
|
+
};
|
|
598
|
+
// 计算每个买方需要的 prefund
|
|
599
|
+
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
600
|
+
const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
|
|
601
|
+
console.log(`[AA DEX 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
|
|
602
|
+
// ✅ 直接分发模式:分发金额 = 买入金额 + 买方 prefund
|
|
603
|
+
const items = buyerSenders.map((to, i) => ({
|
|
604
|
+
to,
|
|
605
|
+
value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n) // ✅ 包含 prefund
|
|
606
|
+
})).filter(x => x.value > 0n);
|
|
586
607
|
// 添加利润接收者到分发列表
|
|
587
608
|
if (extractProfit && profitWei > 0n) {
|
|
588
609
|
items.push({ to: profitRecipient, value: profitWei });
|
|
@@ -390,8 +390,29 @@ export class AAPortalSwapExecutor {
|
|
|
390
390
|
if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
|
|
391
391
|
if (hopCount <= 0) {
|
|
392
392
|
console.log('[AA Portal 批量换手] 进入直接分发模式 (hopCount <= 0)');
|
|
393
|
-
// ✅
|
|
394
|
-
|
|
393
|
+
// ✅ 关键修复:计算每个买方执行买入 UserOp 需要的 prefund(gas 费用)
|
|
394
|
+
// EntryPoint 在 validation 阶段会扣除 prefund,此时分发还没执行
|
|
395
|
+
// 因此需要为每个买方额外预留 prefund
|
|
396
|
+
const feeData = await this.aaManager.getFeeData();
|
|
397
|
+
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
|
|
398
|
+
const estimateBuyerPrefund = (deployed) => {
|
|
399
|
+
// 买入操作的 gas:Portal 买入约 450,000,部署需要额外 verificationGas
|
|
400
|
+
const callGas = 450000n;
|
|
401
|
+
const verifyGas = deployed ? 250000n : 800000n;
|
|
402
|
+
const preVerifyGas = 60000n;
|
|
403
|
+
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
404
|
+
// 加 20% buffer 以确保足够
|
|
405
|
+
return (totalGas * gasPrice * 120n) / 100n;
|
|
406
|
+
};
|
|
407
|
+
// 计算每个买方需要的 prefund
|
|
408
|
+
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
409
|
+
const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
|
|
410
|
+
console.log(`[AA Portal 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
|
|
411
|
+
// ✅ 直接分发模式:分发金额 = 买入金额 + 买方 prefund
|
|
412
|
+
const items = buyerSenders.map((to, i) => ({
|
|
413
|
+
to,
|
|
414
|
+
value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n) // ✅ 包含 prefund
|
|
415
|
+
})).filter(x => x.value > 0n);
|
|
395
416
|
if (extractProfit && profitWei > 0n) {
|
|
396
417
|
items.push({ to: profitRecipient, value: profitWei });
|
|
397
418
|
}
|