four-flap-meme-sdk 4.0.3 → 4.0.4
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/abis/openfour.d.ts +94 -0
- package/dist/abis/openfour.js +63 -0
- package/dist/bundle/four-meme/core/create-openfour.d.ts +23 -0
- package/dist/bundle/four-meme/core/create-openfour.js +118 -0
- package/dist/bundle/four-meme/core/index.d.ts +2 -0
- package/dist/bundle/four-meme/core/index.js +1 -0
- package/dist/bundle/four-meme/private/buy-quote.d.ts +35 -0
- package/dist/bundle/four-meme/private/buy-quote.js +70 -0
- package/dist/bundle/four-meme/private/buy.js +82 -87
- package/dist/bundle/four-meme/private/index.d.ts +2 -0
- package/dist/bundle/four-meme/private/index.js +1 -0
- package/dist/bundle/four-meme/private/openfour-buy.d.ts +10 -0
- package/dist/bundle/four-meme/private/openfour-buy.js +65 -0
- package/dist/bundle/four-meme/types/index.d.ts +7 -0
- package/dist/chains/bsc/constants.d.ts +1 -0
- package/dist/core/clients/four.d.ts +10 -1
- package/dist/core/clients/four.js +66 -0
- package/dist/core/constants/addresses.d.ts +2 -0
- package/dist/core/constants/addresses.js +2 -0
- package/dist/domains/four/tax-token.d.ts +2 -0
- package/dist/domains/four/tax-token.js +13 -4
- package/dist/domains/four/tax-token.test.d.ts +1 -0
- package/dist/domains/four/tax-token.test.js +89 -0
- package/dist/domains/openfour/create-arg.d.ts +13 -0
- package/dist/domains/openfour/create-arg.js +78 -0
- package/dist/domains/openfour/index.d.ts +4 -0
- package/dist/domains/openfour/index.js +3 -0
- package/dist/domains/openfour/registry.d.ts +4 -0
- package/dist/domains/openfour/registry.js +44 -0
- package/dist/domains/openfour/schema.d.ts +20 -0
- package/dist/domains/openfour/schema.js +88 -0
- package/dist/domains/openfour/types.d.ts +76 -0
- package/dist/domains/openfour/types.js +1 -0
- package/dist/exports/root-bundle-and-tooling.d.ts +4 -2
- package/dist/exports/root-bundle-and-tooling.js +2 -2
- package/dist/exports/root-foundations.d.ts +1 -0
- package/dist/exports/root-foundations.js +1 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS } from '../../../core/constan
|
|
|
8
8
|
import { TM2_ABI } from '../../../abis/common.js';
|
|
9
9
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, } from '../../config-helpers.js';
|
|
10
10
|
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
11
|
+
import { isFourErc20BuyQuote, resolveFourBuyPayment, signQuoteApproveTxs } from './buy-quote.js';
|
|
11
12
|
const CHAIN_ID = CHAINS.BSC.chainId;
|
|
12
13
|
/**
|
|
13
14
|
* 私有购买(单笔)(仅签名版本 - 不依赖 Merkle)
|
|
@@ -15,46 +16,45 @@ const CHAIN_ID = CHAINS.BSC.chainId;
|
|
|
15
16
|
* ✅ 优化:并行获取 gasPrice 和 nonce
|
|
16
17
|
*/
|
|
17
18
|
export async function fourPrivateBuyMerkle(params) {
|
|
18
|
-
const { privateKey, tokenAddress, funds, to, config } = params;
|
|
19
|
-
// ✅ 直接使用传入的 RPC URL 创建 provider(不依赖 Merkle)
|
|
19
|
+
const { privateKey, tokenAddress, funds, to, config, quoteTokenAddress, quoteDecimals } = params;
|
|
20
20
|
const provider = createSignProvider(config.rpcUrl, CHAIN_ID, 'BSC');
|
|
21
21
|
const wallet = new Wallet(privateKey, provider);
|
|
22
22
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
23
23
|
const txType = getTxType(config);
|
|
24
24
|
const gasLimit = getGasLimit(config, 800000);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const payment = await resolveFourBuyPayment({
|
|
26
|
+
provider,
|
|
27
|
+
tokenAddress,
|
|
28
|
+
funds,
|
|
29
|
+
quote: { quoteTokenAddress, quoteDecimals },
|
|
30
|
+
});
|
|
31
|
+
const originalFundsWei = payment.fundsWei;
|
|
32
|
+
const erc20Quote = isFourErc20BuyQuote(quoteTokenAddress);
|
|
33
|
+
const extractProfit = shouldExtractProfit() && !erc20Quote;
|
|
28
34
|
let profitWei = 0n;
|
|
29
35
|
let actualFundsWei = originalFundsWei;
|
|
36
|
+
let actualValueWei = payment.valueWei;
|
|
30
37
|
if (extractProfit) {
|
|
31
38
|
const { profit, remaining } = calculateProfit(originalFundsWei);
|
|
32
39
|
profitWei = profit;
|
|
33
40
|
actualFundsWei = remaining;
|
|
41
|
+
actualValueWei = remaining;
|
|
34
42
|
}
|
|
35
43
|
const tm2 = new ethers.Contract(tmAddr, TM2_ABI, wallet);
|
|
36
|
-
// ✅ 获取贿赂金额
|
|
37
44
|
const bribeAmount = getBribeAmount(config);
|
|
38
45
|
const needBribeTx = bribeAmount > 0n;
|
|
39
46
|
const needProfitTx = extractProfit && profitWei > 0n;
|
|
40
|
-
|
|
41
|
-
const [gasPrice,
|
|
47
|
+
const nonceManager = new NonceManager(provider);
|
|
48
|
+
const [gasPrice, unsigned] = await Promise.all([
|
|
42
49
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
43
|
-
wallet.getNonce(),
|
|
44
50
|
tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address, actualFundsWei, 0n, {
|
|
45
|
-
value:
|
|
51
|
+
value: actualValueWei,
|
|
46
52
|
}),
|
|
47
53
|
]);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const profitNonce = needProfitTx ? baseNonce + nonceIdx : undefined;
|
|
53
|
-
// ✅ 并行签名所有交易(贿赂、买入、利润)
|
|
54
|
-
const signPromises = [];
|
|
55
|
-
// 贿赂交易
|
|
56
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
57
|
-
signPromises.push(wallet.signTransaction({
|
|
54
|
+
const signedTxs = [];
|
|
55
|
+
if (needBribeTx) {
|
|
56
|
+
const bribeNonce = await nonceManager.getNextNonce(wallet);
|
|
57
|
+
signedTxs.push(await wallet.signTransaction({
|
|
58
58
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
59
59
|
value: bribeAmount,
|
|
60
60
|
nonce: bribeNonce,
|
|
@@ -64,22 +64,32 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
64
64
|
type: txType,
|
|
65
65
|
}));
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
if (erc20Quote && payment.quoteAddress && payment.approvalWei > 0n) {
|
|
68
|
+
const approves = await signQuoteApproveTxs({
|
|
69
|
+
wallets: [wallet],
|
|
70
|
+
quoteAddress: payment.quoteAddress,
|
|
71
|
+
spender: tmAddr,
|
|
72
|
+
amounts: [actualFundsWei],
|
|
73
|
+
nonceManager,
|
|
74
|
+
gasPrice,
|
|
75
|
+
chainId: CHAIN_ID,
|
|
76
|
+
txType,
|
|
77
|
+
provider,
|
|
78
|
+
});
|
|
79
|
+
signedTxs.push(...approves);
|
|
80
|
+
}
|
|
81
|
+
signedTxs.push(await wallet.signTransaction({
|
|
69
82
|
...unsigned,
|
|
70
83
|
from: wallet.address,
|
|
71
|
-
nonce:
|
|
84
|
+
nonce: await nonceManager.getNextNonce(wallet),
|
|
72
85
|
gasLimit,
|
|
73
86
|
gasPrice,
|
|
74
87
|
chainId: CHAIN_ID,
|
|
75
88
|
type: txType,
|
|
76
|
-
value:
|
|
89
|
+
value: actualValueWei,
|
|
77
90
|
}));
|
|
78
|
-
// ✅ 并行签名完成后按顺序返回
|
|
79
|
-
const signedTxs = await Promise.all(signPromises);
|
|
80
|
-
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
81
91
|
let profitHopWallets;
|
|
82
|
-
if (needProfitTx
|
|
92
|
+
if (needProfitTx) {
|
|
83
93
|
const profitHopResult = await buildProfitHopTransactions({
|
|
84
94
|
provider,
|
|
85
95
|
payerWallet: wallet,
|
|
@@ -89,14 +99,15 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
89
99
|
gasPrice,
|
|
90
100
|
chainId: CHAIN_ID,
|
|
91
101
|
txType,
|
|
92
|
-
startNonce:
|
|
102
|
+
startNonce: await nonceManager.getNextNonce(wallet),
|
|
93
103
|
});
|
|
94
104
|
signedTxs.push(...profitHopResult.signedTransactions);
|
|
95
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
105
|
+
profitHopWallets = profitHopResult.hopWallets;
|
|
96
106
|
}
|
|
107
|
+
nonceManager.clearTemp();
|
|
97
108
|
return {
|
|
98
109
|
signedTransactions: signedTxs,
|
|
99
|
-
profitHopWallets,
|
|
110
|
+
profitHopWallets,
|
|
100
111
|
};
|
|
101
112
|
}
|
|
102
113
|
/**
|
|
@@ -105,25 +116,30 @@ export async function fourPrivateBuyMerkle(params) {
|
|
|
105
116
|
* ✅ 优化:并行获取 gasPrice 和 nonces,批量构建交易
|
|
106
117
|
*/
|
|
107
118
|
export async function fourBatchPrivateBuyMerkle(params) {
|
|
108
|
-
const { privateKeys, fundsList, tokenAddress, config } = params;
|
|
119
|
+
const { privateKeys, fundsList, tokenAddress, config, quoteTokenAddress, quoteDecimals } = params;
|
|
109
120
|
if (privateKeys.length !== fundsList.length) {
|
|
110
121
|
throw new Error('privateKeys and fundsList length mismatch');
|
|
111
122
|
}
|
|
112
|
-
// ✅ 直接使用传入的 RPC URL 创建 provider(不依赖 Merkle)
|
|
113
123
|
const provider = createSignProvider(config.rpcUrl, CHAIN_ID, 'BSC');
|
|
114
124
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
115
125
|
const txType = getTxType(config);
|
|
116
126
|
const finalGasLimit = getGasLimit(config, 800000);
|
|
127
|
+
const erc20Quote = isFourErc20BuyQuote(quoteTokenAddress);
|
|
117
128
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const payments = await Promise.all(fundsList.map((funds) => resolveFourBuyPayment({
|
|
130
|
+
provider,
|
|
131
|
+
tokenAddress,
|
|
132
|
+
funds,
|
|
133
|
+
quote: { quoteTokenAddress, quoteDecimals },
|
|
134
|
+
})));
|
|
135
|
+
const originalAmountsWei = payments.map((p) => p.fundsWei);
|
|
136
|
+
const valueWeiList = payments.map((p) => p.valueWei);
|
|
137
|
+
const extractProfit = shouldExtractProfit() && !erc20Quote;
|
|
121
138
|
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei);
|
|
122
|
-
const actualAmountsWei = remainingAmounts;
|
|
123
|
-
|
|
139
|
+
const actualAmountsWei = extractProfit ? remainingAmounts : originalAmountsWei;
|
|
140
|
+
const actualValueWei = extractProfit ? remainingAmounts : valueWeiList;
|
|
124
141
|
const bribeAmount = getBribeAmount(config);
|
|
125
142
|
const needBribeTx = bribeAmount > 0n;
|
|
126
|
-
// ✅ 找出投入金额最多的钱包(作为贿赂和利润支付者)- 同步计算
|
|
127
143
|
let maxFundsIndex = 0;
|
|
128
144
|
let maxFunds = originalAmountsWei[0];
|
|
129
145
|
for (let i = 1; i < originalAmountsWei.length; i++) {
|
|
@@ -135,74 +151,53 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
135
151
|
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
136
152
|
const nonceManager = new NonceManager(provider);
|
|
137
153
|
const needProfitTx = extractProfit && totalProfit > 0n;
|
|
138
|
-
|
|
139
|
-
const payerNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
|
|
140
|
-
// ✅ 优化:并行获取 gasPrice、nonces 和构建未签名交易
|
|
141
|
-
const [gasPrice, unsignedList, noncesResult] = await Promise.all([
|
|
154
|
+
const [gasPrice, unsignedList] = await Promise.all([
|
|
142
155
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
143
156
|
Promise.all(tm2Contracts.map((c, i) => c.buyTokenAMAP.populateTransaction(0n, tokenAddress, wallets[i].address, actualAmountsWei[i], 0n, {
|
|
144
|
-
value:
|
|
157
|
+
value: actualValueWei[i],
|
|
145
158
|
}))),
|
|
146
|
-
(async () => {
|
|
147
|
-
if (payerNonceCount > 1) {
|
|
148
|
-
// payer 需要多个连续 nonce
|
|
149
|
-
const payerNonces = await nonceManager.getNextNonceBatch(wallets[maxFundsIndex], payerNonceCount);
|
|
150
|
-
// 其他钱包各需要 1 个 nonce
|
|
151
|
-
const otherWallets = wallets.filter((_, i) => i !== maxFundsIndex);
|
|
152
|
-
const otherNonces = otherWallets.length > 0 ? await nonceManager.getNextNoncesForWallets(otherWallets) : [];
|
|
153
|
-
// 组装最终的 nonces 数组(保持原顺序)
|
|
154
|
-
const nonces = [];
|
|
155
|
-
let otherIdx = 0;
|
|
156
|
-
let nonceIdx = 0;
|
|
157
|
-
const bribeNonce = needBribeTx ? payerNonces[nonceIdx++] : undefined;
|
|
158
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
159
|
-
if (i === maxFundsIndex) {
|
|
160
|
-
nonces.push(payerNonces[nonceIdx++]); // 买入交易
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
nonces.push(otherNonces[otherIdx++]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const profitNonce = needProfitTx ? payerNonces[nonceIdx] : undefined;
|
|
167
|
-
return { nonces, bribeNonce, profitNonce };
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// 所有钱包各 1 个 nonce
|
|
171
|
-
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
172
|
-
return { nonces, bribeNonce: undefined, profitNonce: undefined };
|
|
173
|
-
}
|
|
174
|
-
})(),
|
|
175
159
|
]);
|
|
176
|
-
const { nonces, bribeNonce, profitNonce } = noncesResult;
|
|
177
|
-
// ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
|
|
178
160
|
const signedTxs = [];
|
|
179
|
-
if (needBribeTx
|
|
180
|
-
|
|
161
|
+
if (needBribeTx) {
|
|
162
|
+
signedTxs.push(await wallets[maxFundsIndex].signTransaction({
|
|
181
163
|
to: BLOCKRAZOR_BUILDER_EOA,
|
|
182
164
|
value: bribeAmount,
|
|
183
|
-
nonce:
|
|
165
|
+
nonce: await nonceManager.getNextNonce(wallets[maxFundsIndex]),
|
|
184
166
|
gasPrice,
|
|
185
167
|
gasLimit: 21000n,
|
|
186
168
|
chainId: CHAIN_ID,
|
|
187
169
|
type: txType,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
const quoteAddress = payments[0]?.quoteAddress;
|
|
173
|
+
if (erc20Quote && quoteAddress) {
|
|
174
|
+
const approves = await signQuoteApproveTxs({
|
|
175
|
+
wallets,
|
|
176
|
+
quoteAddress,
|
|
177
|
+
spender: tmAddr,
|
|
178
|
+
amounts: actualAmountsWei,
|
|
179
|
+
nonceManager,
|
|
180
|
+
gasPrice,
|
|
181
|
+
chainId: CHAIN_ID,
|
|
182
|
+
txType,
|
|
183
|
+
provider,
|
|
188
184
|
});
|
|
189
|
-
signedTxs.push(
|
|
185
|
+
signedTxs.push(...approves);
|
|
190
186
|
}
|
|
191
|
-
|
|
187
|
+
const buyNonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
192
188
|
const signedList = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
|
|
193
189
|
...unsigned,
|
|
194
190
|
from: wallets[i].address,
|
|
195
|
-
nonce:
|
|
191
|
+
nonce: buyNonces[i],
|
|
196
192
|
gasLimit: finalGasLimit,
|
|
197
193
|
gasPrice,
|
|
198
194
|
chainId: CHAIN_ID,
|
|
199
195
|
type: txType,
|
|
200
|
-
value:
|
|
196
|
+
value: actualValueWei[i],
|
|
201
197
|
})));
|
|
202
198
|
signedTxs.push(...signedList);
|
|
203
|
-
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
204
199
|
let profitHopWallets;
|
|
205
|
-
if (needProfitTx
|
|
200
|
+
if (needProfitTx) {
|
|
206
201
|
const profitHopResult = await buildProfitHopTransactions({
|
|
207
202
|
provider,
|
|
208
203
|
payerWallet: wallets[maxFundsIndex],
|
|
@@ -212,14 +207,14 @@ export async function fourBatchPrivateBuyMerkle(params) {
|
|
|
212
207
|
gasPrice,
|
|
213
208
|
chainId: CHAIN_ID,
|
|
214
209
|
txType,
|
|
215
|
-
startNonce:
|
|
210
|
+
startNonce: await nonceManager.getNextNonce(wallets[maxFundsIndex]),
|
|
216
211
|
});
|
|
217
212
|
signedTxs.push(...profitHopResult.signedTransactions);
|
|
218
|
-
profitHopWallets = profitHopResult.hopWallets;
|
|
213
|
+
profitHopWallets = profitHopResult.hopWallets;
|
|
219
214
|
}
|
|
220
215
|
nonceManager.clearTemp();
|
|
221
216
|
return {
|
|
222
217
|
signedTransactions: signedTxs,
|
|
223
|
-
profitHopWallets,
|
|
218
|
+
profitHopWallets,
|
|
224
219
|
};
|
|
225
220
|
}
|
|
@@ -8,4 +8,6 @@
|
|
|
8
8
|
* 对外 export 符号与路径保持不变。
|
|
9
9
|
*/
|
|
10
10
|
export { fourPrivateBuyMerkle, fourBatchPrivateBuyMerkle } from './buy.js';
|
|
11
|
+
export { openFourBatchBuyByBudgetMerkle } from './openfour-buy.js';
|
|
12
|
+
export type { OpenFourBatchBuySignParams } from './openfour-buy.js';
|
|
11
13
|
export { fourPrivateSellMerkle, fourBatchPrivateSellMerkle } from './sell.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FourBatchPrivateMerkleResult, FourSignConfig } from '../types/index.js';
|
|
2
|
+
export type OpenFourBatchBuySignParams = {
|
|
3
|
+
privateKeys: string[];
|
|
4
|
+
fundsList: string[];
|
|
5
|
+
tokenAddress: string;
|
|
6
|
+
quoteTokenAddress?: string;
|
|
7
|
+
quoteDecimals?: number;
|
|
8
|
+
config: FourSignConfig;
|
|
9
|
+
};
|
|
10
|
+
export declare function openFourBatchBuyByBudgetMerkle(params: OpenFourBatchBuySignParams): Promise<FourBatchPrivateMerkleResult>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenFour 发币后跟买:buyByBudget
|
|
3
|
+
* - quote = WBNB:msg.value 付 BNB
|
|
4
|
+
* - 其它 ERC20:approve Core + value=0
|
|
5
|
+
*/
|
|
6
|
+
import { Contract, Wallet, parseEther, parseUnits } from 'ethers';
|
|
7
|
+
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
8
|
+
import { NonceManager, getOptimizedGasPrice } from '../../../utils/bundle-helpers.js';
|
|
9
|
+
import { CHAINS } from '../../../core/constants/index.js';
|
|
10
|
+
import { getTxType, getGasPriceConfig } from '../../config-helpers.js';
|
|
11
|
+
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
12
|
+
import { OPENFOUR_CORE_ABI } from '../../../abis/openfour.js';
|
|
13
|
+
import { loadOpenFourAddresses } from '../../../domains/openfour/index.js';
|
|
14
|
+
import { isFourErc20BuyQuote, resolveQuoteDecimals, signQuoteApproveTxs } from './buy-quote.js';
|
|
15
|
+
const CHAIN_ID = CHAINS.BSC.chainId;
|
|
16
|
+
export async function openFourBatchBuyByBudgetMerkle(params) {
|
|
17
|
+
const { privateKeys, fundsList, tokenAddress, config, quoteTokenAddress, quoteDecimals } = params;
|
|
18
|
+
if (privateKeys.length !== fundsList.length) {
|
|
19
|
+
throw new Error('privateKeys and fundsList length mismatch');
|
|
20
|
+
}
|
|
21
|
+
const provider = createSignProvider(config.rpcUrl, CHAIN_ID, 'BSC');
|
|
22
|
+
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
23
|
+
const txType = getTxType(config);
|
|
24
|
+
const gasLimit = getGasLimit(config, 900000);
|
|
25
|
+
const addrs = await loadOpenFourAddresses(provider);
|
|
26
|
+
const erc20Quote = isFourErc20BuyQuote(quoteTokenAddress);
|
|
27
|
+
const decimals = erc20Quote
|
|
28
|
+
? await resolveQuoteDecimals(provider, { quoteTokenAddress, quoteDecimals })
|
|
29
|
+
: 18;
|
|
30
|
+
const amountsWei = fundsList.map((a) => (erc20Quote ? parseUnits(a, decimals) : parseEther(a)));
|
|
31
|
+
const cores = wallets.map((w) => new Contract(addrs.core, OPENFOUR_CORE_ABI, w));
|
|
32
|
+
const nonceManager = new NonceManager(provider);
|
|
33
|
+
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
34
|
+
const signedTxs = [];
|
|
35
|
+
if (erc20Quote && quoteTokenAddress) {
|
|
36
|
+
signedTxs.push(...(await signQuoteApproveTxs({
|
|
37
|
+
wallets,
|
|
38
|
+
quoteAddress: quoteTokenAddress,
|
|
39
|
+
spender: addrs.core,
|
|
40
|
+
amounts: amountsWei,
|
|
41
|
+
nonceManager,
|
|
42
|
+
gasPrice,
|
|
43
|
+
chainId: CHAIN_ID,
|
|
44
|
+
txType,
|
|
45
|
+
provider,
|
|
46
|
+
})));
|
|
47
|
+
}
|
|
48
|
+
const unsignedList = await Promise.all(cores.map((c, i) => c.buyByBudget.populateTransaction(tokenAddress, amountsWei[i], 0n, 0n, '0x', {
|
|
49
|
+
value: erc20Quote ? 0n : amountsWei[i],
|
|
50
|
+
})));
|
|
51
|
+
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
52
|
+
const buys = await Promise.all(unsignedList.map((unsigned, i) => wallets[i].signTransaction({
|
|
53
|
+
...unsigned,
|
|
54
|
+
from: wallets[i].address,
|
|
55
|
+
nonce: nonces[i],
|
|
56
|
+
gasLimit,
|
|
57
|
+
gasPrice,
|
|
58
|
+
chainId: CHAIN_ID,
|
|
59
|
+
type: txType,
|
|
60
|
+
value: erc20Quote ? 0n : amountsWei[i],
|
|
61
|
+
})));
|
|
62
|
+
signedTxs.push(...buys);
|
|
63
|
+
nonceManager.clearTemp();
|
|
64
|
+
return { signedTransactions: signedTxs };
|
|
65
|
+
}
|
|
@@ -197,6 +197,9 @@ export type FourPrivateBuySignParams = FourPrivateSignBase & {
|
|
|
197
197
|
tokenAddress: string;
|
|
198
198
|
funds: string;
|
|
199
199
|
to?: string;
|
|
200
|
+
/** ERC20 计价跟买;不传则按 BNB `msg.value` */
|
|
201
|
+
quoteTokenAddress?: string;
|
|
202
|
+
quoteDecimals?: number;
|
|
200
203
|
};
|
|
201
204
|
/** ✅ 私有卖出参数(Merkle 版本 - 完整) */
|
|
202
205
|
export type FourPrivateSellMerkleParams = FourPrivateMerkleBase & {
|
|
@@ -219,12 +222,16 @@ export type FourBatchPrivateBuyMerkleParams = FourPrivateMerkleBase & {
|
|
|
219
222
|
privateKeys: string[];
|
|
220
223
|
fundsList: string[];
|
|
221
224
|
tokenAddress: string;
|
|
225
|
+
quoteTokenAddress?: string;
|
|
226
|
+
quoteDecimals?: number;
|
|
222
227
|
};
|
|
223
228
|
/** ✅ 批量私有购买参数(仅签名版本 - 精简) */
|
|
224
229
|
export type FourBatchPrivateBuySignParams = FourPrivateSignBase & {
|
|
225
230
|
privateKeys: string[];
|
|
226
231
|
fundsList: string[];
|
|
227
232
|
tokenAddress: string;
|
|
233
|
+
quoteTokenAddress?: string;
|
|
234
|
+
quoteDecimals?: number;
|
|
228
235
|
};
|
|
229
236
|
/** ✅ 批量私有卖出参数(Merkle 版本 - 完整) */
|
|
230
237
|
export type FourBatchPrivateSellMerkleParams = FourPrivateMerkleBase & {
|
|
@@ -16,6 +16,7 @@ export declare const BSC_ADDRESSES: {
|
|
|
16
16
|
readonly TokenManagerV1: "0xf7F823d0E790219dBf727bDb971837574655fCB0";
|
|
17
17
|
readonly TokenManagerV2: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
|
|
18
18
|
readonly TokenManagerHelper3: "0xF251F83e40a78868FcfA3FA4599Dad6494E46034";
|
|
19
|
+
readonly OpenFourRegistry: "0x912CEf0C3aE9Ab6eB3Ec87cab69371cFb317Ab94";
|
|
19
20
|
readonly FlapPortal: "0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0";
|
|
20
21
|
readonly PancakeV2Router: "0x10ED43C718714eb63d5aA57B78B54704E256024E";
|
|
21
22
|
readonly PancakeV2Factory: "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FourRaisedToken, type FourTokenLabel } from '../../domains/four/raised-token.js';
|
|
2
|
+
import type { OpenFourCreateReq, OpenFourTemplate, OpenFourTemplateConfig } from '../../domains/openfour/types.js';
|
|
2
3
|
export type { FourRaisedToken, FourTokenLabel };
|
|
3
4
|
export type NetworkCode = 'BSC';
|
|
4
5
|
export type FourConfig = {
|
|
@@ -45,7 +46,9 @@ export type LoginReq = {
|
|
|
45
46
|
* - recipientAddress: 接收者地址(不使用时传空字符串 "")
|
|
46
47
|
* - minSharing: 最低持仓分红门槛(ether 单位,须满足 d × 10^n, n≥5, 1≤d≤9)
|
|
47
48
|
*
|
|
48
|
-
* 约束:burnRate + divideRate + liquidityRate + recipientRate
|
|
49
|
+
* 约束:burnRate + divideRate + liquidityRate + recipientRate
|
|
50
|
+
* + giggleCharityRate + binanceCharityRate = 100
|
|
51
|
+
* 带 giggleCharityRate / binanceCharityRate 即为 TaxToken9(即使都是 0)
|
|
49
52
|
*/
|
|
50
53
|
export type FourTokenTaxInfo = {
|
|
51
54
|
feeRate: 1 | 3 | 5 | 10;
|
|
@@ -55,6 +58,8 @@ export type FourTokenTaxInfo = {
|
|
|
55
58
|
recipientRate: number;
|
|
56
59
|
recipientAddress: string;
|
|
57
60
|
minSharing: number;
|
|
61
|
+
giggleCharityRate?: number;
|
|
62
|
+
binanceCharityRate?: number;
|
|
58
63
|
};
|
|
59
64
|
export type CreateTokenCustom = {
|
|
60
65
|
name: string;
|
|
@@ -142,5 +147,9 @@ export declare class FourClient {
|
|
|
142
147
|
error: string;
|
|
143
148
|
})[]>;
|
|
144
149
|
getTokenById(id: string | number, accessToken?: string): Promise<FourTokenDetail>;
|
|
150
|
+
searchTokenTemplates(): Promise<OpenFourTemplate[]>;
|
|
151
|
+
listPublishedTokenTemplates(): Promise<OpenFourTemplate[]>;
|
|
152
|
+
getTokenTemplateConfig(templateId: number | string): Promise<OpenFourTemplateConfig[]>;
|
|
153
|
+
createOpenFourToken(accessToken: string, req: OpenFourCreateReq): Promise<CreateTokenResp>;
|
|
145
154
|
}
|
|
146
155
|
export declare function buildLoginMessage(nonce: string): string;
|
|
@@ -339,6 +339,72 @@ export class FourClient {
|
|
|
339
339
|
}
|
|
340
340
|
return (j.data ?? j);
|
|
341
341
|
}
|
|
342
|
+
async searchTokenTemplates() {
|
|
343
|
+
const urls = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
|
|
344
|
+
let last = [];
|
|
345
|
+
for (const url of urls) {
|
|
346
|
+
try {
|
|
347
|
+
const r = await fetch(`${url}/v1/public/token_template/search`, {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers: { 'Content-Type': 'application/json' },
|
|
350
|
+
body: JSON.stringify({ sort: 'LAST' }),
|
|
351
|
+
});
|
|
352
|
+
if (!r.ok)
|
|
353
|
+
continue;
|
|
354
|
+
const j = (await r.json());
|
|
355
|
+
if (j.code !== '0' && j.code !== 0)
|
|
356
|
+
continue;
|
|
357
|
+
const list = Array.isArray(j.data) ? j.data : [];
|
|
358
|
+
if (list.length > 0)
|
|
359
|
+
return list;
|
|
360
|
+
last = list;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return last;
|
|
367
|
+
}
|
|
368
|
+
async listPublishedTokenTemplates() {
|
|
369
|
+
const list = await this.searchTokenTemplates();
|
|
370
|
+
return list.filter((t) => String(t.status || '').toUpperCase() === 'PUBLISHED');
|
|
371
|
+
}
|
|
372
|
+
async getTokenTemplateConfig(templateId) {
|
|
373
|
+
const urls = [...new Set([this.baseUrl, FOUR_MEME_OFFICIAL_API])];
|
|
374
|
+
let last = [];
|
|
375
|
+
for (const url of urls) {
|
|
376
|
+
try {
|
|
377
|
+
const r = await fetch(`${url}/v1/public/token_template/config?templateId=${templateId}`);
|
|
378
|
+
if (!r.ok)
|
|
379
|
+
continue;
|
|
380
|
+
const j = (await r.json());
|
|
381
|
+
if (j.code !== '0' && j.code !== 0)
|
|
382
|
+
continue;
|
|
383
|
+
const list = Array.isArray(j.data) ? j.data : [];
|
|
384
|
+
if (list.length > 0)
|
|
385
|
+
return list;
|
|
386
|
+
last = list;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return last;
|
|
393
|
+
}
|
|
394
|
+
async createOpenFourToken(accessToken, req) {
|
|
395
|
+
const r = await fetch(`${this.baseUrl}/v1/private/token_template/token/create`, {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: { 'Content-Type': 'application/json', 'meme-web-access': accessToken },
|
|
398
|
+
body: JSON.stringify(req),
|
|
399
|
+
});
|
|
400
|
+
const j = await r.json();
|
|
401
|
+
if (j.code !== '0' && j.code !== 0) {
|
|
402
|
+
throw new Error(`createOpenFourToken failed: ${JSON.stringify(j)}`);
|
|
403
|
+
}
|
|
404
|
+
const data = j.data;
|
|
405
|
+
const first = Array.isArray(data) ? data[0] : data;
|
|
406
|
+
return first;
|
|
407
|
+
}
|
|
342
408
|
}
|
|
343
409
|
export function buildLoginMessage(nonce) {
|
|
344
410
|
return `You are sign in Meme ${nonce}`;
|
|
@@ -22,6 +22,8 @@ export declare const ADDRESSES: {
|
|
|
22
22
|
readonly TokenManagerV2: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
|
|
23
23
|
/** 查询辅助合约(Helper3) */
|
|
24
24
|
readonly TokenManagerHelper3: "0xF251F83e40a78868FcfA3FA4599Dad6494E46034";
|
|
25
|
+
/** OpenFour Registry(链上发现 Core / Tools) */
|
|
26
|
+
readonly OpenFourRegistry: "0x912CEf0C3aE9Ab6eB3Ec87cab69371cFb317Ab94";
|
|
25
27
|
readonly FlapPortal: "0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0";
|
|
26
28
|
readonly PancakeV2Router: "0x10ED43C718714eb63d5aA57B78B54704E256024E";
|
|
27
29
|
readonly PancakeV2Factory: "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73";
|
|
@@ -34,6 +34,8 @@ export const ADDRESSES = {
|
|
|
34
34
|
TokenManagerV2: '0x342399a59943B5815849657Aa0e06D7058D9d5C6',
|
|
35
35
|
/** 查询辅助合约(Helper3) */
|
|
36
36
|
TokenManagerHelper3: '0xF251F83e40a78868FcfA3FA4599Dad6494E46034',
|
|
37
|
+
/** OpenFour Registry(链上发现 Core / Tools) */
|
|
38
|
+
OpenFourRegistry: '0x912CEf0C3aE9Ab6eB3Ec87cab69371cFb317Ab94',
|
|
37
39
|
// ---------- Flap Portal ----------
|
|
38
40
|
FlapPortal: '0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0',
|
|
39
41
|
// ---------- PancakeSwap 官方合约 ----------
|
|
@@ -296,16 +296,25 @@ export function validateFourTokenTaxInfo(info) {
|
|
|
296
296
|
if (![1, 3, 5, 10].includes(info.feeRate)) {
|
|
297
297
|
return { valid: false, error: `feeRate 必须为 1, 3, 5 或 10,当前: ${info.feeRate}` };
|
|
298
298
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
299
|
+
const giggle = info.giggleCharityRate ?? 0;
|
|
300
|
+
const binance = info.binanceCharityRate ?? 0;
|
|
301
|
+
const isTaxToken9 = info.giggleCharityRate != null || info.binanceCharityRate != null;
|
|
302
|
+
const rateSum = info.burnRate + info.divideRate + info.liquidityRate + info.recipientRate + giggle + binance;
|
|
301
303
|
if (rateSum !== 100) {
|
|
302
304
|
return {
|
|
303
305
|
valid: false,
|
|
304
|
-
error:
|
|
306
|
+
error: isTaxToken9
|
|
307
|
+
? `burnRate + divideRate + liquidityRate + recipientRate + giggleCharityRate + binanceCharityRate 必须等于 100,当前: ${rateSum}`
|
|
308
|
+
: `burnRate + divideRate + liquidityRate + recipientRate 必须等于 100,当前: ${rateSum}`,
|
|
305
309
|
};
|
|
306
310
|
}
|
|
307
311
|
// 每项比例 >= 0
|
|
308
|
-
if (info.burnRate < 0 ||
|
|
312
|
+
if (info.burnRate < 0 ||
|
|
313
|
+
info.divideRate < 0 ||
|
|
314
|
+
info.liquidityRate < 0 ||
|
|
315
|
+
info.recipientRate < 0 ||
|
|
316
|
+
giggle < 0 ||
|
|
317
|
+
binance < 0) {
|
|
309
318
|
return { valid: false, error: '所有比例值不能为负数' };
|
|
310
319
|
}
|
|
311
320
|
// minSharing 验证: d × 10^n, n >= 5, 1 <= d <= 9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|