npm package: @solana/client
Description: Framework-agnostic Solana client orchestration layer powering higher-level experiences
Author: Solana Maintainers
Latest version: 1.7.0

# @solana/client

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.

## Install

```bash
npm install @solana/client
```

## ConnectorKit (optional)

ConnectorKit integration is available as a **stable, opt-in entrypoint**: `@solana/client/connectorkit`.

This requires installing `@solana/connector` (an **optional peer dependency** of `@solana/client`):

```bash
npm install @solana/connector
```

```ts
import { connectorKit } from "@solana/client/connectorkit";

const walletConnectors = connectorKit({ defaultConfig: { /* ... */ } });
```

## Quickstart

1. Choose Wallet Standard connectors (auto-discovery is the fastest way to start).
2. Create a Solana client.
3. Call actions, watchers, and helpers anywhere in your app (React, APIs, workers, etc.).

```ts
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());
}
```

## Common Solana flows (copy/paste)

### Connect, disconnect, and inspect wallet state

```ts
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();
```

### Fetch and watch lamports

```ts
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();
```

### Request an airdrop (devnet/testnet)

```ts
const signature = await client.actions.requestAirdrop(address, 1_000_000_000n); // 1 SOL
console.log(signature.toString());
```

### Send SOL

```ts
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());
```

### SPL token balance + transfer

```ts
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 support

Token 2022 mints are supported via the `tokenProgram` option:

```ts
// 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,
});
```

### Fetch address lookup tables

```ts
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]);
```

### Fetch nonce accounts

```ts
const nonce = await client.actions.fetchNonceAccount(
  toAddress("NonceAccountAddress111111111111111111111111"),
);
console.log(`Nonce: ${nonce.blockhash}`);
console.log(`Authority: ${nonce.authority}`);
```

### Build and send arbitrary transactions

```ts
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());
```

### Watch signature confirmations

```ts
const watcher = client.watchers.watchSignature(
  { signature, commitment: "confirmed" },
  (notification) => console.log("Signature update:", notification),
);

// Later…
watcher.abort();
```

## Cluster monikers and endpoints

Pass a cluster moniker to auto-resolve RPC + WebSocket URLs. Monikers map to:

- `mainnet` / `mainnet-beta` → `https://api.mainnet-beta.solana.com`
- `testnet` → `https://api.testnet.solana.com`
- `devnet` (default) → `https://api.devnet.solana.com`
- `localnet` / `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.

```ts
import { autoDiscover, createClient } from "@solana/client";

const client = createClient({
  cluster: "mainnet", // or 'devnet' | 'testnet' | 'localnet' | 'localhost'
  walletConnectors: autoDiscover(),
});
```

Custom endpoint with inferred WebSocket:

```ts
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:

```ts
import { resolveCluster } from "@solana/client";

const resolved = resolveCluster({ moniker: "testnet" });
console.log(resolved.endpoint, resolved.websocketEndpoint);
```

Notes:

- Default moniker is `devnet` when nothing is provided; moniker becomes `custom` when you pass a raw `endpoint`.
- `createClient`, `createDefaultClient` (`resolveClientConfig`), and `SolanaProvider` all use `resolveCluster` under the hood, so the moniker/endpoint behavior is consistent across entrypoints.

## Wallet connector filtering

Use `filterByNames` with `autoDiscover()` to filter wallets by name without wallet-specific code:

```ts
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:

```ts
const connectors = autoDiscover({
  filter: (wallet) => wallet.name.toLowerCase().includes("phantom"),
});
```

## Notes and defaults

- Wallet connectors: `autoDiscover()` picks up Wallet Standard injectables; use `filterByNames()` to filter by name, or compose `phantom()`, `solflare()`, `backpack()`, etc. when you need explicit control.
- Store: built on Zustand; pass `createStore` to `createClient` for custom persistence or server-side stores. `serializeSolanaState` / `deserializeSolanaState` help 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`, and `watchSignature` stream updates into the store and return an `abort()` handle for cleanup.
- Helpers: `solTransfer`, `splToken`, and `transaction` cover common transfers plus low-level `prepare`/`sign`/`toWire` flows. Transaction versions default to `0` when any instruction references address lookup tables, otherwise `legacy`; override with `version` when needed.

## Scripts

- `pnpm build` – compile JS and type definitions
- `pnpm test:typecheck` – strict type-checking
- `pnpm lint` / `pnpm format` – Biome-powered linting and formatting

## More resources

- [Documentation](https://www.framework-kit.com/) — full guides and API reference
- Playground: `examples/vite-react` (run with `pnpm install && pnpm dev`).
- Next.js reference app: `examples/nextjs`.
- Client APIs live in `src/actions.ts`, `src/watchers`, and `src/features/*` for helper internals.