Solana Kit favors structured errors over string parsing.
That means you can reason about failures by:
- error code
- error domain
- attached structured context
instead of brittle substring matching against user-facing messages.
Domain helpers#
Typed Error Domains#
solana_kit_errors includes domain helpers layered over numeric error codes. Use them to route error handling without hardcoding code ranges throughout your application.
import 'package:solana_kit_errors/solana_kit_errors.dart';
void handleSolanaFailure(SolanaError error) {
if (error.isInDomain(SolanaErrorDomain.rpc)) {
print('RPC failure: $error');
return;
}
if (error.isInDomain(SolanaErrorDomain.transaction)) {
print('Transaction failure: $error');
return;
}
print('Unhandled Solana error: $error');
}
This keeps your error-routing logic readable while still preserving the exact numeric code and context payload when you need lower-level diagnostics.
Security note
Attach small, structured, non-sensitive context to
SolanaErrorvalues so service boundaries can classify failures without parsing strings.Avoid logging private keys, auth tokens, wallet session payloads, or full structured error contexts in production logs.
Preferred construction helper#
Use createSolanaError(...) and wrapSolanaError(...) when you want consistent null stripping, context naming, and nested-cause preservation.
import 'package:solana_kit_errors/solana_kit_errors.dart';
void main() {
final cause = StateError('decoder failed');
final error = wrapSolanaError(
SolanaErrorCode.accountsFailedToDecodeAccount,
cause,
context: {
SolanaErrorContextKeys.address:
'11111111111111111111111111111111',
SolanaErrorContextKeys.operation: 'decodeAccount',
},
);
print(error.context[SolanaErrorContextKeys.causeType]);
}
Prefer shared keys such as address, operation, methodName, path,
statusCode, and url so diagnostics stay predictable across packages.
Match program errors from your program#
Transaction failures surface as SolanaError values. When a transaction fails with a custom program error, the RPC response identifies the failing instruction by index — pair it with the transaction message to attribute the error to a program and match custom error codes.
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_programs/solana_kit_programs.dart';
Future<void> handleTransactionFailure(Object error) async {
const myProgramAddress = Address('11111111111111111111111111111111');
final transactionMessage = TransactionMessageInput(
instructions: {0: InstructionInput(programAddress: myProgramAddress)},
);
if (isProgramError(error, transactionMessage, myProgramAddress, 42)) {
// Custom program error code 42 from this program.
} else if (isProgramError(error, transactionMessage, myProgramAddress)) {
// Any other custom error from this program.
}
}
transactionMessage is a lightweight TransactionMessageInput — a map from instruction index to
InstructionInput(programAddress: ...). Build it from the same instructions you sent, so matching stays accurate even when the transaction mixes instructions from several programs.
Practical guidance#
Catch SolanaError at service boundaries#
Boundary layers such as repositories, API services, or command handlers are good places to classify and map errors.
Preserve context when rethrowing#
If you convert one failure into another, keep the original structured information whenever possible.
Keep secrets out of diagnostics#
Structured context is useful, but it should not become a dumping ground for secrets.
Avoid attaching or logging:
- private keys or seed material
- auth tokens
- full wallet session payloads
- raw encrypted messages unless you are in a controlled debugging environment
Prefer small, typed, non-sensitive context fields that still let you classify and route failures.
Avoid broad untyped catches in core flows#
Catching Object too early can erase useful domain information.
Why this matters#
Typed diagnostics help you answer questions like:
- was this an RPC transport failure?
- was the account data malformed?
- was the transaction missing a signer?
- did a lifetime constraint expire?
- did a program-specific error occur during execution?
That is much easier than parsing free-form strings downstream.