Solana Framework-Kit Tutorial
December 2, 2025A comprehensive guide to building Solana dApps with Framework-Kit - an evolving multi-framework client library (React-first) that orchestrates wallets, transactions, and reactive data flows.
Introduction
What is Framework-Kit?
Framework-Kit is an experimental, evolving multi-framework Solana client library that provides:
- Unified Wallet Orchestration: Supports Wallet Standard (Phantom, Solflare, Backpack, etc.) with auto-discovery
- Reactive Data Flows: Built-in Zustand store and SWR-based query layer for freshness-aware data
- Transaction Helpers: Simplified transaction building, signing, and submission workflows
- Framework Agnostic Core: Headless client works anywhere (React, Svelte, API routes, workers)
- React-First Hooks: Comprehensive React bindings with providers and UI helpers
| Era | Library | Key Features |
|---|---|---|
| 2020-2024: The Legacy Era | @solana/web3.js (v1.x) |
• Monolithic, class-based architecture • Everything bundled together (can't tree-shake) • ~311KB/226KB bundle impact even for simple apps • Connection class with dozens of methods you don't need • Still works, but archived/maintenance-only |
| 2024-2025: The Modern Foundation | @solana/kit (formerly web3.js v2) |
• Modular, function-based architecture • Fully tree-shakable (use only what you need) • Native BigInt, Ed25519, modern JS features • Type-safe RPC methods • Low-level primitives only (verbose boilerplate) |
| 2025: The Abstraction Layer | Gill | • Built on @solana/kit• Higher-level abstractions (createTransaction, createSolanaClient) • Reduces boilerplate significantly • Still requires manual state management |
| 2025+: The Reactive Framework | Framework-Kit | • Built on modern foundations • Reactive state management (Zustand + SWR) • Framework bindings (React hooks, providers) • Real-time subscriptions built-in • Complete solution for building dApps |
Package Overview
| Package | Description |
|---|---|
@solana/client |
Headless Solana client - works in any runtime |
@solana/react-hooks |
React hooks and providers powered by the client |
@solana/web3-compat |
Migration layer for existing @solana/web3.js code |
Installation
Prerequisites
- Node.js >= 20.18.0
- pnpm >= 10.20.0 (recommended) or npm
Install Packages
# Using pnpm (recommended)
pnpm add @solana/client @solana/react-hooks
# Using npm
npm install @solana/client @solana/react-hooks
# For web3.js migration compatibility
pnpm add @solana/web3-compat
Getting Started
Basic React Setup
The quickest way to get started is with React:
import { createClient, autoDiscover } from "@solana/client";
import { SolanaProvider, useWalletConnection } from "@solana/react-hooks";
// 1. Create a Solana client with wallet connectors
const client = createClient({
endpoint: "https://api.devnet.solana.com",
walletConnectors: autoDiscover(),
});
// 2. Create wallet connection buttons
function WalletButtons() {
const { connectors, connect, disconnect, wallet, status } = useWalletConnection();
if (status === "connected") {
return (
<div>
<p>Connected: {wallet?.account.address.toString()}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return (
<>
{connectors.map((connector) => (
<button key={connector.id} onClick={() => connect(connector.id)}>
Connect {connector.name}
</button>
))}
</>
);
}
// 3. Wrap your app with SolanaProvider
export function App() {
return (
<SolanaProvider client={client}>
<WalletButtons />
</SolanaProvider>
);
}
Headless Client Usage (No React)
The @solana/client package works independently of React:
import { createClient, phantom, solflare, backpack, autoDiscover } from "@solana/client";
// Configure wallet connectors
const walletConnectors = [...phantom(), ...solflare(), ...backpack(), ...autoDiscover()];
const client = createClient({
endpoint: "https://api.devnet.solana.com",
websocketEndpoint: "wss://api.devnet.solana.com",
walletConnectors,
});
// Connect a wallet
await client.actions.connectWallet("phantom");
// Fetch account data
const account = await client.actions.fetchAccount(address);
console.log("Balance:", account.lamports?.toString());
// Watch balance changes in real-time
const watcher = client.watchers.watchBalance({ address }, (lamports) => {
console.log("New balance:", lamports.toString());
});
// Stop watching when done
watcher.abort();
Core Concepts
Client Store (State Management)
Framework-Kit uses a Zustand store to manage all state. The store contains:
type ClientState = {
cluster: {
endpoint: string;
websocketEndpoint?: string;
commitment: "processed" | "confirmed" | "finalized";
status: { status: "idle" | "connecting" | "ready" | "error"; latencyMs?: number };
};
wallet: {
status: "disconnected" | "connecting" | "connected" | "error";
session?: WalletSession;
connectorId?: string;
};
accounts: {
[address: string]: {
address: Address;
lamports: bigint | null;
data?: unknown;
fetching: boolean;
slot: bigint | null;
};
};
transactions: {
[id: string]: {
signature?: string;
status: "idle" | "sending" | "waiting" | "confirmed" | "failed";
error?: unknown;
};
};
subscriptions: { account: Record<string, SubscriptionStatus>; signature: Record<string, SubscriptionStatus> };
lastUpdatedAt: number;
};
Actions
Actions are Promise-based functions that interact with the Solana network:
| Action | Description |
|---|---|
connectWallet(connectorId) |
Connect a wallet via Wallet Standard |
disconnectWallet() |
Disconnect the active wallet |
fetchAccount(address) |
One-time account data fetch |
fetchBalance(address) |
Get lamport balance |
sendTransaction(tx) |
Submit a transaction |
setCluster(endpoint) |
Switch RPC endpoint |
requestAirdrop(address, lamports) |
Request devnet SOL |
Watchers (Subscriptions)
Watchers subscribe to real-time updates:
| Watcher | Description |
|---|---|
watchBalance(config, callback) |
Subscribe to balance changes |
watchAccount(config, callback) |
Subscribe to account data changes |
watchSignature(signature, callback) |
Track transaction confirmation |
Helpers
Pre-built utilities for common operations:
- Transaction Helper (
client.helpers.transaction) - SOL Transfer Helper (
client.solTransfer) - SPL Token Helper (
client.splToken)
React Integration
Provider Setup
Wrap your app with SolanaProvider:
import { createClient, autoDiscover } from "@solana/client";
import { SolanaProvider } from "@solana/react-hooks";
const client = createClient({
endpoint: "https://api.devnet.solana.com",
walletConnectors: autoDiscover(),
});
export function App() {
return (
<SolanaProvider client={client}>
{/* Your components go here */}
</SolanaProvider>
);
}
Available Hooks
Wallet Hooks
| Hook | Purpose |
|---|---|
useWallet() |
Access wallet status and session |
useWalletConnection() |
Connect, disconnect, list connectors |
useWalletSession() |
Get current wallet session |
Data Hooks
| Hook | Purpose |
|---|---|
useBalance(address) |
Reactive balance tracking |
useAccount(address) |
Account data with auto-refresh |
Transaction Hooks
| Hook | Purpose |
|---|---|
useSolTransfer() |
Send SOL easily |
useSplToken(mint) |
SPL token balance and transfers |
useSendTransaction() |
Send arbitrary transactions |
useTransactionPool() |
Build complex transactions |
Query Hooks
| Hook | Purpose |
|---|---|
useLatestBlockhash() |
Get current blockhash |
useProgramAccounts(program) |
Query program accounts |
useSimulateTransaction(wire) |
Simulate transactions |
useSignatureStatus(signature) |
Check transaction status |
useWaitForSignature(signature) |
Wait for confirmation |
Hook Examples
Connect, Disconnect, and Show Balance
import { useWalletConnection, useBalance } from "@solana/react-hooks";
function WalletPanel() {
const { connectors, connect, disconnect, wallet, status } = useWalletConnection();
const address = wallet?.account.address;
const balance = useBalance(address);
if (status === "connected") {
return (
<div>
<p>{address?.toString()}</p>
<p>Lamports: {balance.lamports?.toString() ?? "loading..."}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return connectors.map((c) => (
<button key={c.id} onClick={() => connect(c.id)}>
Connect {c.name}
</button>
));
}
Read Lamport Balance (Auto-Fetch + Watch)
import { useBalance } from "@solana/react-hooks";
function BalanceCard({ address }: { address: string }) {
const { lamports, fetching, slot } = useBalance(address);
if (fetching) return <p>Loading...</p>;
return (
<p>
Lamports: {lamports?.toString() ?? "0"} (slot {slot?.toString() ?? "-"})
</p>
);
}
Send SOL
import { useSolTransfer } from "@solana/react-hooks";
function SendSol({ destination }: { destination: string }) {
const { send, isSending, status, signature, error } = useSolTransfer();
return (
<div>
<button
disabled={isSending}
onClick={() => send({ destination, lamports: 100_000_000n /* 0.1 SOL */ })}
>
{isSending ? "Sending..." : "Send 0.1 SOL"}
</button>
<p>Status: {status}</p>
{signature && <p>Signature: {signature}</p>}
{error && <p role="alert">Error: {String(error)}</p>}
</div>
);
}
SPL Token Balance + Transfer
import { useSplToken } from "@solana/react-hooks";
function TokenPanel({ mint, destinationOwner }: { mint: string; destinationOwner: string }) {
const { balance, send, isSending, owner } = useSplToken(mint);
return (
<div>
<p>Owner: {owner ?? "Connect wallet"}</p>
<p>Balance: {balance?.uiAmount ?? "0"}</p>
<button
disabled={isSending || !owner}
onClick={() => send({ amount: 1n, destinationOwner })}
>
{isSending ? "Sending..." : "Send 1 token"}
</button>
</div>
);
}
Build and Send Arbitrary Transactions
import type { TransactionInstructionInput } from "@solana/client";
import { useTransactionPool } from "@solana/react-hooks";
function TransactionFlow({ ix }: { ix: TransactionInstructionInput }) {
const pool = useTransactionPool();
return (
<div>
<button onClick={() => pool.addInstruction(ix)}>Add instruction</button>
<button disabled={pool.isSending} onClick={() => pool.prepareAndSend()}>
{pool.isSending ? "Sending..." : "Prepare & Send"}
</button>
<p>Blockhash: {pool.latestBlockhash.blockhash ?? "loading..."}</p>
</div>
);
}
Track Confirmations for a Signature
import { useWaitForSignature } from "@solana/react-hooks";
function SignatureWatcher({ signature }: { signature: string }) {
const wait = useWaitForSignature(signature, { commitment: "finalized" });
if (wait.waitStatus === "error") return <p role="alert">Failed</p>;
if (wait.waitStatus === "success") return <p>Finalized!</p>;
if (wait.waitStatus === "waiting") return <p>Waiting...</p>;
return <p>Provide a signature</p>;
}
Query Program Accounts
import { SolanaQueryProvider, useProgramAccounts } from "@solana/react-hooks";
function ProgramAccounts({ program }: { program: string }) {
const query = useProgramAccounts(program);
if (query.isLoading) return <p>Loading...</p>;
if (query.isError) return <p role="alert">RPC error</p>;
return (
<div>
<button onClick={() => query.refresh()}>Refresh</button>
<ul>
{query.accounts.map(({ pubkey }) => (
<li key={pubkey.toString()}>{pubkey.toString()}</li>
))}
</ul>
</div>
);
}
// Wrap with SolanaQueryProvider
function ProgramAccountsSection({ program }: { program: string }) {
return (
<SolanaQueryProvider>
<ProgramAccounts program={program} />
</SolanaQueryProvider>
);
}
Using Suspense (Opt-in)
Enable Suspense per subtree for cleaner loading states:
import { SolanaQueryProvider, useBalance } from "@solana/react-hooks";
import { Suspense } from "react";
function BalanceDetails({ address }: { address: string }) {
const balance = useBalance(address);
return <p>Lamports: {balance.lamports?.toString() ?? "0"}</p>;
}
export function WalletPanel({ address }: { address: string }) {
return (
<SolanaQueryProvider suspense>
<Suspense fallback={<p>Loading balance...</p>}>
<BalanceDetails address={address} />
</Suspense>
</SolanaQueryProvider>
);
}
Provider SWR Config (Optional)
Customize the SWR query layer:
export function App() {
return (
<SolanaProvider
client={client}
query={{
config: {
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: 30_000,
},
}}
>
<WalletPanel />
</SolanaProvider>
);
}
Default SWR settings:
revalidateOnFocus:truerevalidateOnReconnect:truerevalidateIfStale:truededupingInterval:2000msfocusThrottleInterval:5000ms
Access the Client Store Directly
import { useClientStore } from "@solana/react-hooks";
function ClusterBadge() {
const cluster = useClientStore((s) => s.cluster);
return <p>Endpoint: {cluster.endpoint}</p>;
}
Next.js Integration
Step 1: Configure Next.js
Create or update next.config.mjs:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: [
"@solana/client",
"@solana/react-hooks",
"@solana/web3-compat",
"@solana/kit",
],
};
export default nextConfig;
Step 2: Create a Client Provider
Create app/providers.tsx:
'use client';
import type { SolanaClientConfig } from '@solana/client';
import { SolanaProvider } from '@solana/react-hooks';
import type { PropsWithChildren } from 'react';
const defaultConfig: SolanaClientConfig = {
cluster: 'devnet',
rpc: 'https://api.devnet.solana.com',
websocket: 'wss://api.devnet.solana.com',
};
function Providers({ children }: PropsWithChildren) {
return <SolanaProvider config={defaultConfig}>{children}</SolanaProvider>;
}
export default Providers;
Step 3: Wrap Your Layout
Update app/layout.tsx:
import type { Metadata } from 'next';
import type { ReactNode } from 'react';
import Providers from './providers';
import './globals.css';
export const metadata: Metadata = {
title: 'My Solana App',
description: 'Built with Framework-Kit',
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Step 4: Use Hooks in Client Components
Create client components that use hooks:
// app/components/wallet-panel.tsx
'use client';
import { useWalletConnection, useBalance } from '@solana/react-hooks';
export function WalletPanel() {
const { connectors, connect, disconnect, wallet, status } = useWalletConnection();
const address = wallet?.account.address;
const balance = useBalance(address);
if (status === 'connected') {
return (
<div>
<p>Connected: {address?.toString()}</p>
<p>Balance: {balance.lamports?.toString() ?? 'Loading...'} lamports</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return (
<div>
<h3>Connect a Wallet</h3>
{connectors.map((c) => (
<button key={c.id} onClick={() => connect(c.id)}>
{c.name}
</button>
))}
</div>
);
}
Important Next.js Notes
- Mark client components with
'use client'- All components using hooks must be client components - SolanaProvider must be in a client component - Create a separate providers file
- No server-side hooks - Use
@solana/clientdirectly in API routes for server-side operations - Environment variables - Use
NEXT_PUBLIC_prefix for client-side access:NEXT_PUBLIC_SOLANA_RPC=https://api.devnet.solana.com NEXT_PUBLIC_SOLANA_WS=wss://api.devnet.solana.com
Features & API Reference
Transaction Helper
The transaction helper handles blockhashes, fee payers, and signing:
// Prepare a transaction
const prepared = await client.helpers.transaction.prepare({
authority: walletSession,
instructions: [instruction],
});
// Send the prepared transaction
const signature = await client.helpers.transaction.send(prepared);
console.log(signature.toString());
Methods:
prepare(request)- Build transaction and refresh blockhashsign(prepared, options)- Collect signaturestoWire(prepared)- Get Base64 wire transactionsend(prepared, options)- Submit transactionprepareAndSend(request, options)- All-in-one with compute unit tuning
SOL Transfer Helper
// Prepare and send SOL
const signature = await client.solTransfer.prepareAndSend({
authority: walletSession,
destination: recipientAddress,
amount: 100_000_000n, // 0.1 SOL
});
SPL Token Helper
// Transfer SPL tokens (auto-creates ATA if needed)
const signature = await client.splToken.prepareAndSend({
mint: tokenMintAddress,
authority: walletSession,
destinationOwner: recipientAddress,
amount: 100n,
ensureDestinationAta: true, // Auto-create ATA
});
Wallet Connectors
import { phantom, solflare, backpack, autoDiscover } from "@solana/client";
// Combine specific and auto-discovered wallets
const walletConnectors = [
...phantom(), // Phantom wallet
...solflare(), // Solflare wallet
...backpack(), // Backpack wallet
...autoDiscover(), // All Wallet Standard wallets
];
const client = createClient({
endpoint: "https://api.devnet.solana.com",
walletConnectors,
});
Examples
The repository includes two example applications:
Vite + React Example
Location: examples/vite-react/
Features demonstrated:
- Wallet connection UI
- Balance tracking with live updates
- SOL transfers with memo support
- SPL token operations
- Transaction pool for complex transactions
- Blockhash polling
- Program account queries
- Transaction simulation
- Signature watching
- Airdrop requests
Run it:
pnpm install
pnpm --filter @solana/example-vite-react dev
# Opens at http://localhost:5173
Next.js Example
Location: examples/nextjs/
Features demonstrated:
- App Router setup with providers
- Client component patterns
- Wallet connection
- Memo transaction submission
Run it:
pnpm install
pnpm --filter @solana/example-nextjs dev
# Opens at http://localhost:3000
Configuration Options
Client Configuration
type SolanaClientConfig = {
// RPC Configuration
endpoint?: string; // HTTP endpoint
rpc?: string; // Alias for endpoint
websocketEndpoint?: string; // WebSocket endpoint
websocket?: string; // Alias for websocketEndpoint
cluster?: "mainnet" | "testnet" | "devnet" | "localnet";
// Commitment Level
commitment?: "processed" | "confirmed" | "finalized";
// Wallet Connectors
walletConnectors?: WalletConnector[];
// Custom Store or RPC Client
createStore?: (state: ClientState) => ClientStore;
rpcClient?: SolanaRpcClient;
// Logging
logger?: (event: { level: string; message: string; data?: Record<string, any> }) => void;
// State Persistence
initialState?: SerializableSolanaState;
};
SolanaProvider Props
<SolanaProvider
client={client} // Pre-created client (optional)
config={config} // Client config (if no client)
query={{
config: swrConfig, // SWR configuration
suspense: true, // Enable Suspense
resetOnClusterChange: true, // Clear cache on cluster switch
disabled: false, // Disable query layer
}}
walletPersistence={{
autoConnect: true, // Auto-connect on load
storage: localStorage, // Custom storage
storageKey: "solana:last-connector",
}}
>
{children}
</SolanaProvider>
Transaction Configuration
type TransactionPrepareRequest = {
instructions: readonly TransactionInstruction[];
authority?: TransactionSigner | WalletSession;
feePayer?: Address | string | TransactionSigner;
commitment?: Commitment;
computeUnitLimit?: bigint | number;
computeUnitPrice?: bigint | number;
lifetime?: { blockhash; lastValidBlockHeight };
version?: "legacy" | "0" | "auto";
abortSignal?: AbortSignal;
};
Troubleshooting
Common Issues
"Cannot find module '@solana/client'"
Make sure you've installed the packages:
pnpm add @solana/client @solana/react-hooks
For Next.js, ensure transpilePackages is configured in next.config.mjs.
Hooks not working / "Invalid hook call"
- Ensure you're using hooks inside components wrapped by
SolanaProvider - In Next.js, mark components using hooks with
'use client' - Check that React version is >= 18
Wallet not connecting
- Ensure wallet extension is installed
- Check that
walletConnectorsincludes the wallet (useautoDiscover()) - Verify you're on HTTPS (required by most wallets) or localhost
Transaction failing
- Check wallet has sufficient SOL for fees
- Verify you're on the correct cluster (devnet vs mainnet)
- Enable simulation to see error logs:
await client.helpers.transaction.prepareAndSend(request, { skipPreflight: false, });
Development Scripts
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Start development servers
pnpm dev
# Run tests
pnpm test
# Type checking
pnpm typecheck
# Linting and formatting
pnpm lint
pnpm format
Summary
Framework-Kit provides a modern, reactive approach to building Solana dApps:
- Install packages -
@solana/clientand@solana/react-hooks - Create a client - Configure endpoint and wallet connectors
- Wrap with SolanaProvider - Enable hooks throughout your app
- Use hooks -
useWalletConnection,useBalance,useSolTransfer, etc. - Build your dApp - Leverage reactive data flows and transaction helpers
The framework handles the complexity of wallet orchestration, state management, and transaction building so you can focus on building great user experiences.