solana_kit_accounts

pub package docs CI coverage

Account fetching, decoding, and assertion utilities for the Solana Kit Dart SDK.

This is the Dart port of @solana/accounts from the Solana TypeScript SDK.

Installation

Add solana_kit_accounts to your pubspec.yaml:

dependencies:
  solana_kit_accounts:

Or, if you are using the umbrella package:

dependencies:
  solana_kit:

Documentation

Usage

The Account type

The Account<TData> class contains all the information relevant to a Solana account: its address, data, and on-chain metadata.

import 'dart:typed_data';

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';

void main() {
  // An encoded account stores its data as raw bytes.
  final encodedAccount = Account<Uint8List>(
    address: const Address('11111111111111111111111111111111'),
    data: Uint8List.fromList([1, 2, 3, 4]),
    executable: false,
    lamports: Lamports(BigInt.from(1000000000)),
    programAddress: const Address('11111111111111111111111111111111'),
    space: BigInt.from(4),
  );

  print(encodedAccount.address); // Address('11111111111111111111111111111111')
  print(encodedAccount.lamports); // Lamports(1000000000)
  print(encodedAccount.executable); // false
  print(encodedAccount.data.length); // 4

  // The EncodedAccount type alias is Account<Uint8List>.
  final EncodedAccount sameType = encodedAccount;
}

MaybeAccount -- handling accounts that may not exist

MaybeAccount<TData> is a sealed class hierarchy that represents an account that may or may not exist on-chain. Pattern matching is the idiomatic way to handle both cases.

import 'dart:typed_data';

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';

void handleAccount(MaybeAccount<Uint8List> maybeAccount) {
  switch (maybeAccount) {
    case ExistingAccount<Uint8List>(:final account):
      print('Account exists with ${account.data.length} bytes');
      print('Lamports: ${account.lamports}');
    case NonExistingAccount():
      print('Account does not exist at ${maybeAccount.address}');
  }

  // Or use the exists flag.
  if (maybeAccount.exists) {
    print('Found at ${maybeAccount.address}');
  }
}

Fetching accounts from the RPC

Use fetchEncodedAccount and fetchEncodedAccounts to retrieve accounts from a Solana RPC node. These functions use the getAccountInfo and getMultipleAccounts RPC methods with base64 encoding.

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_rpc/solana_kit_rpc.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';

Future<void> main() async {
  final rpc = createSolanaRpc('https://api.devnet.solana.com');

  // Fetch a single account.
  final maybeAccount = await fetchEncodedAccount(
    rpc,
    const Address('11111111111111111111111111111111'),
  );

  if (maybeAccount.exists) {
    print('Account found at ${maybeAccount.address}');
  }

  // Fetch with a specific commitment level.
  final confirmedAccount = await fetchEncodedAccount(
    rpc,
    const Address('11111111111111111111111111111111'),
    config: FetchAccountConfig(commitment: Commitment.confirmed),
  );

  // Fetch multiple accounts at once.
  final accounts = await fetchEncodedAccounts(
    rpc,
    [
      const Address('11111111111111111111111111111111'),
      const Address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
    ],
  );
  print('Fetched ${accounts.length} accounts');
}

Fetching JSON-parsed accounts

The fetchJsonParsedAccount and fetchJsonParsedAccounts functions use the jsonParsed encoding, which returns human-readable data for well-known program accounts (e.g. SPL Token accounts).

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_rpc/solana_kit_rpc.dart';

Future<void> main() async {
  final rpc = createSolanaRpc('https://api.devnet.solana.com');
  const tokenAccountAddress = Address(
    'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
  );

  // Fetch a single JSON-parsed account.
  final account = await fetchJsonParsedAccount(rpc, tokenAccountAddress);
  if (account case ExistingAccount<Object>(:final account)) {
    if (account.data case JsonParsedAccountData<Map<String, Object?>>(:final parsedAccountMeta, :final data)) {
      print('Program: ${parsedAccountMeta?.program}');
      print('Type: ${parsedAccountMeta?.type}');
      print('Parsed data: $data');
    }
  }
}

