Typed Clients Blueprint
Typed clients are how off-chain TypeScript talks to a Solana program without hand-encoding discriminators, account metas, and Borsh layouts. Once you treat the IDL as the shared contract and every client generator as a consumer of that contract, Anchor's Program API, Codama output, gill helpers, instruction builders, and account decoders stop looking like competing libraries and start looking like one pipeline from anchor build to a signed transaction.
Typed Clients Basics is the hands-on entry; The Anchor TS Client, Codama, gill, Building Instructions, Fetching & Decoding Accounts, and Keeping Clients in Sync each zoom into one stage. This page sits underneath: how the pieces connect, which path to pick, and what you pin so frontends and bots stay honest after program upgrades.
Summary
- A program's public surface is described by an IDL; typed clients regenerate instruction builders, codecs, and fetch helpers from that JSON so TypeScript cannot silently invent layouts that diverge from on-chain encoding.
- Insight: Hand-written account lists and arg buffers drift; one IDL-driven client surface cuts mainnet encode bugs, speeds UI and bot work, and makes CI able to fail when the interface moves without a client update.
- Key Concepts: Anchor IDL, Anchor TS
Program, Codama + @solana/kit, gill, typed instruction builders, fetch/decode helpers, discriminators and Borsh, CI regen and drift checks. - When to Use: Shipping or maintaining TypeScript apps against Anchor (or IDL-described) programs, choosing between classic Anchor client and kit-first stacks, or designing monorepo release gates for program plus frontend.
- Limitations/Trade-offs: Client quality tracks IDL quality; remaining accounts and some native programs need extra docs; Anchor TS and Codama target different runtimes so pick one transaction story per app; gill does not generate your program's IDL surface.
- Related Topics: Anchor TS client, Codama, gill, building instructions, fetching and decoding accounts, keeping clients in sync.
Foundations
Solana programs expose bytes: instruction data (usually an 8-byte discriminator plus args) and account lists with signer and writable flags. Nothing on the wire is "TypeScript-native." Without a shared description of those shapes, every dApp, script, and indexer invents its own encoding and eventually disagrees with the program.
Anchor fills that gap for framework programs. Macros on instructions, accounts, events, and errors feed anchor build, which emits IDL JSON under target/idl/. That file is the contract for off-chain work in this section: instruction names and accounts, types, errors, events, and program address metadata. Typed clients exist to consume that contract, not to replace it.
Think of the off-chain stack as layers from IDL out to UX:
+------------------------------------------+
| App / bot / API (business flows) | <- UI, jobs, wallets
+------------------------------------------+
| Convenience (optional gill helpers) | <- RPC + send patterns
+------------------------------------------+
| Program client surface |
| Codama-generated kit codecs / ixs |
| or Anchor TS Program.methods.* |
+------------------------------------------+
| Runtime primitives (@solana/kit 7.0.0) | <- RPC, messages, signers
+------------------------------------------+
| IDL artifact (JSON, git/npm pin) | <- versioned contract
+------------------------------------------+
| Program build (Anchor 0.32.1 + sBPF) |
+------------------------------------------+Two TypeScript program client stories dominate:
- Anchor TS client (
@coral-xyz/anchor): load IDL intonew Program(...), callprogram.methods.<ix>(...).accounts(...).rpc(). Strong fit foranchor test, brownfield dApps, and teams already on Anchor Provider patterns. - Codama: parse Anchor IDL into a node graph and render @solana/kit-oriented modules (
get*Instruction,fetch*/decode*helpers, error maps). Strong fit for greenfield kit stacks and tree-shakable transaction assembly.
@solana/kit 7.0.0 is the modern RPC, codec, signer, and transaction-message layer. Codama is the usual bridge from Anchor IDL into kit types. Kit alone does not know your program's account layouts; without Codama (or hand-written codecs) you are back to inventing bytes.
gill sits above kit as optional convenience: client factory patterns, moniker-based RPC, and common send helpers. It does not replace Codama output for your custom program. You still generate program-specific builders and decoders from the IDL, then compose them with kit or gill send paths.
Reads and writes share the same generated surface: builders encode discriminator + args and account metas; fetch helpers decode RPC bytes with the matching layout. One generator keeps both paths aligned.
Platform pins for this site: Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.
Mechanics & Interactions
IDL in, typed surface out
The spine is always:
programs/.../lib.rs --macros--> anchor build
|
v
target/idl/foo.json
|
+--------------------------+--------------------------+
v v
Anchor TS Program(idl) Codama CLI + renderers
methods / account.fetch get*Instruction, fetch*
| |
v v
classic Provider / tests kit transaction messagesRegenerate on every public interface change. Hand-editing generated folders or the IDL JSON to "match the UI" creates silent drift: the program still encodes the macro-defined layout while clients encode fiction. Details for generation live in Codama; the classic path is The Anchor TS Client.
Choosing Anchor TS vs Codama
| Concern | Anchor TS Program | Codama + kit |
|---|---|---|
| Primary runtime | Historical web3.js / Provider model | @solana/kit 7.0.0 messages and codecs |
| DX shape | Fluent .methods / .rpc() | Explicit builders + appendTransactionMessageInstruction |
| Best fit | Tests, legacy dApps, fast Anchor prototypes | Greenfield kit apps, shared server/web packages |
| Migration cost | Low if already Anchor-shaped | Pay once for kit message pipeline literacy |
Do not mix kit transaction assembly with web3.js instruction objects without a deliberate bridge. Pick one client runtime per app and regenerate when the IDL changes. Prefer Codama + kit for new modules; keep Anchor TS where migration cost dominates.
Building instructions
A generated instruction builder answers: which accounts, which flags, which arg types, which discriminator. You pass addresses and signers; the builder emits a kit-native (or Anchor-native) instruction object ready to append into a transaction message.
getIncrementInstruction({ counter, authority })
|
v
Instruction {
programAddress,
accounts: [ { counter, writable }, { authority, signer } ],
data: discriminator || borsh(args)
}
|
v
append to TransactionMessage -> sign -> sendNever hand-reorder account metas. Order and flags come from the IDL; init-style flows still need System Program and funding accounts when required. Deep dive: Building Instructions.
Fetching and decoding accounts
Reads are the dual of writes:
- RPC returns owner, lamports, and data bytes (
getAccountInfoor a generated fetch helper). - Generated decoder checks discriminator / layout and returns a typed object.
- Callers handle
null(missing or closed accounts) before UI or business logic.
RPC getAccountInfo(address)
|
v
raw bytes + owner
|
v
decodeFoo / fetchFoo --> { data: { ... }, ... } | nullWrong owner, cluster program id, or a stale decoder yields garbage that looks valid until a write fails. Prefer generated fetch* helpers; use raw RPC + decode* for custom batching. Details: Fetching & Decoding Accounts.
Where gill fits
gill reduces kit boilerplate (client creation, monikers, send-and-confirm patterns). Program-specific knowledge still comes from Codama (or Anchor TS). A healthy composition is:
gill createSolanaClient --> rpc + send helpers
Codama generated module --> getIx / fetchAccount
App code --> compose bothTreat gill as acceleration on top of kit literacy, not as a black box that invents your program API. See gill.
Keeping clients in sync
Typed clients only stay correct if regeneration is mechanical and enforced:
- Edit program surface under Anchor 0.32.1; build with pinned Agave/CLI and Rust.
- Review IDL diff; commit the artifact if it is the team's source of truth.
- Run Codama (or refresh Anchor types); fail CI on dirty generated trees.
- Run unit/integration tests against localnet or Surfpool-style harnesses.
- Coordinate frontend/bot deploys with program deploys when the change is breaking.
- Tag releases with program id, cluster, and IDL content hash (or commit SHA).
program change --> IDL diff --> client diff --> app rebuild --> deploy train
^
|
CI: git diff --exit-code generated/On-chain IDL fetch helps discovery; production clients still pin trusted artifacts. Full practice: Keeping Clients in Sync.
One day of work across the pipeline
- Change an instruction or account struct;
anchor build. - Confirm
target/idlmatches the intended public surface. - Regenerate Codama clients (or reload Anchor IDL in tests).
- Update call sites; fix TypeScript errors.
- Simulate the instruction; fetch and decode the affected account.
- Land CI gates so regen cannot be skipped.
Advanced Considerations & Applications
Match stack age, runtime choice, and release rigor to a path, then add gates when multi-developer drift or mainnet risk rises.
| Path | What you ship | Strengths | Weaknesses | Best fit |
|---|---|---|---|---|
| Anchor TS only | IDL + Program in app/tests | Fast fluent API; natural with Anchor tests | Kit migration later; historical web3.js coupling | Brownfield Anchor dApps |
| Codama + kit | Generated codecs in monorepo or package | Modern kit stack; explicit messages | Codegen step and pin discipline | Greenfield product UIs and APIs |
| Codama + kit + gill | Above + gill client helpers | Faster bootstrap; shared send patterns | Another version to track; still need Codama | Scaffolded apps and small teams |
| Multi-program monorepo | One IDL and generated folder per program | Clear namespaces; shared CI | Naming collisions if folders merge | Workspaces with several program crates |
| Full release train | IDL hash, npm/git pin, dirty-tree CI, coordinated deploy | Integrator-ready; audit-friendly | Process cost | Mainnet programs with external clients |
Multi-program apps: never merge IDL surfaces into one JSON. Namespace generated output (clients/program_a, clients/program_b) and avoid export name collisions.
Indexers and bots are clients too; a stale worker decoder is the same bug class as a stale UI hook, only quieter.
Native programs may lack a complete Anchor IDL. Prefer a maintained compatible IDL, Codama from a custom description, or hand-built kit codecs.
Remaining accounts often sit outside fixed IDL lists. Document them and pass them explicitly; codegen may not invent the full runtime account graph.
For design reviews: which TS runtime you standardize on, who owns the IDL artifact, how CI detects drift, whether gill is allowed, and how breaking IDL changes roll out.
Common Misconceptions
- "Typed clients mean I can skip learning kit messages." Builders still land in transaction messages; simulation, fees, and signers remain your responsibility.
- "Anchor TS and Codama are drop-in replacements." Both read IDL, but they target different client stacks; choose one primary transaction path per app.
- "gill generates my program client." gill helps with kit workflows; program-specific instructions and decoders still come from Codama or Anchor TS.
- "I can hand-edit generated files for a hotfix." The next regen wipes them; put overrides in wrapper modules only.
- "If the UI type-checks, the IDL matches mainnet." Types match the IDL you generated against, not necessarily the deployed program; pin program id, cluster, and IDL hash.
- "Fetch helpers are optional polish." Shared decoders prevent two layouts (read path vs write path) from diverging inside one codebase.
- "CI regen is overkill for a solo repo." Drift appears the first time you change an account field and forget a second package.
- "Importing the full IDL JSON at runtime is always fine." Large IDLs bloat bundles; prefer tree-shakable generated modules or lazy loads.
FAQs
What is the single most important idea in typed clients?
One versioned IDL is the shared contract; every typed TypeScript helper is a regenerated consumer of that contract, not a parallel hand-written ABI.
Where does the IDL come from?
For Anchor programs, anchor build (0.32.1 on this site) emits JSON under target/idl/ from macros. Commit and review that artifact as API product.
When should I use the Anchor TS client?
When you maintain classic Anchor tests or brownfield dApps on the Provider / .methods model and are not yet standardizing on kit transaction assembly.
When should I use Codama?
When the app standardizes on @solana/kit 7.0.0 and you want generated instruction builders and account decoders that match the Anchor IDL without hand codecs.
How does Codama relate to the Anchor IDL?
Codama ingests Anchor IDL JSON (via nodes-from-anchor and related packages), builds an internal graph, and renders kit-oriented TypeScript. Rebuild when the IDL changes and pin the CLI/renderer versions in CI.
Is gill required?
No. It is optional sugar for kit RPC and send patterns. Program-specific typing still depends on Codama or Anchor TS.
How do I build a typed instruction?
Call the generated get*Instruction (Codama) or program.methods.* (Anchor TS) with accounts and args, then place the result in your chosen send path. See Building Instructions.
How do I fetch a typed account?
Use generated fetch* helpers with kit RPC, or getAccountInfo plus decode*. Always handle missing accounts and verify owner/program id assumptions. See Fetching & Decoding Accounts.
What breaks if IDL and program diverge?
Wrong discriminators, account order, or layouts cause simulation failures, cryptic custom errors, corrupt account decodes, or silent UI lies that fail only on write.
How should CI keep clients in sync?
Build the program, regenerate clients, git diff --exit-code on generated paths, and run tests. Pair with release tags that include program hash and IDL version. See Keeping Clients in Sync.
Can I mix kit and web3.js in one feature?
Only with an explicit bridge. Prefer one runtime per feature boundary so instruction and address types do not thrash.
Do typed clients replace on-chain validation?
No. Clients reduce encode mistakes; the program still enforces constraints, ownership, and signer rules. Simulation is still mandatory for serious flows.
How do multi-program monorepos stay clean?
One IDL and one generated output directory per program, shared CI regen for all, and explicit package exports so import paths cannot silently cross wires.
Where does @solana/kit fit relative to Codama?
Kit is the RPC and transaction stack (this site: 7.0.0). Codama bridges Anchor IDL into that stack. Kit does not compile sBPF or own the IDL format.
Related
- The Anchor TS Client -
Program, methods, and classic Provider flows - Codama - generating @solana/kit clients from Anchor IDL
- gill - higher-level kit helpers for common RPC and send paths
- Building Instructions - typed instruction builders in transaction messages
- Fetching & Decoding Accounts - typed reads with generated codecs
- Keeping Clients in Sync - IDL-driven regeneration and CI drift gates
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.