Liquid staking Dapps
Overview
With the adoption of IIP13
, that allows representing IoTeX staking buckets as Non-fungible Tokens (NFTs), liquid staking solutions that rely on smart contracts to manage their stakes via system-level smart contracts can now be developed on IoTeX.
-> Visit the original IIP-13 Proposal
In this section we will highlight the details of how Staking as NFT works and provide documentation on how to engage with staking buckets to offer liquid staking solutions on IoTeX.
How it works
Liquid Staking has been enabled with the implementation of a so called "System Staking Contract". This is the primary contract responsible for providing the functionalities laid out in IIP-13. It manages tasks such as creating, modifying, transferring, and querying staking Buckets and their associated NFT tokens.
Visit the original IIP-13 Proposal ->
The System Staking Contract
Testnet
0x52ab0fe2c3a94644de0888a3ba9ea1443672e61f
Mainnet
0x68db92a6a78a39dcaff1745da9e89e230ef49d3d
Bucket Types
In the context of staking as NFT, a "Bucket Type" represent a specific staking configuration, including the deposit amount, the preset lock duration, and the status of the StakeLock option. Currently, three bucket types are supported in the implementation, that are listed below:
10,000
91
1,572,480
ON by default
100,000
91
1,572,480
ON by default
1,000,000
91
1,572,480
ON by default
Examples
In this section we explore in details how to interact with the SystemStaking contract from another contract to enable liquid staking functionalities.
Setting up the Environment
When developing your Liquid Staking contract, ensure you include the
ISystemStaking
contract interface from theIIP13
repository.Ensure your client contract implements
IERC721Receiver
, as it will receive the NFT Bucket from theSystemStaking
contract upon staking creation.
import "./ISystemStaking.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract MyLiquidStaking is IERC721Receiver {
// Holds the System staking contract address
ISystemStaking public SystemStakingContract;
// Constructor
// Address for IoTeX Testnet: 0x52ab0fe2c3a94644de0888a3ba9ea1443672e61f
// Address for IoTeX Mainnet: 0x68db92a6a78a39dcaff1745da9e89e230ef49d3d
constructor(address _SystemStakingContractAddress) {
// Set the System staking contract address
SystemStakingContract = ISystemStaking(_SystemStakingContractAddress);
...
Staking IOTX from your Contract
The SystemStaking contract provides three stake functions that enable the creation of a new staking bucket in different scenarios. Each of them returns the id assigned to the newly created bucket, which coincides with the NFT token id:
// Create a single staking bucket with a specific duration and delegate
function stake(uint256 _duration, address _delegate) external payable returns (uint256);
// Create multiple staking buckets, each with the same amount and duration, for different delegates:
function stake(uint256 _amount, uint256 _duration, address[] memory _delegates) external payable returns (uint256);
// Create several staking buckets, each with the same amount and duration, all assigned to a single delegate
function stake(uint256 _amount, uint256 _duration, address _delegate, uint256 _count) external payable returns (uint256);
For instance, to generate a new NFT bucket with a 100 IOTX deposit, assigned to an eligible IoTeX delegate, you would use:
uint256 tokenId =
SystemStakingContract.stake{value: msg.value}(
1572480, // 91 days in IoTeX blocks
0xCD397ac1364676d8553D9ce78e4B0d27f236926d // Owner address of the delegate
);
Changing the StakeLock status
When a new staking bucket is created using the SystemStaking contract, the StakeLock option is enabled by default. In this state, the bucket remains locked for its designated lock duration, and this duration does not decrease over time. While the activated StakeLock option ensures higher rewards, if you plan to unstake a bucket at a specific time, you must deactivate the StakeLock option for that bucket well in advance, specifically the same number of days as the lock duration.
// Disables StakeLock for a bucket
function unlock(uint256 _tokenId) external
// Disables StakeLock for multiple buckets (must be owned by the same owner)
function unlock(uint256[] calldata _tokenIds) external
// Enables StakeLock for a bucket and sets a new lock duration (must be >= to the remaining lock time)
function lock(uint256 _tokenId, uint256 _duration) external;
// Enables StakeLock for multiple bucket and sets a new lock duration (same for all)
function lock(uint256[] calldata _tokenIds, uint256 _duration) external;
Unstaking a bucket
Before you can withdraw the IOTX deposit, unstaking a bucket is a prerequisite. For a bucket to qualify for unstaking, it must be in the "unlocked" status. This indicates that the StakeLock
option for that specific bucket must have remained deactivated throughout the entire lock duration of the bucket. Once initiated, the unstaking process lasts 3 days (equivalent to 51,840 IoTeX blocks). During this period, the bucket remains locked, does not generate staking rewards, and is immune to any operations.
// Unstakes a bucket
function unstake(uint256 _tokenId) external;
// Unstakes multiple buckets
function unstake(uint256[] calldata _tokenIds) external;
Withdrawing a bucket
Before you can withdraw a deposit, it must have completed the unstaking process.
// Withdraws a staking bucket deposit to a certain recipient address
function withdraw(uint256 _tokenId, address payable _recipient) external;
// Withdraws multiple staking bucket deposits to a certain recipient address
function withdraw(uint256[] calldata _tokenIds, address payable _recipient) external;
More operations
function blocksToUnstake(uint256 _tokenId) external view returns (uint256);
function blocksToWithdraw(uint256 _tokenId) external view returns (uint256);
function bucketOf(uint256 _tokenId) external view returns (uint256, uint256, uint256, uint256, address);
function merge(uint256[] calldata tokenIds, uint256 _newDuration) external payable;
function expandBucket(uint256 _tokenId, uint256 _newAmount, uint256 _newDuration) external payable;
function changeDelegate(uint256 _tokenId, address _delegate) external;
function changeDelegates(uint256[] calldata _tokenIds, address _delegate) external;
function lockedVotesTo(address[] calldata _delegates) external view returns (uint256[][] memory counts_);
function unlockedVotesTo(address[] calldata _delegates) external view returns (uint256[][] memory counts_);
Conclusion
These are the foundational steps for interacting with IIP-13 contracts, that allows the creation of Liquid Staking solutions. Liquid staking allows users to access the benefits of staking in a blockchain network while still maintaining liquidity and flexibility over their staked assets, which create a more attractive and versatile staking experience.
Last updated
Was this helpful?