Where is Solana's ABI? Why your inline ethers.js habit doesn't port, and how Codama closes the gap
Ethereum lets you paste a two-line human-readable ABI and call a contract. Solana asks for an IDL file and a codegen step. The difference isn't neglect: it's the account model. Here's why, and what the modern Codama + Solana Kit workflow looks like.
devrels.xyz/a/145short linkEvery developer who arrives on Solana from Ethereum hits the same wall in their first week. On Ethereum, calling a contract you don't own takes four lines, and the interface definition is a string you type from memory:
import { Contract, JsonRpcProvider } from "ethers"
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 value)",
]
const usdc = new Contract("0xA0b8...eB48", abi, provider)
await usdc.transfer(recipient, 1_000_000n)
On Solana, the equivalent task sends you hunting for a JSON file called an IDL, and the recommended workflow involves a code generator. To someone used to pasting a signature string into ethers.js, this feels heavy. "Ethereum circa 2018" is the usual verdict, and as feedback about ergonomics it has been fair at various points in Solana's history.
But the difference is not neglect, and in 2026 the workflow on the other side of it is genuinely good. This article explains why Solana can't have inline ABIs, what the IDL actually carries, and what the modern Codama + Solana Kit pipeline looks like in practice.
Why the one-liner works on Ethereum
Ethereum's inline ABI is possible because of two properties of the EVM ecosystem that Solana deliberately does not share.
One canonical encoding. Every EVM contract, no matter the language it was written in, speaks ABI encoding: the first four bytes of calldata are the keccak-256 hash of the function signature, and the arguments follow in one standardized layout. That means the string "function transfer(address,uint256)" contains everything a client library needs to build the calldata. The signature is the wire format.
An implicit state model. An Ethereum call is an address plus calldata. The contract reads and writes whatever storage it likes at runtime; the caller doesn't declare any of it. So the interface description never has to mention state at all.
Why it can't work on Solana
Both properties are inverted on Solana, and the second one is the killer.
No serialization standard at the runtime level. The SVM hands a program a byte array and lets it interpret those bytes however it wants. Anchor programs use an 8-byte discriminator plus Borsh. The SPL Token program uses its own packed layout with a 1-byte instruction tag. Other programs use bincode, or something bespoke. There is no equivalent of the four-byte selector convention baked into every tool in the ecosystem, so a bare signature string doesn't tell a client how to build the instruction data.
Every account must be enumerated. This is the structural reason. A Solana instruction must list, up front, every account it will read or write, each flagged as signer or writable. That list is how the runtime schedules non-overlapping transactions in parallel, and it routinely includes PDAs derived from seeds, associated token accounts, sysvars, and the programs being invoked. A realistic instruction takes eight or twelve accounts, several of them computed rather than known.
Try to imagine the inline string for that. It isn't "function swap(uint64 amountIn)"; it's that plus "and pass the pool state PDA derived from these two mints, the vault A token account, the vault B token account, the user's ATA for each side, the token program, and the pool's oracle, in this order, with these writable flags." A one-line signature format that could express all of that would just be a JSON schema with worse syntax. That schema exists. It is the IDL.
What an IDL actually carries
An IDL (Interface Definition Language file) is a JSON description of a program's full surface: instructions with their arguments and account lists (including PDA seed derivations, so clients can compute addresses for you), account state layouts, custom types, events, and error codes. Anchor generates one at build time, and the format was formalized as a proper spec in Anchor 0.30. For non-Anchor programs, Shank macros annotate native Rust to produce the same artifact.
Events are the clearest example of why the IDL earns its size. An Ethereum event is a log with ABI-typed topics; the same inline string that declares it can decode it. A Solana event (in the Anchor convention) is a Borsh-serialized blob written to the program log, or emitted through a self-CPI. Without the IDL it is base64 noise. With it, explorers like Solscan and SolanaFM and APIs like Helius decode it into named, typed fields. The information has to live somewhere, and Solana's answer is "in one machine-readable file" rather than "in a string the caller retypes."
Codama: the part that fixed the vibe
The "2018" feeling was never really about the IDL existing. It was about what you did with it: hand-writing deserializers, or dragging the whole IDL into your bundle so a runtime library could interpret it on the fly. That is the part that changed.
Codama (the renamed and matured Kinobi, by Loris Leiva) treats the IDL as a compiler input instead of a runtime dependency. It parses an Anchor IDL (or its own richer Codama IDL format) into a node tree, lets visitors transform it, and renders generated, typed clients in TypeScript and Rust. We covered the architecture in depth in our Codama article; the short version of the workflow is:
npm install codama
npx codama init # point it at your (Anchor) IDL, pick presets
npx codama run js # generate a TypeScript client
npx codama run rust # generate a Rust clientThe output plugs straight into Solana Kit, and this is where the DX comparison flips. The generated function knows the accounts, derives the PDAs, applies the right flags, and type-checks the arguments:
import { pipe, createTransactionMessage, setTransactionMessageFeePayer, appendTransactionMessageInstruction, signAndSendTransactionMessageWithSigners } from "@solana/kit"
import { getTransferInstruction } from "./generated"
const ix = getTransferInstruction({
source: sourceAta,
destination: destinationAta,
authority: owner, // a signer object, checked at compile time
amount: 1_000_000n,
})
const tx = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(owner.address, m),
(m) => appendTransactionMessageInstruction(ix, m),
)
await signAndSendTransactionMessageWithSigners(tx)Nothing in the ethers experience tells you at compile time that you forgot a signer or passed accounts in the wrong order. The Solana pipeline does, precisely because the interface definition is richer than a signature string. New programs get this for free: pnpm create solana-program scaffolds program plus generated clients with Codama already wired in.
The honest scorecard
Where Ethereum still wins: ad-hoc interaction. Paste two lines, call a view function, move on. Nothing on Solana matches that for sheer immediacy, and for quick scripts against a program whose IDL you don't have handy, it stings.
Where Solana now wins: anything you maintain. Generated clients are fully typed end to end, account ordering mistakes become compile errors, PDA derivation lives in the client instead of your head, and the same IDL feeds your docs, your explorer decoding, and your Rust CLI.
The remaining gaps: there is no drop-in human-readable string format and probably never will be, because the account model makes the minimal honest description a structured document. Closed-source programs without published IDLs are still a pain; for Anchor-based ones, sec3's IDL Guesser recovers the IDL from deployed bytecode, and verified builds attack the problem from the publishing side. And yes, shipping an IDL to decode events is heavier than an event signature string; that is the price of a runtime with no canonical encoding.
If you're making the jump
Translate your instincts rather than fighting the platform. The ABI string in your ethers snippet becomes an IDL file; TypeChain becomes Codama; new Contract(...) becomes a generated client module; and the account list, the thing with no Ethereum equivalent, is the thing to actually learn. Start with our comparison of the JavaScript client stacks if you're choosing libraries, and the framework comparison if you're also writing the program side.
Resources
- github.com/codama-idl/codama for generating typed clients, docs, and CLIs from Solana IDLs
- Anchor documentation for the IDL spec and idl-build tooling
- github.com/solana-program/create-solana-program to scaffold a program with Codama clients wired in
- github.com/sec3-service/IDLGuesser to recover IDLs from closed-source Anchor programs
- @lorismatic is Loris Leiva, Codama's creator
- Our Codama deep-dive and Solana Kit overview
Keep reading
The IDL describes everything about a Solana program, so why are you still writing a backend to expose it? Orquestra takes an Anchor or Codama IDL and hosts the rest: REST endpoints for instructions and accounts, PDA derivation, an unsigned transaction builder, llms.txt for AI agents, and a public MCP server. A look at what it does and where it fits.
Anchor's TypeScript client is a runtime IDL interpreter. codama is a build-time code generator that emits tree-shakeable, type-safe instruction builders. The difference matters.
The fastest Solana vanity grinder just made its slow path fast. v0.8.0 batches ed25519 keygen 8 lanes wide with AVX-512 IFMA on CPU and rebuilds the OpenCL keypair kernels — Montgomery batch inversion, a radix-32 comb for fixed-base scalarmult, fused base58 early-reject. Real signable vanity keypairs at 65M/s per 4090.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
