solana_kit_options

pub package docs website CI coverage

A Rust-like Option<T> type and corresponding codec for encoding and decoding optional values in Solana on-chain data.

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

Installation

Install the package directly:

dart pub add solana_kit_options

If your app uses several Solana Kit packages together, you can also depend on the umbrella package instead:

dart pub add solana_kit

Inside this monorepo, Dart workspace resolution uses the local package automatically.

Documentation

For architecture notes, getting-started guides, and cross-package examples, start with the workspace docs site and then drill down into the package README and API reference.

Usage

The Option type

Dart has nullable types (T?), but they cannot represent nested optionality. For example, Option<Option<int>> cannot be expressed as int?? because there is no way to distinguish Some(None) from None. This package provides a sealed class hierarchy that mirrors Rust's Option<T>.

import 'package:solana_kit_options/solana_kit_options.dart';

// Create a Some value.
final present = some(42);         // Some<int>(42)
final alsoPresent = Some(42);     // Same thing using the constructor

// Create a None value.
final absent = none<int>();       // None<int>()
final alsoAbsent = None<int>();   // Same thing using the constructor

// Pattern matching with Dart's sealed class support.
final message = switch (present) {
  Some(:final value) => 'Got $value',
  None() => 'Nothing',
};
// message == 'Got 42'

Checking option variants

import 'package:solana_kit_options/solana_kit_options.dart';

final opt = some(42);

isSome(opt);      // true
isNone(opt);      // false

isOption(opt);    // true
isOption(42);     // false
isOption(null);   // false

Unwrapping options

Extract the contained value, or get null / a fallback:

import 'package:solana_kit_options/solana_kit_options.dart';

// Unwrap to nullable.
unwrapOption(some(42));         // 42
unwrapOption(none<int>());      // null

// Unwrap with a fallback.
unwrapOptionOr(some(42), () => 0);     // 42
unwrapOptionOr(none<int>(), () => 0);  // 0

// Wrap a nullable value into an Option.
wrapNullable(42);           // Some(42)
wrapNullable<int>(null);    // None<int>()

Recursive unwrapping

Deeply unwrap nested options within complex data structures:

import 'package:solana_kit_options/solana_kit_options.dart';

// Nested options are fully unwrapped.
unwrapOptionRecursively(some(some('Hello')));      // 'Hello'
unwrapOptionRecursively(some(none<String>()));      // null

// Works with maps and lists too.
final data = {
  'name': 'Alice',
  'age': some(30),
  'nickname': none<String>(),
  'scores': [some(100), none<int>(), some(85)],
};

final unwrapped = unwrapOptionRecursively(data);
// unwrapped == {
//   'name': 'Alice',
//   'age': 30,
//   'nickname': null,
//   'scores': [100, null, 85],
// }

// With a fallback for None values.
unwrapOptionRecursively(some(none<int>()), () => -1); // -1

Encoding and decoding options

The getOptionCodec function creates a codec that encodes Option<T> values with a configurable prefix byte (default: u8, where 0 = None and 1 = Some).

import 'dart:typed_data';
import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

final optionU16 = getOptionCodec(getU16Codec());

// Encode Some(42).
final someBytes = optionU16.encode(some(42));
// someBytes == [0x01, 0x2a, 0x00]
//              ^prefix=1^ ^-u16-^

// Encode None.
final noneBytes = optionU16.encode(none<int>());
// noneBytes == [0x00]
//              ^prefix=0^

// You can also pass raw values or null for convenience.
optionU16.encode(42);    // Same as encoding some(42)
optionU16.encode(null);  // Same as encoding none()

// Decode back to Option<int>.
final decoded = optionU16.decode(Uint8List.fromList([0x01, 0x2a, 0x00]));
// decoded == Some(42)

final decodedNone = optionU16.decode(Uint8List.fromList([0x00]));
// decodedNone == None<int>()

Zeroes none value

Use zeroes to represent None instead of omitting the value. This keeps the total encoded size constant for fixed-size items, which is useful in account data layouts where field offsets must be predictable:

import 'dart:typed_data';
import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

final optionU16 = getOptionCodec(
  getU16Codec(),
  noneValue: ZeroesOptionNoneValue(),
);

// Some(42): prefix + value.
optionU16.encode(some(42));   // [0x01, 0x2a, 0x00]

// None: prefix + zeroed bytes (same total size as Some).
optionU16.encode(none<int>()); // [0x00, 0x00, 0x00]

Constant none value

Use a specific byte sequence to represent None:

import 'dart:typed_data';
import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

final optionU16 = getOptionCodec(
  getU16Codec(),
  noneValue: ConstantOptionNoneValue(Uint8List.fromList([0xff, 0xff])),
);

optionU16.encode(none<int>()); // [0x00, 0xff, 0xff]

No prefix

Omit the boolean prefix entirely. Useful when the presence of data can be inferred from context (e.g., remaining bytes or the none value itself):

import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

final optionU16 = getOptionCodec(
  getU16Codec(),
  hasPrefix: false,
  noneValue: ZeroesOptionNoneValue(),
);

// Some(42): just the value, no prefix.
optionU16.encode(some(42));    // [0x2a, 0x00]

// None: zeroed bytes, no prefix.
optionU16.encode(none<int>()); // [0x00, 0x00]

Custom prefix codec

Use a different number codec for the prefix:

import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

// Use u32 instead of u8 for the prefix.
final optionU8 = getOptionCodec(
  getU8Codec(),
  prefix: getU32Codec(),
);

optionU8.encode(some(42)); // [0x01, 0x00, 0x00, 0x00, 0x2a]
//                          ^------u32 prefix------^ ^-u8-^

Using separate encoders and decoders

For composition with other codec utilities, use the encoder-only and decoder-only variants:

import 'package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart';
import 'package:solana_kit_options/solana_kit_options.dart';

// Encoder only.
final encoder = getOptionEncoder(getU16Encoder());
final bytes = encoder.encode(some(42));

// Decoder only.
final decoder = getOptionDecoder(getU16Decoder());
final option = decoder.decode(bytes);
// option == Some(42)

API Reference

Option type

Type / Function Description
Option<T> Sealed class: either Some<T> or None<T>
Some<T>(T value) Contains a present value
None<T>() Contains no value
some<T>(T value) Factory function for Some<T>
none<T>() Factory function for None<T>
isOption(input) Returns true if the input is an Option
isSome(option) Returns true if the option is Some
isNone(option) Returns true if the option is None

Unwrap utilities

Function Description
unwrapOption<T>(option) Returns the contained value or null
unwrapOptionOr<T>(option, fallback) Returns the contained value or calls fallback()
wrapNullable<T>(nullable) Converts T? into Option<T>
unwrapOptionRecursively(input, [fallback]) Deep-unwraps nested options in maps, lists, and values

Option codecs

Function Description
getOptionEncoder<T>(item, {prefix, hasPrefix, noneValue}) Encode Option<T> or T? values
getOptionDecoder<T>(item, {prefix, hasPrefix, noneValue}) Decode bytes into Option<T>
getOptionCodec<TFrom, TTo>(item, {prefix, hasPrefix, noneValue}) Combined option codec

None value types

Type Description
OmitOptionNoneValue() None is omitted from encoding (default)
ZeroesOptionNoneValue() None is encoded as zeroes (requires fixed-size item)
ConstantOptionNoneValue(bytes) None is encoded as a specific byte sequence

Example

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

  • Import path: package:solana_kit_options/solana_kit_options.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.

Libraries

solana_kit_options
Rust-style optional value modeling for the Solana Kit Dart SDK.