Contract detail
WrappedNLP
Live Robinhood Chain account facts for 0x7ce2c710…86dfaf4e with at least 25 observed transactions. The history total is updating as indexing continues.
- Address type
- Smart contract
- Transactions
- 25+ observed
- First indexed block
- #65933
- Latest indexed block
- #77065342
- History coverage
- Recent activity available; total is catching up
- Native balance
- 0 ETH
- Balance observed at
- Block #35750928
- Indexed token transfers
- 4
- ERC-20 transfers
- 11
- Emitted events
- 17
- Token tracker
- Wrapped NLP: WETH (wNLP-WETH)
- Verification
- Verified
- Compiler
- v0.8.28+commit.7893614a
- Verification match
- full
- Creation transaction
- 0x6471fb6e…94e412e7
- Created at block
- #65933
- Language
- solidity
- EVM version
- cancun
- License
- none
- Compiler optimization
- Enabled · 2000000 runs
Related activity
Transactions
Verified contract
Contract source code
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20Metadata as IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {NativeLPToken} from "./NativeLPToken.sol";
import {WithdrawQueue} from "./WithdrawQueue.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {ReentrancyGuardTransient} from "./libraries/ReentrancyGuardTransient.sol";
/**
* @title WrappedNLP - Wrapped NativeLPToken
* @dev Converts rebasing NativeLPToken into static-balance ERC20 for protocol compatibility
* while preserving accumulated yield value.
*
* @notice Redemption Options:
* - Queue-based: Free redemption via WithdrawQueue contract (time-delayed for JIT protection)
* - Instant: Direct redemption with configurable fee (immediate, open to all users)
*/
contract WrappedNLP is ERC20, Ownable2Step, ReentrancyGuardTransient {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////////////////*/
/// @notice Whether instant redeem is enabled
bool public instantRedeemEnabled = true;
/// @notice The Native LPToken address
NativeLPToken public immutable nlp;
/// @notice The underlying token of the NativeLPToken
IERC20 public immutable underlying;
/// @notice Address that receives instant redeem fees
address public feeRecipient;
/// @notice The authorized WithdrawQueue contract
WithdrawQueue public withdrawQueue;
/// @notice Minimum amount for instant redeem
uint256 public constant MIN_INSTANT_REDEEM_AMOUNT = 1000;
/// @notice Instant redeem fee in basis points (1 bip = 0.01%)
uint256 public instantRedeemFeeBips;
/// @notice Mapping of whitelisted addresses that can directly call unwrapAndRedeem
mapping(address => bool) public redeemWhitelist;
/*//////////////////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Event emitted when NativeLP tokens are wrapped into WrappedNLP tokens
event Wrapped(address indexed user, uint256 nlpAmount, uint256 wNLPAmount);
/// @notice Event emitted when underlying tokens are deposited and wrapped into WrappedNLP tokens
event DepositAndWrapped(address indexed from, address indexed to, uint256 underlyingAmount, uint256 wNLPAmount);
/// @notice Event emitted when WrappedNLP tokens are unwrapped and redeemed for underlying tokens
event UnwrappedAndRedeemed(address indexed from, address indexed to, uint256 wNLPAmount, uint256 underlyingAmount);
/// @notice Event emitted when instant redeem fee is charged
event InstantRedeemFeeCollected(address indexed user, uint256 feeAmount);
/// @notice Event emitted when fee recipient is updated
event FeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient);
/// @notice Event emitted when instant redeem is executed
event InstantRedeemed(address indexed user, uint256 wNLPAmount, uint256 underlyingAmount, uint256 feeAmount);
/// @notice Event emitted when withdraw queue address is updated
event WithdrawQueueUpdated(address indexed oldQueue, address indexed newQueue);
/// @notice Event emitted when redeem whitelist status is updated
event RedeemWhitelistUpdated(address indexed account, bool status);
/// @notice Event emitted when instant redeem fee bips is updated
event InstantRedeemFeeBipsUpdated(uint256 oldFeeBips, uint256 newFeeBips);
/// @notice Event emitted when instant redeem enabled status is updated
event InstantRedeemEnabledUpdated(bool enabled);
constructor(string memory _name, string memory _symbol, NativeLPToken _nlp) ERC20(_name, _symbol) {
nlp = _nlp;
underlying = nlp.underlying();
feeRecipient = msg.sender;
instantRedeemFeeBips = nlp.earlyWithdrawFeeBips();
}
/*//////////////////////////////////////////////////////////////////////////
WRAP AND REDEEM
//////////////////////////////////////////////////////////////////////////*/
/// @notice Wraps NativeLP tokens into WrappedNLP tokens
/// @param amount Amount of NativeLP tokens to deposit
/// @return wNLPAmount Amount of WrappedNLP tokens received
function wrap(uint256 amount) external nonReentrant returns (uint256 wNLPAmount) {
require(amount > 0, ErrorsLib.ZeroAmount());
wNLPAmount = nlp.getSharesByUnderlying(amount);
IERC20(nlp).safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, wNLPAmount);
emit Wrapped(msg.sender, amount, wNLPAmount);
}
/// @notice Deposit underlying tokens directly into NLP and receive WrappedNLP tokens for a specified recipient
/// @param to The address to receive the WrappedNLP tokens
/// @param amount Amount of underlying tokens to deposit
/// @return wNLPAmount Amount of WrappedNLP tokens received
function depositAndWrap(address to, uint256 amount) external nonReentrant returns (uint256 wNLPAmount) {
require(amount > 0, ErrorsLib.ZeroAmount());
require(to != address(0), ErrorsLib.ZeroAddress());
underlying.safeTransferFrom(msg.sender, address(this), amount);
underlying.safeApprove(address(nlp), amount);
wNLPAmount = nlp.deposit(amount);
_mint(to, wNLPAmount);
emit DepositAndWrapped(msg.sender, to, amount, wNLPAmount);
}
/// @notice Unwrap WrappedNLP tokens and redeem underlying tokens to a specified address
/// @dev Only WithdrawQueue contract or whitelisted addresses can call this function
/// @param wNLPAmount Amount of WrappedNLP tokens to unwrap and redeem
/// @param to Address that will receive the underlying tokens
/// @return underlyingAmount Amount of underlying tokens sent to the recipient
function unwrapAndRedeem(uint256 wNLPAmount, address to) external nonReentrant returns (uint256 underlyingAmount) {
require(wNLPAmount > 0, ErrorsLib.ZeroAmount());
require(to != address(0), ErrorsLib.ZeroAddress());
require(msg.sender == address(withdrawQueue) || redeemWhitelist[msg.sender], ErrorsLib.NotTrustedOperator());
_burn(msg.sender, wNLPAmount);
underlyingAmount = nlp.redeemTo(wNLPAmount, to);
emit UnwrappedAndRedeemed(msg.sender, to, wNLPAmount, underlyingAmount);
}
/// @notice Instantly redeem WrappedNLP tokens for underlying tokens with fee
/// @param wNLPAmount Amount of WrappedNLP tokens to redeem
/// @param to Address that will receive the underlying tokens
/// @return underlyingAmount Amount of underlying tokens received after fee
function instantRedeem(uint256 wNLPAmount, address to) external nonReentrant returns (uint256 underlyingAmount) {
require(instantRedeemEnabled, "Instant redeem disabled");
require(wNLPAmount >= MIN_INSTANT_REDEEM_AMOUNT, "Below minimum instant redeem amount");
require(to != address(0), ErrorsLib.ZeroAddress());
_burn(msg.sender, wNLPAmount);
underlyingAmount = nlp.redeemTo(wNLPAmount, address(this));
uint256 fee = (underlyingAmount * instantRedeemFeeBips) / 10_000;
if (fee > 0) {
underlyingAmount -= fee;
// Transfer fee to fee recipient
underlying.safeTransfer(feeRecipient, fee);
emit InstantRedeemFeeCollected(msg.sender, fee);
}
underlying.safeTransfer(to, underlyingAmount);
emit InstantRedeemed(msg.sender, wNLPAmount, underlyingAmount, fee);
}
/*//////////////////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Gets the number of decimals for this token
/// @return The number of decimals, matching the underlying token
function decimals() public view override returns (uint8) {
return nlp.decimals();
}
/// @notice Calculates the amount of WrappedNLP tokens for a given amount of NativeLP tokens
/// @param _nlpAmount Amount of NativeLP tokens
/// @return Amount of WrappedNLP tokens
function getWnlpByNlp(uint256 _nlpAmount) external view returns (uint256) {
return nlp.getSharesByUnderlying(_nlpAmount);
}
/// @notice Calculates the amount of NLP tokens for a given amount of WrappedNLP tokens
/// @param wNLPAmount Amount of WrappedNLP tokens
/// @return Amount of NativeLP tokens
function getNlpByWnlp(uint256 wNLPAmount) external view returns (uint256) {
return nlp.getUnderlyingByShares(wNLPAmount);
}
/// @notice Returns the amount of NativeLP tokens per 1 WrappedNLP token
/// @dev Used to calculate the exchange rate from WrappedNLP to NativeLP
/// @return Amount of NativeLP tokens equivalent to 1 WrappedNLP token
function nlpPerToken() external view returns (uint256) {
return nlp.getUnderlyingByShares(10 ** decimals());
}
/// @notice Returns the amount of WrappedNLP tokens per 1 NativeLP token
/// @dev Used to calculate the exchange rate from NativeLP to WrappedNLP
/// @return Amount of WrappedNLP tokens equivalent to 1 NativeLP token
function tokensPerNlp() external view returns (uint256) {
return nlp.getSharesByUnderlying(10 ** decimals());
}
/*//////////////////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Sets the WithdrawQueue contract address
/// @param _withdrawQueue The new WithdrawQueue contract address
function setWithdrawQueue(address _withdrawQueue) external onlyOwner {
require(_withdrawQueue != address(0), ErrorsLib.ZeroAddress());
address oldQueue = address(withdrawQueue);
withdrawQueue = WithdrawQueue(_withdrawQueue);
emit WithdrawQueueUpdated(oldQueue, _withdrawQueue);
}
/// @notice Sets the redeem whitelist status for multiple addresses
/// @param accounts The addresses to update
/// @param statuses The new whitelist statuses corresponding to each account
function setRedeemWhitelist(address[] calldata accounts, bool[] calldata statuses) external onlyOwner {
for (uint256 i = 0; i < accounts.length; i++) {
require(accounts[i] != address(0), ErrorsLib.ZeroAddress());
redeemWhitelist[accounts[i]] = statuses[i];
emit RedeemWhitelistUpdated(accounts[i], statuses[i]);
}
}
/// @notice Sets the address that receives instant redeem fees
/// @param _feeRecipient The new fee recipient address
function setFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), ErrorsLib.ZeroAddress());
address oldRecipient = feeRecipient;
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(oldRecipient, _feeRecipient);
}
/// @notice Sets the instant redeem fee in basis points
/// @param _instantRedeemFeeBips The new instant redeem fee in basis points
function setInstantRedeemFeeBips(uint256 _instantRedeemFeeBips) external onlyOwner {
require(_instantRedeemFeeBips <= 2500, ErrorsLib.InvalidFeeBips());
uint256 oldFeeBips = instantRedeemFeeBips;
instantRedeemFeeBips = _instantRedeemFeeBips;
emit InstantRedeemFeeBipsUpdated(oldFeeBips, _instantRedeemFeeBips);
}
/// @notice Sets whether instant redeem is enabled
/// @param _enabled Whether instant redeem should be enabled
function setInstantRedeemEnabled(bool _enabled) external onlyOwner {
instantRedeemEnabled = _enabled;
emit InstantRedeemEnabledUpdated(_enabled);
}
}