initnmjnjnj
This commit is contained in:
@@ -463,31 +463,33 @@ export async function executeAddLiquidity(p: {
|
||||
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) {
|
||||
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 need = wrapAmt + ethers.utils.parseEther('0.001').toBigInt(); // wrap + буфер на газ
|
||||
const need = wethDeficit + ethers.utils.parseEther('0.001').toBigInt(); // недостающий WETH + буфер на газ
|
||||
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, ждёт
|
||||
// 1 conf). Дальше WETH = обычный ERC20: fee/approve/mint без изменений. Юзеру нужен ETH + второй токен.
|
||||
// useNativeEth: заворачиваем ТОЛЬКО недостающий WETH (wethDeficit). Если WETH уже хватает — пропускаем
|
||||
// (используем имеющийся). Дальше WETH = обычный ERC20: fee/approve/mint без изменений.
|
||||
let wrapTxid: string | undefined;
|
||||
if (p.useNativeEth) {
|
||||
let wrapAmount = 0n;
|
||||
if (isWeth(pool.token0)) wrapAmount = a0;
|
||||
else if (isWeth(pool.token1)) wrapAmount = a1;
|
||||
if (wrapAmount > 0n) {
|
||||
if (p.useNativeEth && wethDeficit > 0n) {
|
||||
const w = await signAndBroadcastWrapEth({
|
||||
mnemonic: p.mnemonic, expectedFromAddress: p.expectedFromAddress, amountWei: wrapAmount.toString(),
|
||||
mnemonic: p.mnemonic, expectedFromAddress: p.expectedFromAddress, amountWei: wethDeficit.toString(),
|
||||
});
|
||||
wrapTxid = w.txid;
|
||||
}
|
||||
}
|
||||
|
||||
// app fee 0.7% по каждому токену (net = то что реально пойдёт в пул)
|
||||
const fee0 = a0 > 0n ? computeAppFee(p.amount0Desired) : 0n;
|
||||
|
||||
@@ -101,7 +101,7 @@ export async function signAndBroadcastEvmApprove(p: SignEvmApproveParams): Promi
|
||||
const signer = wallet.connect(provider);
|
||||
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 capWei = ethers.utils.parseUnits(String(MAX_GAS_PRICE_GWEI), '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 (maxPriorityFeePerGas.gt(maxFeePerGas)) maxPriorityFeePerGas = maxFeePerGas;
|
||||
|
||||
// Отправить один approve(spender, value), дождаться 1 conf. Nonce берётся 'pending' перед КАЖДОЙ tx
|
||||
// (после 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, ethers.BigNumber.from(p.amount)]);
|
||||
|
||||
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,
|
||||
to: p.token, data, value: 0, chainId: expectedChainId, nonce,
|
||||
gasLimit: APPROVE_GAS_LIMIT, type: 2, maxFeePerGas, maxPriorityFeePerGas,
|
||||
});
|
||||
// Wait 1 conf чтобы bridge tx (next) видел updated allowance
|
||||
await Promise.race([
|
||||
sent.wait(1),
|
||||
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}`);
|
||||
return { txid: sent.hash };
|
||||
return sent.hash;
|
||||
};
|
||||
|
||||
const needed = ethers.BigNumber.from(p.amount);
|
||||
|
||||
// Текущий allowance — для idempotency + USDT-safe reset. (amount '0'/revoke ниже не попадает в эти ветки.)
|
||||
let current = ethers.BigNumber.from(0);
|
||||
try {
|
||||
current = await token.allowance(wallet.address, p.spender);
|
||||
} catch (err: any) {
|
||||
logger.warn(`EVM approve: allowance read failed (assume 0): ${err?.message}`);
|
||||
}
|
||||
|
||||
// Уже достаточно → не шлём лишнюю tx (повторы, и WETH-нога с остаточным allowance).
|
||||
if (needed.gt(0) && current.gte(needed)) {
|
||||
logger.info(`EVM approve skip: allowance ${current.toString()} >= needed ${needed.toString()} (token=${p.token} spender=${p.spender})`);
|
||||
return { txid: '' };
|
||||
}
|
||||
|
||||
// USDT-style: контракт реверит approve(ненулевое) пока allowance != 0 → сначала reset в 0, потом ставим нужное.
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user