Program clients#
A program client is a crate that knows the wire format of one deployed Solana program. You use it from application code to build that program's instructions, check instructions you received, and decode its accounts. Clients use these Kit-owned types where they apply: Address, AccountMeta, Instruction, EncodedAccount, and the bounded codecs from solana_kit_core. That means the output goes straight into the message, signer, and RPC APIs described in the rest of this book.
What a client gives you#
Most clients follow the same shape. Each part is optional per crate. The table later on this page shows what each crate has.
| Capability | What it looks like |
|---|---|
| Data codecs | Generated clients typically have a plain struct per instruction or account (TransferSol, Mint), with a matching …Codec that implements the core Encoder and Decoder traits. solana_kit_anchor is the exception: it codes values at runtime through its Coder and ValueCodec. |
| Instruction builders | value.instruction(accounts, limits) returns an Instruction. Clients that support custom deployments (Token, Token-2022, the Metaplex and compression clients, Squads, Subscriptions, and the Attestation Service) also offer instruction_with_program; System and the other runtime programs do not. |
| Strict parsers | Type::parse(&instruction, limits) and a crate-level parse_instruction check the program identity, the discriminator, the account count, and that no bytes are left over. Some, such as System, also check each account's privileges; the Stake parsers do not check account roles. |
| Account decoding | decode_* functions take an already fetched EncodedAccount and return Account<T>. They check the owner and allocation and keep all metadata (lamports, rent epoch, space). |
| PDAs and defaults | find_* functions behind a pda (or crypto) feature derive program addresses with core's deterministic crypto. Some clients also fill fixed sysvars or declared defaults for you. |
| Errors | A program error enum with from_code(u32). Unknown codes come back as Unknown(code), so no code is ever lost. |
Nearly every operation takes an explicit Limits value from solana_kit_core::codecs. Limits::new(max_bytes, max_items) caps the bytes read and the number of items allocated. Malformed or hostile input fails with an error and never causes an unbounded allocation. See Codecs for how the budgets work.
use solana_kit_core::{Address, codecs::Limits};
use solana_kit_system::{
ClientError, SystemInstructionData, TransferSol, TransferSolAccounts, parse_instruction,
};
fn round_trip() -> Result<(), ClientError> {
let limits = Limits::new(1232, 32);
let instruction = TransferSol { amount: 500 }.instruction(
TransferSolAccounts {
source: Address::from_bytes([1; 32]),
destination: Address::from_bytes([2; 32]),
},
limits,
)?;
match parse_instruction(&instruction, limits)? {
SystemInstructionData::TransferSol(transfer) => assert_eq!(transfer.amount, 500),
other => panic!("unexpected System instruction: {other:?}"),
}
Ok(())
}Clients never fetch accounts, sign, or send anything on their own. You fetch with RPC, pass the bytes to a decode_* function, and put the built instructions into a message (see Messages and transactions). The exceptions are Token-2022's client feature and Bubblegum's das feature, which add typed fetch helpers that you drive with your own transport.
Every program crate#
"Portable" means the crate's contract tests also run in the shared suite on native Rust, Chrome, Wasmtime (WASI Preview 1 and 2), and the pinned LeptosKit interpreter. Validator execution and installed-app journeys are still pending for every crate in this table, so none of them has been tested against a live cluster.
| Crate | Program | Source | Verification | Page |
|---|---|---|---|---|
solana_kit_system | System: 14 instructions, nonce state | Generated + hand | Portable | System |
solana_kit_token | SPL Token: 28 instructions, 3 accounts | Generated | Portable | Token and Token-2022 |
solana_kit_token_2022 | Token-2022: 100 builders, extensions | Generated + hand | Portable | Token and Token-2022 |
solana_kit_compute_budget | Compute Budget: 5 layouts, helpers | Generated + hand | Portable | Compute Budget, Memo, and ATA |
solana_kit_memo | Memo v1, v3, v4 | Handwritten | Portable | Compute Budget, Memo, and ATA |
solana_kit_associated_token_account | Associated Token Account | Handwritten | Portable | Compute Budget, Memo, and ATA |
solana_kit_stake | Stake: 17 instructions, activation math | Generated + hand | Portable | Stake, Loader, Config, and lookup tables |
solana_kit_loader | Upgradeable loader v3 and loader v4 | Generated + hand | Portable | Stake, Loader, Config, and lookup tables |
solana_kit_config | Config: Store and account layout | Generated + hand | Portable | Stake, Loader, Config, and lookup tables |
solana_kit_address_lookup_table | Address Lookup Table: 5 instructions | Generated + hand | Portable | Stake, Loader, Config, and lookup tables |
solana_kit_mpl_core | MPL Core: 42 instructions, plugins | Generated + hand | Portable | Metaplex |
solana_kit_mpl_token_metadata | Token Metadata: 58 operations | Generated + hand | Portable | Metaplex |
solana_kit_mpl_bubblegum | Bubblegum: 36 instructions, cNFT helpers | Generated + hand | Portable | Metaplex |
solana_kit_account_compression | SPL Account Compression: 7 instructions | Generated + hand | Portable | Metaplex |
solana_kit_anchor | Anchor programs with an IDL 0.30 file; unsupported generic fields fail when used | Runtime coder | Portable | Anchor |
solana_kit_squads | Squads V4 multisig: 36 instructions | Generated + hand | Portable | More program clients |
solana_kit_sns | Solana Name Service (offline) | Handwritten | Native | More program clients |
solana_kit_subscriptions | Recurring-payments Subscriptions | Generated + hand | Portable | More program clients |
solana_kit_attestation_service | Solana Attestation Service | Generated + hand | Portable | More program clients |
[dependencies]
solana_kit_core = { git = "https://github.com/pina-rs/solana_kit" }
solana_kit_system = { git = "https://github.com/pina-rs/solana_kit" }
solana_kit_token = { git = "https://github.com/pina-rs/solana_kit" }Generated and handwritten clients#
Most data codecs, builders, and parsers are generated. The private generator in tools/codama-renderer reads each program's pinned input, a Codama IDL for some programs and Anchor or Shank sources for others (see docs/parity/program-inputs.tsv), and lowers it through one shared typed wire model and emitter. Each program has its own command, such as system, token, token-2022-data, compute-budget, alt-config, stake-loader-data, mpl-core-generate, token-metadata-generate, bubblegum-generate, compression-generate, squads-generate, subscriptions-generate, and attestation-generate. Generated files are committed and start with a // Generated from … header. Drift checks regenerate them and require byte-identical output, so do not edit them by hand.
The generator is not a general Codama renderer. It supports the node shapes these programs use and rejects anything else before it writes output. Behavior the IDL cannot express is handwritten next to the generated module, for example:
- System nonce accounts. The flat IDL cannot describe uninitialized allocated storage.
- Token-2022 account dispatch, extension validation, and pre-initialization helpers.
- Compute Budget's instruction-list helpers, Stake's activation and withdrawal math, and Loader's write-then-deploy plans.
- Memo and Associated Token Account, which are fully handwritten. The generator cannot yet model untagged remainder layouts or their account contracts.
- SNS, which has no instruction builders. It covers derivation and data parsing only.
solana_kit_anchor is the third kind of client. Instead of generated Rust, it parses an Anchor IDL at runtime and encodes and decodes values dynamically.
The reproducible input ledger is docs/parity/program-inputs.tsv. It records each program's repository, revision, input path, checksum, and license. The generator design notes are in docs/verification/generator-design.md.
Conventions that differ from the Dart SDK#
These clients port the Dart solana_kit program packages, but they choose strict, explicit behavior wherever the Dart code was permissive:
- Parsers check the whole invocation, not only the data bytes. A wrong program address, a missing signer, or trailing bytes is an error.
- No signer, payer, or funding amount is invented. Authorities and optional accounts are inputs you pass in.
- Generated clients that take raw
AccountMetalists with optional roles (MPL Core, Token Metadata, Squads) use a readonly program-ID sentinel for an omitted optional account. Clients with typed…Accountsstructs useOption<Address>. - Custom deployments are explicit, through
instruction_with_program,parse_with_program, or aprogramargument.
Each crate's README and THIRD_PARTY_NOTICES.md list the exact differences. Parity and status explains how claims are tracked.