LP-3030
ImplementedLRC-20 Wrapper Extension
Token wrapper extension for converting between LRC-20 tokens
LP-3030: LRC-20 Wrapper Extension
Abstract
This LP defines the Wrapper extension for LRC-20 tokens, enabling 1:1 wrapping and unwrapping of underlying tokens.
Motivation
Token wrapping is essential for:
- Bridging tokens between chains (e.g., WETH, WLUX)
- Adding functionality to existing tokens
- Creating standardized versions of non-standard tokens
- Enabling governance on external tokens
Specification
Interface
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.31;
import "./ILRC20.sol";
/**
* @title ILRC20Wrapper
* @notice LRC-20 extension for wrapping underlying tokens
*/
interface ILRC20Wrapper is ILRC20 {
/**
* @notice Returns the underlying token
* @return The wrapped token address
*/
function underlying() external view returns (ILRC20);
/**
* @notice Deposit underlying tokens and mint wrapped tokens
* @param account Recipient of wrapped tokens
* @param amount Amount to wrap
* @return Wrapped amount
*/
function depositFor(address account, uint256 amount) external returns (bool);
/**
* @notice Burn wrapped tokens and withdraw underlying
* @param account Recipient of underlying tokens
* @param amount Amount to unwrap
* @return Unwrapped amount
*/
function withdrawTo(address account, uint256 amount) external returns (bool);
}
Implementation
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.31;
import "./LRC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title LRC20Wrapper
* @notice LRC-20 that wraps an underlying LRC-20 token
*/
abstract contract LRC20Wrapper is LRC20 {
using SafeERC20 for ILRC20;
ILRC20 private immutable _underlying;
error LRC20WrapperInvalidUnderlying(address token);
error LRC20WrapperInvalidSender(address sender);
error LRC20WrapperInvalidReceiver(address receiver);
/**
* @notice Initialize wrapper with underlying token
* @param underlyingToken The token to wrap
*/
constructor(ILRC20 underlyingToken) {
if (address(underlyingToken) == address(this)) {
revert LRC20WrapperInvalidUnderlying(address(this));
}
_underlying = underlyingToken;
}
/**
* @notice Returns the underlying token
*/
function underlying() public view virtual returns (ILRC20) {
return _underlying;
}
/**
* @notice Returns decimals matching underlying
*/
function decimals() public view virtual override returns (uint8) {
try ILRC20Metadata(address(_underlying)).decimals() returns (uint8 value) {
return value;
} catch {
return super.decimals();
}
}
/**
* @notice Deposit underlying and mint wrapped tokens
* @param account Recipient
* @param amount Amount to wrap
*/
function depositFor(address account, uint256 amount) public virtual returns (bool) {
if (account == address(this)) {
revert LRC20WrapperInvalidReceiver(account);
}
_underlying.safeTransferFrom(msg.sender, address(this), amount);
_mint(account, amount);
return true;
}
/**
* @notice Burn wrapped tokens and withdraw underlying
* @param account Recipient
* @param amount Amount to unwrap
*/
function withdrawTo(address account, uint256 amount) public virtual returns (bool) {
if (account == address(this)) {
revert LRC20WrapperInvalidReceiver(account);
}
_burn(msg.sender, amount);
_underlying.safeTransfer(account, amount);
return true;
}
/**
* @notice Recover accidentally sent tokens
* @param account Recipient
*/
function _recover(address account) internal virtual returns (uint256) {
uint256 value = _underlying.balanceOf(address(this)) - totalSupply();
_mint(account, value);
return value;
}
}
Native Token Wrapper (WLUX)
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.31;
import "./LRC20.sol";
/**
* @title WLUX
* @notice Wrapped LUX - native token wrapper
*/
contract WLUX is LRC20 {
event Deposit(address indexed account, uint256 amount);
event Withdrawal(address indexed account, uint256 amount);
constructor() LRC20("Wrapped LUX", "WLUX") {}
/**
* @notice Deposit native LUX and receive WLUX
*/
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
/**
* @notice Burn WLUX and withdraw native LUX
* @param amount Amount to unwrap
*/
function withdraw(uint256 amount) public {
_burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}
/**
* @notice Fallback to deposit on ETH receive
*/
receive() external payable {
deposit();
}
}
Rationale
1:1 Backing
Wrapper maintains 1:1 backing to:
- Ensure full redeemability
- Prevent fractional reserve risks
- Maintain price parity
SafeERC20 Usage
Uses SafeERC20 to:
- Handle non-standard tokens (no return value)
- Prevent transfer failures
- Support USDT-style tokens
Recovery Mechanism
_recover function handles:
- Accidentally sent tokens
- Donation accounting
- Admin recovery operations
Backwards Compatibility
This extension is fully backwards compatible with LRC-20:
- Interface: Extends
ILRC20without modifying core functions - Storage: Uses separate storage slot for underlying token
- Events: Adds
DepositandWithdrawalevents - EIP-20: Compatible with all EIP-20 clients and tools
Existing LRC-20 implementations can be wrapped by:
- Deploying
LRC20Wrapperwith the original token as underlying - Original token remains accessible for legacy operations
- Wrapper and original can coexist
Use Cases
- WLUX: Wrap native LUX for DeFi compatibility
- Governance Wrappers: Add voting to existing tokens
- Bridge Tokens: Create chain-specific wrapped versions
- Upgrade Paths: Wrap legacy tokens for new functionality
Security Considerations
- Self-Wrapping: Prevent wrapping the wrapper itself
- Zero Address: Validate recipients are not zero address
- Recovery Access: Protect
_recoverwith access control - Decimal Matching: Ensure decimals match underlying
Reference Implementation
~/work/lux/standard/contracts/tokens/LRC20/LRC20Wrapper.sol~/work/lux/standard/contracts/tokens/WLUX.sol
Related LPs
- LP-3020: LRC-20 Fungible Token Standard (base)
- LP-3023: LRC-20 Bridgable Extension
- LP-3800: Bridged Asset Standard
Document Maintainer: Lux Industries Last Updated: 2025-12-25