
Solana Kit
- 4 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Build Solana apps with the Kit JavaScript SDK - RPC, signers, transaction messages, account decode, codecs, and program clients.
About
Solana Kit is a functional, tree-shakeable JavaScript SDK covering RPC and subscriptions, signers, transaction messages, account fetch/decode, and codecs. A developer uses it to build Solana client tooling.
- Functional, tree-shakeable API with RPC and RPC Subscriptions
- Signers, transaction messages, codecs, and program clients
Solana Kit by the numbers
- 4 all-time installs (skills.sh)
- Ranked #347 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill solana-kitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Build Solana apps with the Kit JavaScript SDK - RPC, signers, transaction messages, account decode, codecs, and program clients.
Files
Skill based on Kit (anza-xyz/kit), generated 2026-02-25.
Concise reference for building Solana apps with Kit: functional API, tree-shakeable imports, RPC + RPC Subscriptions, signers, transaction messages, account fetch/decode, codecs, and program clients.
Core References
| Topic | Description | Reference |
|---|---|---|
| Setup | Install, RPC/RPC Subscriptions, typed client | core-setup |
| RPC | HTTP client — getBalance, getAccountInfo, getLatestBlockhash, send | core-rpc |
| RPC Subscriptions | WebSocket — accountNotifications, slotNotifications | core-rpc-subscriptions |
| Functional | pipe(), pipeline transforms | core-functional |
| Signers | KeyPairSigner, airdrop, wallet swap, no-op | core-signers |
| Transactions | pipe, fee payer, lifetime, instructions, sign, send-and-confirm | core-transactions |
| Transaction confirmation | Block height exceedence, recent signature, nonce invalidation, timeout | core-transaction-confirmation |
| Accounts | fetchEncodedAccount, program fetch/decode (fetchMint, decodeMint) | core-accounts |
| Address lookup tables | fetchLookupTables, compress message, decompile with lookups | core-address-lookup-tables |
| Addresses | Address type, validation, PDA derivation, codecs | core-addresses |
| Sysvars | Fetch/decode Clock, Rent, EpochSchedule, etc. | core-sysvars |
Features
| Topic | Description | Reference |
|---|---|---|
| Instructions | Program clients — System, Token, Compute Budget | features-instructions |
| Instruction plans | Sequential/parallel plans, planner, executor | features-instruction-plans |
| Codecs | Encode/decode structs, program getXCodec | features-codecs |
| Compatible program clients | @solana-program/*, Codama-generated clients | features-compatible-clients |
| Compat (Web3.js) | fromLegacyPublicKey, fromLegacyKeypair, fromVersionedTransaction | features-compat |
| Errors | SolanaError, isSolanaError, context | features-errors |
| GraphQL | createSolanaRpcGraphQL, nested queries, caching/batching | features-graphql |
| Key pairs | generateKeyPair, import bytes, polyfill | features-keypairs |
| Offchain messages | Build, sign, verify, encode/decode (sRFC 3) | features-offchain-messages |
| Program errors | isProgramError — attribute tx failure to program/code | features-program-errors |
| React | useSignIn, useWalletAccountTransactionSigner, useSignAndSendTransaction | features-react |
| RPC transports | Custom transport — failover, retry, round-robin, sharding | features-rpc-transports |
| RPC API augmentation | mainnet/devnet, cherry-pick methods, custom RPC methods | features-rpc-api-augmentation |
| Create Solana program | pnpm create solana-program, Codama-generated JS client | features-create-solana-program |
| Unstable subscriptions | createSolanaRpcSubscriptions_UNSTABLE, block/slotsUpdates | features-unstable-subscriptions |
Best practices
| Topic | Description | Reference |
|---|---|---|
| Abort RPC/subscriptions | AbortController, timeout, cancel on navigation | best-practices-abort-rpc |
| Tree-shaking | Narrow imports, sub-packages, smaller bundles | best-practices-tree-shaking |
| Upgrade from Web3.js | Connection → RPC, PublicKey → address, compatible clients | best-practices-upgrade |
Generation Info
- Source:
sources/solana-kit(https://github.com/anza-xyz/kit) - Git SHA:
69a380bada7881be085b9c49bddabbb026edd7f7 - Generated: 2026-02-25
Aborting RPC and subscriptions (Kit)
RPC requests and subscriptions accept an abortSignal so you can cancel in-flight work, set timeouts, or clean up when the user navigates away.
RPC requests
Each RPC method returns a call object; .send() accepts options including abortSignal. Pass AbortController.signal to cancel when the controller aborts:
const controller = new AbortController();
const slot = await rpc.getSlot().send({ abortSignal: controller.signal });
// Later: controller.abort() cancels the request.Use AbortSignal.timeout(ms) for a timeout: await rpc.getSlot().send({ abortSignal: AbortSignal.timeout(5000) }).
Subscriptions
subscribe() requires abortSignal. Use it to stop the subscription (e.g. when leaving the page or when a condition is met). If the subscription fails (connection down), the for-await loop throws; if aborted, it exits without throwing. Always pass a signal and abort in cleanup.
Key points
- Pass abortSignal to .send() for requests and to .subscribe() for subscriptions. Use AbortSignal.timeout(ms) for timeouts. Aborted subscriptions exit the loop; failed subscriptions throw.
<!-- Source: sources/solana-kit/README.md Aborting RPC Requests, Aborting RPC Subscriptions -->
Tree-shaking (Kit)
Kit is designed to be fully tree-shakeable: only the functions and types you import are included in the bundle. This keeps payloads small for browsers, serverless, and React Native. Prefer narrow imports and avoid pulling the whole package when you need a few helpers.
Prefer narrow imports
// Good: only what you use is bundled.
import { createSolanaRpc, address, pipe, getTransferSolInstruction } from '@solana/kit';
// Also good: use sub-packages when you need customization.
import { createSolanaRpc } from '@solana/rpc';
import { address } from '@solana/addresses';
import { getTransferSolInstruction } from '@solana-program/system';Avoid importing the entire kit barrel if you only need one area (e.g. RPC + addresses). Use @solana/kit for convenience when you use several areas; use @solana/rpc, @solana/addresses, @solana/transactions, etc. when you want to minimize bundle size or customize composition.
Why it matters
- Web: Smaller JS improves load time and TTI.
- Serverless (Lambda, Cloudflare): Smaller bundles reduce cold start and deployment size.
- React Native: Less JS to parse and execute.
Kit’s API is functional and modular (no large class that pulls in every method), so bundlers can eliminate unused code. Build-time checks in the repo enforce that the public API remains tree-shakeable.
Version-specific imports
For features with multiple versions (e.g. offchain messages), import the version you use so compilers for other versions are dropped:
import { compileOffchainMessageV1Envelope } from '@solana/kit';
// Instead of compileOffchainMessageEnvelope, which can pull in other version compilers.Key points
- Import only the functions and types you need from
@solana/kitor sub-packages. - Prefer sub-packages when you need fine-grained control or minimal dependencies.
- Avoid importing large barrels or “all of RPC” if you only use a few methods; the functional API allows each method to be tree-shaken.
<!-- Source references:
- sources/solana-kit/README.md (Tree-Shakability)
- sources/solana-kit/docs/content/docs/tree-shaking (concept)
-->
Upgrade from Web3.js (Kit)
Kit (formerly Web3.js v2) is a functional, tree-shakeable rewrite. There is no single Connection class; use createSolanaRpc and createSolanaRpcSubscriptions and compose only what you need.
Connection → RPC + RPC Subscriptions
| Web3.js | Kit |
|---|---|
new Connection(url, { commitment, wsEndpoint }) | createSolanaRpc(url) and createSolanaRpcSubscriptions(wsUrl) |
connection.getBalance(publicKey) | rpc.getBalance(address('...')).send() → result.value |
connection.onAccountChange(publicKey, callback) | rpcSubscriptions.accountNotifications(address('...')).subscribe({ abortSignal }) then for await |
PublicKey → Address
- Use
address('base58string')from@solana/kit. Type isAddress. - No
PublicKeyclass; addresses are nominal string types.
Sending transactions
- Web3.js:
connection.sendTransaction(transaction, signers, options). - Kit: Build
TransactionMessagewithpipe, set fee payer and lifetime, append instructions, thensignTransactionMessageWithSigners(message)→assertIsSendableTransaction(transaction)→sendAndConfirmTransaction(transaction, { commitment })(fromsendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })).
Compatible program clients
Use Codama-generated clients that match Kit’s instruction/account/codec patterns. Examples:
@solana-program/system,@solana-program/token,@solana-program/token-2022@solana-program/compute-budget,@solana-program/memo,@solana-program/address-lookup-table,@solana-program/stake
Install only the programs your app uses. See the compatible clients doc for the full table.
Key points
- Tree-shaking: import only the functions you use from
@solana/kitso unused code is dropped. - No default commitment on the client; pass commitment per call (e.g.
getBalance(..., { commitment: 'confirmed' })or in send/confirm options).
<!-- Source references:
- sources/solana-kit/docs/content/docs/upgrade-guide.mdx
- sources/solana-kit/docs/content/docs/compatible-clients.mdx
-->
Fetching and decoding accounts (Kit)
Fetch account data via RPC, then decode with codecs or program-client helpers. Prefer program clients when available (e.g. fetchMint, decodeMint) for type-safe decoded data.
Raw RPC
import { address } from '@solana/kit';
const { value: account } = await rpc.getAccountInfo(address('1234..5678')).send();
const { value: accounts } = await rpc.getMultipleAccounts([address('1234..5678')]).send();Unified fetch (Kit helpers)
import { fetchEncodedAccount, fetchEncodedAccounts, assertAccountExists } from '@solana/kit';
const account = await fetchEncodedAccount(rpc, address('1234..5678'));
const accounts = await fetchEncodedAccounts(rpc, [address('1234..5678')]);
if (account.exists) {
// account.data is Uint8Array
} else {
// account.address, exists: false
}
assertAccountExists(account); // throws if missing; narrows type for TSDecode with program client (recommended)
Program clients expose fetchX, decodeX, and getXCodec:
import { fetchEncodedAccount } from '@solana/kit';
import { fetchMint, decodeMint, getMintCodec, Mint } from '@solana-program/token';
// One-shot fetch + decode (asserts account exists)
const mintAccount = await fetchMint(rpc, address('...'));
// mintAccount satisfies Account<Mint>
// Or decode an already-fetched encoded account
const encoded = await fetchEncodedAccount(rpc, address('...'));
assertAccountExists(encoded);
const mintAccount = decodeMint(encoded);
// Manual codec
const codec = getMintCodec();
const data = codec.decode(encoded.data);Manual codec (no program client)
Use codecs from @solana/kit (or @solana/codecs) to define layout:
import { getStructCodec, getU64Codec, getU8Codec, getBooleanCodec, getOptionCodec, getAddressCodec } from '@solana/kit';
const mintCodec = getStructCodec([
['mintAuthority', getOptionCodec(getAddressCodec())],
['supply', getU64Codec()],
['decimals', getU8Codec()],
['isInitialized', getBooleanCodec()],
['freezeAuthority', getOptionCodec(getAddressCodec())],
]);
const decoded = mintCodec.decode(account.data);Key points
- Use
fetchEncodedAccount/fetchEncodedAccountsfor a consistentMaybeEncodedAccountshape and encoding. - Prefer program client
fetchX(rpc, address)when the account type has a client (Token Mint, etc.); usedecodeX(encoded)orgetXCodec().decode(data)when you already have bytes. - Use
unwrapOptionfor Rust-styleOption<T>fields when displaying or passing to APIs expectingT | null.
<!-- Source references:
- sources/solana-kit/docs/content/docs/getting-started/fetch-account.mdx
-->
Address lookup tables (Kit)
Address lookup tables (v0 transactions) let you reference many accounts by index in a table, reducing transaction size. Kit provides fetchLookupTables, compressTransactionMessageUsingAddressLookupTables, and decompileTransactionMessageFetchingLookupTables.
Fetch lookup table contents
fetchLookupTables(lookupTableAddresses, rpc, config) returns a map of lookup table address to ordered array of addresses. Use when you need addresses for decompiling or compressing.
Compress a message
Given a transaction message and a map of lookup table address to addresses (AddressesByLookupTableAddress), compressTransactionMessageUsingAddressLookupTables(message, addressesByLookupTableAddress) returns a new message with non-signer accounts that appear in the tables represented as AccountLookupMeta instead of AccountMeta, reducing compiled size. Use fetchAddressLookupTable from @solana-program/address-lookup-table to build the map.
Decompile with lookups
decompileTransactionMessageFetchingLookupTables(compiledTransactionMessage, rpc, config) returns a TransactionMessage from a CompiledTransactionMessage; if the message uses address lookups, it fetches lookup table contents via RPC. Use when you have a compiled message (e.g. from getTransaction) and need to inspect or modify it.
Key points
- Only v0 transaction messages support lookup tables. Build the AddressesByLookupTableAddress map from fetchAddressLookupTable or fetchLookupTables. Package: @solana/transaction-messages, @solana-program/address-lookup-table.
<!-- Source: sources/solana-kit/packages/kit/README.md fetchLookupTables decompileTransactionMessageFetchingLookupTables; packages/transaction-messages/README.md compressTransactionMessageUsingAddressLookupTables -->
Addresses (Kit)
Kit uses a nominal Address type for base58-encoded Solana addresses and provides validation, PDA derivation, and encode/decode codecs. Use @solana/addresses standalone or via @solana/kit.
Types
- Address: String that validates as a base58 Solana address. Use for function parameters that expect a well-formed address.
- ProgramDerivedAddress: Tuple of
[Address, number](PDA and bump). UseassertIsProgramDerivedAddress/isProgramDerivedAddressfor validation. - ProgramDerivedAddressBump: Integer 0–255 used as the bump seed so the derived address is off the Ed25519 curve.
Validation and coercion
import { address, assertIsAddress, isAddress } from '@solana/addresses';
// Coerce untrusted string to Address (throws if invalid).
const addr = address(userInput);
// Assert in place (throws if invalid).
assertIsAddress(someString);
// Type guard (refines type when true).
if (isAddress(ownerAddress)) {
await rpc.getBalance(ownerAddress).send();
}For known-good literal addresses, use a type cast to avoid runtime validation: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' as Address.
PDA derivation
import { getAddressEncoder, getProgramDerivedAddress } from '@solana/addresses';
const addressEncoder = getAddressEncoder();
const [pda, bumpSeed] = await getProgramDerivedAddress({
programAddress: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address,
seeds: [
addressEncoder.encode(ownerAddress),
addressEncoder.encode(tokenProgramAddress),
addressEncoder.encode(mintAddress),
],
});Address with seed
Derive an address from a base address, program address, and seed (string or bytes).
import { createAddressWithSeed } from '@solana/addresses';
const derivedAddress = await createAddressWithSeed({
baseAddress: 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Address,
programAddress: '445erYq578p2aERrGW9mn9KiYe3fuG6uHdcJ2LPPShGw' as Address,
seed: 'data-account',
});Codecs
- Encode:
getAddressEncoder().encode(addr)→ 32-byteUint8Array. - Decode:
getAddressDecoder().decode(bytes)→Addressand read offset.
Public key to address
import { getAddressFromPublicKey } from '@solana/addresses';
const address = await getAddressFromPublicKey(publicCryptoKey);Key points
- Prefer
address()for untrusted input; use type assertion for literals to avoid unnecessary checks. - Use
getProgramDerivedAddresswith up to 16 seeds; seeds are encoded (e.g. withgetAddressEncoder().encode(...)) when they are addresses. - For PDA/bump tuples from untrusted sources, use
assertIsProgramDerivedAddress()orisProgramDerivedAddress().
<!-- Source references:
- sources/solana-kit/packages/addresses/README.md
-->
Functional pipeline (Kit)
Kit uses a functional style; transaction messages are built by applying transforms in sequence. The pipe() helper from @solana/functional (re-exported by @solana/kit) runs a value through a list of functions: pipe(initial, fn1, fn2, ...) returns fn2(fn1(initial)). Use it to build transaction messages without binding each step to a variable.
Example
Use pipe(createTransactionMessage({ version: 0 }), m => setTransactionMessageFeePayer(addr, m), m => setTransactionMessageLifetimeUsingBlockhash(bh, m), m => appendTransactionMessageInstruction(ix, m)) to build a message. Package: @solana/functional, re-exported from @solana/kit.
Key points
- pipe(initial, f1, f2, ...) is equivalent to f2(f1(initial)). General-purpose; not limited to transaction messages.
RPC Subscriptions (Kit)
RPC Subscriptions provide WebSocket-based notifications (account changes, slot updates, signature status). Use with AbortSignal to cancel cleanly.
Create and subscribe
import { createSolanaRpcSubscriptions, address } from '@solana/kit';
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.devnet.solana.com');
const abortController = new AbortController();
const accountNotifications = await rpcSubscriptions
.accountNotifications(address('1234..5678'), { commitment: 'confirmed' })
.subscribe({ abortSignal: abortController.signal });
for await (const accountInfo of accountNotifications) {
console.log(accountInfo);
}Slot notifications example
const slotNotifications = await rpcSubscriptions
.slotNotifications()
.subscribe({ abortSignal: AbortSignal.timeout(10_000) });
for await (const n of slotNotifications) {
console.log('Slot', n.slot);
}Key points
- Always pass
abortSignal(e.g.AbortController.signalorAbortSignal.timeout(ms)) to.subscribe()for cancellation and cleanup. - Subscriptions are async iterables; use
for awaitor iterate manually. - Endpoints:
wss://api.mainnet-beta.solana.com,wss://api.testnet.solana.com,wss://api.devnet.solana.com.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/rpc-subscriptions.mdx
-->
RPC (Kit)
RPC is the HTTP interface to a Solana node. Use it to read state (balances, accounts, blockhash) and send transactions. Kit types follow the Solana JSON RPC HTTP API.
Create and use
import { address, createSolanaRpc } from '@solana/kit';
const rpc = createSolanaRpc('https://api.devnet.solana.com');
const { value: balance } = await rpc.getBalance(address('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb')).send();All RPC methods return a call object; call .send() to execute. The result is typically { value: T }.
Common methods (agent use)
- getBalance(address) — lamports for an account.
- getAccountInfo(address) / getMultipleAccounts(address[]) — account data (encoding options affect format).
- getLatestBlockhash() —
{ blockhash, lastValidBlockHeight }for transaction lifetime (blockhash strategy). - getMinimumBalanceForRentExemption(size) — min lamports for rent-exempt account of given size.
- sendTransaction(encoded, options) — submit transaction (usually via Kit helpers that encode and set options).
- simulateTransaction(...) — simulate without sending.
Packages
RPC is in @solana/kit. Standalone: @solana/rpc. Sub-packages: @solana/rpc-api, @solana/rpc-spec, @solana/rpc-types, @solana/rpc-transport-http, etc., for custom implementations.
Key points
- Use
.send()on the return value of any RPC method to run the request. - Public endpoints:
https://api.mainnet-beta.solana.com,https://api.testnet.solana.com,https://api.devnet.solana.com. Prefer a dedicated RPC for production.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/rpc.mdx
-->
Setup (Kit)
Kit is a JavaScript SDK for Solana (Node, web, React Native). No single Connection-style class: use createSolanaRpc and createSolanaRpcSubscriptions, then compose a custom client type so only used APIs are bundled (tree-shaking).
Install
npm install @solana/kitFor program interactions, install the program clients you need (e.g. System, Token, Compute Budget):
npm install @solana-program/system @solana-program/memo @solana-program/token @solana-program/compute-budgetCreate RPC and RPC Subscriptions
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
sendAndConfirmTransactionFactory,
} from '@solana/kit';
const rpc = createSolanaRpc('https://api.devnet.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.devnet.solana.com');
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });Typed client pattern
Define a small Client type with only the RPC/subscriptions and helpers your app uses. This keeps bundle size minimal.
import { Rpc, RpcSubscriptions, SolanaRpcApi, SolanaRpcSubscriptionsApi } from '@solana/kit';
export type Client = {
rpc: Rpc<SolanaRpcApi>;
rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>;
};
export function createClient(): Client {
return {
rpc: createSolanaRpc('http://127.0.0.1:8899'),
rpcSubscriptions: createSolanaRpcSubscriptions('ws://127.0.0.1:8900'),
};
}Key points
- Use
createSolanaRpc(url)for HTTP RPC;createSolanaRpcSubscriptions(wsUrl)for WebSocket subscriptions. - For sending transactions you typically need both RPC and RPC Subscriptions plus a strategy like
sendAndConfirmTransactionFactory. - Addresses: use
address('base58...')from@solana/kit(replacesPublicKey).
<!-- Source references:
- sources/solana-kit/docs/content/docs/index.mdx
- sources/solana-kit/docs/content/docs/getting-started/setup.mdx
-->
Signers (Kit)
Signers wrap an Address and provide signing logic (keypair, wallet, server, etc.). Use them so transaction/message building and sending can collect and invoke signers automatically without hard-coding key handling.
KeyPairSigner (local keypair)
import { generateKeyPairSigner, airdropFactory, createSolanaRpc, createSolanaRpcSubscriptions, lamports } from '@solana/kit';
const wallet = await generateKeyPairSigner();
// Airdrop (test env only)
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const airdrop = airdropFactory({ rpc, rpcSubscriptions });
await airdrop({
recipientAddress: wallet.address,
lamports: lamports(1_000_000_000n),
commitment: 'confirmed',
});Other keypair helpers: createSignerFromKeyPair(keyPair), createKeyPairSignerFromBytes(64Bytes), createKeyPairSignerFromPrivateKeyBytes(32Bytes).
Signer types (transactions)
- TransactionPartialSigner — signs transactions without modifying them; can run in parallel; order doesn’t matter.
- TransactionModifyingSigner — may modify then sign; must run first for a given transaction.
- TransactionSendingSigner — signs and sends in one step (e.g. some wallets); only one per transaction, must be last.
KeyPairSigner implements TransactionPartialSigner and MessagePartialSigner. Use wallet adapters (e.g. useWalletAccountTransactionSendingSigner from @solana/react) for in-browser wallets.
Message signing
import { createSignableMessage, generateKeyPairSigner } from '@solana/kit';
const signer = await generateKeyPairSigner();
const message = createSignableMessage('Hello world!');
const [signatures] = await signer.signMessages([message]);No-op signer
For testing or when you will supply signatures elsewhere (e.g. server):
import { createNoopSigner, address } from '@solana/kit';
const noop = createNoopSigner(address('1234..5678'));
// signMessages/signTransactions return empty signature dictionaries.Key points
- Prefer signer objects over raw
CryptoKeyPairin transaction/message APIs so wallet and keypair are swappable. - Fee payer and instruction signers are set via signers;
signTransactionMessageWithSignersgathers and runs them.
<!-- Source references:
- sources/solana-kit/docs/content/docs/getting-started/signers.mdx
- sources/solana-kit/docs/content/docs/concepts/signers.mdx
-->
Sysvars (Kit)
Sysvars are special on-chain accounts that expose runtime state. Kit provides typed fetch-and-decode helpers and codecs for each supported sysvar.
Fetch and decode
import { createSolanaRpc, fetchSysvarClock } from '@solana/kit';
const rpc = createSolanaRpc('https://api.devnet.solana.com');
const clock = await fetchSysvarClock(rpc);Low-level
import { assertAccountExists, decodeAccount, fetchEncodedSysvarAccount, getSysvarClockDecoder, SYSVAR_CLOCK_ADDRESS } from '@solana/kit';
const maybeEncoded = await fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS);
assertAccountExists(maybeEncoded);
const decoded = decodeAccount(maybeEncoded, getSysvarClockDecoder());Supported sysvars
Clock, EpochRewards, EpochSchedule, Fees, LastRestartSlot, RecentBlockhashes, Rent, SlotHashes, SlotHistory, StakeHistory.
Key points
- Prefer fetchSysvar*(rpc) when you only need one sysvar. Standalone: @solana/sysvars.
<!-- Source: sources/solana-kit/packages/sysvars/README.md -->
Transaction confirmation (Kit)
Kit provides configurable confirmation strategies for sent transactions: wait for a signature to reach a commitment level, react to blockhash expiry or nonce advancement, and time out. Use @solana/transaction-confirmation (or via @solana/kit).
Recent signature confirmation
Resolves when the transaction reaches the target commitment; throws if the transaction fails.
import { createRecentSignatureConfirmationPromiseFactory } from '@solana/transaction-confirmation';
const getRecentSignatureConfirmationPromise = createRecentSignatureConfirmationPromiseFactory({
rpc,
rpcSubscriptions,
});
await getRecentSignatureConfirmationPromise({ commitment: 'confirmed', signature });Block height exceedence (blockhash expiry)
When the transaction uses a blockhash lifetime, it is invalid after that blockhash expires. This promise rejects when the current block height exceeds the last valid block height.
import { createBlockHeightExceedencePromiseFactory } from '@solana/transaction-confirmation';
const getBlockHeightExceedencePromise = createBlockHeightExceedencePromiseFactory({
rpc,
rpcSubscriptions,
});
await getBlockHeightExceedencePromise({ lastValidBlockHeight });
// Throws SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED when exceeded → re-sign and retry.Nonce invalidation (durable nonce)
For nonce-based transactions, reject when the nonce account value changes (nonce advanced).
import { createNonceInvalidationPromiseFactory } from '@solana/transaction-confirmation';
const getNonceInvalidationPromise = createNonceInvalidationPromiseFactory({
rpc,
rpcSubscriptions,
});
await getNonceInvalidationPromise({ currentNonceValue, nonceAccountAddress });
// Throws SOLANA_ERROR__NONCE_INVALID or SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND.Timeout
When no other heuristic applies, race with a timeout: 30s for processed, 60s otherwise.
import { safeRace } from '@solana/promises';
import { getTimeoutPromise } from '@solana/transaction-confirmation';
await safeRace([
getCustomTransactionConfirmationPromise(/* ... */),
getTimeoutPromise({ commitment }),
]);
// TimeoutError (DOMException) on timeout.Custom strategies
- Recent tx:
waitForRecentTransactionConfirmation({ getBlockHeightExceedencePromise, getRecentSignatureConfirmationPromise }). - Recent tx with timeout:
waitForRecentTransactionConfirmationUntilTimeout({ getTimeoutPromise, getRecentSignatureConfirmationPromise }). - Durable nonce:
waitForDurableNonceTransactionConfirmation({ getNonceInvalidationPromise, getRecentSignatureConfirmationPromise }).
Supply your own promise factories for each hook to plug in RPC/subscription clients and abort signals.
Key points
- Use the factory that matches your transaction lifetime: blockhash vs nonce.
- Combine with
sendAndConfirmTransaction(or equivalent) which typically uses these under the hood; use these APIs when building custom confirmation flows. - Handle
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDEDandSOLANA_ERROR__NONCE_INVALIDby re-signing and resending when appropriate.
<!-- Source references:
- sources/solana-kit/packages/transaction-confirmation/README.md
-->
Transactions (Kit)
Transactions are built immutably with pipe: create message, set fee payer, set lifetime, append instructions, then sign and send. Use signer objects so Kit can collect and invoke signers automatically.
Build with pipe
import {
createTransactionMessage,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
assertIsSendableTransaction,
} from '@solana/kit';
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(wallet, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([createAccountIx, initializeMintIx], tx),
);Fee payer and lifetime
- Fee payer:
setTransactionMessageFeePayerSigner(signer, tx)orsetTransactionMessageFeePayer(address, tx). - Blockhash lifetime:
setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)(fromrpc.getLatestBlockhash().send()). - Durable nonce:
setTransactionMessageLifetimeUsingDurableNonce({ nonce, nonceAccountAddress, nonceAuthorityAddress }, tx)for long-lived or offline signing.
Adding instructions
- Single:
appendTransactionMessageInstruction(instruction, tx)orprependTransactionMessageInstruction(instruction, tx). - Multiple:
appendTransactionMessageInstructions([...], tx)/prependTransactionMessageInstructions([...], tx).
Instructions come from program clients (e.g. getCreateAccountInstruction, getInitializeMintInstruction).
Compute unit limit (optional)
Use Compute Budget to set or estimate CU limit before signing:
import { estimateComputeUnitLimitFactory, getSetComputeUnitLimitInstruction } from '@solana-program/compute-budget';
import { appendTransactionMessageInstruction } from '@solana/kit';
const estimateComputeUnitLimit = estimateComputeUnitLimitFactory({ rpc });
const units = await estimateComputeUnitLimit(transactionMessage);
const txWithLimit = appendTransactionMessageInstruction(
getSetComputeUnitLimitInstruction({ units }),
transactionMessage,
);Sign and send
const transaction = await signTransactionMessageWithSigners(transactionMessage);
assertIsSendableTransaction(transaction);
// Signature available before send:
const signature = getSignatureFromTransaction(transaction);
await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });Use sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }) for blockhash lifetime; use sendAndConfirmDurableNonceTransactionFactory for durable-nonce transactions.
Serialization
- Encode for send:
getBase64EncodedWireTransaction(transaction). - Decode from RPC:
getTransactionDecoder().decode(bytes); message:getCompiledTransactionMessageDecoder().decode(transaction.messageBytes); decompile:decompileTransactionMessage(compiledMessage).
Key points
- Always set fee payer and lifetime before appending instructions. Use signers so
signTransactionMessageWithSignerscan sign. - After signing, call
assertIsSendableTransaction(transaction); then send or get signature viagetSignatureFromTransaction(transaction).
<!-- Source references:
- sources/solana-kit/docs/content/docs/getting-started/build-transaction.mdx
- sources/solana-kit/docs/content/docs/getting-started/send-transaction.mdx
- sources/solana-kit/docs/content/docs/concepts/transactions.mdx
-->
Codecs (Kit)
Codecs encode values to Uint8Array and decode back. They are composable and used for account data and instruction data. Program clients expose getXCodec() (e.g. getMintCodec()); you can build custom codecs from primitives.
Compose a struct codec
import {
getStructCodec,
getU32Codec,
getU8Codec,
getBooleanCodec,
addCodecSizePrefix,
getUtf8Codec,
} from '@solana/kit';
type Person = { name: string; age: number; verified: boolean };
const getPersonCodec = (): Codec<Person> =>
getStructCodec([
['name', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
['age', getU32Codec()],
['verified', getBooleanCodec()],
]);
const codec = getPersonCodec();
const bytes = codec.encode({ name: 'John', age: 42, verified: true });
const decoded = codec.decode(bytes);Encoder-only / Decoder-only
Use getXxxEncoder / getXxxDecoder when only encoding or decoding so the other half can be tree-shaken.
Common codecs
- Numbers:
getU8Codec,getU32Codec,getU64Codec, etc. - Strings:
getUtf8Codec,addCodecSizePrefix(getUtf8Codec(), getU32Codec()). - Structs:
getStructCodec([['field', codec], ...]). - Option:
getOptionCodec(codec)(Rust-like Option). - Address:
getAddressCodec().
Program client codecs
import { getMintCodec } from '@solana-program/token';
const codec = getMintCodec();
const data = codec.decode(account.data);Key points
- Prefer program client
getXCodec()anddecodeX(encodedAccount)when available. - For custom layouts (e.g. program-specific account), compose from
getStructCodec,getOptionCodec, number/string/address codecs. Match the on-chain layout order and sizes.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/codecs.mdx
-->
Web3.js compatibility (Kit)
The @solana/compat package converts legacy web3.js 1.x class instances to Kit types so you can interoperate with code or libraries that still use the old API.
PublicKey to Address
import { fromLegacyPublicKey } from '@solana/compat';
const address = fromLegacyPublicKey(new PublicKey('49XBVQsvSW44ULKL9qufS9YqQPbdcps1TQRijx4FQ9sH'));Keypair to CryptoKeyPair
import { fromLegacyKeypair } from '@solana/compat';
const cryptoKeyPair = await fromLegacyKeypair(Keypair.generate());VersionedTransaction to Transaction
import { fromVersionedTransaction } from '@solana/compat';
const transaction = fromVersionedTransaction(legacyVersionedTransaction);TransactionInstruction to Instruction
import { fromLegacyTransactionInstruction } from '@solana/compat';
const instruction = fromLegacyTransactionInstruction(legacyInstruction);When to use
Migrating incrementally; consuming libraries that return PublicKey, Keypair, or VersionedTransaction. Do not use for new code; prefer Kit types directly. Package: @solana/compat, re-exported from @solana/kit. All conversions are one-way (legacy to Kit). fromLegacyKeypair is async.
<!-- Source: sources/solana-kit/packages/compat/README.md, README.md Compatibility Layer -->
Compatible program clients (Kit)
Program clients that work with Kit are JavaScript libraries that expose instructions, account fetch/decode, and codecs aligned with Kit’s types and RPC. The official set is generated with Codama; you can generate clients for your own programs the same way.
Official program clients
Install the library for each program your app uses. All are under the @solana-program/ scope and work with Kit’s RPC, signers, and transaction APIs.
| Program | Package |
|---|---|
| Address Lookup Table | @solana-program/address-lookup-table |
| Compute Budget | @solana-program/compute-budget |
| Memo | @solana-program/memo |
| Token (SPL) | @solana-program/token |
| Token-2022 (extensions) | @solana-program/token-2022 |
| Stake | @solana-program/stake |
| System | @solana-program/system |
Repositories live under solana-program on GitHub; Anza maintains these packages.
Usage with Kit
Use these clients to build instructions, fetch/decode accounts, and get codecs. Example with System and Token:
import { createSolanaRpc } from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
import { getCreateAccountInstruction, getTransferCheckedInstruction } from '@solana-program/token';
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
// Build instructions with get*Instruction(), use with pipe(), add to transaction, sign, send.Program clients follow the same patterns as Kit’s built-in instruction helpers: return instruction objects that you compose into transaction messages with Kit’s transaction and signer APIs.
Generating your own clients
Use Codama to generate TypeScript program clients from your program IDL. The output is designed to work with Kit’s addresses, codecs, and RPC. Install and run Codama in your repo, then use the generated package like the official @solana-program/* clients.
Key points
- Prefer these clients over hand-rolled instruction builders for maintained programs to stay aligned with Kit and avoid encoding bugs.
- For Token-2022 extensions use
@solana-program/token-2022; for classic SPL Token use@solana-program/token. - Compatible clients expose getInstruction, fetchAccount / decode, and getCodec-style APIs that match Kit’s conventions.
<!-- Source references:
- sources/solana-kit/docs/content/docs/compatible-clients.mdx
-->
Create Solana program (Kit)
The create-solana-program installer scaffolds a program repo and can generate a JavaScript client compatible with Kit. Run: pnpm create solana-program (or npm create solana-program). Follow prompts to pick framework (e.g. Anchor), then select the JS client to get a generated library like the @solana-program/* packages — getXxxInstruction, fetchXxx, decodeXxx, PDA helpers. The client is generated by Codama from the program IDL. Use it with Kit for instructions, accounts, and transactions. Repo: github.com/solana-program/create-solana-program.
Key points
- Use when starting a new program and you want a Kit-compatible JS client. Generated code follows the same patterns as @solana-program/system, @solana-program/token, etc.
<!-- Source: sources/solana-kit/README.md Create Solana Program -->
Errors (Kit)
Kit uses a typed error system: SolanaError with a code, message, and optional context. Use isSolanaError(e) or isSolanaError(e, code) to detect and narrow; context is typed when the code is known. Error messages are stripped in production builds to keep bundle size small.
Detect and handle
import {
isSolanaError,
SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING,
SOLANA_ERROR__TRANSACTION__EXCEEDS_SIZE_LIMIT,
} from '@solana/kit';
try {
assertIsSendableTransaction(transaction);
await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });
} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {
console.error('Missing signatures for:', e.context.addresses.join(', '));
} else if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__EXCEEDS_SIZE_LIMIT)) {
console.error(`Size limit ${e.context.transactionSizeLimit}, actual ${e.context.transactionSize}`);
}
throw e;
}Generic check
if (isSolanaError(e)) {
// e is SolanaError; use e.code, e.context as needed
}Key points
- Always use
isSolanaError(e)orisSolanaError(e, code)in catch blocks to get typed context. - Error codes are in the
SolanaErrorCodeunion; context shape depends on the code. See package@solana/errorsor Kit API docs for full list.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/errors.mdx
-->
GraphQL RPC (Kit)
@solana/rpc-graphql provides a GraphQL layer on top of the Solana JSON-RPC. Use when you want nested queries, field selection, and automatic caching/batching.
Setup
import { createSolanaRpc } from '@solana/rpc';
import { createSolanaRpcGraphQL } from '@solana/rpc-graphql';
const rpc = createSolanaRpc('https://api.devnet.solana.com');
const rpcGraphQL = createSolanaRpcGraphQL(rpc);RPC must satisfy GetAccountInfoApi, GetBlockApi, GetMultipleAccountsApi, GetProgramAccountsApi, GetTransactionApi.
Query accounts
rpcGraphQL.query(source, variableValues). Query account(address: $address) { lamports data(encoding: BASE_64) }. Nested: owner { address lamports }. Parsed types: ... on MintAccount { data { decimals supply } }, ... on TokenAccount { data { mint owner } }.
Transactions and blocks
transaction(signature: $signature, commitment: $commitment) { slot meta { computeUnitsConsumed } message { instructions { ... on CreateAccountInstruction { lamports programId } } } }. block(slot: $slot) for blockhash, blockTime, rewards, transactions.
RPC optimizations
Caching (same resource fetched once), request coalescing, batch loading (getMultipleAccounts), minimized payloads (dataSlice from query).
Key points
Use rpcGraphQL.query(source, variableValues). Prefer GraphQL for nested account/transaction data and batch/cache; use raw RPC for one-off methods or custom transports.
<!-- Source: sources/solana-kit/packages/rpc-graphql/README.md, README.md GraphQL -->
Instruction plans (Kit)
Instruction plans describe multi-step operations (possibly multiple transactions) as a tree of instructions: sequential, parallel, or message-packers. A transaction planner turns an instruction plan into a transaction plan (built transaction messages). A transaction plan executor signs and sends those messages and returns a transaction plan result.
Creating plans
import {
singleInstructionPlan,
sequentialInstructionPlan,
parallelInstructionPlan,
nonDivisibleSequentialInstructionPlan,
} from '@solana/kit';
const plan = sequentialInstructionPlan([
parallelInstructionPlan([depositAlice, depositBob]),
activateVault,
parallelInstructionPlan([withdrawAlice, withdrawBob]),
]);
const atomicPlan = nonDivisibleSequentialInstructionPlan([createAccount, initializeMint]);- sequentialInstructionPlan — children run in order.
- parallelInstructionPlan — children can run in parallel (separate transactions).
- nonDivisibleSequentialInstructionPlan — must run atomically (single tx or bundle).
Transaction planner
import {
createTransactionPlanner,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
} from '@solana/kit';
const transactionPlanner = createTransactionPlanner({
createTransactionMessage: () =>
pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayerSigner(payer, m),
),
});
const transactionPlan = await transactionPlanner(instructionPlan, { abortSignal });Optional: onTransactionMessageUpdated(message) to add instructions (e.g. compute limit, guards) during planning.
Transaction plan executor
import {
createTransactionPlanExecutor,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
assertIsSendableTransaction,
assertIsTransactionWithBlockhashLifetime,
sendAndConfirmTransactionFactory,
} from '@solana/kit';
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
const transactionPlanExecutor = createTransactionPlanExecutor({
executeTransactionMessage: async (context, message) => {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const withLifetime = setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message);
context.message = withLifetime;
const transaction = await signTransactionMessageWithSigners(withLifetime);
context.transaction = transaction;
assertIsSendableTransaction(transaction);
assertIsTransactionWithBlockhashLifetime(transaction);
await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });
return transaction;
},
});
const result = await transactionPlanExecutor(transactionPlan, { abortSignal });Context (context) is preserved on each result (success/fail/cancel) for debugging; standard fields: message, transaction, signature.
Result handling
- Successful:
isSuccessfulSingleTransactionPlanResult(result);result.context.signature,result.context.transaction. - Failed:
isFailedSingleTransactionPlanResult(result);result.error,result.context. - On failure the executor throws
SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN; the fullTransactionPlanResultis on the error context.
Key points
- Use instruction plans when an operation spans multiple instructions or transactions and you want planning (how many txs, ordering) and execution (sign + send) separated.
- Use
fillProvisorySetComputeUnitLimitInstructionin the planner andestimateAndUpdateProvisoryComputeUnitLimitFactoryin the executor for dynamic CU limits. UsesetTransactionMessageLifetimeUsingDurableNonce+sendAndConfirmDurableNonceTransactionFactoryfor durable nonce flows.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/instruction-plans.mdx
-->
Instructions (Kit)
Instructions are built with program clients (e.g. @solana-program/system, @solana-program/token, @solana-program/compute-budget). Each client exposes getXxxInstruction(...) with typed inputs.
System: CreateAccount
import { getCreateAccountInstruction } from '@solana-program/system';
import { getMintSize, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
const mintRent = await rpc.getMinimumBalanceForRentExemption(BigInt(getMintSize())).send();
const createAccountIx = getCreateAccountInstruction({
payer: wallet,
newAccount: mintSigner,
space: getMintSize(),
lamports: mintRent,
programAddress: TOKEN_PROGRAM_ADDRESS,
});Token: InitializeMint
import { getInitializeMintInstruction } from '@solana-program/token';
const initializeMintIx = getInitializeMintInstruction({
mint: mintSigner.address,
decimals: 0,
mintAuthority: wallet.address,
freezeAuthority: wallet.address,
});Compute Budget
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
estimateComputeUnitLimitFactory,
} from '@solana-program/compute-budget';
const setLimitIx = getSetComputeUnitLimitInstruction({ units: 50_000 });
const setPriceIx = getSetComputeUnitPriceInstruction({ microLamports: 10_000n });
const estimateCULimit = estimateComputeUnitLimitFactory({ rpc });
const units = await estimateCULimit(transactionMessage);
const limitIx = getSetComputeUnitLimitInstruction({ units });Compatible program clients
Install only the programs you use. Common packages:
@solana-program/system— CreateAccount, TransferSol, etc.@solana-program/token— Mint, Token Account, SPL Token instructions/codecs.@solana-program/token-2022— Token Extensions.@solana-program/compute-budget— SetComputeUnitLimit, SetComputeUnitPrice.@solana-program/memo— Memo.@solana-program/address-lookup-table— Address lookup table fetch.@solana-program/stake— Stake.
All are Codama-generated and follow getXxxInstruction, getXxxCodec, fetchXxx, decodeXxx patterns.
Key points
- Pass signers where the program expects a signer (e.g.
payer,newAccount); passaddressfor read-only or when you only have the public key. - Use
estimateComputeUnitLimitFactory+getSetComputeUnitLimitInstructionto set CU limit from simulation; add limit/price instructions to the transaction message before signing.
<!-- Source references:
- sources/solana-kit/docs/content/docs/getting-started/instructions.mdx
- sources/solana-kit/docs/content/docs/compatible-clients.mdx
-->
Key pairs (Kit)
Kit uses the Web Crypto API for Ed25519. Prefer Signers (e.g. KeyPairSigner) for transaction and message signing in app code; use key pairs when you need raw keys (e.g. ephemeral account creation, custom signer impl).
Generate
import { generateKeyPair, generateKeyPairSigner } from '@solana/kit';
const keyPair: CryptoKeyPair = await generateKeyPair();
const signer = await generateKeyPairSigner();Import from bytes
- 64-byte secret (full key):
createKeyPairSignerFromBytes(bytes). - 32-byte private key:
createKeyPairSignerFromPrivateKeyBytes(bytes).
import { createKeyPairSignerFromBytes } from '@solana/kit';
import fs from 'fs';
const keypairFile = fs.readFileSync('~/.config/solana/id.json');
const keypairBytes = new Uint8Array(JSON.parse(keypairFile.toString()));
const signer = await createKeyPairSignerFromBytes(keypairBytes);Signer from key pair
import { createSignerFromKeyPair, generateKeyPair } from '@solana/kit';
const keyPair = await generateKeyPair();
const signer = await createSignerFromKeyPair(keyPair);Polyfill
In runtimes without Ed25519 (e.g. older Node), use @solana/webcrypto-ed25519-polyfill and install before any crypto usage.
Key points
- For signing transactions and messages in app code, use Signers (
generateKeyPairSigner,createSignerFromKeyPair, wallet adapters); reserve rawCryptoKeyPairfor key generation/import and low-level signer implementations.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/keypairs.mdx
-->
Offchain messages (Kit)
Offchain messages let one or more parties sign a message (e.g. contract text or encoded data) without submitting an onchain transaction. Kit provides create, sign, verify, encode, and decode utilities; messages are ratified when all required signatories have signed.
Installation
Included in @solana/kit, or install standalone: @solana/offchain-messages.
Building an offchain message
Use the OffchainMessage type: version (use 1 for sRFC 3), requiredSignatories (signers or addresses), and content (UTF-8 string or decoded data).
import {
Address,
createSignerFromKeyPair,
OffchainMessage,
partiallySignOffchainMessageWithSigners,
getOffchainMessageEnvelopeEncoder,
} from '@solana/kit';
const signer = await createSignerFromKeyPair(keypair);
const offchainMessage: OffchainMessage = {
version: 1,
requiredSignatories: [signer, { address: address('ARiEL3q7uXvN9yZK8s2a5GfpHmQdR7cBv') }],
content: 'Agreed terms: ...',
};
const envelope = await partiallySignOffchainMessageWithSigners(offchainMessage);
const bytes = getOffchainMessageEnvelopeEncoder().encode(envelope);Signing
- With signers on the message:
signOffchainMessageWithSigners(offchainMessage)returns aFullySignedOffchainMessageEnvelopewhen all required signers are present. UsepartiallySignOffchainMessageWithSignerswhen only a subset can sign. - With keypairs on an envelope:
signOffchainMessageEnvelope([keyPair], offchainMessageEnvelope)(orpartiallySignOffchainMessageEnvelopefor partial signing). Compile first withcompileOffchainMessageEnvelope(offchainMessage)if you only have anOffchainMessage.
Verifying
import {
verifyOffchainMessageEnvelope,
isSolanaError,
SOLANA_ERROR__OFFCHAIN_MESSAGE__SIGNATURE_VERIFICATION_FAILURE,
} from '@solana/kit';
try {
await verifyOffchainMessageEnvelope(receivedOffchainMessageEnvelope);
} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__OFFCHAIN_MESSAGE__SIGNATURE_VERIFICATION_FAILURE)) {
// e.context.signatoriesWithInvalidSignatures, signatoriesWithMissingSignatures
}
throw e;
}Verification confirms signatures only; it does not validate content or the list of signatories. Compare envelope.content to expected bytes or decode and inspect before accepting.
Serializing and deserializing
- Encode envelope:
getOffchainMessageEnvelopeEncoder().encode(envelope). - Decode envelope:
getOffchainMessageEnvelopeDecoder().decode(bytes)→OffchainMessageEnvelope. - Decode envelope content to OffchainMessage:
getOffchainMessageDecoder().decode(envelope.content).
Key points
- Use
version: 1for the current schema (sRFC 3). Use version-specific compilers (e.g.compileOffchainMessageV1Envelope) to avoid bundling unused compilers. - Required signatories can be
MessageSigner(for self-signing) or{ address: Address }when the key is not available. - Handle
SOLANA_ERROR__OFFCHAIN_MESSAGE__SIGNATURES_MISSINGwhen signing if not all signers are provided.
<!-- Source references:
- sources/solana-kit/docs/content/docs/concepts/offchain-messages.mdx
- sources/solana-kit/packages/offchain-messages (README / API)
-->
Program errors (Kit)
When a transaction fails, the RPC returns the failed instruction index but not the program’s custom error by default. Use isProgramError() from @solana/programs (or @solana/kit) to detect whether the failure came from a specific program and optionally match an error code.
Usage
import { isProgramError } from '@solana/kit';
try {
await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });
} catch (error) {
if (isProgramError(error, transactionMessage, myProgramAddress, 42)) {
// Custom program error code 42 from this program.
} else if (isProgramError(error, transactionMessage, myProgramAddress)) {
// Any custom program error from this program.
} else {
throw error;
}
}Parameters
- error: The thrown value (typically from send/sendAndConfirm or RPC).
- transactionMessage: The transaction message that was executed. Required because the RPC only gives the instruction index; the message is used to resolve the program ID for that instruction.
- programAddress: The program address to attribute the error to.
- code (optional): If provided, the custom program error code must match this value.
Key points
- Use when handling transaction failures to distinguish program-specific errors (e.g. insufficient funds, wrong state) from network or validation errors.
- Always pass the same transaction message that was sent; the helper uses it to map the failed instruction to a program.
- Combine with
isSolanaError()for Kit’s own errors (e.g. block height exceeded, missing signatures) and useisProgramError()for on-program failure reasons.
<!-- Source references:
- sources/solana-kit/packages/programs/README.md
-->
React (Kit)
@solana/react provides React hooks that bridge Wallet Standard (e.g. Phantom) and Kit: sign-in, sign message, sign/send transactions, selected account state.
Provider and selected account
Wrap with SelectedWalletAccountContextProvider (filterWallet, stateSync: getSelectedWallet, storeSelectedWallet, deleteSelectedWallet). useSelectedWalletAccount() returns [account, setAccount, filteredWallets].
Hooks
- useSignIn(wallet) — Sign In With Solana; returns account, signedMessage, signature.
- useSignMessage(account) — sign bytes; returns { signature, signedMessage }.
- useWalletAccountMessageSigner(account) — MessageModifyingSigner for createSignableMessage.
- useSignTransaction(account, chain) — returns function({ transaction, options? }) => { signedTransaction }. Chain: solana:mainnet, solana:devnet.
- useWalletAccountTransactionSigner(account, chain) — TransactionModifyingSigner.
- useSignAndSendTransaction(account, chain) — returns function({ transaction, options? }) => { signature } (Uint8Array).
- useWalletAccountTransactionSendingSigner(account, chain) — use with signAndSendTransactionMessageWithSigners.
- useSignTransactions / useSignAndSendTransactions — batch multiple transactions.
Key points
Hooks expect Wallet Standard UiWalletAccount (or UiWallet for useSignIn). Signer hooks return ModifyingSigner types because the wallet may change message/transaction. Use stateSync to persist selected wallet (e.g. localStorage).
<!-- Source: sources/solana-kit/packages/react/README.md -->
RPC API augmentation (Kit)
The RPC API is type-driven; you can constrain it by cluster or method set, or add custom methods (e.g. provider-specific getAsset) with zero bundle cost.
Constrain by cluster
Wrap the URL with mainnet() or devnet() so the RPC type reflects cluster-specific methods (e.g. requestAirdrop only on devnet):
import { createSolanaRpc, mainnet, devnet } from '@solana/kit';
const mainnetRpc = createSolanaRpc(mainnet('https://api.mainnet-beta.solana.com'));
const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));Cherry-pick methods
Cast the RPC to a type with only the methods you need: createSolanaRpc(url) as Rpc<GetAccountInfoApi & GetMultipleAccountsApi>. Or build the API with createSolanaRpcApi<GetAccountInfoApi & GetMultipleAccountsApi>(DEFAULT_RPC_CONFIG) and createRpc({ api, transport }).
Custom RPC methods
Define a type spec (e.g. GetAssetApi with getAsset(args) return type) and create the client with createJsonRpcApi<YourApi>(), createDefaultRpcTransport({ url }), createRpc({ api, transport }). The library supports any JSON-RPC method; use for provider-specific APIs (e.g. Helius DAS getAsset).
Key points
- Types don't affect bundle size. Use cluster helpers for correct method availability; use cherry-pick or custom API for cleaner types or custom endpoints.
<!-- Source: sources/solana-kit/README.md Augmenting/Constraining the RPC API -->
Custom RPC transports (Kit)
Kit's RPC client can use a custom transport instead of the default HTTP one. Use createSolanaRpcFromTransport(transport). Transport is a function with the same shape as RpcTransport: receives request payload and context, returns JSON-RPC response or throws.
Failover
Try each URL in sequence on failure; throw last error if all fail.
const transports = urls.map(url => createDefaultRpcTransport({ url }));
async function failoverTransport(...args) {
let lastError;
for (const t of transports) {
try { return await t(...args); } catch (e) { lastError = e; }
}
throw lastError;
}
const rpc = createSolanaRpcFromTransport(failoverTransport);Retry with backoff
Retry up to N times with exponential delay before giving up.
Round-robin
Distribute requests across transports in sequence (next = (next + 1) % length).
Sharding by method
Route by payload.method (e.g. sendTransaction to one endpoint, getAccountInfo to another). Select transport in your wrapper and call it with the same args.
Key points
Use createSolanaRpcFromTransport(transport); resulting rpc has the same API as createSolanaRpc(url). Failover and retry for resilience; round-robin and sharding for load and rate limits.
<!-- Source: sources/solana-kit/README.md (Custom RPC Transports) -->
Unstable RPC subscriptions (Kit)
Stable RPC subscriptions (accountNotifications, slotNotifications, etc.) are available from createSolanaRpcSubscriptions. Unstable subscriptions (e.g. blockSubscribe, slotsUpdatesSubscribe) are not in the default API. If your RPC server supports them, use createSolanaRpcSubscriptions_UNSTABLE or createSolanaRpcSubscriptionsFromTransport_UNSTABLE to get a client that includes SolanaRpcSubscriptionsApiUnstable (e.g. BlockNotificationsApi, SlotsUpdatesNotificationsApi). Same .subscribe({ abortSignal }) pattern; only use when your endpoint documents support for these methods.
Key points
- Use createSolanaRpcSubscriptions_UNSTABLE('ws://...') or createSolanaRpcSubscriptionsFromTransport_UNSTABLE(transport). Check Solana docs for unstable subscription method names and support.
<!-- Source: sources/solana-kit/README.md Including Unstable Subscriptions -->