AGPL-3.0 License
Cardinal staking encompasses a suite of contracts for issuing and staking NFTs and FTs. The simple program is a stake pool that tracks total stake duration. In addition, there is an implementation of a token minting reward distributor. Cardinal staking works well with any standard NFT collection and also composes with other programs in the Cardinal NFT infrastructure ecosystem.
Program addresses are the same on devnet, testnet, and mainnet-beta.
CYUGdQhsWCKXTWgbyuybhRfPTshxneywND8KnHeMfwQe
ALUFRuJCRacDhtKiMNJ43GtQ1q7Q9U8FKDJdp4VMyTwU
Cardinal stake pool is meant to be composable. A simple reward distributor is provided out of the box, but any complex reward distribution logic can be implemented in a similar manner. Other implementations of other reward distributors are welcomed and encouraged. Examples that could be built:
Stake Pool
Stake pools are the base component of staking. A stake pool, as it sounds, is a PDA owned by the stake pool program containing the following fields
#[account]
pub struct StakePool {
pub bump: u8,
pub identifier: u64,
pub authority: Pubkey,
pub requires_creators: Vec<Pubkey>,
pub requires_collections: Vec<Pubkey>,
pub requires_authorization: bool,
pub overlay_text: String,
pub image_uri: String,
pub reset_on_stake: bool,
pub total_staked: u32,
pub cooldown_seconds: Option<u32>,
pub min_stake_seconds: Option<u32>,
}
Requires_creators, requires_collections and requires_authorization are the 3 different ways that a stake pool can gate which NFTs can be staked in the pool.
Overlay text is used when creating receipts. This text will be automatically displayed on top of the NFT to indicate it is currently staked. For example, it could be "STAKED" or "TRAINING" as shown below.
Reset_on_stake, cooldown_period and min_stake_seconds are three additional functionalities you can add to your pool.
Stake Entry
Stake pools are a collection of stake entries. Each stake entry stores information related to a specific NFT and how long it has been staked.
Every time a new NFT is staked, a stake entry must first be created. This can happen in a single transaction by combining the init_entry
with stake
instructions. If a receipt mint is created, the current client will do this in two transactions due to compute limitations.
Stake entries also retain ownership of the given mint(s) while it is staked.
There are separate instructions for stake
and claim_receipt_mint
. Read below to learn more about receipts. The client will automatically stake the NFT and then optionally claim a receipt that can either contain the "orginal" mint OR a dynamic/mutable copy receipt mint.
Either or both of these mints must be returned to the stake_entry
before the user can unstake. This will be done automatically when calling the unstake
API.
#[account]
pub struct StakeEntry {
pub bump: u8,
pub pool: Pubkey,
pub amount: u64,
pub original_mint: Pubkey,
pub original_mint_claimed: bool,
pub last_staker: Pubkey,
pub last_staked_at: i64,
pub total_stake_seconds: i128,
pub stake_mint_claimed: bool,
pub kind: u8,
pub stake_mint: Option<Pubkey>,
}
Stake Receipts
Stake pool is designed to support general staking as well as a enable the concept of stake receipts.
Receipts is a feature that allows the user to have a representation of the staked NFT in their wallet
ReceiptType::Original
ReceiptType::Receipt
Reward Distributors
While just using the stake_pool
can be sufficient to keep track of total stake duration and lock the NFT in the user's wallet, a reward distributor can be optionally added to distribute rewards to staked NFTs.
Reward distributor is modeled similar to a stake pool, having both a reward_distributor
and a reward_entry
. The reward entry is unique for each mint and keeps track how many reward have been given out to that NFT to ensure that it gets its fair share.
As mentioned above, reward distibutor is a basic example of a fixed linear payout structure, but modeling this as a separate program allows for arbitrary pluggable reward logic for reward distribution.
#[account]
pub struct RewardDistributor {
pub bump: u8,
pub stake_pool: Pubkey,
pub kind: u8,
pub authority: Pubkey,
pub reward_mint: Pubkey,
pub reward_amount: u64,
pub reward_duration_seconds: u64,
pub rewards_issued: u64,
pub max_supply: Option<u64>,
pub default_multiplier: u64,
pub multiplier_decimals: u8,
}
#[account]
pub struct RewardEntry {
pub bump: u8,
pub stake_entry: Pubkey,
pub reward_distributor: Pubkey,
pub reward_seconds_received: u64,
pub multiplier: u64,
}
The reward distributor also can be of 2 different kinds, Mint
or Treasury
/ Transfer
max_supply
mint_authority
by closing the reward_distributor
using the close
instruction.reward_distributor
upon intialization.max_supply
.associated_token_account
of the reward_distributor
for the reward_mint
.In both kinds of reward distributors, if the max_supply
is hit, or the treasury runs out, the remaining rewards will be given out and the reward_seconds_received
will be partially updated.
Because reward distributor is modeled separately from the stake_pool, a user can optionally claim their rewards at any time for the amount of time they have staked. Typically, this is done automatically when calling unstake
by the client.
Reward Distributor Multipliers
Multipliers is a feature that can set a given token (via its reward_entry) to receive more rewards than the others. A reward distributor has two fields one for the default_multiplier
, defaults to 1
, and another for the multiplier_decimals
, defaults to 0
. Every time a reward entry is initialized, its multiplier gets set to the default_multiplier
of its reward distributor. Only the authority of the pool can change the multiplier by calling update_reward_entry
instruction. In the calculation of the claimable rewards for an entry, the multiplier
is divided by ten to the power of the distributor's multiplier_decimals
, achieving the outcome of decimal multipliers.
https://www.notion.so/cardinal-labs/Cardinal-Staking-Fees-14e66a64fb2d4615892937c5dbaa91cc
If you are developing using Cardinal staking contracts and libraries, feel free to reach out for support on Discord. We will work with you or your team to answer questions, provide development support and discuss new feature requests.
For issues please, file a GitHub issue.
Cardinal Protocol is licensed under the GNU Affero General Public License v3.0.
In short, this means that any changes to this code must be made open source and available under the AGPL-v3.0 license, even if only used privately.