initnmjnjnj

This commit is contained in:
ZOMBIIIIIII
2026-06-17 23:23:41 +03:00
parent 7ac5086abe
commit 6f01f4af34
2 changed files with 58 additions and 36 deletions

View File

@@ -463,30 +463,32 @@ export async function executeAddLiquidity(p: {
throw badRequest(`Недостаточно ${pool.sym1}: нужно ${formatSmallestUnits(a1.toString(), pool.dec1)}, есть ${formatSmallestUnits(bal.toString(), pool.dec1)}`); throw badRequest(`Недостаточно ${pool.sym1}: нужно ${formatSmallestUnits(a1.toString(), pool.dec1)}, есть ${formatSmallestUnits(bal.toString(), pool.dec1)}`);
} }
} }
// WETH-нога при useNativeEth: заворачиваем ТОЛЬКО недостающее (учитываем уже имеющийся WETH от прошлых
// попыток — не плодим WETH и не требуем лишний ETH). wethDeficit переиспользуется в шаге wrap ниже.
let wethDeficit = 0n;
if (p.useNativeEth) { if (p.useNativeEth) {
const wrapAmt = t0IsWeth ? a0 : t1IsWeth ? a1 : 0n; const wrapAmt = t0IsWeth ? a0 : t1IsWeth ? a1 : 0n;
if (wrapAmt > 0n) { const wethLegAddr = t0IsWeth ? pool.token0 : t1IsWeth ? pool.token1 : '';
if (wrapAmt > 0n && wethLegAddr) {
let wethBal = 0n;
try { wethBal = await readErc20Balance('ETH', wethLegAddr, p.expectedFromAddress); } catch { /* assume 0 */ }
wethDeficit = wethBal >= wrapAmt ? 0n : wrapAmt - wethBal;
const ethBal = await readEvmNativeBalance('ETH', p.expectedFromAddress); const ethBal = await readEvmNativeBalance('ETH', p.expectedFromAddress);
const need = wrapAmt + ethers.utils.parseEther('0.001').toBigInt(); // wrap + буфер на газ const need = wethDeficit + ethers.utils.parseEther('0.001').toBigInt(); // недостающий WETH + буфер на газ
if (ethBal < need) { if (ethBal < need) {
throw badRequest(`Недостаточно ETH: нужно ~${formatSmallestUnits(need.toString(), 18)} (wrap + газ), есть ${formatSmallestUnits(ethBal.toString(), 18)}`); throw badRequest(`Недостаточно ETH: нужно ~${formatSmallestUnits(need.toString(), 18)} (wrap недостающего WETH + газ), есть ${formatSmallestUnits(ethBal.toString(), 18)}`);
} }
} }
} }
// useNativeEth: если у пула есть WETH-нога — заворачиваем нужный ETH в WETH (WETH.deposit, ждёт // useNativeEth: заворачиваем ТОЛЬКО недостающий WETH (wethDeficit). Если WETH уже хватает — пропускаем
// 1 conf). Дальше WETH = обычный ERC20: fee/approve/mint без изменений. Юзеру нужен ETH + второй токен. // (используем имеющийся). Дальше WETH = обычный ERC20: fee/approve/mint без изменений.
let wrapTxid: string | undefined; let wrapTxid: string | undefined;
if (p.useNativeEth) { if (p.useNativeEth && wethDeficit > 0n) {
let wrapAmount = 0n; const w = await signAndBroadcastWrapEth({
if (isWeth(pool.token0)) wrapAmount = a0; mnemonic: p.mnemonic, expectedFromAddress: p.expectedFromAddress, amountWei: wethDeficit.toString(),
else if (isWeth(pool.token1)) wrapAmount = a1; });
if (wrapAmount > 0n) { wrapTxid = w.txid;
const w = await signAndBroadcastWrapEth({
mnemonic: p.mnemonic, expectedFromAddress: p.expectedFromAddress, amountWei: wrapAmount.toString(),
});
wrapTxid = w.txid;
}
} }
// app fee 0.7% по каждому токену (net = то что реально пойдёт в пул) // app fee 0.7% по каждому токену (net = то что реально пойдёт в пул)

View File

