solana_kit_options
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
- Package page: https://pub.dev/packages/solana_kit_options
- API reference: https://pub.dev/documentation/solana_kit_options/latest/
- Workspace docs: https://openbudgetfun.github.io/solana_kit/
- Package catalog entry: https://openbudgetfun.github.io/solana_kit/reference/package-catalog#solana_kit_options
- Source code: https://github.com/openbudgetfun/solana_kit/tree/main/packages/solana_kit_options
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
mdtto keep package guidance aligned. - After updating shared docs templates, run
docs:updatefrom 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.