Production-grade React + Vite frontend for a Solana staking dApp (Anchor-based). It delivers a streamlined UX for staking SPL tokens, claiming rewards, and managing associated token accounts (ATAs) on devnet. Pairs with the on-chain program in the sibling stake-flow repo and includes an optional serverless mint endpoint for admin/dev workflows.
- Overview
- Highlights
- Tech Stack
- Quick Start
- Configuration
- Environment Variables
- Project Structure
- Staking Flows
- Serverless Mint
- Development
- Deployment
- Troubleshooting
- Security
- Accessibility & UX
- Browser Support
- Contributing
- Acknowledgments
- Devnet-first UI built around Anchor IDL and PDAs/ATAs.
- Works with Phantom and Solflare via Solana Wallet Adapter.
- Clean error handling, deduplicated toasts, and accessible defaults.
- Modern stack: React 19, Vite 7, TypeScript, TailwindCSS.
- Wallet Adapter UI with refined overrides in
src/index.css. - IDL-driven program interactions, PDAs and ATAs handled automatically.
- Developer-friendly: local API emulation, clear logs, Solscan links.
- React 19, TypeScript, Vite 7
- TailwindCSS (utility-first; UI overrides for wallet components)
- Solana Wallet Adapter,
@solana/web3.js,@solana/spl-token - Anchor IDL (
src/idl/stake_flow.json) - React Hot Toast for notifications
- Prerequisites
- Node.js 18+
- Phantom or Solflare wallet configured for
devnet
- Install & Run
npm installnpm run dev- Open the preview URL and connect your wallet
- Program and mints (devnet) are defined in
src/config.ts:STAKE_FLOW_PROGRAM_ID = "4cUDbCQvhBSzWbTivv3ZscDkePVweqRFAHbgDUKLkfdK"STAKE_MINT_ADDRESS = "BeyV4AuCPvchhJc7NXSaAa2ECbPVkj39wy9CY7fu8opD"REWARD_MINT_ADDRESS = "GQCW1M9szh426zC5a51BLZbPhvXoPnMKCeRWepyCziK3"TOKEN_DECIMALS = 9
- Transaction explorer:
solscanTxUrl(sig)(targetsdevnet).
- Located in
.envfor local development. Do not commit production secrets. - Keys
VITE_ENABLE_STAKE_FAUCET— show devnet faucet UI in the appMINT_NETWORK— cluster, e.g.,devnetRPC_URL— optional RPC endpoint (defaults per cluster)MINT_ADDRESS— SPL mint used for stake tokenMINT_DECIMALS— token decimals (e.g.,9)MINT_AUTHORITY_SECRET_KEY— mint authority private key (base58 or JSON array); set only in provider envMAX_MINT_PER_REQUEST— upper bound for per-request minting
- Example
VITE_ENABLE_STAKE_FAUCET=true
MINT_NETWORK=devnet
RPC_URL=
MINT_ADDRESS=BeyV4AuCPvchhJc7NXSaAa2ECbPVkj39wy9CY7fu8opD
MINT_DECIMALS=9
MINT_AUTHORITY_SECRET_KEY=...[base58 or JSON array]...
MAX_MINT_PER_REQUEST=100
src/App.tsx— shell, header, wallet gate, routing toStakeFlowUIsrc/components/StakeFlowUI.tsx— core staking UI and flowssrc/utils/— PDAs, tokens, formatting, error handling helperssrc/idl/— Anchor IDL JSON for the programsrc/config.ts— program/mint addresses and Solscan helperapi/mint.ts— optional serverless mint endpoint (Node runtime)vite.config.ts— dev server middleware for local API emulation
- Stake
- Enter amount, ensure Stake ATA exists, verify balance, submit stake instruction
- View position details (amount, pending rewards, lock until)
- Claim
- PDA mints rewards to your Reward ATA; app polls for finalized state
- Solscan link opens after success
- Unstake
- Early-unstake penalty applies and transfers to the penalty vault
- Net amount returns to user’s Stake ATA; position updates accordingly
- Overview
api/mint.tsmints stake tokens to a connected wallet (devnet-only)- Local API emulation maps
/api/*to files inapi/viavite.config.ts
- Request Contract (
POST /api/mint.ts)- Fields:
wallet,amount,signature(base58),timestamp(ms) - Response:
transaction(base64),lastValidBlockHeight, optionalsimulationLogs
- Fields:
- Client Example (React + Wallet Adapter)
import bs58 from 'bs58'
import { useWallet } from '@solana/wallet-adapter-react'
async function requestMint(amountUi: number) {
const { publicKey, wallet } = useWallet()
if (!publicKey || !wallet?.adapter?.signMessage) throw new Error('Wallet not ready or does not support signMessage')
const timestamp = Date.now()
const message = `stakeflow-mint:${publicKey.toBase58()}:${timestamp}`
const encoded = new TextEncoder().encode(message)
const signature = await wallet.adapter.signMessage(encoded)
const res = await fetch('/api/mint.ts', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ wallet: publicKey.toBase58(), amount: amountUi, signature: bs58.encode(signature), timestamp }),
})
const json = await res.json()
if (!res.ok) throw new Error(json?.error || 'Mint request failed')
return json
}- Scripts
npm run dev— start dev servernpm run build— typecheck and build production bundlenpm run preview— preview production build locallynpm run lint— run ESLint
- Styling
- TailwindCSS for layout and visuals; wallet UI overrides in
src/index.css
- TailwindCSS for layout and visuals; wallet UI overrides in
- Error Handling
- Centralized handling in
src/main.tsxandsrc/App.tsx src/utils/errors.tsnormalizes common cases and messages
- Centralized handling in
- Vercel (recommended)
- Configure environment variables in Project Settings (do not commit secrets)
- Use default build (
vite build) and preview (vite preview) commands api/mint.tsruns in Vercel’s Node runtime
- Other Hosts
- Any static host that serves
dist/works - For serverless endpoints, adapt the dev middleware or move API code to your host’s functions
- Any static host that serves
- Wallet not found: install/open Phantom or Solflare; ensure extension is active
- Connection issues: switch to
devnet, check RPC availability, and retry - Insufficient funds: use Solana devnet faucet to get SOL for fees
- Transaction errors: read toast messages and console logs; update wallet for v0 support
- Never commit real
MINT_AUTHORITY_SECRET_KEYto source control - Keep the mint endpoint to development; apply rate limiting and server-side checks if ever exposed
- Update wallets and dependencies to maintain v0 transactions and
signMessagesupport
- High-contrast dark theme; focus rings on interactive elements
- Toasts provide immediate feedback; prompts deduplicated under React StrictMode
- Modern evergreen browsers (Chrome, Edge, Firefox, Safari) on desktop
- Wallet adapters depend on extension availability and recent versions
- Run
npm run lintand ensure no errors before a PR - Keep changes minimal and focused; avoid unrelated refactors
- Document new env variables or config in this README
- Built on Solana, Anchor, and Solana Wallet Adapter
- UI tuned for clarity, speed, and developer-friendly testing on devnet