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/clientConnectorKit integration is available as a stable, opt-in entrypoint: @solana/client/connectorkit.
This requires installing @solana/connector (an optional peer dependency of @solana/client):
npm install @solana/connectorimport { connectorKit } from "@solana/client/connectorkit";
const walletConnectors = connectorKit({ defaultConfig: { /* ... */ } });- 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.
// Recommended: use canonical ids like "wallet-standard:phantom" (aliases like "phantom" also work).
await client.actions.connectWallet("wallet-standard: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());Token 2022 mints are supported via the tokenProgram option:
// Auto-detect Token or Token 2022 (recommended)
const token = client.splToken({
mint: "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo", // PYUSD
tokenProgram: "auto",
});
// Or explicitly specify Token 2022 program
import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana/client";
const token2022 = client.splToken({
mint: mintAddress,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
});
// Balance and transfers work the same way
const balance = await token.fetchBalance(wallet.session.account.address);
const signature = await token.sendTransfer({
amount: 10,
authority: wallet.session,
destinationOwner: recipientAddress,
});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.
Use filterByNames with autoDiscover() to filter wallets by name without wallet-specific code:
import { autoDiscover, filterByNames } from "@solana/client";
// Only show Phantom and Solflare
const connectors = autoDiscover({
filter: filterByNames("phantom", "solflare"),
});
const client = createClient({
cluster: "devnet",
walletConnectors: connectors,
});This approach follows Wallet Standard's wallet-agnostic discovery pattern while still allowing you to curate which wallets appear in your app.
You can also write custom filter functions:
const connectors = autoDiscover({
filter: (wallet) => wallet.name.toLowerCase().includes("phantom"),
});- Wallet connectors:
autoDiscover()picks up Wallet Standard injectables; usefilterByNames()to filter by name, or composephantom(),solflare(),backpack(), etc. 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
- Documentation — full guides and API reference
- 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.