Decoding accounts

Transform an EncodedAccount into an Account<TData> using a Decoder.

import 'dart:typed_data';

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_codecs_core/solana_kit_codecs_core.dart';
import 'package:solana_kit_rpc/solana_kit_rpc.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';

// A simple data type for demonstration.
class MyData {
  const MyData(this.value);
  final int value;
}

// A decoder that reads a little-endian u32 from the account data.
class MyDataDecoder implements Decoder<MyData> {
  const MyDataDecoder();

  @override
  MyData decode(Uint8List bytes, [int offset = 0]) => read(bytes, offset).$1;

  @override
  (MyData, int) read(Uint8List bytes, int offset) {
    final byteData = ByteData.sublistView(bytes);
    final value = byteData.getUint32(offset, Endian.little);
    return (MyData(value), offset + 4);
  }

  @override
  int get fixedSize => 4;

  @override
  int? get maxSize => 4;
}

void main() {
  // Create an encoded account.
  final encodedAccount = Account<Uint8List>(
    address: const Address('11111111111111111111111111111111'),
    data: Uint8List.fromList([42, 0, 0, 0]),
    executable: false,
    lamports: Lamports(BigInt.from(1000000)),
    programAddress: const Address('11111111111111111111111111111111'),
    space: BigInt.from(4),
  );

  // Decode the account using a custom decoder.
  const decoder = MyDataDecoder();
  final decodedAccount = decodeAccount<MyData>(encodedAccount, decoder);
  print(decodedAccount.data.value); // 42
}

For MaybeAccount, use decodeMaybeAccount:

Future<void> fetchAndDecode() async {
  final rpc = createSolanaRpc('https://api.devnet.solana.com');
  const decoder = MyDataDecoder();

  final maybeFetched = await fetchEncodedAccount(
    rpc,
    const Address('11111111111111111111111111111111'),
  );
  final maybeDecoded = decodeMaybeAccount<MyData>(maybeFetched, decoder);

  switch (maybeDecoded) {
    case ExistingAccount<MyData>(:final account):
      print('Decoded value: ${account.data.value}');
    case NonExistingAccount():
      print('Account not found');
  }
}

Asserting account state

Use assertion functions to validate account existence and decoding state.

import 'dart:typed_data';

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_errors/solana_kit_errors.dart';
import 'package:solana_kit_rpc/solana_kit_rpc.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';

Future<void> main() async {
  final rpc = createSolanaRpc('https://api.devnet.solana.com');

  // Assert that a MaybeAccount exists.
  // Throws SolanaError with code accountsAccountNotFound if it does not.
  final maybeAccount = await fetchEncodedAccount(
    rpc,
    const Address('11111111111111111111111111111111'),
  );
  assertAccountExists(maybeAccount);

  // Assert multiple accounts all exist.
  // Throws SolanaError with code accountsOneOrMoreAccountsNotFound.
  final accounts = await fetchEncodedAccounts(rpc, [
    const Address('11111111111111111111111111111111'),
    const Address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
  ]);
  assertAccountsExist(accounts);

  // Assert that an account stores decoded data (not Uint8List).
  // Throws SolanaError with code accountsExpectedDecodedAccount
  // if the data field is still a Uint8List (i.e. not yet decoded).
  // assertAccountDecoded(decodedAccount);

  // Assert all accounts in a list are decoded.
  // Throws SolanaError with code accountsExpectedAllAccountsToBeDecoded.
  // assertAccountsDecoded(decodedAccounts);
}

Parsing raw RPC account data

Convert raw JSON-RPC account maps into typed MaybeAccount instances.

import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';

