Skip to content
solana_kit

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.

CapabilityWhat it looks like
Data codecsGenerated 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 buildersvalue.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 parsersType::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 decodingdecode_* 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 defaultsfind_* 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.
ErrorsA 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.

CrateProgramSourceVerificationPage
solana_kit_systemSystem: 14 instructions, nonce stateGenerated + handPortableSystem
solana_kit_tokenSPL Token: 28 instructions, 3 accountsGeneratedPortableToken and Token-2022
solana_kit_token_2022Token-2022: 100 builders, extensionsGenerated + handPortableToken and Token-2022
solana_kit_compute_budgetCompute Budget: 5 layouts, helpersGenerated + handPortableCompute Budget, Memo, and ATA
solana_kit_memoMemo v1, v3, v4HandwrittenPortableCompute Budget, Memo, and ATA
solana_kit_associated_token_accountAssociated Token AccountHandwrittenPortableCompute Budget, Memo, and ATA
solana_kit_stakeStake: 17 instructions, activation mathGenerated + handPortableStake, Loader, Config, and lookup tables
solana_kit_loaderUpgradeable loader v3 and loader v4Generated + handPortableStake, Loader, Config, and lookup tables
solana_kit_configConfig: Store and account layoutGenerated + handPortableStake, Loader, Config, and lookup tables
solana_kit_address_lookup_tableAddress Lookup Table: 5 instructionsGenerated + handPortableStake, Loader, Config, and lookup tables
solana_kit_mpl_coreMPL Core: 42 instructions, pluginsGenerated + handPortableMetaplex
solana_kit_mpl_token_metadataToken Metadata: 58 operationsGenerated + handPortableMetaplex
solana_kit_mpl_bubblegumBubblegum: 36 instructions, cNFT helpersGenerated + handPortableMetaplex
solana_kit_account_compressionSPL Account Compression: 7 instructionsGenerated + handPortableMetaplex
solana_kit_anchorAnchor programs with an IDL 0.30 file; unsupported generic fields fail when usedRuntime coderPortableAnchor
solana_kit_squadsSquads V4 multisig: 36 instructionsGenerated + handPortableMore program clients
solana_kit_snsSolana Name Service (offline)HandwrittenNativeMore program clients
solana_kit_subscriptionsRecurring-payments SubscriptionsGenerated + handPortableMore program clients
solana_kit_attestation_serviceSolana Attestation ServiceGenerated + handPortableMore 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 AccountMeta lists with optional roles (MPL Core, Token Metadata, Squads) use a readonly program-ID sentinel for an omitted optional account. Clients with typed …Accounts structs use Option<Address>.
  • Custom deployments are explicit, through instruction_with_program, parse_with_program, or a program argument.

Each crate's README and THIRD_PARTY_NOTICES.md list the exact differences. Parity and status explains how claims are tracked.

esc