This repo contains the Anchor program that powers NFT staking rewards on Solana. Users opt to lock up their NFTs with the protocol in exchange for periodic rewards in the form of a Token created exclusively for the purpose of rewarding NFT stakers.
The author(s) of this software do not claim that there is any monetary value in the token rewards distributed by this protocol.
Cluster | Address |
---|---|
devnet | 3zPPaZhN3tAkSJhjcEcyT7kAM6b2stQmJf65Fw9sMZa3 |
This NFT Staking Protocol consists of two main components, the Rewarder and user Stake Accounts. A Rewarder has information on the reward token, the reward rate, and the NFT metada that is allowed to earn rewards. A Stake Account has information on the owner of the account, the last time the owner claimed their rewards, and also acts as the wallet for any staked NFTs. NFT Owners can always view staked NFTs by finding associated NFT accounts for their Stake Account.
The Rewarder is an on-chain account that stores about the Token that is awarded to stakers, the rate at which tokens are awarded, and the NFTs that are allowed to be staked to earn rewards with the Rewarder.
Rewards are currently calculated in a straightforward fashion where stakers earn the rewardRate
in the reward token every second per staked NFT. There is no limit on the supply of the reward token as more will always be minted to award to stakers.
rewardEarned = elapsedSeconds * rewardRate * numStakedNFTs
Rewarders are created per collection at the Program Derived Address derived from the following seeds:
[collectionName, StakingProgramID, "rewarder"]
To ensure that only NFTs from the desired collection can earn rewards the protocol inspects associated Metaplex Token Metadata for staked NFTs. When enforceMetadata
is set to true, the protocol will compare 3 fields from the metadata to verify authenticity:
- UpdateAuthority
- Creators
- Collection Name
The metadata for staked NFTs must have matching update authority and creators to those stored in the Rewarder. The name of the NFT is compared to the collection
field of the Rewarder where the name must begin with the collection
. For example if the collection
is "gmoot"
the an NFT with the name "gmoot bag #69"
will be allowed. Rewarder operators should always ensure that at least 1 creator is verified using the SignMetadata instruction to ensure only verified NFTs can be staked.
Name | Type | Description |
---|---|---|
authority | Pubkey | The owner of the Rewarder. Can sign transactions to update the Rewarder |
reward_mint | Pubkey | The address of the reward Token Mint that is used to reward stakers |
reward_authority_bump | u8 | The PDA bump for the address that is used to sign MintTo instructions when rewarding stakers. Stored to save on-chain compute of recalculating |
reward_rate | u64 | The amount of reward tokens earned per second per staked NFT |
allowed_update_authority | Pubkey | The Pubkey required to match the Metaplex Token Metadata update authority |
creators | Array<Creator> | The allowed list of creators for verified NFTs. Creator matches the Metaplex definition of {address: Pubkey, verified: bool, share: u8} |
collection | string | The name of the NFT collection that is allowed to earn rewards. Staked NFTs must have this value as the first part of the name in the Metaplex Token Metadata. For example if the collection is "gmoot" the an NFT with the name "gmoot bag #69" will be allowed |
enforce_metadata | bool | A flag indicating whether or not the Metaplex Token Metadata is required for the Stake instruction. When set to false any NFT will be allowed to earn rewards. |
total_staked | u32 | The number of NFTs currently staked to this Rewarder |
A basic typescript client is provided in this repo at ts/cli.ts
to facilitate the creation and fetching of a Rewarder. From the ts
directory, you can run npm i
to install dependencies and then execute the CLI with:
npm start -- rewarder create -h
#Output
Usage: cli rewarder create [options]
Options:
-d, --decimals <number> The number of decimals for the reward token
-r, --rewardRate <number> The number reward per second per nft staked for the rewarder
-n, --name <string> The name of the NFT collection the rewarder is for
-c, --creators <path> the path to a json array of nft creator objects
-h, --help display help for command
The user Stake Account is a PDA stores the information that is used to calculate the earned rewards for the total number of staked NFTs for the owner. The Stake Account holds any locked up NFTs and allows integrations to list any staked NFTs in the same way you would for another wallet, given the stake account address.
The Stake Account address is calculated using the following seeds:
[collectionName, StakingProgramID, "stake_account", rewarderPubkey, ownerPubkey]
Name | Type | Description |
---|---|---|
owner | Pubkey | The owner of the stake account. Required signer for updating the stake account in anyway |
rewarder | Pubkey | The Rewarder that this stake account is associated with |
num_staked | u16 | The number of nfts the owner has staked with this stake account |
bump | u8 | The PDA bump of this stake account that is used to sign transaction when unstaking NFTs. Stored to save on-chain compute |
last_claimed | i64 | The unix timestamp of the last time that the owner claimed rewards for this stake account |