initnmjnjnj
This commit is contained in:
@@ -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 = то что реально пойдёт в пул)
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user