Framework-agnostic building blocks for Solana RPC, subscriptions, wallets, and transactions. Works in any runtime (React, Svelte, API routes, workers, etc.).
Status: Experimental – expect rapid iteration.
npm install @solana/client- Choose Wallet Standard connectors (auto-discovery is the fastest way to start).
- Create a Solana client.
- Call actions, watchers, and helpers anywhere in your app (React, APIs, workers, etc.).
import { autoDiscover, createClient } from "@solana/client";
const client = createClient({
endpoint: "https://api.devnet.solana.com",
websocketEndpoint: "wss://api.devnet.solana.com",
walletConnectors: autoDiscover(),
});
// Connect Wallet Standard apps via their connector ids.
await client.actions.connectWallet("phantom");
// Fetch an account once.
const wallet = client.store.getState().wallet;
if (wallet.status === "connected") {
const account = await client.actions.fetchAccount(wallet.session.account.address);
console.log(account.lamports?.toString());
}const connectors = client.connectors.all; // Wallet Standard-aware connectors
await client.actions.connectWallet(connectors[0].id);
const wallet = client.store.getState().wallet;
if (wallet.status === "connected") {
console.log(wallet.session.account.address.toString());
}
await client.actions.disconnectWallet();import { toAddress } from "@solana/client";
const address = toAddress("Fg6PaFpoGXkYsidMpWFKfwtz6DhFVyG4dL1x8kj7ZJup");
const lamports = await client.actions.fetchBalance(address);
console.log(`Lamports: ${lamports.toString()}`);
const watcher = client.watchers.watchBalance({ address }, (nextLamports) => {
console.log("Updated balance:", nextLamports.toString());
});
// Later…
watcher.abort();const signature = await client.actions.requestAirdrop(address, 1_000_000_000n); // 1 SOL
console.log(signature.toString());const wallet = client.store.getState().wallet;
if (wallet.status !== "connected") throw new Error("Connect wallet first");
const signature = await client.solTransfer.sendTransfer({
amount: 100_000_000n, // 0.1 SOL
authority: wallet.session, // Wallet Standard session
destination: "Ff34MXWdgNsEJ1kJFj9cXmrEe7y2P93b95mGu5CJjBQJ",
});
console.log(signature.toString());const wallet = client.store.getState().wallet;
if (wallet.status !== "connected") throw new Error("Connect wallet first");
const usdc = client.splToken({ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }); // USDC
const balance = await usdc.fetchBalance(wallet.session.account.address);
console.log(`Balance: ${balance.uiAmount}`);
const signature = await usdc.sendTransfer({
amount: 1n,
authority: wallet.session,
destinationOwner: "Ff34MXWdgNsEJ1kJFj9cXmrEe7y2P93b95mGu5CJjBQJ",
});
console.log(signature.toString());import { toAddress } from "@solana/client";
// Single lookup table
const lut = await client.actions.fetchLookupTable(
toAddress("AddressLookupTab1e1111111111111111111111111"),
);
console.log(`Addresses in LUT: ${lut.addresses.length}`);
// Multiple lookup tables
const luts = await client.actions.fetchLookupTables([lutAddress1, lutAddress2]);const nonce = await client.actions.fetchNonceAccount(
toAddress("NonceAccountAddress111111111111111111111111"),
);
console.log(`Nonce: ${nonce.blockhash}`);
console.log(`Authority: ${nonce.authority}`);import { getTransferSolInstruction } from "@solana-program/system";
const wallet = client.store.getState().wallet;
if (wallet.status !== "connected") throw new Error("Connect wallet first");
const prepared = await client.transaction.prepare({
authority: wallet.session,
instructions: [
getTransferSolInstruction({
destination: "Ff34MXWdgNsEJ1kJFj9cXmrEe7y2P93b95mGu5CJjBQJ",
lamports: 10_000n,
source: wallet.session.account.address,
}),
],
version: "auto", // defaults to 0 when lookups exist, otherwise 'legacy'
});
// Inspect or serialize first.
const wire = await client.transaction.toWire(prepared);
// Submit.
const signature = await client.transaction.send(prepared);
console.log(signature.toString());const watcher = client.watchers.watchSignature(
{ signature, commitment: "confirmed" },
(notification) => console.log("Signature update:", notification),
);
// Later…
watcher.abort();Pass a cluster moniker to auto-resolve RPC + WebSocket URLs. Monikers map to:
mainnet/mainnet-beta→https://api.mainnet-beta.solana.comtestnet→https://api.testnet.solana.comdevnet(default) →https://api.devnet.solana.comlocalnet/localhost→http://127.0.0.1:8899
WebSocket URLs are inferred (wss:// or ws://) when not supplied. Override with endpoint/rpc or websocket/websocketEndpoint when you need a custom host.
import { autoDiscover, createClient } from "@solana/client";
const client = createClient({
cluster: "mainnet", // or 'devnet' | 'testnet' | 'localnet' | 'localhost'
walletConnectors: autoDiscover(),
});Custom endpoint with inferred WebSocket:
const client = createClient({
endpoint: "http://127.0.0.1:8899", // websocket inferred as ws://127.0.0.1:8900
});Use resolveCluster directly when you need the resolved URLs without creating a client:
import { resolveCluster } from "@solana/client";
const resolved = resolveCluster({ moniker: "testnet" });
console.log(resolved.endpoint, resolved.websocketEndpoint);Notes:
- Default moniker is
devnetwhen nothing is provided; moniker becomescustomwhen you pass a rawendpoint. createClient,createDefaultClient(resolveClientConfig), andSolanaProviderall useresolveClusterunder the hood, so the moniker/endpoint behavior is consistent across entrypoints.
- Wallet connectors:
autoDiscover()picks up Wallet Standard injectables; composephantom(),solflare(),backpack(), orinjected()when you need explicit control. - Store: built on Zustand; pass
createStoretocreateClientfor custom persistence or server-side stores.serializeSolanaState/deserializeSolanaStatehelp save and restore cluster + wallet metadata. - Actions:
fetchAccount,fetchBalance,fetchLookupTable,fetchLookupTables,fetchNonceAccount,setCluster,requestAirdrop,sendTransaction, and wallet connect/disconnect keep the store in sync. - Watchers:
watchAccount,watchBalance, andwatchSignaturestream updates into the store and return anabort()handle for cleanup. - Helpers:
solTransfer,splToken, andtransactioncover common transfers plus low-levelprepare/sign/toWireflows. Transaction versions default to0when any instruction references address lookup tables, otherwiselegacy; override withversionwhen needed.
pnpm build– compile JS and type definitionspnpm test:typecheck– strict type-checkingpnpm lint/pnpm format– Biome-powered linting and formatting
- Playground:
examples/vite-react(run withpnpm install && pnpm dev). - Next.js reference app:
examples/nextjs. - Client APIs live in
src/actions.ts,src/watchers, andsrc/features/*for helper internals.