void main() {
  const addr = Address('11111111111111111111111111111111');

  // Parse a base64-encoded RPC response.
  final account = parseBase64RpcAccount(addr, {
    'data': ['AQAAAA==', 'base64'],
    'executable': false,
    'lamports': 1000000000,
    'owner': '11111111111111111111111111111111',
    'space': 4,
  });

  if (account case ExistingAccount(:final data)) {
    print('Data length: ${data.length}');
  }

  // Parse a null response (account not found).
  final missing = parseBase64RpcAccount(addr, null);
  print(missing.exists); // false

  // Parse a jsonParsed-encoded RPC response.
  final jsonParsed = parseJsonRpcAccount(addr, {
    'data': {
      'parsed': {
        'info': {'balance': 1000},
        'type': 'account',
      },
      'program': 'system',
      'space': 4,
    },
    'executable': false,
    'lamports': 1000000000,
    'owner': '11111111111111111111111111111111',
    'space': 4,
  });
}

API Reference

Classes

Class Description
BaseAccount Base properties: executable, lamports, programAddress, space.
Account<TData> Extends BaseAccount with address and data of type TData.
MaybeAccount<TData> Sealed class: account that may or may not exist.
ExistingAccount<TData> Extends MaybeAccount. Wraps an Account<TData> with exists == true.
NonExistingAccount<TData> Extends MaybeAccount. Only has address with exists == false.
FetchAccountConfig Optional config: commitment, minContextSlot.
JsonParsedAccountData<TData> Parsed account data with optional ParsedAccountMeta.
ParsedAccountMeta Metadata from jsonParsed responses: program, type.

Type aliases

Type Description
EncodedAccount Account<Uint8List> -- an account with raw byte data.
MaybeEncodedAccount MaybeAccount<Uint8List> -- a maybe-account with raw byte data.

Fetch functions

Function Description
fetchEncodedAccount(rpc, address, {config?}) Fetches a single MaybeEncodedAccount using getAccountInfo with base64 encoding.
fetchEncodedAccounts(rpc, addresses, {config?}) Fetches multiple MaybeEncodedAccounts using getMultipleAccounts.
fetchJsonParsedAccount(rpc, address, {config?}) Fetches a single MaybeAccount<Object> using jsonParsed encoding.
fetchJsonParsedAccounts(rpc, addresses, {config?}) Fetches multiple MaybeAccount<Object>s using jsonParsed encoding.

Decode functions

Function Description
decodeAccount<T>(encodedAccount, decoder) Decodes an EncodedAccount into an Account<T>.
decodeMaybeAccount<T>(maybeEncoded, decoder) Decodes a MaybeEncodedAccount into a MaybeAccount<T>.

Assertion functions

Function Description
assertAccountExists<T>(maybeAccount) Throws if the account does not exist.
assertAccountsExist<T>(accounts) Throws if any account does not exist.
assertAccountDecoded<T>(account) Throws if the account data is still a Uint8List.
assertMaybeAccountDecoded<T>(account) Throws if an existing account's data is still encoded.
assertAccountsDecoded<T>(accounts) Throws if any account's data is still encoded.
assertMaybeAccountsDecoded<T>(accounts) Throws if any existing account's data is still encoded.

Parse functions

Function Description
parseBase64RpcAccount(address, rpcAccount?) Parses a base64-encoded RPC account map into a MaybeEncodedAccount.
parseBase58RpcAccount(address, rpcAccount?) Parses a base58-encoded RPC account map into a MaybeEncodedAccount.
parseJsonRpcAccount(address, rpcAccount?) Parses a jsonParsed RPC account map into a MaybeAccount<JsonParsedAccountData>.
parseBaseAccount(rpcAccount) Extracts BaseAccount properties from a raw RPC account map.

Constants

Constant Description
baseAccountSize 128 -- the number of bytes for account metadata (excluding data).

Example

Use example/main.dart as a runnable starting point for solana_kit_accounts.

  • Import path: package:solana_kit_accounts/solana_kit_accounts.dart
  • This section is centrally maintained with mdt to keep package guidance aligned.
  • After updating shared docs templates, run docs:update from the repo root.

Maintenance

  • Validate docs in CI and locally with docs:check.
  • Keep examples focused on one workflow and reference package README sections for deeper API details.