Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Solana development skill covering @solana/kit v5, Anchor programs, LiteSVM testing, and security patterns.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/kit/plugins.md
1---2title: Plugins & Client Composition3description: Solana Kit plugin architecture, all-in-one RPC/LiteSVM plugins, signer plugins, custom client composition, and plugin ordering rules.4---56# Solana Kit Plugins & Client Composition78Kit clients are built by chaining `.use(plugin)` calls onto `createClient()`. Each plugin extends the client with new properties or methods. Plugins that depend on others (e.g., RPC needs a payer) must come after their dependencies — TypeScript enforces this.910## All-in-One Clients1112### Production Client (mainnet/devnet/custom)1314```bash15npm install @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer16```1718```ts19import { createClient } from '@solana/kit';20import { solanaRpc } from '@solana/kit-plugin-rpc';21import { signer } from '@solana/kit-plugin-signer';2223const client = createClient()24.use(signer(mySigner)) // sets payer + identity to the same keypair25.use(solanaRpc({ rpcUrl: 'https://api.mainnet-beta.solana.com' }));2627await client.sendTransaction([myInstruction]);28```2930`solanaRpc` installs an RPC connection, RPC subscriptions, minimum-balance computation, transaction planner, and transaction executor in one call. It requires a `payer` to be set first — `signer()` covers that and the identity role at the same time. Reach for the role-specific `payer()` + `identity()` only when fees and authority must come from different keypairs.3132**`solanaRpc` options:**3334| Option | Type | Description |35|--------|------|-------------|36| `rpcUrl` | `string` | RPC endpoint (required) |37| `rpcSubscriptionsUrl` | `string` | WS endpoint (defaults to `rpcUrl` with `http`→`ws`) |38| `rpcConfig` | `object` | Forwarded to `createSolanaRpc` |39| `rpcSubscriptionsConfig` | `object` | Forwarded to `createSolanaRpcSubscriptions` |40| `transactionConfig` | `object` | Tx planner options (priority fees, etc.) |41| `maxConcurrency` | `number` | Concurrent tx limit (default: 10) |42| `skipPreflight` | `boolean` | Always skip preflight (default: false) |4344### Cluster-Specialized Variants4546```ts47import { createClient } from '@solana/kit';48import { solanaMainnetRpc, solanaDevnetRpc, solanaLocalRpc } from '@solana/kit-plugin-rpc';49import { signer, signerFromFile } from '@solana/kit-plugin-signer';5051// Mainnet — type-narrowed; airdrop is NOT exposed52const main = createClient().use(signer(s)).use(solanaMainnetRpc({ rpcUrl: '...' }));5354// Devnet — defaults to https://api.devnet.solana.com, includes airdrop55const dev = createClient().use(signer(s)).use(solanaDevnetRpc());5657// Local — defaults to http://127.0.0.1:8899, includes airdrop58const local = await createClient()59.use(signerFromFile('~/.config/solana/id.json'))60.use(solanaLocalRpc());61```6263### LiteSVM Test Client6465```bash66npm install @solana/kit @solana/kit-plugin-litesvm @solana/kit-plugin-signer67```6869```ts70import { createClient, lamports } from '@solana/kit';71import { litesvm } from '@solana/kit-plugin-litesvm';72import { airdropSigner, generatedSigner } from '@solana/kit-plugin-signer';7374const client = await createClient()75.use(generatedSigner())76.use(litesvm())77.use(airdropSigner(lamports(1_000_000_000n)));7879client.svm.setAccount(myTestAccount);80client.svm.addProgramFromFile(myProgramAddress, 'program.so');8182await client.sendTransaction([myInstruction]);83```8485`litesvm()` is Node.js only. Browser/React Native builds throw.8687---8889## Client API Surface9091See [overview.md](overview.md#client-api) for the full surface (`client.rpc`, `client.payer`, `client.sendTransaction`, etc.). Plugin-specific additions:9293- `solanaDevnetRpc` / `solanaLocalRpc` / `litesvm` add `client.airdrop(address, lamports)`.94- `litesvm` additionally adds `client.svm` for direct LiteSVM access.95- The low-level composition (Custom Client Composition below) also exposes `client.transactionPlanner` and `client.transactionPlanExecutor` directly.9697---9899## Signer Plugins (`@solana/kit-plugin-signer`)100101Kit clients hold two signer roles:102- **`payer`** — pays fees and rent103- **`identity`** — wallet/authority for application accounts104105In most apps both roles are the same keypair, so **default to the `signer*` variants** — they install one keypair into both slots in one call. Use the role-specific `payer*` / `identity*` variants only when fees and authority come from different keypairs (e.g., gasless flows, treasury accounts, multisig).106107| Variant | Sets |108|---|---|109| `signer*` (recommended default) | both `payer` and `identity` (same keypair) |110| `payer*` | only `payer` |111| `identity*` | only `identity` |112113| Plugin | Behavior |114|---|---|115| `signer(s)` / `payer(s)` / `identity(s)` | Install an existing `TransactionSigner` |116| `generatedSigner()` / `generatedPayer()` / `generatedIdentity()` | Async; generate a new keypair |117| `generatedSignerWithSol(amount)` / `generatedPayerWithSol(amount)` / `generatedIdentityWithSol(amount)` | Async; generate + airdrop. Requires an airdrop function already on the client (for low-level composition, install `rpcAirdrop()` first). |118| `signerFromFile(path)` / `payerFromFile(path)` / `identityFromFile(path)` | Async; load keypair from a JSON file |119| `airdropSigner(amount)` / `airdropPayer(amount)` / `airdropIdentity(amount)` | Airdrop SOL to an already-installed signer. Use with all-in-one `solanaLocalRpc` / `solanaDevnetRpc` / `litesvm` when the RPC plugin needs a payer first. |120121```ts122import { createClient, lamports } from '@solana/kit';123import { rpcAirdrop, solanaRpcConnection } from '@solana/kit-plugin-rpc';124import { generatedSignerWithSol } from '@solana/kit-plugin-signer';125126// `solanaRpcConnection` installs both `rpc` and `rpcSubscriptions`; the WS URL127// is derived from `rpcUrl` (override with `rpcSubscriptionsUrl` if needed).128// Airdrop function must exist before generatedSignerWithSol.129const client = await createClient()130.use(solanaRpcConnection({ rpcUrl: 'http://127.0.0.1:8899' }))131.use(rpcAirdrop())132.use(generatedSignerWithSol(lamports(10_000_000_000n)));133```134135**Role-split example** (different keypairs for fees vs. authority):136137```ts138import { createClient } from '@solana/kit';139import { solanaDevnetRpc } from '@solana/kit-plugin-rpc';140import { payer, identity } from '@solana/kit-plugin-signer';141142const client = createClient()143.use(payer(feePayerSigner)) // pays fees144.use(identity(walletSigner)) // owns/authorizes accounts145.use(solanaDevnetRpc());146```147148---149150## Custom Client Composition151152When the all-in-one bundles don't fit (custom transaction planner, partial capabilities, third-party services), build the client out of low-level plugins:153154```ts155import { createClient } from '@solana/kit';156import {157rpc,158rpcAirdrop,159rpcGetMinimumBalance,160rpcTransactionPlanner,161rpcTransactionPlanExecutor,162} from '@solana/kit-plugin-rpc';163import { signerFromFile } from '@solana/kit-plugin-signer';164import { planAndSendTransactions } from '@solana/kit-plugin-instruction-plan';165166const client = await createClient()167.use(rpc('https://api.devnet.solana.com')) // adds client.rpc + client.rpcSubscriptions168.use(signerFromFile('path/to/keypair.json')) // adds client.payer + client.identity169.use(rpcAirdrop()) // adds client.airdrop170.use(rpcGetMinimumBalance()) // adds client.getMinimumBalance171.use(rpcTransactionPlanner()) // adds client.transactionPlanner172.use(rpcTransactionPlanExecutor()) // adds client.transactionPlanExecutor173.use(planAndSendTransactions()); // adds client.sendTransaction(s)174```175176### Plugin Ordering177178Plugins that depend on others must come after their dependencies. TypeScript enforces this:179180```ts181// ✅ Correct — signer + rpc before planner/executor182createClient()183.use(signer(mySigner))184.use(rpc(url))185.use(rpcTransactionPlanner())186.use(planAndSendTransactions());187188// ❌ Type error — solanaRpc requires payer189createClient()190.use(solanaRpc({ rpcUrl: url }))191.use(signer(mySigner));192```193194### Async Plugins195196Some plugins are async (e.g., `signerFromFile`, `generatedSigner`, `generatedSignerWithSol`). The `.use()` method handles awaiting automatically — `await` the final client:197198```ts199const client = await createClient()200.use(signerFromFile('./keypair.json')) // async201.use(solanaLocalRpc());202```203204---205206## Plugin Catalog207208### Official Plugins209210| Package | Plugins | Purpose |211|---------|---------|---------|212| `@solana/kit-plugin-rpc` | `solanaRpc`, `solanaMainnetRpc`, `solanaDevnetRpc`, `solanaLocalRpc`, `rpc`, `solanaRpcConnection`, `rpcAirdrop`, `rpcGetMinimumBalance`, `rpcTransactionPlanner`, `rpcTransactionPlanExecutor` | RPC connectivity + tx planning/execution |213| `@solana/kit-plugin-signer` | `signer*` (default — sets both roles), `payer*`, `identity*` (role-specific); each comes in plain, `generated*`, `*WithSol`, `*FromFile`, and `airdrop*` forms | Signer management |214| `@solana/kit-plugin-litesvm` | `litesvm`, `litesvmConnection`, `litesvmAirdrop`, `litesvmTransactionPlanner`, `litesvmTransactionPlanExecutor` | In-memory test environment |215| `@solana/kit-plugin-airdrop` | `airdrop`, `rpcAirdrop` | SOL faucet (typically pulled in transitively) |216| `@solana/kit-plugin-instruction-plan` | `planAndSendTransactions` | Instruction batching + sending sugar |217218### Program Plugins219220Codama-generated `@solana-program/*` packages also export program plugins that attach fluent APIs to the client:221222```ts223import { createClient } from '@solana/kit';224import { solanaLocalRpc } from '@solana/kit-plugin-rpc';225import { signerFromFile } from '@solana/kit-plugin-signer';226import { tokenProgram } from '@solana-program/token';227228const client = await createClient()229.use(signerFromFile('~/.config/solana/id.json'))230.use(solanaLocalRpc())231.use(tokenProgram());232233// Fluent API — auto-derives ATAs, defaults payer from client234await client.token.instructions235.transferToATA({ mint, authority: ownerSigner, recipient, amount: 50n, decimals: 2 })236.sendTransaction();237```238239| Package | Plugin | Adds |240|---------|--------|------|241| `@solana-program/token` | `tokenProgram()` | `client.token.instructions` (createMint, mintToATA, transferToATA, etc.) |242243### Example Implementations244245| Package | Exports | Purpose | Code Example |246|---------|---------|---------|--------------|247| `@solana/kora` | `createKitKoraClient`, `koraPlugin` | Gasless transactions | https://github.com/solana-foundation/kora/blob/main/sdks/ts/src/kit/index.ts |248249---250251## Building Custom Plugins252253See [advanced.md](advanced.md) for the full guide on authoring plugins and assembling domain-specific clients.254255A plugin is a function that takes a client and returns a new one:256257```ts258type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> =259(input: TInput) => TOutput;260```261262Quick example:263264```ts265function myCustomPlugin() {266return <T extends object>(client: T) => ({267...client,268myMethod: () => console.log('hello'),269});270}271272const client = createClient().use(myCustomPlugin());273client.myMethod(); // 'hello'274```275276Plugins can require capabilities from previous plugins:277278```ts279function myRpcPlugin() {280return <T extends { rpc: SolanaRpc }>(client: T) => ({281...client,282fetchBalance: (addr: Address) => client.rpc.getBalance(addr).send(),283});284}285286// ✅ Works — rpc installed first287createClient().use(rpc(url)).use(myRpcPlugin());288289// ❌ Type error — rpc not present290createClient().use(myRpcPlugin());291```292