by Stabilis Labs
DISCLAIMER: THIS PACKAGE HAS NOT BEEN THOROUGHLY TESTED YET. USE ONLY FOR INSPIRATION NOW.
This package enables advanced staking of resources. This is done by staking tokens to a Staking ID. This ID records how many tokens are staked, and whether they are locked. By showing this ID, users can stake, unstake, lock their tokens and claim staking rewards. Assignment of rewards is done periodically.
The 3 main advantages over simple OneResourcePool staking that are accomplished are:
- Staking reward can be a token different from the staked token.
- Staked tokens can be locked (e.g. for voting or to reward not selling).
- An unstaking delay can be set (is technically also possible using the OneResourcePool).
This NFT staking ID approach has some disadvantages over simple OneResourcePool staking:
- Wallet display of staked tokens is more difficult, as staked amounts are stored by an NFT (staking ID). Ideally, users need to use some kind of front-end to see their staked tokens. Alternatively, you could provide the staker with a placeholder token, so they can easily see how much they've staked.
- Staking rewards are distributed periodically, not continuously.
- User needs to claim rewards manually. Though this could be automated in some way.
- Staked tokens are not liquid, making it impossible to use them in traditional DEXes. Though they are transferable to other user's staking IDs, so a DEX could be built on top of this system. This way, liquidity could be provided while still earning staking fees.
- It is more complex to set up and manage.
To set up a staking component, you will need to know how to build (for now, since no package is deployed already) and deploy scrypto packages and write and send transaction manifests. In the future, information on how to do this will be available here. For now, please refer to Radix' documentation: https://docs.radixdlt.com/docs
Though, if you have trouble with writing Transaction Manifests, using https://instruct-stokenet.radixbillboard.com/ is heavily recommended! It will automatically detect what arguments a chosen method expects.
Setting up a staking component is fairly easy. First, clone this repo, build the package, and deploy it to ledger (in the future, a package will be deployed on main net and stokenet already for you to use).
Now the package is deployed to ledger, it's time to instantiate your own Staking component by calling the new
function, which looks like this:
pub fn new(
controller: ResourceAddress,
rewards: FungibleBucket,
period_interval: i64,
name: String,
symbol: String,
dao_controlled: bool,
max_unstaking_delay: i64,
) -> Global<Staking>
- The
controller
argument is the ResourceAddress corresponding to the desired Owner Role. In other words, holding that resource gives access to the OWNER role in the Staking Component. - The
rewards
are argument is a bucket of fungible resources you wish to award for staking (or locking) tokens. - The
period_interval
argument is the amount of days every reward cycle has. - The
name
andsymbol
arguments influence your component's metadata. - The
dao_controlled
argument influences the amount of influence the OWNER has. If the owner badge is held by a centralized entity, setting this value to false stops the owner from locking staked tokens. If it's set to true, the owner badge can be used to lock staked tokens (for instance, if a staking id is used to vote). - The
max_unstaking_delay
sets an upper limit to the delay between unstaking and being able to redeem your unstaked tokens. This delay can be set by the component's owner, and this maximum value provides a guarantee, so the owner can not lock all staked tokens indefinitely.
When the component is deployed, you can interact with it. One of the first first methods you might want to call is the add_stakable
method, which enables staking of a chosen resource:
pub fn add_stakable(&mut self, address: ResourceAddress, reward_amount: Decimal, lock: Lock)
- The
address
argument is the address of the resource that becomes stakable. - The
reward_amount
is the amount of tokens from the reward vault you want to reward every reward cycle. - The
lock argument
is a Lock struct, which specifies whether the reward for locking this stake, and looks like:
pub struct Lock {
pub payment: Decimal,
pub duration: i64,
}
- The payment argument is the amount of tokens rewarded for locking (per locked token)
- The duration argument is the amount of days the stake will be locked
If you don't wish to add locking capability, simply set both to 0.
IMPORTANT: This method requires the Owner role, so be sure to show proof of your owner badge in the Manifest.
To stake, a user needs to create a staking ID by calling the create_id
method, which does not require any arguments, and will return a Bucket with a Staking ID.
To stake to a Staking ID, the stake
method is called, which looks like:
pub fn stake(&mut self, stake_bucket: Bucket, id_proof: Option<NonFungibleProof>) -> Option<Bucket>
- The
stake_bucket
argument is a bucket of either the stakable tokens, or a stake transfer receipt (which is a receipt that can be used to transfer stake from one ID to another) - The
id_proof
argument is Some(NonFungibleProof) of the Staking ID, to prove the user is in possession of it. If None is passed, a Staking ID is created for the user. - If no proof of a Staking ID is supplied, a newly created Staking ID is returned.
Unstaking consists of two steps:
- Requesting unstake and receiving an unstaking receipt / stake transfer receipt.
- Redeeming unstaked tokens using the unstaking receipt.
The first step can be done through calling the start_unstake
method:
pub fn start_unstake(
&mut self,
id_proof: NonFungibleProof,
address: ResourceAddress,
amount: Decimal,
stake_transfer: bool,
) -> Bucket
- The
id_proof
argument is a NonFungibleProof of the Staking ID, to prove the user is in possession of it. - The
address
argument is the ResourceAddress of the token you wish to unstake - The
amount
argument is the amount of tokens you wish to unstake - The
stake_transfer
argument is a bool that decides whether you receive an unstaking receipt or a stake transfer receipt. The former can be redeemed in return for your staked tokens after the unstaking delay, while the latter can be immediately used to transfer your staked tokens to another Staking ID. - The method returns a Bucket containing an unstaking receipt or a stake transfer receipt.
To redeem an unstaking receipt, the finish_unstake
method is called:
pub fn finish_unstake(&mut self, receipt: Bucket) -> Bucket
- The
receipt
argument is an unstaking receipt (if the unstaking delay has not yet passed, the method will fail) - The Bucket returned contains the unstaked tokens.
Locking stake can be done through the lock_stake
method:
pub fn lock_stake(&mut self, address: ResourceAddress, id_proof: NonFungibleProof) -> FungibleBucket
- The
address
argument is the ResourceAddress of the token you wish to lock - The
id_proof
argument is a NonFungibleProof of the Staking ID, to prove the user is in possession of it. - The returned Bucket contains the locking rewards.
Claiming accrued rewards is done by calling the update_id
method:
pub fn update_id(&mut self, id_proof: NonFungibleProof) -> FungibleBucket
- The
id_proof
argument is a NonFungibleProof of the Staking ID you wish to claim rewards for. - A FungibleBucket of rewards is returned.
IMPORTANT: The max_claim_delay
parameter of the system determines the amount of previous periods you can still claim rewards from. By default, it's set to 5, but it can be altered by the component owner.
To update the system, a plethora of admin methods exists . Please refer to the blueprint for these. They are very simple, but all require proof of the owner badge, so be sure to include this in the manifest.
This package is far from perfect, so all contributions are welcome! If you want your contribution to be reviewed asap, contact @dusanrexxa02 on Telegram.
This package is released under modified MIT License.
Copyright 2024 Stabilis Labs
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software for non-production informational and educational purposes without
restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
This notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE HAS BEEN CREATED AND IS PROVIDED FOR NON-PRODUCTION, INFORMATIONAL
AND EDUCATIONAL PURPOSES ONLY.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE, ERROR-FREE PERFORMANCE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES,
COSTS OR OTHER LIABILITY OF ANY NATURE WHATSOEVER, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE, MISUSE OR OTHER DEALINGS IN THE SOFTWARE. THE AUTHORS SHALL
OWE NO DUTY OF CARE OR FIDUCIARY DUTIES TO USERS OF THE SOFTWARE.