Skip to content

Conversation

@leandrogavidia
Copy link
Contributor

Add Native SOL Staking Support (Stake, Unstake, Withdraw)

Overview

This PR implements complete native SOL staking functionality adding support for stake, unstake, and withdraw operations through a new useStake React hook.

Summary of Changes

🎯 Core Features

  • Stake SOL - Delegate SOL to validators
  • Unstake SOL - Deactivate stake accounts
  • Withdraw SOL - Retrieve funds from deactivated stake accounts
  • Query Stake Accounts - Fetch and filter stake accounts by wallet and validator

Video demo

Google Drive link


New Files:

  • None (functionality added to existing files)

Modified Files:

  • packages/client/src/features/stake.ts - Complete staking implementation

    • Added StakePrepareConfig, StakeSendOptions types
    • Added UnstakePrepareConfig, UnstakeSendOptions types
    • Added WithdrawPrepareConfig, WithdrawSendOptions types
    • Added PreparedStake, PreparedUnstake, PreparedWithdraw types
    • Implemented createStakeHelper() factory with 11 methods:
      • prepareStake() - Creates stake account and delegation instructions
      • sendPreparedStake() - Executes stake transaction
      • sendStake() - Convenience wrapper
      • prepareUnstake() - Creates deactivate instruction
      • sendPreparedUnstake() - Executes unstake transaction
      • sendUnstake() - Convenience wrapper
      • prepareWithdraw() - Creates withdraw instruction
      • sendPreparedWithdraw() - Executes withdraw transaction
      • sendWithdraw() - Convenience wrapper
      • getStakeAccounts() - Queries stake accounts with optional validator filter
  • packages/client/src/controllers/stakeController.ts - State management controller

    • Added StakeController type with separate state management for stake/unstake/withdraw
    • Added StakeInput, UnstakeInput, WithdrawInput types
    • Implemented createStakeController() with:
      • Individual state tracking for each operation
      • Separate error handling per operation
      • Authority resolution with wallet fallback
      • Subscribe/reset methods for each operation
  • packages/client/src/client/createClientHelpers.ts - Client wrapper integration

    • Added wrapStakeHelper() to apply default commitment levels
    • Integrated stake helper into client helpers
  • packages/client/src/index.ts - Export all new types and functions

    • Exported all stake/unstake/withdraw types
    • Exported StakeController, StakeInput, UnstakeInput, WithdrawInput
    • Exported StakeAccount type

Dependencies Added:

  • @solana-program/stake@^0.9.0 - Official Solana stake program instruction builders

@solana/react-hooks Package

Modified Files:

  • packages/react-hooks/src/hooks.ts - React hook implementation
    • Added useStake(validatorId) hook with complete state management
    • Returns object with:
      • stake() - Function to stake SOL
      • unstake() - Function to unstake/deactivate
      • withdraw() - Function to withdraw from deactivated accounts
      • getStakeAccounts() - Function to query stake accounts
      • signature, unstakeSignature, withdrawSignature - Transaction signatures
      • status, unstakeStatus, withdrawStatus - Operation states
      • error, unstakeError, withdrawError - Error details
      • isStaking, isUnstaking, isWithdrawing - Loading flags
      • reset(), resetUnstake(), resetWithdraw() - State reset functions
      • helper - Direct access to StakeHelper
      • validatorId - Normalized validator address
    • Added defensive check for getStakeAccounts availability with helpful error message

🎨 Example Application Updates

Modified Files:

  • examples/react-hooks/src/components/StakePanel.tsx - Comprehensive staking UI

    • Full stake/unstake/withdraw interface
    • Auto-fetch stake accounts on wallet connect
    • Auto-refresh after successful operations
    • Status indicators (Active/Deactivated) with color coding
    • Deactivation epoch display
    • Smart button states:
      • Unstake enabled only for active stakes
      • Withdraw enabled only for deactivated stakes
    • Real-time transaction status for all operations
    • Error handling and display
  • examples/react-hooks/src/App.tsx - Configuration updates

    • Changed RPC endpoints to devnet for testing
    • Updated from mainnet to devnet configuration

Technical Implementation Details

Stake Transaction Flow

  1. Generates new keypair for stake account
  2. Calculates rent exemption (200 bytes)
  3. Creates account with getCreateAccountInstruction
  4. Initializes stake account with getInitializeInstruction
  5. Delegates to validator with getDelegateStakeInstruction
  6. Returns transaction signature and stake account address

Unstake Transaction Flow

  1. Creates deactivate instruction with getDeactivateInstruction
  2. Uses SYSVAR_CLOCK for timing
  3. Marks stake for deactivation (takes 1-2 epochs to complete)
  4. Returns transaction signature

