Столкнулся с такой проблемой на токене H где в позе было залито 95% USDC и не давало достать токены из-за паузы на трансферы самого H
Оказывается можно полностью отказаться от одного токена через контракт и забрать другой, в этом гайде пошагово расписаны шаги как это сделать:
Нужно собрать такие данные:
tokenId = ID Uniswap v4 NFT позиции
liquidity = размер ликвидности позиции
recipient = адрес, куда вывести нормальный токен
goodToken = адрес токена, который нужно вывести
pausedToken = адрес токена, который нельзя перевести
goodTokenIsCurrency0 = true / false
amountMin = минимальная сумма нормального токена
Где взять tokenId
Открой адрес своего кошелька на Etherscan.
Перейди:
NFT Transfers / ERC-721 Token Txns
Ищи NFT от контракта:
Uniswap v4 Position Manager
0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e
В строке будет
Token ID. Это и есть tokenId.Где взять liquidity
Открой контракт Uniswap v4 Position Manager:
0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e
На Etherscan открой:
Read Proxy Contract
Вызови:
getPositionLiquidity(tokenId)
Ответ
liquidity нужно вставить в encoder.Где понять currency0 / currency1
На том же контракте вызови:
getPoolAndPositionInfo(tokenId)
В ответе будет:
currency0 = первый токен
currency1 = второй токен
Если выводимый токен —
currency0, тогда:amount0Min = amountMin
amount1Min = 0
Если выводимый токен —
currency1, тогда:amount0Min = 0
amount1Min = amountMin
Онлайн-способ: StackBlitz или Replit
Можно использовать любой онлайн Node.js runner:
StackBlitz
Replit Node.js
Можно залить инструкцию в гпт и он выполнит скрипт самостоятельно
Нельзя вставлять private key или seed phrase. Этот скрипт только генерирует calldata и ничего не подписывает.
encode.js
Создай файл
encode.js:import { AbiCoder, MaxUint256 } from "ethers";
const coder = AbiCoder.defaultAbiCoder();
/**
* ====== ЗАПОЛНИТЬ ЭТИ ПОЛЯ ======
*/
// ID NFT-позиции Uniswap v4
const tokenId = 123123;
// liquidity из getPositionLiquidity(tokenId)
const liquidity = 28680156276946644n;
// куда вывести нормальный токен
const recipient = "0xВАШ_КОШЕЛЕК";
// токен, который нужно достать
const goodToken = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // example: USDC
// paused-токен, который нужно оставить/clear
const pausedToken = "0xcf5104D094e3864CfCBDa43B82e1cEFD26A016eB";
// true, если goodToken = currency0
// false, если goodToken = currency1
const goodTokenIsCurrency0 = true;
// минимальная сумма goodToken в raw units
// example: USDC 1490 = 1490 * 10^6 = 1490000000
const amountMin = 1490000000n;
/**
* ====== НИЖЕ НЕ ТРОГАТЬ ======
*/
// 0x01 = DECREASE_LIQUIDITY
// 0x0e = TAKE
// 0x13 = CLEAR_OR_TAKE
const actions = "0x010e13";
const amount0Min = goodTokenIsCurrency0 ? amountMin : 0n;
const amount1Min = goodTokenIsCurrency0 ? 0n : amountMin;
const params = [
// DECREASE_LIQUIDITY(tokenId, liquidity, amount0Min, amount1Min, hookData)
coder.encode(
["uint256", "uint256", "uint128", "uint128", "bytes"],
[
tokenId,
liquidity,
amount0Min,
amount1Min,
"0x"
]
),
// TAKE(goodToken, recipient, OPEN_DELTA)
// OPEN_DELTA = 0 means take all available positive delta
coder.encode(
["address", "address", "uint256"],
[
goodToken,
recipient,
0n
]
),
// CLEAR_OR_TAKE(pausedToken, MaxUint256)
// This forfeits the paused token delta instead of trying to transfer it.
coder.encode(
["address", "uint256"],
[
pausedToken,
MaxUint256
]
)
];
const unlockData = coder.encode(
["bytes", "bytes[]"],
[actions, params]
);
const deadline = Math.floor(Date.now() / 1000) + 3600;
console.log("unlockData:");
console.log(unlockData);
console.log("\ndeadline:");
console.log(deadline);package.json
Если онлайн-редактор просит
package.json, используй:{
"type": "module",
"dependencies": {
"ethers": "^6.13.0"
},
"scripts": {
"start": "node encode.js"
}
}Как запустить
В терминале онлайн-редактора:
npm install
node encode.js
На выходе будет:
unlockData:
0x...
deadline:
178...
Как вызвать на Etherscan
Открой контракт:
0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e
Перейди:
Write Proxy Contract
Подключи кошелек через:
Connect to Web3
Найди функцию:
modifyLiquidities(bytes unlockData, uint256 deadline)
Заполни:
unlockData = 0x... из скрипта
deadline = timestamp из скрипта
value = 0
Не используй:
modifyLiquiditiesWithoutUnlock
Что произойдет
Транзакция сделает:
DECREASE_LIQUIDITY
TAKE(goodToken)
CLEAR_OR_TAKE(pausedToken)
То есть нормальный токен будет отправлен на
recipient.Paused-токен не будет переведен. Его delta будет очищена через
CLEAR_OR_TAKE.Это не сохранение paused-токена. Это отказ от него ради вывода второго токена.
Примеры описаны на сети ETH, применимо и к другим EVM чейнам по аналогии