@solana/kit: the modern JS client, functional + tree-shakeable
@solana/kit replaces the classic web3.js with a functional, tree-shakeable, strict-typed client. Here's the primitive shape and the migration patterns.
devrels.xyz/a/73short link@solana/kit (formerly @solana/web3.js v2) is the official successor to the classic @solana/web3.js client. It's not a refresh — it's a ground-up redesign around functional composition, branded types, and tree shaking. A swap-only bundle drops from ~250KB (classic web3.js) to ~25KB (kit).
The trade: more verbose, more types, more explicit. Here's the design as it actually is.
Branded types instead of strings
In classic web3.js, a Solana address was a PublicKey class. In kit, it's a branded primitive — a string with a compile-time tag that distinguishes it from a regular string:
import type { Address } from "@solana/kit"
import { address } from "@solana/kit"
// Address is a string brand. You can't pass a raw string where an
// Address is expected — TypeScript catches it at compile time.
const usdc: Address = address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
// At runtime, it's a string. No class, no method overhead.
console.log(typeof usdc) // "string"
console.log(usdc) // "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"Same pattern for Signature, Blockhash, Slot, Lamports, etc — each is a compile-time-checked brand on a primitive value.
Functional composition with pipe
import {
createSolanaRpc,
createKeyPairSignerFromBytes,
pipe,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners,
lamports,
} from "@solana/kit"
import { getTransferSolInstruction } from "@solana-program/system"
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com")
const signer = await createKeyPairSignerFromBytes(secretBytes)
const ix = getTransferSolInstruction({
source: signer,
destination: recipient,
amount: lamports(10_000_000n),
})
const { value: bh } = await rpc.getLatestBlockhash().send()
const tx = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(signer.address, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(bh, m),
(m) => appendTransactionMessageInstruction(ix, m),
)
const sig = await signAndSendTransactionMessageWithSigners(tx)Each step is a pure function: takes a message, returns a new message. No mutation, no this, every step independently importable and tree-shakeable.
Codecs for serialization
Kit ships @solana/codecs, a typed binary codec library. Use it to serialize / deserialize account data without hand-rolling Borsh:
import {
getStructCodec, getU64Codec, getBoolCodec, getAddressCodec,
} from "@solana/kit"
const VaultCodec = getStructCodec([
["owner", getAddressCodec()],
["balance", getU64Codec()],
["is_active", getBoolCodec()],
])
// Encode an object → Uint8Array
const bytes = VaultCodec.encode({ owner: someAddress, balance: 1000n, is_active: true })
// Decode Uint8Array → typed object
const vault = VaultCodec.decode(accountInfo.data)
console.log(vault.balance) // bigintEach codec is composable and bidirectional. Anchor IDLs generate codec definitions for free via codama.
Per-program instruction packages
Where classic web3.js bundled every program's instruction builders inside the main package, kit ships them as separate packages — install only what you use:
npm install @solana/kit # core
npm install @solana-program/system # SystemProgram (transfer, createAccount, …)
npm install @solana-program/token # SPL Token instructions
npm install @solana-program/token-2022 # Token-2022 instructions
npm install @solana-program/compute-budget # ComputeBudgetProgram
npm install @solana-program/address-lookup-table # ALT instructions
npm install @solana-program/memo # Memo programYour transfer-only bundle pulls in @solana/kit + @solana-program/system and stops there. ~25KB gzipped.
The rpc object
Every JSON-RPC method maps to a chainable call returning a promise of a typed response:
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com")
// .send() actually fires the call; everything before is a builder
const slot = await rpc.getSlot().send()
const balance = await rpc.getBalance(myAddress).send()
const accountInfo = await rpc.getAccountInfo(myAddress, { encoding: "base64" }).send()
const programAccts = await rpc.getProgramAccounts(programId, {
filters: [{ dataSize: 165 }],
encoding: "base64",
}).send()
// Subscriptions are a separate transport
import { createSolanaRpcSubscriptions } from "@solana/kit"
const subs = createSolanaRpcSubscriptions("wss://api.mainnet-beta.solana.com")
const notifications = await subs.accountNotifications(myAddress).subscribe({ abortSignal })
for await (const update of notifications) {
console.log("account changed:", update)
}Why bigint everywhere
Lamports, slots, supply, balances — all bigint in kit. JavaScript's Number can't safely represent values past 2^53 - 1; Solana's u64 amounts blow past that for large balances. Kit's bigint-everywhere policy prevents the silent truncation that used to plague classic web3.js code.
Migration cheat sheet
Classic web3.js → @solana/kit
─────────────────────────────────────────────────────────────────
new Connection(url) createSolanaRpc(url)
new PublicKey(str) address(str) (or just the Address brand)
keypair.publicKey signer.address
LAMPORTS_PER_SOL lamports(1_000_000_000n)
new Transaction().add(ix) pipe(createTransactionMessage(…), appendInstruction(ix, …))
conn.sendTransaction(tx, signers) signAndSendTransactionMessageWithSigners(tx)
SystemProgram.transfer(…) getTransferSolInstruction(…) from @solana-program/system
conn.getAccountInfo(pk) rpc.getAccountInfo(addr, opts).send()
conn.onAccountChange(pk, cb) subscribe via createSolanaRpcSubscriptionsReferences
- anza-xyz/kit
- @solana/kit on npm
- Kit vs web3.js vs gill — comparison
- codama — auto-generate kit clients from IDLs
Kit isn't a drop-in replacement — it's a redesign. The functional shape, branded types, and tree shaking are the reasons it exists. If your bundle size, types, or RPC subscription story matters, the migration is worth it.
Keep reading
Legacy web3.js is on borrowed time. @solana/kit is the official successor. gill is the ergonomic wrapper. Here's the actual difference and what to use when.
Solana's TypeScript ecosystem fractured: web3.js v1, then the Kit rewrite, then Gill and Kite wrappers — and choice paralysis. web3.js 3.0 is the convergence move: the API everyone already knows, rebuilt on Kit's modern internals. What it actually is, with the package facts the announcement leaves out.
TypeScript 7.0 ships a native Go compiler — 10x faster than 6.0, parallel checkers and builders, a rebuilt file watcher from Parcel's C++ watcher ported to Go. The tradeoff: new defaults (strict, esnext, noUncheckedSideEffectImports) and dropped targets (ES5, amd, umd, classic moduleResolution) that will break existing configs. Here's the full rundown.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
