V4PoolLiquiditySeeder
0xb5a2ffd350c58e560ddeab5d6e5aa6bff500177a
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
5,587 bytes
ABI entries
26
3 read · 9 write
License
none
Contract information
- Address
- 0xb5a2ffd350c58e560ddeab5d6e5aa6bff500177a
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x907Dbe35f1…24570010fb
- Creation tx
- 0x17ac7d6fe1…8e83f8deda
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (3)
lpTokenBurned() → bool
manager() → address
owner() → address
Events (5)
ERC20RescuedLpTokenBurnedNativeEthRescuedOwnershipTransferredPoolSeeded
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "poolManager",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "LpTokenAlreadyBurned",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "RefundFailed",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ERC20Rescued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "LpTokenBurned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "NativeEthRescued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "hook",
"type": "address"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"indexed": false,
"internalType": "int128",
"name": "amount0Delta",
"type": "int128"
},
{
"indexed": false,
"internalType": "int128",
"name": "amount1Delta",
"type": "int128"
}
],
"name": "PoolSeeded",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "pensionToken",
"type": "address"
},
{
"internalType": "contract IHooks",
"name": "hook",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
}
],
"name": "addLiquidity",
"outputs": [
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "burnLpToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pensionToken",
"type": "address"
},
{
"internalType": "contract IHooks",
"name": "hook",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
}
],
"name": "initializeAndAddLiquidity",
"outputs": [
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "lpTokenBurned",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "manager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pensionToken",
"type": "address"
},
{
"internalType": "contract IHooks",
"name": "hook",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IPoolManager.ModifyLiquidityParams",
"name": "params",
"type": "tuple"
}
],
"name": "removeLiquidity",
"outputs": [
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IERC20",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "rescueERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "rescueNative",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawData",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {IUnlockCallback} from "v4-core/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "v4-core/interfaces/IHooks.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {BalanceDelta} from "v4-core/types/BalanceDelta.sol";
import {Currency, CurrencyLibrary} from "v4-core/types/Currency.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
contract V4PoolLiquiditySeeder is IUnlockCallback, Ownable {
using CurrencyLibrary for Currency;
using SafeERC20 for IERC20;
IPoolManager public immutable manager;
bool public lpTokenBurned;
struct CallbackData {
address payer;
PoolKey key;
IPoolManager.ModifyLiquidityParams params;
}
event PoolSeeded(
address indexed payer,
address indexed token,
address indexed hook,
int24 tickLower,
int24 tickUpper,
int256 liquidityDelta,
int128 amount0Delta,
int128 amount1Delta
);
event LpTokenBurned(address indexed owner);
event NativeEthRescued(address indexed to, uint256 amount);
event ERC20Rescued(address indexed token, address indexed to, uint256 amount);
error LpTokenAlreadyBurned();
error NotPoolManager();
error RefundFailed();
error ZeroAddress();
constructor(IPoolManager poolManager, address initialOwner) Ownable(initialOwner) {
manager = poolManager;
}
receive() external payable {}
function initializeAndAddLiquidity(
address pensionToken,
IHooks hook,
uint24 fee,
int24 tickSpacing,
uint160 sqrtPriceX96,
IPoolManager.ModifyLiquidityParams calldata params
) external payable onlyOwner returns (BalanceDelta delta) {
PoolKey memory key = PoolKey({
currency0: CurrencyLibrary.ADDRESS_ZERO,
currency1: Currency.wrap(pensionToken),
fee: fee,
tickSpacing: tickSpacing,
hooks: hook
});
manager.initialize(key, sqrtPriceX96);
delta = _modifyLiquidity(msg.sender, key, params);
_refundNative(msg.sender);
emit PoolSeeded(
msg.sender,
pensionToken,
address(hook),
params.tickLower,
params.tickUpper,
params.liquidityDelta,
delta.amount0(),
delta.amount1()
);
}
function addLiquidity(
address pensionToken,
IHooks hook,
uint24 fee,
int24 tickSpacing,
IPoolManager.ModifyLiquidityParams calldata params
) external payable onlyOwner returns (BalanceDelta delta) {
PoolKey memory key = PoolKey({
currency0: CurrencyLibrary.ADDRESS_ZERO,
currency1: Currency.wrap(pensionToken),
fee: fee,
tickSpacing: tickSpacing,
hooks: hook
});
delta = _modifyLiquidity(msg.sender, key, params);
_refundNative(msg.sender);
emit PoolSeeded(
msg.sender,
pensionToken,
address(hook),
params.tickLower,
params.tickUpper,
params.liquidityDelta,
delta.amount0(),
delta.amount1()
);
}
function removeLiquidity(
address pensionToken,
IHooks hook,
uint24 fee,
int24 tickSpacing,
IPoolManager.ModifyLiquidityParams calldata params
) external onlyOwner returns (BalanceDelta delta) {
if (lpTokenBurned) revert LpTokenAlreadyBurned();
PoolKey memory key = PoolKey({
currency0: CurrencyLibrary.ADDRESS_ZERO,
currency1: Currency.wrap(pensionToken),
fee: fee,
tickSpacing: tickSpacing,
hooks: hook
});
delta = _modifyLiquidity(msg.sender, key, params);
_refundNative(msg.sender);
emit PoolSeeded(
msg.sender,
pensionToken,
address(hook),
params.tickLower,
params.tickUpper,
params.liquidityDelta,
delta.amount0(),
delta.amount1()
);
}
/// @notice Permanently disables liquidity removal for the position owned by this helper.
/// @dev Uniswap v4 direct PoolManager positions do not mint ERC-20 LP tokens. This burns the helper's removal authority.
function burnLpToken() external onlyOwner {
if (lpTokenBurned) revert LpTokenAlreadyBurned();
lpTokenBurned = true;
emit LpTokenBurned(msg.sender);
}
function rescueNative(address payable to, uint256 amount) external onlyOwner {
if (to == address(0)) revert ZeroAddress();
(bool success,) = to.call{value: amount}("");
if (!success) revert RefundFailed();
emit NativeEthRescued(to, amount);
}
function rescueERC20(IERC20 token, address to, uint256 amount) external onlyOwner {
if (to == address(0)) revert ZeroAddress();
token.safeTransfer(to, amount);
emit ERC20Rescued(address(token), to, amount);
}
function _modifyLiquidity(address payer, PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params)
internal
returns (BalanceDelta delta)
{
return abi.decode(manager.unlock(abi.encode(CallbackData(payer, key, params))), (BalanceDelta));
}
function _refundNative(address to) internal {
uint256 refund = address(this).balance;
if (refund > 0) {
(bool success,) = payable(to).call{value: refund}("");
if (!success) revert RefundFailed();
}
}
function unlockCallback(bytes calldata rawData) external returns (bytes memory) {
if (msg.sender != address(manager)) revert NotPoolManager();
CallbackData memory data = abi.decode(rawData, (CallbackData));
(BalanceDelta delta,) = manager.modifyLiquidity(data.key, data.params, "");
int128 amount0 = delta.amount0();
int128 amount1 = delta.amount1();
if (amount0 < 0) {
manager.settle{value: uint128(-amount0)}();
}
if (amount1 < 0) {
uint256 tokenAmount = uint128(-amount1);
manager.sync(data.key.currency1);
IERC20(Currency.unwrap(data.key.currency1)).safeTransferFrom(data.payer, address(manager), tokenAmount);
manager.settle();
}
if (amount0 > 0) manager.take(data.key.currency0, data.payer, uint128(amount0));
if (amount1 > 0) manager.take(data.key.currency1, data.payer, uint128(amount1));
return abi.encode(delta);
}
}
Chain explorer6394msChain node91ms