Withdraw Transaction Flow

  1. Creates withdraw instruction with getWithdrawInstruction
  2. Uses SYSVAR_CLOCK and SYSVAR_STAKE_HISTORY sysvars
  3. Transfers lamports from deactivated stake account to destination
  4. Only works after cooldown period completes
  5. Returns transaction signature

Query Implementation

  • Uses getProgramAccounts with memcmp filter at offset 44 (withdrawer authority)
  • Optional validator filtering by checking delegation voter address
  • Returns parsed stake account data with all metadata

Architecture Patterns

Separation of Concerns

  • Helper Layer (stake.ts) - Pure transaction building and RPC interaction
  • Controller Layer (stakeController.ts) - State management and lifecycle
  • Hook Layer (hooks.ts) - React integration with subscription management
  • UI Layer (StakePanel.tsx) - User interface and interaction

State Management

  • Each operation (stake/unstake/withdraw) has independent state
  • Prevents race conditions and state conflicts
  • Allows concurrent operations without interference

Error Handling

  • Separate error tracking per operation
  • Authority validation with helpful error messages
  • Defensive checks for method availability
  • Comprehensive error display in UI

Testing Considerations

Manual Testing Checklist

  • Stake SOL to devnet validator
  • Query stake accounts by wallet address
  • Filter stake accounts by validator
  • Unstake/deactivate active stake account
  • View deactivation status and epoch
  • Withdraw from deactivated account (after cooldown)
  • Auto-refresh functionality
  • Error handling and display
  • Multi-operation support (can stake while having unstaked accounts)

Breaking Changes

None - This is a purely additive feature.

Migration Guide

Not applicable - New feature with no breaking changes.

Usage Example

import { useStake } from '@solana/react-hooks';

function StakingComponent() {
  const validatorId = 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu';
  
  const {
    stake,
    unstake,
    withdraw,
    getStakeAccounts,
    signature,
    status,
    isStaking,
  } = useStake(validatorId);

  // Stake 1 SOL
  const handleStake = async () => {
    const sig = await stake({
      amount: 1_000_000_000n, // lamports
    });
    console.log('Staked:', sig);
  };

  // Unstake (deactivate)
  const handleUnstake = async (stakeAccount: string) => {
    const sig = await unstake({ stakeAccount });
    console.log('Unstaked:', sig);
  };

  // Withdraw after cooldown
  const handleWithdraw = async (stakeAccount: string) => {
    const sig = await withdraw({
      stakeAccount,
      destination: walletAddress,
      amount: 1_000_000_000n,
    });
    console.log('Withdrawn:', sig);
  };

  // Query stake accounts
  const handleQuery = async () => {
    const accounts = await getStakeAccounts(walletAddress, validatorId);
    console.log('Stake accounts:', accounts);
  };

  return (/* UI components */);
}

Screenshots

The StakePanel component provides a comprehensive UI with:

  • Validator selection
  • Amount input
  • Stake/Unstake/Withdraw buttons
  • Account status indicators (Active/Deactivated)
  • Transaction status displays
  • Auto-refresh capabilities

Dependencies

  • @solana-program/stake@^0.9.0 - Official stake program instruction builders
  • @solana-program/system@^0.9.0 - System program instructions (existing)
  • All existing @solana/kit dependencies

Documentation

  • Comprehensive JSDoc comments in all new functions
  • Type definitions for all configurations and return types
  • Inline code comments explaining stake program constants and flows

Additional Notes

  • Tested on Solana devnet with Certus One validator
  • Tested on Solana mainnet with Helius validator
  • All operations follow Solana stake program specifications
  • Implements best practices from official Solana documentation

- Introduced StakePanel component for staking SOL to a validator.
- Implemented useStake hook to manage staking operations and state.
- Created stakeController to handle staking logic and interactions with the stake program.
- Added stake feature helpers for preparing and sending stake transactions.
- Updated client and react-hooks packages to include staking dependencies and types.
- Enhanced tests for stake functionality, ensuring proper error handling and state management.
@GuiBibeau GuiBibeau self-requested a review December 3, 2025 08:40
@GuiBibeau
Copy link
Collaborator

@leandrogavidia this is solid work 🔥 Can you add a changeset (minor), and we'll get that merged in?

@leandrogavidia
Copy link
Contributor Author

leandrogavidia commented Dec 3, 2025

@leandrogavidia this is solid work 🔥 Can you add a changeset (minor), and we'll get that merged in?

@GuiBibeau Thank you! Yes, of course! Let me know what are the changes ,please! 💯

@GuiBibeau
Copy link
Collaborator

@leandrogavidia actually i'll do a minor release with other changes including yours, you can ignore this for now!

changeset is the npm release tool we use for controlling pushes to NPM

@GuiBibeau GuiBibeau merged commit 45efbdb into solana-foundation:main Dec 3, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants