Skip to content

tokens/nft-staking: propose an NFT staking example (delegate + freeze, checkpointed rewards) #726

Description

@Harsh-H-Shah

Proposal

I'd like to add tokens/nft-staking/anchor: stake an NFT to earn reward tokens over time, and claim those rewards without unstaking.

The NFT never leaves the owner's wallet. Instead of moving it into a vault, the program takes delegate authority over the owner's token account with approve and freezes it in place via Metaplex FreezeDelegatedAccount, reversing with ThawDelegatedAccount + revoke on unstake. That is how NFT staking usually works in production, and it is a deliberate contrast with the vault custody tokens/escrow already teaches.

Raising this first per CONTRIBUTING, since a new example is a substantial change.

What it teaches

  • Delegate-and-freeze custodyapprove + FreezeDelegatedAccount appears nowhere today. The only other "delegate" matches are Token-2022's permanent-delegate, a mint-level admin override rather than a revocable per-account approval.
  • Reward accrual against a checkpoint — every current Clock use (token-fundraiser, games/gacha, games/world-cup) is a one-shot deadline check. None settle a value repeatedly across an account's life, which is the mechanism under every yield-bearing protocol on Solana and has its own failure mode: settle without recording that you settled, and the next call pays for the same span again.
  • emit! events — not used in any example today.

Design

Account Seeds Holds
StakeConfig ["config"] Collection, reward rate, stake cap, freeze period
UserAccount ["user", user] Lifetime points, currently staked count
StakeAccount ["stake", nft_mint, config] Owner, mint, staked_at, last_claimed_at

Instructions: initialize_config, initialize_user, stake, claim, unstake. StakeAccount doubles as the SPL delegate for the staked NFT, and the reward mint's authority is the config PDA so only this program can mint.

Two design notes worth flagging, since both are easy to miss and the example is partly there to teach them:

  • claim advances last_claimed_at by exactly the whole days it pays for, in the same instruction as the payout. Snapping it to now instead would silently swallow the part-day remainder.
  • approve and the Metaplex freeze both succeed on a zero-balance token account, and anyone can open an ATA for any mint — so stake has to check the balance explicitly, or a caller can farm rewards from an NFT they never held.

Scope

Anchor only to start, on the current pins (anchor-lang/anchor-spl 1.0.2, freeze/thaw through anchor-spl's metadata feature with no direct mpl-token-metadata dependency). Tests on LiteSVM with clock warping, against the real Token Metadata program from the prepare.mjs fixture.

Prior art: #58 and #118 both built NFT staking and neither landed

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions