basic_token_staking
Conceptual Seahorse program for basic staking of BRB token .
# seahorse_example_staking.py
# Conceptual Seahorse program for basic staking of a token (could be BRB).
# Assumes the token exists (e.g., the MPL-404 BRB token).
from seahorse.prelude import *
# Assume BRB Token Mint Pubkey is known
# declare_id('BRBTokenMintAddressHERE') # Replace with actual Mint address
# brb_token_mint = Pubkey('BRBTokenMintAddressHERE')
# Declare the Token Program ID (could be Token-2022 or legacy SPL Token)
# Replace with the correct program ID used by your BRB token
declare_id('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') # Legacy SPL Token example
token_program = Program('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
# --- Staking Vault Configuration ---
@account
class StakingVault:
admin: Pubkey
brb_token_mint: Pubkey # Mint of the token being staked
vault_token_account: Pubkey # PDA token account holding staked tokens
total_staked: u64
reward_rate_per_slot: u64 # Simple reward calculation example
# --- User Stake Record ---
@account
class StakeRecord:
owner: Pubkey # The user who staked
amount_staked: u64
last_claimed_slot: u64 # Slot when rewards were last claimed
# --- Instruction: Initialize Staking Vault ---
@instruction
def init_staking_vault(
vault: Empty[StakingVault], # Account to be created
vault_token_account: Empty[TokenAccount], # Token account PDA to be created
brb_token_mint: Account, # Mint account of the token
admin: Signer, # Authority initializing the vault
reward_rate: u64
):
# Create the vault PDA token account to hold staked tokens
# Authority is the vault account itself (PDA)
vault_pda_token_account = vault_token_account.init(
payer = admin,
seeds = ['vault-token-account', vault], # Use vault address as seed
mint = brb_token_mint,
authority = vault # The vault PDA is the authority
)
# Create the StakingVault config account
v = vault.init(payer=admin, seeds=['staking-vault', brb_token_mint])
v.admin = admin.key()
v.brb_token_mint = brb_token_mint.key()
v.vault_token_account = vault_pda_token_account.key()
v.total_staked = 0
v.reward_rate_per_slot = reward_rate # e.g., units of reward per slot per token staked
print("Staking Vault Initialized")
# --- Instruction: Create Stake Record ---
# Users need to create this once before staking
@instruction
def create_stake_record(
record: Empty[StakeRecord], # Account to be created
vault: StakingVault, # Reference the vault config
staker: Signer # The user creating the record
):
# Create the StakeRecord account for the user
rec = record.init(
payer = staker,
seeds = ['stake-record', vault, staker.key()] # Seeds ensure one record per user per vault
)
rec.owner = staker.key()
rec.amount_staked = 0
rec.last_claimed_slot = Clock.get().slot # Initialize to current slot
print(f"Stake Record created for {staker.key()}")
# --- Instruction: Stake Tokens ---
@instruction
def stake_tokens(
vault: Mut[StakingVault], # Vault to update total staked
record: Mut[StakeRecord], # User's stake record to update
staker: Signer, # User initiating the stake
staker_token_account: TokenAccount, # User's source token account
vault_token_account: Mut[TokenAccount], # PDA token account holding staked tokens
amount: u64
):
# 1. Check authority and ownership
assert staker.key() == record.owner, "Signer mismatch"
assert staker_token_account.owner == staker.key(), "Token account owner mismatch"
assert vault_token_account.key() == vault.vault_token_account, "Vault token account mismatch"
assert amount > 0, "Stake amount must be positive"
# 2. Calculate and claim pending rewards before staking more
# (Good practice to avoid reward calculation complexity)
# claim_rewards(vault, record, staker, vault_token_account, staker_token_account) # Call claim logic if separate
# 3. Transfer tokens from staker to vault PDA token account using CPI
transfer(
from_ = staker_token_account,
to = vault_token_account,
authority = staker,
amount = amount
).invoke(token_program.key()) # Invoke SPL Token transfer
# 4. Update stake record and vault total
record.amount_staked += amount
vault.total_staked += amount
# Update last_claimed_slot if rewards were claimed implicitly here
record.last_claimed_slot = Clock.get().slot
print(f"{amount} tokens staked by {staker.key()}. Total staked: {record.amount_staked}")
# --- Instruction: Unstake Tokens ---
@instruction
def unstake_tokens(
vault: Mut[StakingVault], # Vault to update total staked
record: Mut[StakeRecord], # User's stake record to update
staker: Signer, # User initiating the unstake (must match record.owner)
staker_token_account: Mut[TokenAccount], # User's destination token account
vault_token_account: Mut[TokenAccount], # PDA token account holding staked tokens
vault_pda_signer: SeahorseSigner, # PDA signer for the vault account
amount: u64
):
# 1. Check authority and ownership
assert staker.key() == record.owner, "Signer mismatch"
assert staker_token_account.owner == staker.key(), "Token account owner mismatch"
assert vault_token_account.key() == vault.vault_token_account, "Vault token account mismatch"
assert amount > 0, "Unstake amount must be positive"
assert record.amount_staked >= amount, "Insufficient staked balance"
# 2. Calculate and claim pending rewards before unstaking
# claim_rewards(...) # Call claim logic
# 3. Transfer tokens from vault PDA back to staker using CPI
# Requires signing with the vault PDA's seeds
transfer(
from_ = vault_token_account,
to = staker_token_account,
authority = vault_pda_signer, # Use the PDA signer derived from seeds
amount = amount
).invoke_signed(
token_program.key(),
signer_seeds = [['vault-token-account', vault]] # Seeds used to create the vault_token_account
)
# 4. Update stake record and vault total
record.amount_staked -= amount
vault.total_staked -= amount
record.last_claimed_slot = Clock.get().slot # Update claim slot
print(f"{amount} tokens unstaked by {staker.key()}. Remaining staked: {record.amount_staked}")
# --- Instruction: Claim Rewards (Simplified Example) ---
@instruction
def claim_rewards(
vault: StakingVault, # Vault config for reward rate
record: Mut[StakeRecord], # User's stake record
staker: Signer, # User claiming rewards
staker_token_account: Mut[TokenAccount], # User's destination token account
reward_source_account: Mut[TokenAccount], # Account holding rewards to distribute
reward_source_authority: Signer # Authority that can send rewards (e.g., admin)
# In a real scenario, rewards might be minted or come from a treasury PDA
):
# 1. Check authority
assert staker.key() == record.owner, "Signer mismatch"
# 2. Calculate elapsed slots and rewards
current_slot = Clock.get().slot
slots_elapsed = current_slot - record.last_claimed_slot
# Simple reward calculation: rate * amount_staked * slots_elapsed
# WARNING: This is a basic example. Real reward calculation needs care
# regarding precision, large numbers, and potential exploits.
# Consider using u128 for intermediate calculations.
reward_amount = (vault.reward_rate_per_slot * record.amount_staked * slots_elapsed) // 1_000_000 # Adjust divisor based on rate scaling
if reward_amount == 0:
print("No rewards to claim yet.")
return
# 3. Transfer rewards from the source to the staker
# This assumes an external account holds rewards. A better model might
# use a rewards pool PDA funded separately.
transfer(
from_ = reward_source_account,
to = staker_token_account,
authority = reward_source_authority,
amount = reward_amount
).invoke(token_program.key()) # Or invoke_signed if source is PDA
# 4. Update the last claimed slot
record.last_claimed_slot = current_slot
print(f"{reward_amount} rewards claimed by {staker.key()}")
Last updated