@@ -101,7 +101,7 @@ export async function signAndBroadcastEvmApprove(p: SignEvmApproveParams): Promi
const signer = wallet.connect(provider); const signer = wallet.connect(provider);
const token = new ethers.Contract(p.token, ERC20_ABI, signer); const token = new ethers.Contract(p.token, ERC20_ABI, signer);
// Fee tier: используем provider.getFeeData() — это OK для approve (low priority). // Fee tier: provider.getFeeData() — OK для approve (low priority). Считаем один раз на все tx.
const feeData = await provider.getFeeData(); const feeData = await provider.getFeeData();
const capWei = ethers.utils.parseUnits(String(MAX_GAS_PRICE_GWEI), 'gwei'); const capWei = ethers.utils.parseUnits(String(MAX_GAS_PRICE_GWEI), 'gwei');
let maxFeePerGas = feeData.maxFeePerGas ?? ethers.utils.parseUnits('30', 'gwei'); let maxFeePerGas = feeData.maxFeePerGas ?? ethers.utils.parseUnits('30', 'gwei');
@@ -109,27 +109,47 @@ export async function signAndBroadcastEvmApprove(p: SignEvmApproveParams): Promi
if (maxFeePerGas.gt(capWei)) maxFeePerGas = capWei; if (maxFeePerGas.gt(capWei)) maxFeePerGas = capWei;
if (maxPriorityFeePerGas.gt(maxFeePerGas)) maxPriorityFeePerGas = maxFeePerGas; if (maxPriorityFeePerGas.gt(maxFeePerGas)) maxPriorityFeePerGas = maxFeePerGas;
const nonce = await provider.getTransactionCount(wallet.address, 'pending'); // Отправить один approve(spender, value), дождаться 1 conf. Nonce берётся 'pending' перед КАЖДОЙ tx
const data = token.interface.encodeFunctionData('approve', [p.spender, ethers.BigNumber.from(p.amount)]); // (после reset предыдущая уже подтверждена → следующая получит инкремент).
const sendOne = async (value: ethers.BigNumber): Promise<string> => {
const nonce = await provider.getTransactionCount(wallet.address, 'pending');
const data = token.interface.encodeFunctionData('approve', [p.spender, value]);
const sent = await signer.sendTransaction({
to: p.token, data, value: 0, chainId: expectedChainId, nonce,
gasLimit: APPROVE_GAS_LIMIT, type: 2, maxFeePerGas, maxPriorityFeePerGas,
});
await Promise.race([
sent.wait(1),
new Promise((_, reject) => setTimeout(() => reject(new Error('approve confirm timeout')), 60_000)),
]);
return sent.hash;
};
const sent = await signer.sendTransaction({ const needed = ethers.BigNumber.from(p.amount);
to: p.token,
data, // Текущий allowance — для idempotency + USDT-safe reset. (amount '0'/revoke ниже не попадает в эти ветки.)
value: 0, let current = ethers.BigNumber.from(0);
chainId: expectedChainId, try {
nonce, current = await token.allowance(wallet.address, p.spender);
gasLimit: APPROVE_GAS_LIMIT, } catch (err: any) {
type: 2, logger.warn(`EVM approve: allowance read failed (assume 0): ${err?.message}`);
maxFeePerGas, }
maxPriorityFeePerGas,
}); // Уже достаточно → не шлём лишнюю tx (повторы, и WETH-нога с остаточным allowance).
// Wait 1 conf чтобы bridge tx (next) видел updated allowance if (needed.gt(0) && current.gte(needed)) {
await Promise.race([ logger.info(`EVM approve skip: allowance ${current.toString()} >= needed ${needed.toString()} (token=${p.token} spender=${p.spender})`);
sent.wait(1), return { txid: '' };
new Promise((_, reject) => setTimeout(() => reject(new Error('approve confirm timeout')), 60_000)), }
]);
logger.info(`EVM approve confirmed: chain=${p.chain} token=${p.token} spender=${p.spender} amount=${p.amount} txid=${sent.hash}`); // USDT-style: контракт реверит approve(ненулевое) пока allowance != 0 → сначала reset в 0, потом ставим нужное.
return { txid: sent.hash }; if (needed.gt(0) && current.gt(0)) {
logger.info(`EVM approve reset-to-0 (USDT-safe): ${current.toString()} → 0 → ${needed.toString()} (token=${p.token} spender=${p.spender})`);
await sendOne(ethers.constants.Zero);
}
const txid = await sendOne(needed);
logger.info(`EVM approve confirmed: chain=${p.chain} token=${p.token} spender=${p.spender} amount=${p.amount} txid=${txid}`);
return { txid };
} }
/** /**